Compare commits

...

1 Commits

Author SHA1 Message Date
Claude Bot
feebc81063 fix(bundler): don't inline process.env.NODE_ENV in --compile builds
When using `bun build --compile`, `process.env.NODE_ENV` and
`process.env.BUN_ENV` were replaced with compile-time constants
(defaulting to "development"), making it impossible to set these
values at runtime via environment variables.

Skip the compile-time define insertion for these two variables when
building standalone executables, so the compiled binary reads them
from the runtime environment as expected.

Fixes #26994

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-13 09:52:05 +00:00
2 changed files with 144 additions and 28 deletions

View File

@@ -1442,6 +1442,7 @@ pub fn definesFromTransformOptions(
NODE_ENV: ?string,
drop: []const []const u8,
omit_unused_global_calls: bool,
is_standalone: bool,
) !*defines.Define {
const input_user_define = maybe_input_define orelse std.mem.zeroes(api.StringMap);
@@ -1484,38 +1485,43 @@ pub fn definesFromTransformOptions(
}
if (behavior != .load_all_without_inlining) {
const quoted_node_env: string = brk: {
if (NODE_ENV) |node_env| {
if (node_env.len > 0) {
if ((strings.startsWithChar(node_env, '"') and strings.endsWithChar(node_env, '"')) or
(strings.startsWithChar(node_env, '\'') and strings.endsWithChar(node_env, '\'')))
{
break :brk node_env;
}
// Don't inline process.env.NODE_ENV/BUN_ENV for standalone executables (--compile),
// since the compiled binary should read these from the runtime environment.
// User-specified --define overrides are still respected (already in user_defines).
if (!is_standalone) {
const quoted_node_env: string = brk: {
if (NODE_ENV) |node_env| {
if (node_env.len > 0) {
if ((strings.startsWithChar(node_env, '"') and strings.endsWithChar(node_env, '"')) or
(strings.startsWithChar(node_env, '\'') and strings.endsWithChar(node_env, '\'')))
{
break :brk node_env;
}
// avoid allocating if we can
if (strings.eqlComptime(node_env, "production")) {
break :brk "\"production\"";
} else if (strings.eqlComptime(node_env, "development")) {
break :brk "\"development\"";
} else if (strings.eqlComptime(node_env, "test")) {
break :brk "\"test\"";
} else {
break :brk try std.fmt.allocPrint(allocator, "\"{s}\"", .{node_env});
// avoid allocating if we can
if (strings.eqlComptime(node_env, "production")) {
break :brk "\"production\"";
} else if (strings.eqlComptime(node_env, "development")) {
break :brk "\"development\"";
} else if (strings.eqlComptime(node_env, "test")) {
break :brk "\"test\"";
} else {
break :brk try std.fmt.allocPrint(allocator, "\"{s}\"", .{node_env});
}
}
}
}
break :brk "\"development\"";
};
break :brk "\"development\"";
};
_ = try user_defines.getOrPutValue(
"process.env.NODE_ENV",
quoted_node_env,
);
_ = try user_defines.getOrPutValue(
"process.env.BUN_ENV",
quoted_node_env,
);
_ = try user_defines.getOrPutValue(
"process.env.NODE_ENV",
quoted_node_env,
);
_ = try user_defines.getOrPutValue(
"process.env.BUN_ENV",
quoted_node_env,
);
}
// Automatically set `process.browser` to `true` for browsers and false for node+js
// This enables some extra dead code elimination
@@ -1932,6 +1938,7 @@ pub const BundleOptions = struct {
},
this.drop,
this.dead_code_elimination and this.minify_syntax,
this.compile,
);
this.defines_loaded = true;
}

View File

@@ -0,0 +1,109 @@
import { describe, expect, test } from "bun:test";
import { mkdtempSync, rmSync, writeFileSync } from "fs";
import { bunEnv, bunExe } from "harness";
import { join } from "path";
// Use a directory on the root filesystem rather than /tmp (tmpfs) since
// debug-mode standalone binaries are ~1.3GB each.
function makeTempDir(prefix: string): string {
return mkdtempSync(join("/workspace", `.tmp-${prefix}-`));
}
describe("issue #26994 - NODE_ENV not baked into --compile output", () => {
test("process.env.NODE_ENV and BUN_ENV read from runtime environment", () => {
const dir = makeTempDir("issue-26994");
try {
writeFileSync(join(dir, "index.ts"), `console.log(process.env.NODE_ENV + "," + process.env.BUN_ENV);`);
const binPath = join(dir, "test-bin");
// Build standalone executable
const buildResult = Bun.spawnSync({
cmd: [bunExe(), "build", "--compile", join(dir, "index.ts"), "--outfile", binPath],
env: bunEnv,
stderr: "pipe",
});
expect(buildResult.stderr.toString()).toBe("");
expect(buildResult.exitCode).toBe(0);
// Run with NODE_ENV=production, BUN_ENV=production
const runProduction = Bun.spawnSync({
cmd: [binPath],
env: { ...bunEnv, NODE_ENV: "production", BUN_ENV: "production" },
stdout: "pipe",
stderr: "pipe",
});
expect(runProduction.stdout.toString().trim()).toBe("production,production");
expect(runProduction.exitCode).toBe(0);
// Run with NODE_ENV=development, BUN_ENV=staging
const runDev = Bun.spawnSync({
cmd: [binPath],
env: { ...bunEnv, NODE_ENV: "development", BUN_ENV: "staging" },
stdout: "pipe",
stderr: "pipe",
});
expect(runDev.stdout.toString().trim()).toBe("development,staging");
expect(runDev.exitCode).toBe(0);
// Run with NODE_ENV=test
const runTest = Bun.spawnSync({
cmd: [binPath],
env: { ...bunEnv, NODE_ENV: "test", BUN_ENV: "test" },
stdout: "pipe",
stderr: "pipe",
});
expect(runTest.stdout.toString().trim()).toBe("test,test");
expect(runTest.exitCode).toBe(0);
} finally {
rmSync(dir, { recursive: true, force: true });
}
}, 30_000);
test("NODE_ENV comparison works at runtime in compiled binary", () => {
const dir = makeTempDir("issue-26994-cond");
try {
writeFileSync(
join(dir, "index.ts"),
`
if (process.env.NODE_ENV === "production") {
console.log("prod mode");
} else {
console.log("dev mode");
}
`,
);
const binPath = join(dir, "test-bin");
// Build standalone executable
const buildResult = Bun.spawnSync({
cmd: [bunExe(), "build", "--compile", join(dir, "index.ts"), "--outfile", binPath],
env: bunEnv,
stderr: "pipe",
});
expect(buildResult.stderr.toString()).toBe("");
expect(buildResult.exitCode).toBe(0);
// Run with production
const runProd = Bun.spawnSync({
cmd: [binPath],
env: { ...bunEnv, NODE_ENV: "production" },
stdout: "pipe",
stderr: "pipe",
});
expect(runProd.stdout.toString().trim()).toBe("prod mode");
expect(runProd.exitCode).toBe(0);
// Run with development
const runDev = Bun.spawnSync({
cmd: [binPath],
env: { ...bunEnv, NODE_ENV: "development" },
stdout: "pipe",
stderr: "pipe",
});
expect(runDev.stdout.toString().trim()).toBe("dev mode");
expect(runDev.exitCode).toBe(0);
} finally {
rmSync(dir, { recursive: true, force: true });
}
}, 30_000);
});