Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
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
63 changes: 63 additions & 0 deletions src/runtime/cli/Arguments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,9 +317,72 @@
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>..."),

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

View check run for this annotation

Claude / Claude Code Review

Missing value-taking Node flag: --security-revert

A few more value-taking Node options are still missing from this list. The concrete one is `--security-revert <CVE>` (and its `--security-reverts` alias) — it binds to `std::vector<std::string> PerProcessOptions::security_reverts` in `node_options.cc` and requires an argument, so `bun --security-revert CVE-2023-1234 app.mjs` still parses `CVE-2023-1234` as the entrypoint. Depending on the target Node version, `--experimental-default-config-file` (an `AddAlias` to `--experimental-config-file`, wh
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>"),
Comment thread
robobun marked this conversation as resolved.
];

pub(crate) const AUTO_OR_RUN_PARAMS: &[ParamType] = &[
// Node.js --test-name-pattern, here rather than in RUNTIME_PARAMS_ so it
// does not collide with `bun test`'s own -t/--test-name-pattern entry in
// TEST_ONLY_PARAMS. Hidden from --help (empty description).
parse_param!("--test-name-pattern <STR>..."),
parse_param!(
"-F, --filter <STR>... Run a script in all workspace packages matching the pattern"
),
Expand Down
120 changes: 119 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,121 @@ 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(stderr).not.toContain("flag value was run as the entrypoint");
expect(JSON.parse(stdout)).toEqual({ argv: ["scriptarg"], execArgv: ["--experimental-loader", "./hooks.mjs"] });
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",
"--test-name-pattern",
];

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