fix(cli): support 'bun node <file>' — node emulation via argv[1] - #37277
fix(cli): support 'bun node <file>' — node emulation via argv[1]#37277springmin wants to merge 3 commits into
Conversation
PRETEND_TO_BE_NODE only triggered on argv0=node (symlink); 'bun node file.js' fell through to AutoCommand and errored with 'Script not found "node"' (as-node.test.ts 0/11 pass). - which(): after the flag-skip loop, treat first_arg_name == "node" like argv0=node: disable unknown-flag warnings, set PRETEND_TO_BE_NODE, return RunAsNodeCommand. New IS_NODE_ARG static marks the argv[1] form (symlink keeps it false). - exec_as_if_node: with IS_NODE_ARG, drop the literal "node" positional — clap's stop_after_positional_at=1 parked everything after it in passthrough, so node flags were never parsed. Re-parse the passthrough head (-e/--eval/-p/--print/--version/--revision/ --help) before the eval/REPL/positional dispatch, then promote the real file back into positionals. Verified: as-node.test.ts 11/11 pass (was 0/11); manual 'bun node <file>', '--bun node <file>', -e/--eval, -p, --version all work.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
WalkthroughChangesThe CLI now identifies Node CLI parsing
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/runtime/cli/mod.rs`:
- Around line 972-980: Update the first-argument prescan in the CLI dispatch
flow so it skips operands for value-taking options such as --cwd before
comparing first_arg_name with b"node". Ensure RunAsNodeCommand is selected only
when node is the actual first positional argument, while preserving distinct
handling for flag and operand forms.
In `@src/runtime/cli/run_command.rs`:
- Around line 3003-3035: Update the run-command argument parsing loop around
ctx.passthrough so unrecognized Node options such as --inspect are parsed and
retained with their operands rather than promoted as ctx.positionals. Use the
established real parser and explicitly handle supported option forms, including
attached --eval=<code>, then promote only the actual script positional while
preserving remaining arguments.
- Around line 3009-3019: Update the eval-option handling around the -e/--eval
and -p/--print branches to track whether an argument was supplied separately
from the script’s byte contents. Preserve an explicitly supplied empty script
for evaluation, and return the explicit option-value error only when no argument
follows the option; ensure the later missing-script check uses presence state
rather than script emptiness.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: c5d8e0f9-fa39-4d7e-a1bf-6794f6cc45ec
📒 Files selected for processing (2)
src/runtime/cli/mod.rssrc/runtime/cli/run_command.rs
| if first_arg_name == b"node" { | ||
| // `bun node <file>`: emulate node even though argv0 is "bun". | ||
| // Node-mode must not warn on flags Bun doesn't know. | ||
| bun_clap::streaming::WARN_ON_UNRECOGNIZED_FLAG | ||
| .store(false, core::sync::atomic::Ordering::Relaxed); | ||
| // SAFETY: single-threaded startup | ||
| PRETEND_TO_BE_NODE.store(true, core::sync::atomic::Ordering::Relaxed); | ||
| IS_NODE_ARG.store(true, core::sync::atomic::Ordering::Relaxed); | ||
| return Tag::RunAsNodeCommand; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Parse the first positional before enabling Node emulation.
The leading-flag loop does not consume option operands. Therefore, bun --cwd node app.js treats the --cwd value as the node command and selects RunAsNodeCommand.
Make the prescan consume value-taking options, or defer this decision until argument parsing identifies the actual first positional. As per coding guidelines, deliberately distinguish input forms at the CLI boundary.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/runtime/cli/mod.rs` around lines 972 - 980, Update the first-argument
prescan in the CLI dispatch flow so it skips operands for value-taking options
such as --cwd before comparing first_arg_name with b"node". Ensure
RunAsNodeCommand is selected only when node is the actual first positional
argument, while preserving distinct handling for flag and operand forms.
Source: Coding guidelines
| while let Some(first) = ctx.passthrough.first().cloned() { | ||
| let first: &[u8] = &first; | ||
| if first == b"--" { | ||
| ctx.passthrough.remove(0); | ||
| break; | ||
| } | ||
| if first == b"-e" || first == b"--eval" { | ||
| ctx.passthrough.remove(0); | ||
| if !ctx.passthrough.is_empty() { | ||
| ctx.runtime_options.eval.script = ctx.passthrough.remove(0); | ||
| } | ||
| } else if first == b"-p" || first == b"--print" { | ||
| ctx.passthrough.remove(0); | ||
| if !ctx.passthrough.is_empty() { | ||
| ctx.runtime_options.eval.script = ctx.passthrough.remove(0); | ||
| ctx.runtime_options.eval.eval_and_print = true; | ||
| } | ||
| } else if first == b"--version" { | ||
| crate::cli::print_version_and_exit(); | ||
| } else if first == b"--revision" { | ||
| crate::cli::print_revision_and_exit(); | ||
| } else if first == b"--help" || first == b"-h" { | ||
| crate::cli::command::tag_print_help(CommandTag::RunAsNodeCommand, true); | ||
| Output::flush(); | ||
| bun_core::Global::exit(0); | ||
| } else { | ||
| break; | ||
| } | ||
| } | ||
| if ctx.positionals.is_empty() && !ctx.passthrough.is_empty() { | ||
| // The real target file (or remaining arg) parked in | ||
| // passthrough; promote it back. | ||
| ctx.positionals.push(ctx.passthrough.remove(0)); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
Do not promote an unparsed Node option as the script.
For bun node --inspect app.js, the loop stops at --inspect, and Line 3035 promotes --inspect into ctx.positionals. The command then attempts to run the option as the script.
Use Node option parsing that preserves options and their operands in ctx.passthrough. Promote only the actual script positional. This must also support attached forms such as --eval=<code>. As per coding guidelines, use a real parser for user input and enumerate input forms.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/runtime/cli/run_command.rs` around lines 3003 - 3035, Update the
run-command argument parsing loop around ctx.passthrough so unrecognized Node
options such as --inspect are parsed and retained with their operands rather
than promoted as ctx.positionals. Use the established real parser and explicitly
handle supported option forms, including attached --eval=<code>, then promote
only the actual script positional while preserving remaining arguments.
Source: Coding guidelines
| if first == b"-e" || first == b"--eval" { | ||
| ctx.passthrough.remove(0); | ||
| if !ctx.passthrough.is_empty() { | ||
| ctx.runtime_options.eval.script = ctx.passthrough.remove(0); | ||
| } | ||
| } else if first == b"-p" || first == b"--print" { | ||
| ctx.passthrough.remove(0); | ||
| if !ctx.passthrough.is_empty() { | ||
| ctx.runtime_options.eval.script = ctx.passthrough.remove(0); | ||
| ctx.runtime_options.eval.eval_and_print = true; | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Distinguish an empty eval string from a missing eval argument.
bun node -e "" and bun node -p "" store an empty script. The later !ctx.runtime_options.eval.script.is_empty() check then skips evaluation and reports a missing script. A missing value after -e or -p follows the same incorrect path.
Track eval-option presence separately from script bytes. Return an explicit option-value error only when the value is absent. As per coding guidelines, distinguish empty input from unset input.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/runtime/cli/run_command.rs` around lines 3009 - 3019, Update the
eval-option handling around the -e/--eval and -p/--print branches to track
whether an argument was supplied separately from the script’s byte contents.
Preserve an explicitly supplied empty script for evaluation, and return the
explicit option-value error only when no argument follows the option; ensure the
later missing-script check uses presence state rather than script emptiness.
Source: Coding guidelines
What does this PR do?
PRETEND_TO_BE_NODEonly triggered onargv0=node(via symlink). Runningbun node file.jsfell through toAutoCommandand errored withScript not found "node"—test/cli/run/as-node.test.tswas 0/11 passing.Changes
src/runtime/cli/mod.rs—which(): after the flag-skip loop, treatfirst_arg_name == "node"exactly likeargv0=node: disable unknown-flag warnings, setPRETEND_TO_BE_NODE, and returnTag::RunAsNodeCommand. A newIS_NODE_ARGstatic marks theargv[1]form (the symlink form keeps it false, so its positional layout is untouched).src/runtime/cli/run_command.rs—exec_as_if_node: whenIS_NODE_ARGis set, drop the literal"node"positional — clap'sstop_after_positional_at=1parked everything after it inpassthrough, so node flags like-ewere never parsed. Re-parse the passthrough head (-e/--eval/-p/--print/--version/--revision/--help) before the eval/REPL/positional dispatch, then promote the real target file back intopositionals.Why does it fix the bug?
With the
"node"placeholder removed and node flags re-parsed,bun node <file>andbun node -e <code>reach the exact same code paths as theargv0=nodesymlink form, which already worked correctly.How did you verify your code works?
bun test test/cli/run/as-node.test.ts— 11/11 pass (was 0/11)bun node file.js,bun --bun node file.js,bun node -e "code",bun node --eval "code",bun node -p "1+1",bun node --versionall workThis is a pure CLI-logic change with no platform-specific code.