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
59 changes: 59 additions & 0 deletions src/runtime/cli/Arguments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,65 @@
parse_param!("--trace-exit"),
parse_param!("--expose-internals"),
parse_param!("--stack-trace-limit <STR>"),
// More Node.js flags Bun does not implement. Same rationale as above: the
// value-taking ones must be declared so the next arg (the value) is not
// parsed as the entrypoint. Keep this list in sync with the value-taking
// options in Node's `src/node_options.cc`.
parse_param!("--experimental-loader <STR>..."),
Comment thread
robobun marked this conversation as resolved.
parse_param!("--allow-fs-read <STR>..."),
parse_param!("--allow-fs-write <STR>..."),
parse_param!("--build-sea <STR>"),
parse_param!("--build-snapshot-config <STR>"),
parse_param!("--diagnostic-dir <STR>"),
parse_param!("--disable-proto <STR>"),
parse_param!("--disable-warning <STR>..."),
parse_param!("--env-file-if-exists <STR>..."),
parse_param!("--experimental-config-file <STR>"),
parse_param!("--experimental-sea-config <STR>"),
parse_param!("--heap-prof-interval <STR>"),
parse_param!("--heapsnapshot-near-heap-limit <STR>"),
parse_param!("--heapsnapshot-signal <STR>"),
parse_param!("--icu-data-dir <STR>"),
parse_param!("--input-type <STR>"),
parse_param!("--inspect-port <STR>"),
parse_param!("--debug-port <STR>"),
parse_param!("--inspect-publish-uid <STR>"),
parse_param!("--localstorage-file <STR>"),
parse_param!("--max-old-space-size-percentage <STR>"),
parse_param!("--network-family-autoselection-attempt-timeout <STR>"),
parse_param!("--openssl-config <STR>"),
parse_param!("--redirect-warnings <STR>"),
parse_param!("--report-dir <STR>"),
parse_param!("--report-directory <STR>"),
parse_param!("--report-filename <STR>"),
parse_param!("--report-signal <STR>"),
parse_param!("--secure-heap <STR>"),
parse_param!("--secure-heap-min <STR>"),
parse_param!("--snapshot-blob <STR>"),
parse_param!("--tls-cipher-list <STR>"),
parse_param!("--tls-keylog <STR>"),
parse_param!("--trace-require-module <STR>"),
parse_param!("--use-largepages <STR>"),
parse_param!("--v8-pool-size <STR>"),
parse_param!("--watch-path <STR>..."),
parse_param!("--watch-kill-signal <STR>"),
parse_param!("--test-concurrency <STR>"),
parse_param!("--test-coverage-branches <STR>"),
parse_param!("--test-coverage-exclude <STR>..."),
parse_param!("--test-coverage-functions <STR>"),
parse_param!("--test-coverage-include <STR>..."),
parse_param!("--test-coverage-lines <STR>"),
parse_param!("--test-global-setup <STR>"),
parse_param!("--test-isolation <STR>"),
parse_param!("--experimental-test-isolation <STR>"),
parse_param!("--test-random-seed <STR>"),
parse_param!("--test-reporter <STR>..."),
parse_param!("--test-reporter-destination <STR>..."),
parse_param!("--test-rerun-failures <STR>"),
parse_param!("--test-shard <STR>"),
parse_param!("--test-skip-pattern <STR>..."),
parse_param!("--experimental-test-tag-filter <STR>..."),
parse_param!("--test-timeout <STR>"),

Check warning on line 378 in src/runtime/cli/Arguments.rs

View check run for this annotation

Claude / Claude Code Review

--test-name-pattern omitted from value-taking Node flag list

`--test-name-pattern` is also a value-taking option in Node's `node_options.cc` (StringVector `test_name_pattern`) but is missing from this list — it's declared only in `TEST_ONLY_PARAMS`, which doesn't feed `AUTO_PARAMS`/`RUN_PARAMS`/run-as-node. So `bun --test-name-pattern foo app.mjs` still parses `foo` as the entrypoint. Note that naively adding it here would collide with the existing `TEST_ONLY_PARAMS` entry in `TEST_PARAMS`, so the fix may need to move/dedupe that declaration.
Comment thread
robobun marked this conversation as resolved.
];

pub(crate) const AUTO_OR_RUN_PARAMS: &[ParamType] = &[
Expand Down
121 changes: 120 additions & 1 deletion test/cli/run/as-node.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, expect, test } from "bun:test";
import { join } from "path";
import { fakeNodeRun, tempDirWithFiles } from "../../harness";
import { bunEnv, bunExe, fakeNodeRun, tempDir, tempDirWithFiles } from "../../harness";

describe("fake node cli", () => {
test("the node cli actually works", () => {
Expand Down Expand Up @@ -102,3 +102,122 @@ describe("fake node cli", () => {
expect(() => fakeNodeRun(temp, [])).toThrow();
});
});

describe("node value-taking CLI flags do not eat the entrypoint", () => {
// Node.js flags that take a value and which Bun does not otherwise implement
// must still consume their value argument so the *next* arg is parsed as the
// entrypoint. Otherwise `bun --experimental-loader ./hooks.mjs app.mjs`
// silently runs hooks.mjs as the program and app.mjs never executes.
const appBody = `console.log(JSON.stringify({ argv: process.argv.slice(2), execArgv: process.execArgv }));`;
const wrongBody = `throw new Error("flag value was run as the entrypoint");`;

async function run(preArgs: string[], cwd: string) {
await using proc = Bun.spawn({
cmd: [bunExe(), ...preArgs, "app.mjs", "scriptarg"],
env: bunEnv,
cwd,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
return { stdout: stdout.trim(), stderr, exitCode };
}

test("--experimental-loader ./hooks.mjs app.mjs runs app.mjs, not hooks.mjs", async () => {
using dir = tempDir("node-value-flag", {
"hooks.mjs": wrongBody,
"app.mjs": appBody,
});
const { stdout, stderr, exitCode } = await run(["--experimental-loader", "./hooks.mjs"], String(dir));
expect({ stdout, stderr }).toEqual({
stdout: JSON.stringify({ argv: ["scriptarg"], execArgv: ["--experimental-loader", "./hooks.mjs"] }),
stderr: "",
});
expect(exitCode).toBe(0);
});

const valueFlags = [
"--experimental-loader",
"--allow-fs-read",
"--allow-fs-write",
"--build-sea",
"--build-snapshot-config",
"--diagnostic-dir",
"--disable-proto",
"--disable-warning",
"--env-file-if-exists",
"--experimental-config-file",
"--experimental-sea-config",
"--heap-prof-interval",
"--heapsnapshot-near-heap-limit",
"--heapsnapshot-signal",
"--icu-data-dir",
"--input-type",
"--inspect-port",
"--debug-port",
"--inspect-publish-uid",
"--localstorage-file",
"--max-old-space-size-percentage",
"--network-family-autoselection-attempt-timeout",
"--openssl-config",
"--redirect-warnings",
"--report-dir",
"--report-directory",
"--report-filename",
"--report-signal",
"--secure-heap",
"--secure-heap-min",
"--snapshot-blob",
"--tls-cipher-list",
"--tls-keylog",
"--trace-require-module",
"--use-largepages",
"--v8-pool-size",
"--watch-path",
"--watch-kill-signal",
"--test-concurrency",
"--test-coverage-branches",
"--test-coverage-exclude",
"--test-coverage-functions",
"--test-coverage-include",
"--test-coverage-lines",
"--test-global-setup",
"--test-isolation",
"--experimental-test-isolation",
"--test-random-seed",
"--test-reporter",
"--test-reporter-destination",
"--test-rerun-failures",
"--test-shard",
"--test-skip-pattern",
"--experimental-test-tag-filter",
"--test-timeout",
];

describe.each([[[]], [["run"]]])("bun %p", runArg => {
test.concurrent.each(valueFlags)("%s <value> app.mjs runs app.mjs", async flag => {
using dir = tempDir("node-value-flag", {
"value.mjs": wrongBody,
"app.mjs": appBody,
});
const { stdout, stderr, exitCode } = await run([...runArg, flag, "./value.mjs"], String(dir));
expect(stderr).not.toContain("flag value was run as the entrypoint");
expect(JSON.parse(stdout)).toEqual({ argv: ["scriptarg"], execArgv: [flag, "./value.mjs"] });
expect(exitCode).toBe(0);
});
});

test("hidden from --help", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "--help"],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, , exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout).not.toContain("--experimental-loader");
expect(stdout).not.toContain("--openssl-config");
expect(stdout).not.toContain("--v8-pool-size");
expect(exitCode).toBe(0);
});
});
Loading