Skip to content

Commit

Permalink
fix(cli): supply cwd from rootDir to forge commands
Browse files Browse the repository at this point in the history
  • Loading branch information
JamesLefrere committed Jan 18, 2025
1 parent 0327b3b commit 9e1af4c
Show file tree
Hide file tree
Showing 9 changed files with 82 additions and 48 deletions.
2 changes: 1 addition & 1 deletion packages/cli/src/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,5 @@ export async function build({
await Promise.all([tablegen({ rootDir, config }), worldgen({ rootDir, config })]);
await forge(["build"], { profile: foundryProfile });
await buildSystemsManifest({ rootDir, config });
await execa("mud", ["abi-ts"], { stdio: "inherit" });
await execa("mud", ["abi-ts"], { stdio: "inherit", cwd: rootDir });
}
2 changes: 1 addition & 1 deletion packages/cli/src/commands/pull.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const commandModule: CommandModule<Options, Options> = {

async handler(opts) {
const profile = opts.profile ?? process.env.FOUNDRY_PROFILE;
const rpc = opts.rpc ?? (await getRpcUrl(profile));
const rpc = opts.rpc ?? (await getRpcUrl({ profile }));
const client = createClient({
transport: http(rpc, {
batch: opts.rpcBatch
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/commands/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const commandModule: CommandModule<typeof testOptions, TestOptions> = {
anvil(anvilArgs);
}

const forkRpc = opts.worldAddress ? await getRpcUrl(opts.profile) : `http://127.0.0.1:${opts.port}`;
const forkRpc = opts.worldAddress ? await getRpcUrl({ profile: opts.profile }) : `http://127.0.0.1:${opts.port}`;

const worldAddress =
opts.worldAddress ??
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/commands/trace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const commandModule: CommandModule<Options, Options> = {
const rootDir = path.dirname(configPath);

const profile = args.profile ?? process.env.FOUNDRY_PROFILE;
const rpc = args.rpc ?? (await getRpcUrl(profile));
const rpc = args.rpc ?? (await getRpcUrl({ profile, cwd: rootDir }));

const config = (await loadConfig(configPath)) as WorldConfig;

Expand Down
11 changes: 8 additions & 3 deletions packages/cli/src/commands/verify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { verify } from "../verify";
import { loadConfig, resolveConfigPath } from "@latticexyz/config/node";
import { World as WorldConfig } from "@latticexyz/world";
import { resolveSystems } from "@latticexyz/world/node";
import { getOutDirectory, getRpcUrl } from "@latticexyz/common/foundry";
import { FoundryExecOptions, getOutDirectory, getRpcUrl } from "@latticexyz/common/foundry";
import { getContractData } from "../utils/getContractData";
import { Hex, createWalletClient, http } from "viem";
import chalk from "chalk";
Expand Down Expand Up @@ -49,9 +49,14 @@ const commandModule: CommandModule<Options, Options> = {

const config = (await loadConfig(configPath)) as WorldConfig;

const outDir = await getOutDirectory(profile);
const foundryExecOptions: FoundryExecOptions = {
profile,
cwd: rootDir,
};

const rpc = opts.rpc ?? (await getRpcUrl(profile));
const outDir = await getOutDirectory(foundryExecOptions);

const rpc = opts.rpc ?? (await getRpcUrl(foundryExecOptions));
console.log(
chalk.bgBlue(
chalk.whiteBright(`\n Verifying MUD contracts${profile ? " with profile " + profile : ""} to RPC ${rpc} \n`),
Expand Down
13 changes: 9 additions & 4 deletions packages/cli/src/runDeploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { createWalletClient, http, Hex, isHex, stringToHex } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { loadConfig, resolveConfigPath } from "@latticexyz/config/node";
import { World as WorldConfig } from "@latticexyz/world";
import { getOutDirectory, getRpcUrl } from "@latticexyz/common/foundry";
import { FoundryExecOptions, getOutDirectory, getRpcUrl } from "@latticexyz/common/foundry";
import chalk from "chalk";
import { MUDError } from "@latticexyz/common/errors";
import { resolveConfig } from "./deploy/resolveConfig";
Expand Down Expand Up @@ -75,9 +75,14 @@ export async function runDeploy(opts: DeployOptions): Promise<WorldDeploy> {
console.log(chalk.green("\nResolved config:\n"), JSON.stringify(config, null, 2));
}

const outDir = await getOutDirectory(profile);
const foundryExecOptions: FoundryExecOptions = {
profile,
cwd: rootDir,
};

const outDir = await getOutDirectory(foundryExecOptions);

const rpc = opts.rpc ?? (await getRpcUrl(profile));
const rpc = opts.rpc ?? (await getRpcUrl(foundryExecOptions));
console.log(
chalk.bgBlue(
chalk.whiteBright(`\n Deploying MUD contracts${profile ? " with profile " + profile : ""} to RPC ${rpc} \n`),
Expand All @@ -92,7 +97,7 @@ export async function runDeploy(opts: DeployOptions): Promise<WorldDeploy> {
const { systems, libraries } = await resolveConfig({
rootDir,
config,
forgeOutDir: outDir,
forgeOutDir: path.join(rootDir, outDir),
});

const artifacts = await findContractArtifacts({ forgeOutDir: outDir });
Expand Down
4 changes: 3 additions & 1 deletion packages/cli/src/utils/getContractData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import { findPlaceholders } from "./findPlaceholders";

/**
* Load the contract's abi and bytecode from the file system
* @param contractName: Name of the contract to load
* @param filename Filename of the contract to load
* @param contractName Name of the contract to load
* @param forgeOutDirectory Directory where the contract was compiled
*/
export function getContractData(
filename: string,
Expand Down
89 changes: 56 additions & 33 deletions packages/common/src/foundry/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { execa, Options } from "execa";
import { execa, ExecaChildProcess, Options } from "execa";

export interface ForgeConfig {
// project
Expand All @@ -18,13 +18,19 @@ export interface ForgeConfig {
[key: string]: unknown;
}

// Execa options for Foundry commands
export interface FoundryExecOptions extends Options {
silent?: boolean;
profile?: string;
}

/**
* Get forge config as a parsed json object.
*/
export async function getForgeConfig(profile?: string): Promise<ForgeConfig> {
export async function getForgeConfig(opts?: FoundryExecOptions): Promise<ForgeConfig> {
const { stdout } = await execa("forge", ["config", "--json"], {
stdio: ["inherit", "pipe", "pipe"],
env: { FOUNDRY_PROFILE: profile },
...getExecOptions(opts),
});

return JSON.parse(stdout) as ForgeConfig;
Expand All @@ -34,87 +40,97 @@ export async function getForgeConfig(profile?: string): Promise<ForgeConfig> {
* Get the value of "src" from forge config.
* The path to the contract sources relative to the root of the project.
*/
export async function getSrcDirectory(profile?: string): Promise<string> {
return (await getForgeConfig(profile)).src;
export async function getSrcDirectory(opts?: FoundryExecOptions): Promise<string> {
return (await getForgeConfig(opts)).src;
}

/**
* Get the value of "script" from forge config.
* The path to the contract sources relative to the root of the project.
*/
export async function getScriptDirectory(profile?: string): Promise<string> {
return (await getForgeConfig(profile)).script;
export async function getScriptDirectory(opts?: FoundryExecOptions): Promise<string> {
return (await getForgeConfig(opts)).script;
}

/**
* Get the value of "test" from forge config.
* The path to the test contract sources relative to the root of the project.
*/
export async function getTestDirectory(profile?: string): Promise<string> {
return (await getForgeConfig(profile)).test;
export async function getTestDirectory(opts?: FoundryExecOptions): Promise<string> {
return (await getForgeConfig(opts)).test;
}

/**
* Get the value of "out" from forge config.
* The path to put contract artifacts in, relative to the root of the project.
*/
export async function getOutDirectory(profile?: string): Promise<string> {
return (await getForgeConfig(profile)).out;
export async function getOutDirectory(opts?: FoundryExecOptions): Promise<string> {
return (await getForgeConfig(opts)).out;
}

/**
* Get the value of "eth_rpc_url" from forge config, default to "http://127.0.0.1:8545"
* @param profile The foundry profile to use
* @param opts The options to pass to getForgeConfig
* @returns The rpc url
*/
export async function getRpcUrl(profile?: string): Promise<string> {
return (await getForgeConfig(profile)).eth_rpc_url || "http://127.0.0.1:8545";
export async function getRpcUrl(opts?: FoundryExecOptions): Promise<string> {
return (await getForgeConfig(opts)).eth_rpc_url || "http://127.0.0.1:8545";
}

/**
* Execute a forge command
* @param args The arguments to pass to forge
* @param options { profile?: The foundry profile to use; silent?: If true, nothing will be logged to the console }
* @param opts { profile?: The foundry profile to use; silent?: If true, nothing will be logged to the console }
*/
export async function forge(
args: string[],
options?: { profile?: string; silent?: boolean; env?: NodeJS.ProcessEnv; cwd?: string },
): Promise<void> {
const execOptions: Options<string> = {
env: { FOUNDRY_PROFILE: options?.profile, ...options?.env },
stdout: "inherit",
stderr: "pipe",
cwd: options?.cwd,
};

await (options?.silent ? execa("forge", args, execOptions) : execLog("forge", args, execOptions));
export async function forge(args: string[], opts?: FoundryExecOptions): Promise<string | ExecaChildProcess> {
return execFoundry("forge", args, opts);
}

/**
* Execute a cast command
* @param args The arguments to pass to cast
* @param options The execution options
* @returns Stdout of the command
*/
export async function cast(args: string[], options?: { profile?: string }): Promise<string> {
return execLog("cast", args, {
env: { FOUNDRY_PROFILE: options?.profile },
});
export async function cast(args: string[], options?: FoundryExecOptions): Promise<string | ExecaChildProcess> {
return execLog("cast", args, getExecOptions(options));
}

/**
* Start an anvil chain
* @param args The arguments to pass to anvil
* @param options The execution options
* @returns Stdout of the command
*/
export async function anvil(args: string[]): Promise<string> {
return execLog("anvil", args);
export async function anvil(args: string[], options?: FoundryExecOptions): Promise<string | ExecaChildProcess> {
return execFoundry("anvil", args, options);
}

/**
* Execute a foundry command
* @param command The command to execute
* @param args The arguments to pass to the command
* @param options The executable options. If `silent` is true, nothing will be logged to the console
*/
async function execFoundry(
command: string,
args: string[],
options?: FoundryExecOptions,
): Promise<string | ExecaChildProcess> {
const execOptions: Options<string> = {
stdout: "inherit",
stderr: "pipe",
...getExecOptions(options),
};
return options?.silent ? execa(command, args, execOptions) : execLog(command, args, execOptions);
}

/**
* Executes the given command, returns the stdout, and logs the command to the console.
* Throws an error if the command fails.
* @param command The command to execute
* @param args The arguments to pass to the command
* @param options The executable options
* @returns The stdout of the command
*/
async function execLog(command: string, args: string[], options?: Options<string>): Promise<string> {
Expand All @@ -130,3 +146,10 @@ async function execLog(command: string, args: string[], options?: Options<string
throw new Error(errorMessage);
}
}

function getExecOptions({ profile, env, silent: _, ...opts }: FoundryExecOptions = {}): Options<string> {
return {
env: { FOUNDRY_PROFILE: profile, ...env },
...opts,
};
}
5 changes: 2 additions & 3 deletions packages/world/ts/node/buildSystemsManifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,8 @@ export async function buildSystemsManifest(opts: { rootDir: string; config: Worl

const systems = await resolveSystems(opts);

// TODO: expose a `cwd` option to make sure this runs relative to `rootDir`
const forgeOutDir = await getForgeOutDirectory();
const contractArtifacts = await findContractArtifacts({ forgeOutDir });
const forgeOutDir = await getForgeOutDirectory({ cwd: opts.rootDir });
const contractArtifacts = await findContractArtifacts({ forgeOutDir: path.join(opts.rootDir, forgeOutDir) });

function getSystemArtifact(system: ResolvedSystem): ContractArtifact {
const artifact = contractArtifacts.find((a) => a.sourcePath === system.sourcePath && a.name === system.label);
Expand Down

0 comments on commit 9e1af4c

Please sign in to comment.