diff --git a/src/bun.js.zig b/src/bun.js.zig index d7abd36838..51ffd58731 100644 --- a/src/bun.js.zig +++ b/src/bun.js.zig @@ -3,6 +3,17 @@ pub const webcore = @import("./bun.js/webcore.zig"); pub const api = @import("./bun.js/api.zig"); pub const bindgen = @import("./bun.js/bindgen.zig"); +pub fn applyStandaloneRuntimeFlags(b: *bun.Transpiler, graph: *const bun.StandaloneModuleGraph) void { + b.options.env.disable_default_env_files = graph.flags.disable_default_env_files; + b.options.env.behavior = if (graph.flags.disable_default_env_files) + .disable + else + .load_all_without_inlining; + + b.resolver.opts.load_tsconfig_json = !graph.flags.disable_autoload_tsconfig; + b.resolver.opts.load_package_json = !graph.flags.disable_autoload_package_json; +} + pub const Run = struct { ctx: Command.Context, vm: *VirtualMachine, @@ -82,18 +93,7 @@ pub const Run = struct { .unspecified => {}, } - // If .env loading is disabled, only load process env vars - // Otherwise, load all .env files - if (graph_ptr.flags.disable_default_env_files) { - b.options.env.behavior = .disable; - } else { - b.options.env.behavior = .load_all_without_inlining; - } - - // Control loading of tsconfig.json and package.json at runtime - // By default, these are disabled for standalone executables - b.resolver.opts.load_tsconfig_json = !graph_ptr.flags.disable_autoload_tsconfig; - b.resolver.opts.load_package_json = !graph_ptr.flags.disable_autoload_package_json; + applyStandaloneRuntimeFlags(b, graph_ptr); b.configureDefines() catch { failWithBuildError(vm); diff --git a/src/bun.js/web_worker.zig b/src/bun.js/web_worker.zig index 7714d889d1..781009e788 100644 --- a/src/bun.js/web_worker.zig +++ b/src/bun.js/web_worker.zig @@ -325,16 +325,30 @@ pub fn start( } this.arena = bun.MimallocArena.init(); + const allocator = this.arena.?.allocator(); + + const map = try allocator.create(bun.DotEnv.Map); + map.* = try this.parent.transpiler.env.map.cloneWithAllocator(allocator); + + const loader = try allocator.create(bun.DotEnv.Loader); + loader.* = bun.DotEnv.Loader.init(map, allocator); + var vm = try jsc.VirtualMachine.initWorker(this, .{ - .allocator = this.arena.?.allocator(), + .allocator = allocator, .args = transform_options, + .env_loader = loader, .store_fd = this.store_fd, .graph = this.parent.standalone_module_graph, }); - vm.allocator = this.arena.?.allocator(); + vm.allocator = allocator; vm.arena = &this.arena.?; var b = &vm.transpiler; + b.resolver.env_loader = b.env; + + if (this.parent.standalone_module_graph) |graph| { + bun.bun_js.applyStandaloneRuntimeFlags(b, graph); + } b.configureDefines() catch { this.flushLogs(); @@ -342,16 +356,6 @@ pub fn start( return; }; - // TODO: we may have to clone other parts of vm state. this will be more - // important when implementing vm.deinit() - const map = try vm.allocator.create(bun.DotEnv.Map); - map.* = try vm.transpiler.env.map.cloneWithAllocator(vm.allocator); - - const loader = try vm.allocator.create(bun.DotEnv.Loader); - loader.* = bun.DotEnv.Loader.init(map, vm.allocator); - - vm.transpiler.env = loader; - vm.loadExtraEnvAndSourceCodePrinter(); vm.is_main_thread = false; jsc.VirtualMachine.is_main_thread_vm = false; diff --git a/test/bundler/bundler_compile_autoload.test.ts b/test/bundler/bundler_compile_autoload.test.ts index 68ec2c9c56..db0f6e860a 100644 --- a/test/bundler/bundler_compile_autoload.test.ts +++ b/test/bundler/bundler_compile_autoload.test.ts @@ -168,6 +168,40 @@ console.log("PRELOAD"); }, }); + // Regression test: standalone workers must not load .env when autoloadDotenv is disabled + itBundled("compile/AutoloadDotenvDisabledWorkerCLI", { + compile: { + autoloadDotenv: false, + }, + backend: "cli", + files: { + "/entry.ts": /* js */ ` + import { rmSync } from "fs"; + + rmSync("./worker.ts", { force: true }); + + const worker = new Worker("./worker.ts"); + console.log(await new Promise(resolve => { + worker.onmessage = event => resolve(event.data); + })); + worker.terminate(); + `, + "/worker.ts": /* js */ ` + postMessage(process.env.TEST_VAR || "not found"); + `, + }, + entryPointsRaw: ["./entry.ts", "./worker.ts"], + outfile: "dist/out", + runtimeFiles: { + "/.env": `TEST_VAR=from_dotenv`, + }, + run: { + stdout: "not found", + file: "dist/out", + setCwd: true, + }, + }); + // Test CLI backend with autoloadDotenv: true itBundled("compile/AutoloadDotenvEnabledCLI", { compile: { diff --git a/test/regression/issue/27431.test.ts b/test/regression/issue/27431.test.ts new file mode 100644 index 0000000000..815a5feb5d --- /dev/null +++ b/test/regression/issue/27431.test.ts @@ -0,0 +1,50 @@ +import { expect, test } from "bun:test"; +import { bunEnv, bunExe, isWindows, tempDir } from "harness"; +import { join } from "path"; + +test.if(isWindows)("standalone worker does not crash when autoloadDotenv is disabled and .env exists", async () => { + const target = process.arch === "arm64" ? "bun-windows-aarch64" : "bun-windows-x64"; + + using dir = tempDir("issue-27431", { + ".env": "TEST_VAR=from_dotenv\n", + "entry.ts": 'console.log(process.env.TEST_VAR || "not found")\nnew Worker("./worker.ts")\n', + "worker.ts": "", + "build.ts": ` + await Bun.build({ + entrypoints: ["./entry.ts", "./worker.ts"], + compile: { + autoloadDotenv: false, + target: "${target}", + outfile: "./app.exe", + }, + }); + `, + }); + + await using build = Bun.spawn({ + cmd: [bunExe(), join(String(dir), "build.ts")], + env: bunEnv, + cwd: String(dir), + stdout: "pipe", + stderr: "pipe", + }); + + const [, buildStderr, buildExitCode] = await Promise.all([build.stdout.text(), build.stderr.text(), build.exited]); + + expect(buildExitCode).toBe(0); + expect(buildStderr).toBe(""); + + await using proc = Bun.spawn({ + cmd: [join(String(dir), "app.exe")], + env: bunEnv, + cwd: String(dir), + stdout: "pipe", + stderr: "pipe", + }); + + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + + expect(stdout).toContain("not found"); + expect(exitCode).toBe(0); + expect(stderr).toBe(""); +});