Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
65 changes: 65 additions & 0 deletions src/runtime/cli/Arguments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,9 +317,74 @@ pub(crate) const RUNTIME_PARAMS_: &[ParamType] = &[
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!("--security-revert <STR>..."),
parse_param!("--security-reverts <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
122 changes: 121 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,123 @@ 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",
"--security-revert",
"--security-reverts",
"--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