fix(cli): support node --run when bun pretends to be node

When `bun run --bun` is used, child processes invoking `node` actually
run bun via a symlink. Node.js v22+ supports `node --run <script>` to
run package.json scripts, but bun's fake node mode silently ignored the
`--run` flag and tried to execute the script name as a file path instead.

Detect `--run` in the raw argv and delegate to `RunCommand.exec()` which
already handles package.json script resolution with pre/post scripts.

Closes #27074

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Claude Bot
2026-02-17 08:12:11 +00:00
parent 70b354aa04
commit a3df1a8cfd
3 changed files with 79 additions and 0 deletions

View File

@@ -1637,6 +1637,21 @@ pub const RunCommand = struct {
return;
}
// Support `node --run <script>` (Node.js v22+ feature).
// The --run flag is silently discarded by the arg parser since it's
// unrecognized, but the script name ends up as ctx.positionals[0].
// Scan the raw argv to detect if --run was present.
if (ctx.positionals.len > 0) {
for (bun.argv) |arg| {
if (strings.eqlComptime(arg, "--run")) {
if (exec(ctx, .{ .bin_dirs_only = false, .log_errors = true, .allow_fast_run_for_extensions = false })) |ok| {
if (ok) return;
} else |_| {}
Global.exit(1);
}
}
}
if (ctx.positionals.len == 0) {
Output.errGeneric("Missing script to execute. Bun's provided 'node' cli wrapper does not support a repl.", .{});
Global.exit(1);

View File

@@ -101,4 +101,39 @@ describe("fake node cli", () => {
const temp = tempDirWithFiles("fake-node", {});
expect(() => fakeNodeRun(temp, [])).toThrow();
});
describe("node --run", () => {
test("runs a package.json script", () => {
const temp = tempDirWithFiles("fake-node", {
"package.json": JSON.stringify({
scripts: {
echo_test: "echo pass",
},
}),
});
expect(fakeNodeRun(temp, ["--run", "echo_test"]).stdout).toBe("pass");
});
test("runs pre/post scripts", () => {
const temp = tempDirWithFiles("fake-node", {
"package.json": JSON.stringify({
scripts: {
premyscript: "echo pre",
myscript: "echo main",
postmyscript: "echo post",
},
}),
});
expect(fakeNodeRun(temp, ["--run", "myscript"]).stdout).toBe("pre\nmain\npost");
});
test("errors on missing script", () => {
const temp = tempDirWithFiles("fake-node", {
"package.json": JSON.stringify({
scripts: {},
}),
});
expect(() => fakeNodeRun(temp, ["--run", "nonexistent"])).toThrow();
});
});
});

View File

@@ -0,0 +1,29 @@
import { expect, test } from "bun:test";
import { bunEnv, bunExe, tempDirWithFiles } from "../../harness";
// https://github.com/oven-sh/bun/issues/27074
// `bun run --bun build` fails when a pre-script uses `node --run`
test("bun run --bun works with node --run in lifecycle scripts", () => {
const temp = tempDirWithFiles("issue-27074", {
"package.json": JSON.stringify({
scripts: {
echo_test: "echo echo_test_ran",
prebuild: "node --run echo_test",
build: "echo build_ran",
},
}),
});
const result = Bun.spawnSync({
cmd: [bunExe(), "run", "--bun", "build"],
cwd: temp,
env: bunEnv,
});
const stdout = result.stdout.toString("utf8").trim();
const stderr = result.stderr.toString("utf8").trim();
expect(stdout).toContain("echo_test_ran");
expect(stdout).toContain("build_ran");
expect(result.exitCode).toBe(0);
});