Skip to content

Commit 9e1af4c

Browse files
committed
fix(cli): supply cwd from rootDir to forge commands
1 parent 0327b3b commit 9e1af4c

File tree

9 files changed

+82
-48
lines changed

9 files changed

+82
-48
lines changed

packages/cli/src/build.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,5 @@ export async function build({
2323
await Promise.all([tablegen({ rootDir, config }), worldgen({ rootDir, config })]);
2424
await forge(["build"], { profile: foundryProfile });
2525
await buildSystemsManifest({ rootDir, config });
26-
await execa("mud", ["abi-ts"], { stdio: "inherit" });
26+
await execa("mud", ["abi-ts"], { stdio: "inherit", cwd: rootDir });
2727
}

packages/cli/src/commands/pull.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ const commandModule: CommandModule<Options, Options> = {
4040

4141
async handler(opts) {
4242
const profile = opts.profile ?? process.env.FOUNDRY_PROFILE;
43-
const rpc = opts.rpc ?? (await getRpcUrl(profile));
43+
const rpc = opts.rpc ?? (await getRpcUrl({ profile }));
4444
const client = createClient({
4545
transport: http(rpc, {
4646
batch: opts.rpcBatch

packages/cli/src/commands/test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ const commandModule: CommandModule<typeof testOptions, TestOptions> = {
3232
anvil(anvilArgs);
3333
}
3434

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

3737
const worldAddress =
3838
opts.worldAddress ??

packages/cli/src/commands/trace.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ const commandModule: CommandModule<Options, Options> = {
5858
const rootDir = path.dirname(configPath);
5959

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

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

packages/cli/src/commands/verify.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { verify } from "../verify";
33
import { loadConfig, resolveConfigPath } from "@latticexyz/config/node";
44
import { World as WorldConfig } from "@latticexyz/world";
55
import { resolveSystems } from "@latticexyz/world/node";
6-
import { getOutDirectory, getRpcUrl } from "@latticexyz/common/foundry";
6+
import { FoundryExecOptions, getOutDirectory, getRpcUrl } from "@latticexyz/common/foundry";
77
import { getContractData } from "../utils/getContractData";
88
import { Hex, createWalletClient, http } from "viem";
99
import chalk from "chalk";
@@ -49,9 +49,14 @@ const commandModule: CommandModule<Options, Options> = {
4949

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

52-
const outDir = await getOutDirectory(profile);
52+
const foundryExecOptions: FoundryExecOptions = {
53+
profile,
54+
cwd: rootDir,
55+
};
5356

54-
const rpc = opts.rpc ?? (await getRpcUrl(profile));
57+
const outDir = await getOutDirectory(foundryExecOptions);
58+
59+
const rpc = opts.rpc ?? (await getRpcUrl(foundryExecOptions));
5560
console.log(
5661
chalk.bgBlue(
5762
chalk.whiteBright(`\n Verifying MUD contracts${profile ? " with profile " + profile : ""} to RPC ${rpc} \n`),

packages/cli/src/runDeploy.ts

+9-4
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { createWalletClient, http, Hex, isHex, stringToHex } from "viem";
66
import { privateKeyToAccount } from "viem/accounts";
77
import { loadConfig, resolveConfigPath } from "@latticexyz/config/node";
88
import { World as WorldConfig } from "@latticexyz/world";
9-
import { getOutDirectory, getRpcUrl } from "@latticexyz/common/foundry";
9+
import { FoundryExecOptions, getOutDirectory, getRpcUrl } from "@latticexyz/common/foundry";
1010
import chalk from "chalk";
1111
import { MUDError } from "@latticexyz/common/errors";
1212
import { resolveConfig } from "./deploy/resolveConfig";
@@ -75,9 +75,14 @@ export async function runDeploy(opts: DeployOptions): Promise<WorldDeploy> {
7575
console.log(chalk.green("\nResolved config:\n"), JSON.stringify(config, null, 2));
7676
}
7777

78-
const outDir = await getOutDirectory(profile);
78+
const foundryExecOptions: FoundryExecOptions = {
79+
profile,
80+
cwd: rootDir,
81+
};
82+
83+
const outDir = await getOutDirectory(foundryExecOptions);
7984

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

98103
const artifacts = await findContractArtifacts({ forgeOutDir: outDir });

packages/cli/src/utils/getContractData.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ import { findPlaceholders } from "./findPlaceholders";
77

88
/**
99
* Load the contract's abi and bytecode from the file system
10-
* @param contractName: Name of the contract to load
10+
* @param filename Filename of the contract to load
11+
* @param contractName Name of the contract to load
12+
* @param forgeOutDirectory Directory where the contract was compiled
1113
*/
1214
export function getContractData(
1315
filename: string,

packages/common/src/foundry/index.ts

+56-33
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { execa, Options } from "execa";
1+
import { execa, ExecaChildProcess, Options } from "execa";
22

33
export interface ForgeConfig {
44
// project
@@ -18,13 +18,19 @@ export interface ForgeConfig {
1818
[key: string]: unknown;
1919
}
2020

21+
// Execa options for Foundry commands
22+
export interface FoundryExecOptions extends Options {
23+
silent?: boolean;
24+
profile?: string;
25+
}
26+
2127
/**
2228
* Get forge config as a parsed json object.
2329
*/
24-
export async function getForgeConfig(profile?: string): Promise<ForgeConfig> {
30+
export async function getForgeConfig(opts?: FoundryExecOptions): Promise<ForgeConfig> {
2531
const { stdout } = await execa("forge", ["config", "--json"], {
2632
stdio: ["inherit", "pipe", "pipe"],
27-
env: { FOUNDRY_PROFILE: profile },
33+
...getExecOptions(opts),
2834
});
2935

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

4147
/**
4248
* Get the value of "script" from forge config.
4349
* The path to the contract sources relative to the root of the project.
4450
*/
45-
export async function getScriptDirectory(profile?: string): Promise<string> {
46-
return (await getForgeConfig(profile)).script;
51+
export async function getScriptDirectory(opts?: FoundryExecOptions): Promise<string> {
52+
return (await getForgeConfig(opts)).script;
4753
}
4854

4955
/**
5056
* Get the value of "test" from forge config.
5157
* The path to the test contract sources relative to the root of the project.
5258
*/
53-
export async function getTestDirectory(profile?: string): Promise<string> {
54-
return (await getForgeConfig(profile)).test;
59+
export async function getTestDirectory(opts?: FoundryExecOptions): Promise<string> {
60+
return (await getForgeConfig(opts)).test;
5561
}
5662

5763
/**
5864
* Get the value of "out" from forge config.
5965
* The path to put contract artifacts in, relative to the root of the project.
6066
*/
61-
export async function getOutDirectory(profile?: string): Promise<string> {
62-
return (await getForgeConfig(profile)).out;
67+
export async function getOutDirectory(opts?: FoundryExecOptions): Promise<string> {
68+
return (await getForgeConfig(opts)).out;
6369
}
6470

6571
/**
6672
* Get the value of "eth_rpc_url" from forge config, default to "http://127.0.0.1:8545"
67-
* @param profile The foundry profile to use
73+
* @param opts The options to pass to getForgeConfig
6874
* @returns The rpc url
6975
*/
70-
export async function getRpcUrl(profile?: string): Promise<string> {
71-
return (await getForgeConfig(profile)).eth_rpc_url || "http://127.0.0.1:8545";
76+
export async function getRpcUrl(opts?: FoundryExecOptions): Promise<string> {
77+
return (await getForgeConfig(opts)).eth_rpc_url || "http://127.0.0.1:8545";
7278
}
7379

7480
/**
7581
* Execute a forge command
7682
* @param args The arguments to pass to forge
77-
* @param options { profile?: The foundry profile to use; silent?: If true, nothing will be logged to the console }
83+
* @param opts { profile?: The foundry profile to use; silent?: If true, nothing will be logged to the console }
7884
*/
79-
export async function forge(
80-
args: string[],
81-
options?: { profile?: string; silent?: boolean; env?: NodeJS.ProcessEnv; cwd?: string },
82-
): Promise<void> {
83-
const execOptions: Options<string> = {
84-
env: { FOUNDRY_PROFILE: options?.profile, ...options?.env },
85-
stdout: "inherit",
86-
stderr: "pipe",
87-
cwd: options?.cwd,
88-
};
89-
90-
await (options?.silent ? execa("forge", args, execOptions) : execLog("forge", args, execOptions));
85+
export async function forge(args: string[], opts?: FoundryExecOptions): Promise<string | ExecaChildProcess> {
86+
return execFoundry("forge", args, opts);
9187
}
9288

9389
/**
9490
* Execute a cast command
9591
* @param args The arguments to pass to cast
92+
* @param options The execution options
9693
* @returns Stdout of the command
9794
*/
98-
export async function cast(args: string[], options?: { profile?: string }): Promise<string> {
99-
return execLog("cast", args, {
100-
env: { FOUNDRY_PROFILE: options?.profile },
101-
});
95+
export async function cast(args: string[], options?: FoundryExecOptions): Promise<string | ExecaChildProcess> {
96+
return execLog("cast", args, getExecOptions(options));
10297
}
10398

10499
/**
105100
* Start an anvil chain
106101
* @param args The arguments to pass to anvil
102+
* @param options The execution options
107103
* @returns Stdout of the command
108104
*/
109-
export async function anvil(args: string[]): Promise<string> {
110-
return execLog("anvil", args);
105+
export async function anvil(args: string[], options?: FoundryExecOptions): Promise<string | ExecaChildProcess> {
106+
return execFoundry("anvil", args, options);
107+
}
108+
109+
/**
110+
* Execute a foundry command
111+
* @param command The command to execute
112+
* @param args The arguments to pass to the command
113+
* @param options The executable options. If `silent` is true, nothing will be logged to the console
114+
*/
115+
async function execFoundry(
116+
command: string,
117+
args: string[],
118+
options?: FoundryExecOptions,
119+
): Promise<string | ExecaChildProcess> {
120+
const execOptions: Options<string> = {
121+
stdout: "inherit",
122+
stderr: "pipe",
123+
...getExecOptions(options),
124+
};
125+
return options?.silent ? execa(command, args, execOptions) : execLog(command, args, execOptions);
111126
}
112127

113128
/**
114129
* Executes the given command, returns the stdout, and logs the command to the console.
115130
* Throws an error if the command fails.
116131
* @param command The command to execute
117132
* @param args The arguments to pass to the command
133+
* @param options The executable options
118134
* @returns The stdout of the command
119135
*/
120136
async function execLog(command: string, args: string[], options?: Options<string>): Promise<string> {
@@ -130,3 +146,10 @@ async function execLog(command: string, args: string[], options?: Options<string
130146
throw new Error(errorMessage);
131147
}
132148
}
149+
150+
function getExecOptions({ profile, env, silent: _, ...opts }: FoundryExecOptions = {}): Options<string> {
151+
return {
152+
env: { FOUNDRY_PROFILE: profile, ...env },
153+
...opts,
154+
};
155+
}

packages/world/ts/node/buildSystemsManifest.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,8 @@ export async function buildSystemsManifest(opts: { rootDir: string; config: Worl
4040

4141
const systems = await resolveSystems(opts);
4242

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

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

0 commit comments

Comments
 (0)