Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions src/runtime/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1524,6 +1524,19 @@
#[cold]
#[inline(never)]
fn exec_run_as_node(log: &mut bun_ast::Log) -> CmdResult {
// `node -v` / `node --version`: print `process.version` and exit 0,
// matching Node.js. Must run before `init()` because `RUN_TABLE` has no
// `-v`/`--version` entry, so clap would reject `-v` and drop
// `--version`. Stop at the first positional or `--` so a script's own
// `--version` flag is left alone (`node app.js --version`).
Comment thread
robobun marked this conversation as resolved.
Outdated
for a in bun::argv().iter().skip(1) {
match a {
b"-v" | b"--version" => print_node_version_and_exit(),
b"--" => break,
_ if a.first() != Some(&b'-') => break,
_ => {}
Comment thread
robobun marked this conversation as resolved.
}
}

Check warning on line 1539 in src/runtime/cli/mod.rs

View check run for this annotation

Claude / Claude Code Review

Scan breaks on the value of a preceding option; node -r ./x --version still fails

The scan breaks on the first non-dash arg, but for value-taking flags in `RUN_TABLE` (`-r/--require`, `--import`, `--title`, `--conditions`, ...) that arg is the flag's *value* — so `node -r ./preload.js --version` breaks on `./preload.js`, never sees `--version`, and still exits 1 with "Missing script". This also bites bare `node -v` when `BUN_OPTIONS='-r ./x'` is set, since those tokens are spliced into argv after argv[0]. Not a regression (same behavior before this PR), so non-blocking — but

Check warning on line 1539 in src/runtime/cli/mod.rs

View check run for this annotation

Claude / Claude Code Review

Lone '-' (stdin sentinel) not treated as positional; node - --version is intercepted

The lone `-` (stdin sentinel) is not treated as a positional here — `a.first() != Some(&b'-')` is false for `b"-"`, so the loop continues past it and `node - --version` now prints the Node version instead of erroring/passing `--version` to the stdin script. Bun's own clap classifier already special-cases `b"-"` as `ArgKind::Positional` (streaming.rs:306), and real Node passes the flag through; add a `b"-" => break` arm alongside `b"--"`.
Comment thread
robobun marked this conversation as resolved.
Comment thread
robobun marked this conversation as resolved.
let ctx = init(Tag::RunAsNodeCommand, log)?;
run_command::RunCommand::exec_as_if_node(ctx)
}
Expand Down Expand Up @@ -2305,3 +2318,14 @@
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);
}
32 changes: 32 additions & 0 deletions test/cli/run/as-node.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,38 @@ 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<process.version>` 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"]));
});
});

Comment thread
coderabbitai[bot] marked this conversation as resolved.
test("process args work", () => {
const temp = tempDirWithFiles("fake-node", {
"index.js": "console.log(JSON.stringify(process.argv.slice(1)))",
Expand Down
Loading