diff --git a/src/runtime/cli/mod.rs b/src/runtime/cli/mod.rs index e97a099938b4..411ff3b8dd5d 100644 --- a/src/runtime/cli/mod.rs +++ b/src/runtime/cli/mod.rs @@ -1524,6 +1524,15 @@ pub mod command { #[cold] #[inline(never)] fn exec_run_as_node(log: &mut bun_ast::Log) -> CmdResult { + // Handle `node -v`/`--version` before init(): RUN_TABLE has no entry for them. + for a in bun::argv().iter().skip(1) { + match a { + b"-v" | b"--version" => print_node_version_and_exit(), + b"--" | b"-" => break, + _ if a.first() != Some(&b'-') => break, + _ => {} + } + } let ctx = init(Tag::RunAsNodeCommand, log)?; run_command::RunCommand::exec_as_if_node(ctx) } @@ -2305,3 +2314,14 @@ pub fn print_revision_and_exit() -> ! { Output::flush(); Global::exit(0); } + +#[cold] +pub fn print_node_version_and_exit() -> ! { + let w = Output::writer(); + let _ = w.write_all( + const_format::concatcp!("v", bun_core::Environment::REPORTED_NODEJS_VERSION, "\n") + .as_bytes(), + ); + Output::flush(); + Global::exit(0); +} diff --git a/test/cli/run/as-node.test.ts b/test/cli/run/as-node.test.ts index f649c20fd3b8..328bf270fbe9 100644 --- a/test/cli/run/as-node.test.ts +++ b/test/cli/run/as-node.test.ts @@ -87,6 +87,46 @@ describe("fake node cli", () => { expect(fakeNodeRun(temp, ["-e", "console.log('pass')"]).stdout).toBe("pass"); }); + describe("-v / --version", () => { + // Engine-version preflights (node-gyp, prepare/postinstall scripts) run + // `node --version` through the bun-node shim; it must behave like Node.js + // and print `v` with exit 0. + test.each(["-v", "--version"])("node %s prints process.version", flag => { + const temp = tempDirWithFiles("fake-node", {}); + const result = Bun.spawnSync([bunExe(), "--bun", "node", flag], { + cwd: temp, + env: { ...bunEnv, NODE_ENV: undefined }, + stdin: Buffer.alloc(0), + }); + expect(result.stderr.toString()).toBe(""); + expect(result.stdout.toString()).toBe(process.version + "\n"); + expect(result.exitCode).toBe(0); + }); + + test("node --version matches node -e 'console.log(process.version)'", () => { + const temp = tempDirWithFiles("fake-node", {}); + const evaled = fakeNodeRun(temp, ["-e", "console.log(process.version)"]).stdout; + expect(fakeNodeRun(temp, ["--version"]).stdout).toBe(evaled); + expect(fakeNodeRun(temp, ["-v"]).stdout).toBe(evaled); + }); + + test("node script.js --version passes the flag through to the script", () => { + const temp = tempDirWithFiles("fake-node", { + "index.js": "console.log(JSON.stringify(process.argv.slice(2)))", + }); + expect(fakeNodeRun(temp, ["index.js", "--version"]).stdout).toBe(JSON.stringify(["--version"])); + expect(fakeNodeRun(temp, ["index.js", "-v"]).stdout).toBe(JSON.stringify(["-v"])); + }); + + test("node -- index.js --version passes the flag through to the script", () => { + const temp = tempDirWithFiles("fake-node", { + "index.js": "console.log(JSON.stringify(process.argv.slice(2)))", + }); + expect(fakeNodeRun(temp, ["--", "index.js", "--version"]).stdout).toBe(JSON.stringify(["--version"])); + expect(fakeNodeRun(temp, ["--", "index.js", "-v"]).stdout).toBe(JSON.stringify(["-v"])); + }); + }); + test("process args work", () => { const temp = tempDirWithFiles("fake-node", { "index.js": "console.log(JSON.stringify(process.argv.slice(1)))",