mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 23:18:47 +00:00
Compare commits
1 Commits
dylan/pyth
...
claude/fix
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2358bdfb20 |
@@ -7,6 +7,11 @@ pub const StandaloneModuleGraph = struct {
|
||||
files: bun.StringArrayHashMap(File),
|
||||
entry_point_id: u32 = 0,
|
||||
compile_exec_argv: []const u8 = "",
|
||||
env_config: api.LoadedEnvConfig = .{
|
||||
.dotenv = .disable,
|
||||
.prefix = "",
|
||||
.defaults = .{ .keys = &.{}, .values = &.{} },
|
||||
},
|
||||
|
||||
// We never want to hit the filesystem for these files
|
||||
// We use the `/$bunfs/` prefix to indicate that it's a virtual path
|
||||
@@ -288,7 +293,11 @@ pub const StandaloneModuleGraph = struct {
|
||||
byte_count: usize = 0,
|
||||
modules_ptr: bun.StringPointer = .{},
|
||||
entry_point_id: u32 = 0,
|
||||
_padding1: u32 = 0, // Ensure compile_exec_argv_ptr is 8-byte aligned
|
||||
compile_exec_argv_ptr: bun.StringPointer = .{},
|
||||
dotenv_behavior: api.DotEnvBehavior = .disable,
|
||||
_padding2: u32 = 0, // Ensure dotenv_prefix_ptr is 8-byte aligned
|
||||
dotenv_prefix_ptr: bun.StringPointer = .{},
|
||||
};
|
||||
|
||||
const trailer = "\n---- Bun! ----\n";
|
||||
@@ -334,6 +343,11 @@ pub const StandaloneModuleGraph = struct {
|
||||
.files = modules,
|
||||
.entry_point_id = offsets.entry_point_id,
|
||||
.compile_exec_argv = sliceToZ(raw_bytes, offsets.compile_exec_argv_ptr),
|
||||
.env_config = .{
|
||||
.dotenv = offsets.dotenv_behavior,
|
||||
.prefix = sliceToZ(raw_bytes, offsets.dotenv_prefix_ptr),
|
||||
.defaults = .{ .keys = &.{}, .values = &.{} },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -349,7 +363,7 @@ pub const StandaloneModuleGraph = struct {
|
||||
return bytes[ptr.offset..][0..ptr.length :0];
|
||||
}
|
||||
|
||||
pub fn toBytes(allocator: std.mem.Allocator, prefix: []const u8, output_files: []const bun.options.OutputFile, output_format: bun.options.Format, compile_exec_argv: []const u8) ![]u8 {
|
||||
pub fn toBytes(allocator: std.mem.Allocator, prefix: []const u8, output_files: []const bun.options.OutputFile, output_format: bun.options.Format, compile_exec_argv: []const u8, dotenv_behavior: api.DotEnvBehavior, dotenv_prefix: []const u8) ![]u8 {
|
||||
var serialize_trace = bun.perf.trace("StandaloneModuleGraph.serialize");
|
||||
defer serialize_trace.end();
|
||||
|
||||
@@ -391,6 +405,7 @@ pub const StandaloneModuleGraph = struct {
|
||||
string_builder.cap += 16;
|
||||
string_builder.cap += @sizeOf(Offsets);
|
||||
string_builder.countZ(compile_exec_argv);
|
||||
string_builder.countZ(dotenv_prefix);
|
||||
|
||||
try string_builder.allocate(allocator);
|
||||
|
||||
@@ -497,6 +512,8 @@ pub const StandaloneModuleGraph = struct {
|
||||
.entry_point_id = @as(u32, @truncate(entry_point_id.?)),
|
||||
.modules_ptr = string_builder.appendCount(std.mem.sliceAsBytes(modules.items)),
|
||||
.compile_exec_argv_ptr = string_builder.appendCountZ(compile_exec_argv),
|
||||
.dotenv_behavior = dotenv_behavior,
|
||||
.dotenv_prefix_ptr = string_builder.appendCountZ(dotenv_prefix),
|
||||
.byte_count = string_builder.len,
|
||||
};
|
||||
|
||||
@@ -949,8 +966,10 @@ pub const StandaloneModuleGraph = struct {
|
||||
windows_options: bun.options.WindowsOptions,
|
||||
compile_exec_argv: []const u8,
|
||||
self_exe_path: ?[]const u8,
|
||||
dotenv_behavior: api.DotEnvBehavior,
|
||||
dotenv_prefix: []const u8,
|
||||
) !CompileResult {
|
||||
const bytes = toBytes(allocator, module_prefix, output_files, output_format, compile_exec_argv) catch |err| {
|
||||
const bytes = toBytes(allocator, module_prefix, output_files, output_format, compile_exec_argv, dotenv_behavior, dotenv_prefix) catch |err| {
|
||||
return CompileResult.fail(std.fmt.allocPrint(allocator, "failed to generate module graph bytes: {s}", .{@errorName(err)}) catch "failed to generate module graph bytes");
|
||||
};
|
||||
if (bytes.len == 0) return CompileResult.fail("no output files to bundle");
|
||||
@@ -1527,3 +1546,4 @@ const macho = bun.macho;
|
||||
const pe = bun.pe;
|
||||
const strings = bun.strings;
|
||||
const Schema = bun.schema.api;
|
||||
const api = Schema;
|
||||
|
||||
@@ -81,12 +81,16 @@ pub const Run = struct {
|
||||
.unspecified => {},
|
||||
}
|
||||
|
||||
b.options.env.behavior = .load_all_without_inlining;
|
||||
b.options.env.behavior = graph.env_config.dotenv;
|
||||
b.options.env.prefix = graph.env_config.prefix;
|
||||
|
||||
b.configureDefines() catch {
|
||||
failWithBuildError(vm);
|
||||
};
|
||||
|
||||
// Load .env files based on the configured behavior
|
||||
b.runEnvLoader(false) catch {};
|
||||
|
||||
AsyncHTTP.loadEnv(vm.allocator, vm.log, b.env);
|
||||
|
||||
vm.loadExtraEnvAndSourceCodePrinter();
|
||||
|
||||
@@ -57,6 +57,8 @@ pub const JSBundler = struct {
|
||||
windows_description: OwnedString = OwnedString.initEmpty(bun.default_allocator),
|
||||
windows_copyright: OwnedString = OwnedString.initEmpty(bun.default_allocator),
|
||||
outfile: OwnedString = OwnedString.initEmpty(bun.default_allocator),
|
||||
env_behavior: api.DotEnvBehavior = .disable,
|
||||
env_prefix: OwnedString = OwnedString.initEmpty(bun.default_allocator),
|
||||
|
||||
pub fn fromJS(globalThis: *jsc.JSGlobalObject, config: jsc.JSValue, allocator: std.mem.Allocator, compile_target: ?CompileTarget) JSError!?CompileOptions {
|
||||
var this = CompileOptions{
|
||||
@@ -69,6 +71,7 @@ pub const JSBundler = struct {
|
||||
.windows_description = OwnedString.initEmpty(allocator),
|
||||
.windows_copyright = OwnedString.initEmpty(allocator),
|
||||
.outfile = OwnedString.initEmpty(allocator),
|
||||
.env_prefix = OwnedString.initEmpty(allocator),
|
||||
.compile_target = compile_target orelse .{},
|
||||
};
|
||||
errdefer this.deinit();
|
||||
@@ -177,6 +180,33 @@ pub const JSBundler = struct {
|
||||
try this.outfile.appendSliceExact(slice.slice());
|
||||
}
|
||||
|
||||
if (try object.getTruthy(globalThis, "env")) |env| {
|
||||
if (env.isString()) {
|
||||
var slice = try env.toSlice(globalThis, bun.default_allocator);
|
||||
defer slice.deinit();
|
||||
const env_str = slice.slice();
|
||||
|
||||
if (bun.strings.indexOfChar(env_str, '*')) |asterisk| {
|
||||
if (asterisk == 0) {
|
||||
this.env_behavior = .load_all;
|
||||
} else {
|
||||
this.env_behavior = .prefix;
|
||||
try this.env_prefix.appendSliceExact(env_str[0..asterisk]);
|
||||
}
|
||||
} else if (bun.strings.eqlComptime(env_str, "inline") or bun.strings.eqlComptime(env_str, "1")) {
|
||||
this.env_behavior = .load_all;
|
||||
} else if (bun.strings.eqlComptime(env_str, "disable") or bun.strings.eqlComptime(env_str, "0")) {
|
||||
this.env_behavior = .disable;
|
||||
} else {
|
||||
return globalThis.throwInvalidArguments("Expected env to be 'inline', 'disable', or a prefix with a '*' character", .{});
|
||||
}
|
||||
} else if (env.isBoolean()) {
|
||||
this.env_behavior = if (env.toBoolean()) .load_all else .disable;
|
||||
} else {
|
||||
return globalThis.throwInvalidArguments("Expected env to be a boolean or string", .{});
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -190,6 +220,7 @@ pub const JSBundler = struct {
|
||||
this.windows_description.deinit();
|
||||
this.windows_copyright.deinit();
|
||||
this.outfile.deinit();
|
||||
this.env_prefix.deinit();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -691,6 +722,13 @@ pub const JSBundler = struct {
|
||||
try this.define.insert(key, value);
|
||||
}
|
||||
|
||||
// Use compile-specific env settings if specified, otherwise use top-level env settings
|
||||
this.env_behavior = compile.env_behavior;
|
||||
if (!compile.env_prefix.isEmpty()) {
|
||||
this.env_prefix.deinit();
|
||||
this.env_prefix = try compile.env_prefix.clone();
|
||||
}
|
||||
|
||||
const base_public_path = bun.StandaloneModuleGraph.targetBasePublicPath(this.compile.?.compile_target.os, "root/");
|
||||
try this.public_path.append(base_public_path);
|
||||
|
||||
|
||||
@@ -2043,6 +2043,8 @@ pub const BundleV2 = struct {
|
||||
compile_options.executable_path.slice()
|
||||
else
|
||||
null,
|
||||
compile_options.env_behavior,
|
||||
compile_options.env_prefix.slice(),
|
||||
) catch |err| {
|
||||
return bun.StandaloneModuleGraph.CompileResult.fail(bun.handleOom(std.fmt.allocPrint(bun.default_allocator, "{s}", .{@errorName(err)})));
|
||||
};
|
||||
|
||||
@@ -429,6 +429,8 @@ pub const BuildCommand = struct {
|
||||
ctx.bundler_options.windows,
|
||||
ctx.bundler_options.compile_exec_argv orelse "",
|
||||
null,
|
||||
this_transpiler.options.env.behavior,
|
||||
this_transpiler.options.env.prefix,
|
||||
) catch |err| {
|
||||
Output.printErrorln("failed to create executable: {s}", .{@errorName(err)});
|
||||
Global.exit(1);
|
||||
|
||||
104
test/bundler/bundler_compile_env.test.ts
Normal file
104
test/bundler/bundler_compile_env.test.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import { describe } from "bun:test";
|
||||
import { itBundled } from "./expectBundled";
|
||||
|
||||
describe("bundler", () => {
|
||||
itBundled("compile/DotEnvDisabledByDefault", {
|
||||
compile: true,
|
||||
files: {
|
||||
"/entry.ts": /* js */ `
|
||||
console.log(process.env.MY_SECRET_VAR || "not set");
|
||||
`,
|
||||
"/.env": `MY_SECRET_VAR=secret_value`,
|
||||
},
|
||||
run: { stdout: "not set" },
|
||||
});
|
||||
|
||||
itBundled("compile/DotEnvWithEnvInlineAPI", {
|
||||
compile: {
|
||||
env: "inline",
|
||||
},
|
||||
backend: "api",
|
||||
files: {
|
||||
"/entry.ts": /* js */ `
|
||||
console.log(process.env.MY_SECRET_VAR || "not set");
|
||||
`,
|
||||
"/.env": `MY_SECRET_VAR=secret_value`,
|
||||
},
|
||||
run: { stdout: "secret_value" },
|
||||
});
|
||||
|
||||
itBundled("compile/DotEnvWithEnvAsteriskAPI", {
|
||||
compile: {
|
||||
env: "*",
|
||||
},
|
||||
backend: "api",
|
||||
files: {
|
||||
"/entry.ts": /* js */ `
|
||||
console.log(process.env.MY_SECRET_VAR || "not set");
|
||||
`,
|
||||
"/.env": `MY_SECRET_VAR=secret_value`,
|
||||
},
|
||||
run: { stdout: "secret_value" },
|
||||
});
|
||||
|
||||
itBundled("compile/DotEnvWithEnvPrefixAPI", {
|
||||
compile: {
|
||||
env: "PUBLIC_*",
|
||||
},
|
||||
backend: "api",
|
||||
files: {
|
||||
"/entry.ts": /* js */ `
|
||||
console.log("PUBLIC:", process.env.PUBLIC_VAR || "not set");
|
||||
console.log("PRIVATE:", process.env.PRIVATE_VAR || "not set");
|
||||
`,
|
||||
"/.env": `PUBLIC_VAR=public_value
|
||||
PRIVATE_VAR=private_value`,
|
||||
},
|
||||
run: {
|
||||
stdout: `PUBLIC: public_value
|
||||
PRIVATE: not set`,
|
||||
},
|
||||
});
|
||||
|
||||
itBundled("compile/DotEnvWithEnvTrueAPI", {
|
||||
compile: {
|
||||
env: true,
|
||||
},
|
||||
backend: "api",
|
||||
files: {
|
||||
"/entry.ts": /* js */ `
|
||||
console.log(process.env.MY_SECRET_VAR || "not set");
|
||||
`,
|
||||
"/.env": `MY_SECRET_VAR=secret_value`,
|
||||
},
|
||||
run: { stdout: "secret_value" },
|
||||
});
|
||||
|
||||
itBundled("compile/DotEnvWithEnvFalseAPI", {
|
||||
compile: {
|
||||
env: false,
|
||||
},
|
||||
backend: "api",
|
||||
files: {
|
||||
"/entry.ts": /* js */ `
|
||||
console.log(process.env.MY_SECRET_VAR || "not set");
|
||||
`,
|
||||
"/.env": `MY_SECRET_VAR=secret_value`,
|
||||
},
|
||||
run: { stdout: "not set" },
|
||||
});
|
||||
|
||||
itBundled("compile/DotEnvWithEnvDisableAPI", {
|
||||
compile: {
|
||||
env: "disable",
|
||||
},
|
||||
backend: "api",
|
||||
files: {
|
||||
"/entry.ts": /* js */ `
|
||||
console.log(process.env.MY_SECRET_VAR || "not set");
|
||||
`,
|
||||
"/.env": `MY_SECRET_VAR=secret_value`,
|
||||
},
|
||||
run: { stdout: "not set" },
|
||||
});
|
||||
});
|
||||
132
test/bundler/compile-dotenv.test.ts
Normal file
132
test/bundler/compile-dotenv.test.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { bunEnv, bunExe, normalizeBunSnapshot, tempDir } from "harness";
|
||||
import { join } from "path";
|
||||
|
||||
test("--compile should not load .env by default", async () => {
|
||||
using dir = tempDir("compile-dotenv-default", {
|
||||
"index.js": /* js */ `
|
||||
console.log(process.env.MY_SECRET_VAR || "not set");
|
||||
`,
|
||||
".env": `MY_SECRET_VAR=secret_value`,
|
||||
});
|
||||
|
||||
// Compile the executable
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "build", "--compile", "index.js"],
|
||||
env: bunEnv,
|
||||
cwd: String(dir),
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stderr).not.toContain("error");
|
||||
expect(stderr).not.toContain("panic");
|
||||
|
||||
// Run the compiled executable
|
||||
await using execProc = Bun.spawn({
|
||||
cmd: [join(String(dir), "index")],
|
||||
env: bunEnv,
|
||||
cwd: String(dir),
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [execStdout, execStderr, execExitCode] = await Promise.all([
|
||||
execProc.stdout.text(),
|
||||
execProc.stderr.text(),
|
||||
execProc.exited,
|
||||
]);
|
||||
|
||||
expect(execExitCode).toBe(0);
|
||||
expect(normalizeBunSnapshot(execStdout, dir)).toMatchInlineSnapshot(`"not set"`);
|
||||
});
|
||||
|
||||
test("--compile with --env should load .env", async () => {
|
||||
using dir = tempDir("compile-dotenv-with-flag", {
|
||||
"index.js": /* js */ `
|
||||
console.log(process.env.MY_SECRET_VAR || "not set");
|
||||
`,
|
||||
".env": `MY_SECRET_VAR=secret_value`,
|
||||
});
|
||||
|
||||
// Compile the executable with --env
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "build", "--compile", "--env=*", "index.js"],
|
||||
env: bunEnv,
|
||||
cwd: String(dir),
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stderr).not.toContain("error");
|
||||
|
||||
// Run the compiled executable
|
||||
await using execProc = Bun.spawn({
|
||||
cmd: [join(String(dir), "index")],
|
||||
env: bunEnv,
|
||||
cwd: String(dir),
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [execStdout, execStderr, execExitCode] = await Promise.all([
|
||||
execProc.stdout.text(),
|
||||
execProc.stderr.text(),
|
||||
execProc.exited,
|
||||
]);
|
||||
|
||||
expect(execExitCode).toBe(0);
|
||||
expect(normalizeBunSnapshot(execStdout, dir)).toMatchInlineSnapshot(`"secret_value"`);
|
||||
});
|
||||
|
||||
test("--compile with --env prefix should only load matching vars", async () => {
|
||||
using dir = tempDir("compile-dotenv-prefix", {
|
||||
"index.js": /* js */ `
|
||||
console.log("PUBLIC:", process.env.PUBLIC_VAR || "not set");
|
||||
console.log("PRIVATE:", process.env.PRIVATE_VAR || "not set");
|
||||
`,
|
||||
".env": `PUBLIC_VAR=public_value
|
||||
PRIVATE_VAR=private_value`,
|
||||
});
|
||||
|
||||
// Compile the executable with --env prefix
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "build", "--compile", "--env=PUBLIC_*", "index.js"],
|
||||
env: bunEnv,
|
||||
cwd: String(dir),
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stderr).not.toContain("error");
|
||||
|
||||
// Run the compiled executable
|
||||
await using execProc = Bun.spawn({
|
||||
cmd: [join(String(dir), "index")],
|
||||
env: bunEnv,
|
||||
cwd: String(dir),
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [execStdout, execStderr, execExitCode] = await Promise.all([
|
||||
execProc.stdout.text(),
|
||||
execProc.stderr.text(),
|
||||
execProc.exited,
|
||||
]);
|
||||
|
||||
expect(execExitCode).toBe(0);
|
||||
expect(normalizeBunSnapshot(execStdout, dir)).toMatchInlineSnapshot(`
|
||||
"PUBLIC: public_value
|
||||
PRIVATE: not set"
|
||||
`);
|
||||
});
|
||||
Reference in New Issue
Block a user