Skip to content

Commit b790181

Browse files
authored
fix(cli): use execa directly instead of custom foundry wrappers (latticexyz#3582)
1 parent 1c8b7f2 commit b790181

File tree

10 files changed

+128
-130
lines changed

10 files changed

+128
-130
lines changed

.changeset/hip-carpets-shave.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@latticexyz/cli": patch
3+
---
4+
5+
Fixed forge/anvil/cast output for all CLI commands.

packages/cli/src/build.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { tablegen } from "@latticexyz/store/codegen";
22
import { buildSystemsManifest, worldgen } from "@latticexyz/world/node";
33
import { World as WorldConfig } from "@latticexyz/world";
4-
import { forge } from "@latticexyz/common/foundry";
54
import { execa } from "execa";
5+
import { printCommand } from "./utils/printCommand";
66

77
type BuildOptions = {
88
foundryProfile?: string;
@@ -17,7 +17,12 @@ type BuildOptions = {
1717

1818
export async function build({ rootDir, config, foundryProfile }: BuildOptions): Promise<void> {
1919
await Promise.all([tablegen({ rootDir, config }), worldgen({ rootDir, config })]);
20-
await forge(["build"], { profile: foundryProfile });
20+
await printCommand(
21+
execa("forge", ["build"], {
22+
stdio: "inherit",
23+
env: { FOUNDRY_PROFILE: foundryProfile ?? process.env.FOUNDRY_PROFILE },
24+
}),
25+
);
2126
await buildSystemsManifest({ rootDir, config });
22-
await execa("mud", ["abi-ts"], { stdio: "inherit" });
27+
await printCommand(execa("mud", ["abi-ts"], { stdio: "inherit" }));
2328
}

packages/cli/src/commands/dev-contracts.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { CommandModule, InferredOptionTypes } from "yargs";
2-
import { anvil, getScriptDirectory, getSrcDirectory } from "@latticexyz/common/foundry";
2+
import { getScriptDirectory, getSrcDirectory } from "@latticexyz/common/foundry";
33
import chalk from "chalk";
44
import chokidar from "chokidar";
55
import { loadConfig, resolveConfigPath } from "@latticexyz/config/node";
@@ -11,6 +11,8 @@ import { deployOptions, runDeploy } from "../runDeploy";
1111
import { BehaviorSubject, debounceTime, exhaustMap, filter } from "rxjs";
1212
import { Address } from "viem";
1313
import { isDefined } from "@latticexyz/common/utils";
14+
import { execa } from "execa";
15+
import { printCommand } from "../utils/printCommand";
1416

1517
const devOptions = {
1618
rpc: deployOptions.rpc,
@@ -47,8 +49,12 @@ const commandModule: CommandModule<typeof devOptions, InferredOptionTypes<typeof
4749
const userHomeDir = homedir();
4850
rmSync(path.join(userHomeDir, ".foundry", "anvil", "tmp"), { recursive: true, force: true });
4951

50-
const anvilArgs = ["--block-time", "1", "--block-base-fee-per-gas", "0"];
51-
anvil(anvilArgs);
52+
await printCommand(
53+
execa("anvil", ["--quiet", ["--block-time", "2"], ["--block-base-fee-per-gas", "0"]].flat(), {
54+
stdio: "inherit",
55+
}),
56+
);
57+
5258
rpc = "http://127.0.0.1:8545";
5359
}
5460

packages/cli/src/commands/devnode.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { homedir } from "os";
33
import path from "path";
44
import type { CommandModule } from "yargs";
55
import { execa } from "execa";
6+
import { printCommand } from "../utils/printCommand";
67

78
type Options = {
89
blocktime: number;
@@ -15,7 +16,7 @@ const commandModule: CommandModule<Options, Options> = {
1516

1617
builder(yargs) {
1718
return yargs.options({
18-
blocktime: { type: "number", default: 1, decs: "Interval in which new blocks are produced" },
19+
blocktime: { type: "number", default: 2, decs: "Interval in which new blocks are produced" },
1920
});
2021
},
2122

@@ -24,11 +25,11 @@ const commandModule: CommandModule<Options, Options> = {
2425
const userHomeDir = homedir();
2526
rmSync(path.join(userHomeDir, ".foundry", "anvil", "tmp"), { recursive: true, force: true });
2627

27-
const anvilArgs = ["-b", String(blocktime), "--block-base-fee-per-gas", "0"];
28-
console.log(`Running: anvil ${anvilArgs.join(" ")}`);
29-
const child = execa("anvil", anvilArgs, {
30-
stdio: ["inherit", "inherit", "inherit"],
31-
});
28+
const child = printCommand(
29+
execa("anvil", ["-b", String(blocktime), "--block-base-fee-per-gas", "0"], {
30+
stdio: "inherit",
31+
}),
32+
);
3233

3334
process.on("SIGINT", () => {
3435
console.log("\ngracefully shutting down from SIGINT (Crtl-C)");

packages/cli/src/commands/test.ts

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import type { CommandModule, InferredOptionTypes, Options } from "yargs";
2-
import { anvil, forge, getRpcUrl } from "@latticexyz/common/foundry";
2+
import { getRpcUrl } from "@latticexyz/common/foundry";
33
import chalk from "chalk";
44
import { deployOptions, runDeploy } from "../runDeploy";
5+
import { execa } from "execa";
6+
import { printCommand } from "../utils/printCommand";
57

68
const testOptions = {
79
...deployOptions,
@@ -28,8 +30,12 @@ const commandModule: CommandModule<typeof testOptions, TestOptions> = {
2830
async handler(opts) {
2931
// Start an internal anvil process if no world address is provided
3032
if (!opts.worldAddress) {
31-
const anvilArgs = ["--block-base-fee-per-gas", "0", "--port", String(opts.port)];
32-
anvil(anvilArgs);
33+
printCommand(
34+
execa("anvil", ["--quiet", ["--port", String(opts.port)], ["--block-base-fee-per-gas", "0"]].flat(), {
35+
stdio: "inherit",
36+
env: { FOUNDRY_PROFILE: opts.profile ?? process.env.FOUNDRY_PROFILE },
37+
}),
38+
);
3339
}
3440

3541
const forkRpc = opts.worldAddress ? await getRpcUrl(opts.profile) : `http://127.0.0.1:${opts.port}`;
@@ -48,12 +54,15 @@ const commandModule: CommandModule<typeof testOptions, TestOptions> = {
4854

4955
const userOptions = opts.forgeOptions?.replaceAll("\\", "").split(" ") ?? [];
5056
try {
51-
await forge(["test", "--fork-url", forkRpc, ...userOptions], {
52-
profile: opts.profile,
53-
env: {
54-
WORLD_ADDRESS: worldAddress,
55-
},
56-
});
57+
await printCommand(
58+
execa("forge", ["test", ["--fork-url", forkRpc], ...userOptions].flat(), {
59+
stdio: "inherit",
60+
env: {
61+
FOUNDRY_PROFILE: opts.profile ?? process.env.FOUNDRY_PROFILE,
62+
WORLD_ADDRESS: worldAddress,
63+
},
64+
}),
65+
);
5766
process.exit(0);
5867
} catch (e) {
5968
console.error(e);

packages/cli/src/commands/trace.ts

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ import fs from "node:fs";
33
import type { CommandModule } from "yargs";
44
import { loadConfig, resolveConfigPath } from "@latticexyz/config/node";
55
import { MUDError } from "@latticexyz/common/errors";
6-
import { cast, getRpcUrl } from "@latticexyz/common/foundry";
6+
import { getRpcUrl } from "@latticexyz/common/foundry";
77
import worldConfig from "@latticexyz/world/mud.config";
88
import { Hex, createClient, http } from "viem";
99
import { getChainId, readContract } from "viem/actions";
1010
import { World as WorldConfig } from "@latticexyz/world";
1111
import { resolveSystems } from "@latticexyz/world/node";
1212
import { worldAbi } from "../deploy/common";
13+
import { execa } from "execa";
14+
import { printCommand } from "../utils/printCommand";
1315

1416
const systemsTableId = worldConfig.namespaces.world.tables.Systems.tableId;
1517

@@ -80,16 +82,21 @@ const commandModule: CommandModule<Options, Options> = {
8082
})),
8183
);
8284

83-
const result = await cast([
84-
"run",
85-
"--label",
86-
`${worldAddress}:World`,
87-
...labels.map(({ label, address }) => ["--label", `${address}:${label}`]).flat(),
88-
`${args.tx}`,
89-
]);
90-
console.log(result);
91-
92-
process.exit(0);
85+
await printCommand(
86+
execa(
87+
"cast",
88+
[
89+
"run",
90+
["--label", `${worldAddress}:World`],
91+
...labels.map(({ label, address }) => ["--label", `${address}:${label}`]),
92+
args.tx,
93+
].flat(),
94+
{
95+
stdio: "inherit",
96+
env: { FOUNDRY_PROFILE: profile ?? process.env.FOUNDRY_PROFILE },
97+
},
98+
),
99+
);
93100
},
94101
};
95102

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { existsSync } from "fs";
22
import path from "path";
3-
import chalk from "chalk";
4-
import { getScriptDirectory, forge } from "@latticexyz/common/foundry";
3+
import { getScriptDirectory } from "@latticexyz/common/foundry";
4+
import { execa } from "execa";
5+
import { printCommand } from "./printCommand";
56

67
export async function postDeploy(
78
postDeployScript: string,
@@ -19,24 +20,25 @@ export async function postDeploy(
1920
return;
2021
}
2122

22-
console.log(chalk.blue(`Executing post deploy script at ${postDeployPath}`));
23-
24-
await forge(
25-
[
26-
"script",
27-
postDeployScript,
28-
"--broadcast",
29-
"--sig",
30-
"run(address)",
31-
worldAddress,
32-
"--rpc-url",
33-
rpc,
34-
"-vvv",
35-
kms ? "--aws" : "",
36-
...userOptions,
37-
],
38-
{
39-
profile: profile,
40-
},
23+
await printCommand(
24+
execa(
25+
"forge",
26+
[
27+
"script",
28+
postDeployScript,
29+
["--sig", "run(address)", worldAddress],
30+
["--rpc-url", rpc],
31+
"--broadcast",
32+
"-vvv",
33+
kms ? ["--aws"] : [],
34+
...userOptions,
35+
].flat(),
36+
{
37+
stdio: "inherit",
38+
env: {
39+
FOUNDRY_PROFILE: profile ?? process.env.FOUNDRY_PROFILE,
40+
},
41+
},
42+
),
4143
);
4244
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import chalk from "chalk";
2+
import { ChildProcess } from "node:child_process";
3+
4+
/**
5+
* Prints the command used to spawn a child process
6+
* and returns the child process.
7+
*
8+
* Using it like this
9+
*
10+
* ```ts
11+
* printCommand(execa("echo", ["hello"]));
12+
* ```
13+
*
14+
* will output
15+
*
16+
* ```plain
17+
* > echo hello
18+
* ```
19+
*/
20+
export function printCommand<proc>(proc: proc extends ChildProcess ? proc : ChildProcess): proc {
21+
console.log("\n" + chalk.gray("> " + proc.spawnargs.join(" ")));
22+
return proc as never;
23+
}
Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { forge } from "@latticexyz/common/foundry";
1+
import { execa } from "execa";
22
import { Address } from "viem";
3+
import { printCommand } from "../utils/printCommand";
34

45
type VerifyContractOptions = {
56
name: string;
@@ -11,13 +12,18 @@ type VerifyContractOptions = {
1112
};
1213

1314
export async function verifyContract(options: VerifyContractOptions) {
14-
const args = ["verify-contract", options.address, options.name, "--rpc-url", options.rpc];
15-
16-
if (options.verifier) {
17-
args.push("--verifier", options.verifier);
18-
}
19-
if (options.verifierUrl) {
20-
args.push("--verifier-url", options.verifierUrl);
21-
}
22-
await forge(args, { cwd: options.cwd });
15+
await printCommand(
16+
execa(
17+
"forge",
18+
[
19+
"verify-contract",
20+
options.address,
21+
options.name,
22+
["--rpc-url", options.rpc],
23+
options.verifier ? ["--verifier", options.verifier] : [],
24+
options.verifierUrl ? ["--verifier-url", options.verifierUrl] : [],
25+
].flat(),
26+
{ stdio: "inherit" },
27+
),
28+
);
2329
}

packages/common/src/foundry/index.ts

Lines changed: 1 addition & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { execa, Options } from "execa";
1+
import { execa } from "execa";
22

33
export interface ForgeConfig {
44
// project
@@ -76,69 +76,3 @@ export async function getRpcUrl(profile?: string): Promise<string> {
7676
"http://127.0.0.1:8545"
7777
);
7878
}
79-
80-
/**
81-
* Execute a forge command
82-
* @param args The arguments to pass to forge
83-
* @param options { profile?: The foundry profile to use; silent?: If true, nothing will be logged to the console }
84-
*/
85-
export async function forge(
86-
args: string[],
87-
options?: { profile?: string; silent?: boolean; env?: NodeJS.ProcessEnv; cwd?: string },
88-
): Promise<void> {
89-
const execOptions = {
90-
env: { FOUNDRY_PROFILE: options?.profile, ...options?.env },
91-
stdout: "inherit",
92-
stderr: "pipe",
93-
cwd: options?.cwd,
94-
} satisfies Options;
95-
96-
await (options?.silent ? execa("forge", args, execOptions) : execLog("forge", args, execOptions));
97-
}
98-
99-
/**
100-
* Execute a cast command
101-
* @param args The arguments to pass to cast
102-
* @returns Stdout of the command
103-
*/
104-
export async function cast(args: string[], options?: { profile?: string }): Promise<string> {
105-
return execLog("cast", args, {
106-
env: { FOUNDRY_PROFILE: options?.profile },
107-
});
108-
}
109-
110-
/**
111-
* Start an anvil chain
112-
* @param args The arguments to pass to anvil
113-
* @returns Stdout of the command
114-
*/
115-
export async function anvil(args: string[]): Promise<string> {
116-
return execLog("anvil", args);
117-
}
118-
119-
/**
120-
* Executes the given command, returns the stdout, and logs the command to the console.
121-
* Throws an error if the command fails.
122-
* @param command The command to execute
123-
* @param args The arguments to pass to the command
124-
* @returns The stdout of the command
125-
*/
126-
async function execLog(command: string, args: string[], options?: Options): Promise<string> {
127-
const commandString = `${command} ${args.join(" ")}`;
128-
try {
129-
console.log(`running "${commandString}"`);
130-
const { stdout } = await execa(command, args, {
131-
...options,
132-
stdout: "pipe",
133-
stderr: "pipe",
134-
lines: false,
135-
encoding: "utf8",
136-
});
137-
return stdout;
138-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
139-
} catch (error: any) {
140-
let errorMessage = error?.stderr || error?.message || "";
141-
errorMessage += `\nError running "${commandString}"`;
142-
throw new Error(errorMessage);
143-
}
144-
}

0 commit comments

Comments
 (0)