mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
Fix argv handling for standalone binaries with compile-exec-argv (#22084)
## Summary
Fixes an issue where `--compile-exec-argv` options were incorrectly
appearing in `process.argv` when no user arguments were provided to a
compiled standalone binary.
## Problem
When building a standalone binary with `--compile-exec-argv`, the exec
argv options would leak into `process.argv` when running the binary
without any user arguments:
```bash
# Build with exec argv
bun build --compile-exec-argv="--user-agent=hello" --compile ./a.js
# Run without arguments - BEFORE fix
./a
# Output showed --user-agent=hello in both execArgv AND argv (incorrect)
{
execArgv: [ "--user-agent=hello" ],
argv: [ "bun", "/$bunfs/root/a", "--user-agent=hello" ], # <- BUG: exec argv leaked here
}
# Expected behavior (matches runtime):
bun --user-agent=hello a.js
{
execArgv: [ "--user-agent=hello" ],
argv: [ "/path/to/bun", "/path/to/a.js" ], # <- No exec argv in process.argv
}
```
## Solution
The issue was in the offset calculation for determining which arguments
to pass through to the JavaScript runtime. The offset was being
calculated before modifying the argv array with exec argv options,
causing it to be incorrect when the original argv only contained the
executable name.
The fix ensures that:
- `process.execArgv` correctly contains the compile-exec-argv options
- `process.argv` only contains the executable, script path, and user
arguments
- exec argv options never leak into `process.argv`
## Test plan
Added comprehensive tests to verify:
1. Exec argv options don't leak into process.argv when no user arguments
are provided
2. User arguments are properly passed through when exec argv is present
3. Existing behavior continues to work correctly
All tests pass:
```
bun test compile-argv.test.ts
✓ 3 tests pass
```
🤖 Generated with [Claude Code](https://claude.ai/code)
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
@@ -141,7 +141,7 @@ During bundling, the parsed YAML is inlined into the bundle as a JavaScript obje
|
||||
var config = {
|
||||
database: {
|
||||
host: "localhost",
|
||||
port: 5432
|
||||
port: 5432,
|
||||
},
|
||||
// ...other fields
|
||||
};
|
||||
|
||||
@@ -73,4 +73,4 @@ console.log(data.hobbies); // => ["reading", "coding"]
|
||||
|
||||
---
|
||||
|
||||
See [Docs > API > YAML](https://bun.com/docs/api/yaml) for complete documentation on YAML support in Bun.
|
||||
See [Docs > API > YAML](https://bun.com/docs/api/yaml) for complete documentation on YAML support in Bun.
|
||||
|
||||
11
src/cli.zig
11
src/cli.zig
@@ -635,15 +635,18 @@ pub const Command = struct {
|
||||
// bun build --compile entry point
|
||||
if (!bun.getRuntimeFeatureFlag(.BUN_BE_BUN)) {
|
||||
if (try bun.StandaloneModuleGraph.fromExecutable(bun.default_allocator)) |graph| {
|
||||
var offset_for_passthrough: usize = if (bun.argv.len > 1) 1 else 0;
|
||||
var offset_for_passthrough: usize = 0;
|
||||
|
||||
const ctx: *ContextData = brk: {
|
||||
if (graph.compile_exec_argv.len > 0) {
|
||||
const original_argv_len = bun.argv.len;
|
||||
var argv_list = std.ArrayList([:0]const u8).fromOwnedSlice(bun.default_allocator, bun.argv);
|
||||
try bun.appendOptionsEnv(graph.compile_exec_argv, &argv_list, bun.default_allocator);
|
||||
offset_for_passthrough += (argv_list.items.len -| bun.argv.len);
|
||||
bun.argv = argv_list.items;
|
||||
|
||||
// Calculate offset: skip executable name + all exec argv options
|
||||
offset_for_passthrough = if (bun.argv.len > 1) 1 + (bun.argv.len -| original_argv_len) else 0;
|
||||
|
||||
// Handle actual options to parse.
|
||||
break :brk try Command.init(allocator, log, .AutoCommand);
|
||||
}
|
||||
@@ -655,6 +658,10 @@ pub const Command = struct {
|
||||
.allocator = bun.default_allocator,
|
||||
};
|
||||
global_cli_ctx = &context_data;
|
||||
|
||||
// If no compile_exec_argv, set offset normally
|
||||
offset_for_passthrough = if (bun.argv.len > 1) 1 else 0;
|
||||
|
||||
break :brk global_cli_ctx;
|
||||
};
|
||||
|
||||
|
||||
@@ -46,4 +46,130 @@ describe("bundler", () => {
|
||||
stdout: /SUCCESS: process.title and process.execArgv are both set correctly/,
|
||||
},
|
||||
});
|
||||
|
||||
// Test that exec argv options don't leak into process.argv when no user arguments are provided
|
||||
itBundled("compile/CompileExecArgvNoLeak", {
|
||||
compile: {
|
||||
execArgv: ["--user-agent=test-agent", "--smol"],
|
||||
},
|
||||
files: {
|
||||
"/entry.ts": /* js */ `
|
||||
// Test that compile-exec-argv options don't appear in process.argv
|
||||
console.log("execArgv:", JSON.stringify(process.execArgv));
|
||||
console.log("argv:", JSON.stringify(process.argv));
|
||||
|
||||
// Check that execArgv contains the expected options
|
||||
if (process.execArgv.length !== 2) {
|
||||
console.error("FAIL: Expected exactly 2 items in execArgv, got", process.execArgv.length);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (process.execArgv[0] !== "--user-agent=test-agent") {
|
||||
console.error("FAIL: Expected --user-agent=test-agent in execArgv[0], got", process.execArgv[0]);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (process.execArgv[1] !== "--smol") {
|
||||
console.error("FAIL: Expected --smol in execArgv[1], got", process.execArgv[1]);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Check that argv only contains the executable and script name, NOT the exec argv options
|
||||
if (process.argv.length !== 2) {
|
||||
console.error("FAIL: Expected exactly 2 items in argv (executable and script), got", process.argv.length, "items:", process.argv);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// argv[0] should be "bun" for standalone executables
|
||||
if (process.argv[0] !== "bun") {
|
||||
console.error("FAIL: Expected argv[0] to be 'bun', got", process.argv[0]);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// argv[1] should be the script path (contains the bundle path)
|
||||
if (!process.argv[1].includes("bunfs")) {
|
||||
console.error("FAIL: Expected argv[1] to contain 'bunfs' path, got", process.argv[1]);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Make sure exec argv options are NOT in process.argv
|
||||
for (const arg of process.argv) {
|
||||
if (arg.includes("--user-agent") || arg === "--smol") {
|
||||
console.error("FAIL: exec argv option leaked into process.argv:", arg);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
console.log("SUCCESS: exec argv options are properly separated from process.argv");
|
||||
`,
|
||||
},
|
||||
run: {
|
||||
// No user arguments provided - this is the key test case
|
||||
args: [],
|
||||
stdout: /SUCCESS: exec argv options are properly separated from process.argv/,
|
||||
},
|
||||
});
|
||||
|
||||
// Test that user arguments are properly passed through when exec argv is present
|
||||
itBundled("compile/CompileExecArgvWithUserArgs", {
|
||||
compile: {
|
||||
execArgv: ["--user-agent=test-agent", "--smol"],
|
||||
},
|
||||
files: {
|
||||
"/entry.ts": /* js */ `
|
||||
// Test that user arguments are properly included when exec argv is present
|
||||
console.log("execArgv:", JSON.stringify(process.execArgv));
|
||||
console.log("argv:", JSON.stringify(process.argv));
|
||||
|
||||
// Check execArgv
|
||||
if (process.execArgv.length !== 2) {
|
||||
console.error("FAIL: Expected exactly 2 items in execArgv, got", process.execArgv.length);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (process.execArgv[0] !== "--user-agent=test-agent" || process.execArgv[1] !== "--smol") {
|
||||
console.error("FAIL: Unexpected execArgv:", process.execArgv);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Check argv contains executable, script, and user arguments
|
||||
if (process.argv.length !== 4) {
|
||||
console.error("FAIL: Expected exactly 4 items in argv, got", process.argv.length, "items:", process.argv);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (process.argv[0] !== "bun") {
|
||||
console.error("FAIL: Expected argv[0] to be 'bun', got", process.argv[0]);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!process.argv[1].includes("bunfs")) {
|
||||
console.error("FAIL: Expected argv[1] to contain 'bunfs' path, got", process.argv[1]);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (process.argv[2] !== "user-arg1") {
|
||||
console.error("FAIL: Expected argv[2] to be 'user-arg1', got", process.argv[2]);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (process.argv[3] !== "user-arg2") {
|
||||
console.error("FAIL: Expected argv[3] to be 'user-arg2', got", process.argv[3]);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Make sure exec argv options are NOT mixed with user arguments
|
||||
if (process.argv.includes("--user-agent=test-agent") || process.argv.includes("--smol")) {
|
||||
console.error("FAIL: exec argv options leaked into process.argv");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log("SUCCESS: user arguments properly passed with exec argv present");
|
||||
`,
|
||||
},
|
||||
run: {
|
||||
args: ["user-arg1", "user-arg2"],
|
||||
stdout: /SUCCESS: user arguments properly passed with exec argv present/,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user