diff options
author | 2023-05-24 18:52:50 -0700 | |
---|---|---|
committer | 2023-05-24 18:52:50 -0700 | |
commit | 63740a382bbf65fc20fb9c1f1211fbc9285bd9e5 (patch) | |
tree | 7cdf3bc65e47ccc62196504798512f28e1d79562 | |
parent | ed1f62ffffdd02db761513e8ac2561afa4b5a8dc (diff) | |
download | bun-63740a382bbf65fc20fb9c1f1211fbc9285bd9e5.tar.gz bun-63740a382bbf65fc20fb9c1f1211fbc9285bd9e5.tar.zst bun-63740a382bbf65fc20fb9c1f1211fbc9285bd9e5.zip |
Load `.env.test`, set NODE_ENV=test in `bun test`, load `.env.{test,production,development}.local` (#3037)
* Support `.env.test` & `.env.{test,production,development}.local`
* Fix bug preventing inlining of process.env.NODE_ENV by default
* Update env_loader.zig
* add env tests
---------
Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
Co-authored-by: dave caruso <me@paperdave.net>
-rw-r--r-- | src/bun_js.zig | 2 | ||||
-rw-r--r-- | src/bundler.zig | 33 | ||||
-rw-r--r-- | src/bundler/bundle_v2.zig | 21 | ||||
-rw-r--r-- | src/cli/run_command.zig | 4 | ||||
-rw-r--r-- | src/cli/test_command.zig | 4 | ||||
-rw-r--r-- | src/defines.zig | 1 | ||||
-rw-r--r-- | src/env_loader.zig | 106 | ||||
-rw-r--r-- | src/install/install.zig | 2 | ||||
-rw-r--r-- | src/install/lockfile.zig | 2 | ||||
-rw-r--r-- | src/js_parser.zig | 10 | ||||
-rw-r--r-- | src/options.zig | 22 | ||||
-rw-r--r-- | test/cli/run/env.test.ts | 256 |
12 files changed, 390 insertions, 73 deletions
diff --git a/src/bun_js.zig b/src/bun_js.zig index 00cb51d20..12876cae8 100644 --- a/src/bun_js.zig +++ b/src/bun_js.zig @@ -211,6 +211,8 @@ pub const Run = struct { } } + vm.bundler.env.loadTracy(); + var callback = OpaqueWrap(Run, Run.start); vm.global.vm().holdAPILock(&run, callback); } diff --git a/src/bundler.zig b/src/bundler.zig index fd4e11d9a..9650f7f60 100644 --- a/src/bundler.zig +++ b/src/bundler.zig @@ -510,14 +510,17 @@ pub const Bundler = struct { // Process always has highest priority. const was_production = this.options.production; this.env.loadProcess(); - if (!was_production and this.env.isProduction()) { + const has_production_env = this.env.isProduction(); + if (!was_production and has_production_env) { this.options.setProduction(true); } - if (this.options.production) { - try this.env.load(&this.fs.fs, dir, false); + if (!has_production_env and this.options.isTest()) { + try this.env.load(&this.fs.fs, dir, .@"test"); + } else if (this.options.production) { + try this.env.load(&this.fs.fs, dir, .production); } else { - try this.env.load(&this.fs.fs, dir, true); + try this.env.load(&this.fs.fs, dir, .development); } }, .disable => { @@ -579,13 +582,15 @@ pub const Bundler = struct { if (NODE_ENV.len > 0 and NODE_ENV[0].data.value == .e_string and NODE_ENV[0].data.value.e_string.eqlComptime("production")) { this.options.production = true; - if (strings.eqlComptime(this.options.jsx.package_name, "react")) { - if (this.options.jsx_optimization_inline == null) { - this.options.jsx_optimization_inline = true; - } + if (this.options.target.isBun()) { + if (strings.eqlComptime(this.options.jsx.package_name, "react")) { + if (this.options.jsx_optimization_inline == null) { + this.options.jsx_optimization_inline = true; + } - if (this.options.jsx_optimization_hoist == null and (this.options.jsx_optimization_inline orelse false)) { - this.options.jsx_optimization_hoist = true; + if (this.options.jsx_optimization_hoist == null and (this.options.jsx_optimization_inline orelse false)) { + this.options.jsx_optimization_hoist = true; + } } } } @@ -1305,14 +1310,6 @@ pub const Bundler = struct { const path = this_parse.path; const loader = this_parse.loader; - if (FeatureFlags.tracing) { - bundler.timer.reset(); - } - defer { - if (FeatureFlags.tracing) { - bundler.elapsed += bundler.timer.read(); - } - } var input_fd: ?StoredFileDescriptorType = null; const source: logger.Source = brk: { diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index 3873c6b12..b0012c25c 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -692,27 +692,8 @@ pub const BundleV2 = struct { thread_pool: ?*ThreadPoolLib, heap: ?ThreadlocalArena, ) !*BundleV2 { - tracy: { - if (bundler.env.get("BUN_TRACY") != null) { - if (!bun.tracy.init()) { - Output.prettyErrorln("Failed to load Tracy. Is it installed in your include path?", .{}); - Output.flush(); - break :tracy; - } - - bun.tracy.start(); - - if (!bun.tracy.isConnected()) { - std.time.sleep(std.time.ns_per_ms * 10); - } + bundler.env.loadTracy(); - if (!bun.tracy.isConnected()) { - Output.prettyErrorln("Tracy is not connected. Is Tracy running on your computer?", .{}); - Output.flush(); - break :tracy; - } - } - } var generator = try allocator.create(BundleV2); bundler.options.mark_builtins_as_external = bundler.options.target.isBun() or bundler.options.target == .node; bundler.resolver.opts.mark_builtins_as_external = bundler.options.target.isBun() or bundler.options.target == .node; diff --git a/src/cli/run_command.zig b/src/cli/run_command.zig index 0077e8179..17b12dcfb 100644 --- a/src/cli/run_command.zig +++ b/src/cli/run_command.zig @@ -506,9 +506,9 @@ pub const RunCommand = struct { if (root_dir_info.getEntries(0)) |dir| { // Run .env again if it exists in a parent dir if (this_bundler.options.production) { - this_bundler.env.load(&this_bundler.fs.fs, dir, false) catch {}; + this_bundler.env.load(&this_bundler.fs.fs, dir, .production) catch {}; } else { - this_bundler.env.load(&this_bundler.fs.fs, dir, true) catch {}; + this_bundler.env.load(&this_bundler.fs.fs, dir, .development) catch {}; } } } diff --git a/src/cli/test_command.zig b/src/cli/test_command.zig index cf5c4fc49..d4a26d0f8 100644 --- a/src/cli/test_command.zig +++ b/src/cli/test_command.zig @@ -454,9 +454,9 @@ pub const TestCommand = struct { ); vm.argv = ctx.passthrough; vm.preload = ctx.preloads; + vm.bundler.options.rewrite_jest_for_tests = true; try vm.bundler.configureDefines(); - vm.bundler.options.rewrite_jest_for_tests = true; vm.loadExtraEnv(); vm.is_main_thread = true; @@ -467,9 +467,11 @@ pub const TestCommand = struct { var TZ_NAME: string = // We use the string "Etc/UTC" instead of "UTC" so there is no normalization difference. "Etc/UTC"; + if (vm.bundler.env.get("TZ")) |tz| { TZ_NAME = tz; } + if (TZ_NAME.len > 0) { _ = vm.global.setTimeZone(&JSC.ZigString.init(TZ_NAME)); } diff --git a/src/defines.zig b/src/defines.zig index 6584f1f7f..45ebffe05 100644 --- a/src/defines.zig +++ b/src/defines.zig @@ -152,6 +152,7 @@ pub const DefineData = struct { user_defines.putAssumeCapacity(entry.key_ptr.*, DefineData{ .value = data, + .can_be_removed_if_unused = @as(js_ast.Expr.Tag, data).isPrimitiveLiteral(), }); } } diff --git a/src/env_loader.zig b/src/env_loader.zig index 30c9dc2cd..7d506a04e 100644 --- a/src/env_loader.zig +++ b/src/env_loader.zig @@ -403,6 +403,10 @@ pub const Loader = struct { @".env.local": ?logger.Source = null, @".env.development": ?logger.Source = null, @".env.production": ?logger.Source = null, + @".env.test": ?logger.Source = null, + @".env.development.local": ?logger.Source = null, + @".env.production.local": ?logger.Source = null, + @".env.test.local": ?logger.Source = null, @".env": ?logger.Source = null, quiet: bool = false, @@ -437,6 +441,30 @@ pub const Loader = struct { this.map.get("bamboo.buildKey")) != null; } + pub fn loadTracy(this: *const Loader) void { + tracy: { + if (this.get("BUN_TRACY") != null) { + if (!bun.tracy.init()) { + Output.prettyErrorln("Failed to load Tracy. Is it installed in your include path?", .{}); + Output.flush(); + break :tracy; + } + + bun.tracy.start(); + + if (!bun.tracy.isConnected()) { + std.time.sleep(std.time.ns_per_ms * 10); + } + + if (!bun.tracy.isConnected()) { + Output.prettyErrorln("Tracy is not connected. Is Tracy running on your computer?", .{}); + Output.flush(); + break :tracy; + } + } + } + } + pub fn getHttpProxy(this: *Loader, url: URL) ?URL { // TODO: When Web Worker support is added, make sure to intern these strings var http_proxy: ?URL = null; @@ -711,28 +739,60 @@ pub const Loader = struct { this: *Loader, fs: *Fs.FileSystem.RealFS, dir: *Fs.FileSystem.DirEntry, - comptime development: bool, + comptime suffix: enum { development, production, @"test" }, ) !void { const start = std.time.nanoTimestamp(); var dir_handle: std.fs.Dir = std.fs.cwd(); - if (dir.hasComptimeQuery(".env.local")) { - try this.loadEnvFile(fs, dir_handle, ".env.local", false); - Analytics.Features.dotenv = true; + switch (comptime suffix) { + .development => { + if (dir.hasComptimeQuery(".env.development.local")) { + try this.loadEnvFile(fs, dir_handle, ".env.development.local", false); + Analytics.Features.dotenv = true; + } + }, + .production => { + if (dir.hasComptimeQuery(".env.production.local")) { + try this.loadEnvFile(fs, dir_handle, ".env.production.local", false); + Analytics.Features.dotenv = true; + } + }, + .@"test" => { + if (dir.hasComptimeQuery(".env.test.local")) { + try this.loadEnvFile(fs, dir_handle, ".env.test.local", false); + Analytics.Features.dotenv = true; + } + }, } - if (comptime development) { - if (dir.hasComptimeQuery(".env.development")) { - try this.loadEnvFile(fs, dir_handle, ".env.development", false); - Analytics.Features.dotenv = true; - } - } else { - if (dir.hasComptimeQuery(".env.production")) { - try this.loadEnvFile(fs, dir_handle, ".env.production", false); + if (comptime suffix != .@"test") { + if (dir.hasComptimeQuery(".env.local")) { + try this.loadEnvFile(fs, dir_handle, ".env.local", false); Analytics.Features.dotenv = true; } } + switch (comptime suffix) { + .development => { + if (dir.hasComptimeQuery(".env.development")) { + try this.loadEnvFile(fs, dir_handle, ".env.development", false); + Analytics.Features.dotenv = true; + } + }, + .production => { + if (dir.hasComptimeQuery(".env.production")) { + try this.loadEnvFile(fs, dir_handle, ".env.production", false); + Analytics.Features.dotenv = true; + } + }, + .@"test" => { + if (dir.hasComptimeQuery(".env.test")) { + try this.loadEnvFile(fs, dir_handle, ".env.test", false); + Analytics.Features.dotenv = true; + } + }, + } + if (dir.hasComptimeQuery(".env")) { try this.loadEnvFile(fs, dir_handle, ".env", false); Analytics.Features.dotenv = true; @@ -743,24 +803,36 @@ pub const Loader = struct { pub fn printLoaded(this: *Loader, start: i128) void { const count = + @intCast(u8, @boolToInt(this.@".env.development.local" != null)) + + @intCast(u8, @boolToInt(this.@".env.production.local" != null)) + + @intCast(u8, @boolToInt(this.@".env.test.local" != null)) + @intCast(u8, @boolToInt(this.@".env.local" != null)) + @intCast(u8, @boolToInt(this.@".env.development" != null)) + @intCast(u8, @boolToInt(this.@".env.production" != null)) + + @intCast(u8, @boolToInt(this.@".env.test" != null)) + @intCast(u8, @boolToInt(this.@".env" != null)); if (count == 0) return; const elapsed = @intToFloat(f64, (std.time.nanoTimestamp() - start)) / std.time.ns_per_ms; const all = [_]string{ + ".env.development.local", + ".env.production.local", + ".env.test.local", ".env.local", ".env.development", ".env.production", + ".env.test", ".env", }; const loaded = [_]bool{ + this.@".env.development.local" != null, + this.@".env.production.local" != null, + this.@".env.test.local" != null, this.@".env.local" != null, this.@".env.development" != null, this.@".env.production" != null, + this.@".env.test" != null, this.@".env" != null, }; @@ -783,6 +855,7 @@ pub const Loader = struct { } pub fn loadEnvFile(this: *Loader, fs: *Fs.FileSystem.RealFS, dir: std.fs.Dir, comptime base: string, comptime override: bool) !void { + _ = fs; if (@field(this, base) != null) { return; } @@ -808,13 +881,7 @@ pub const Loader = struct { }, } }; - Fs.FileSystem.setMaxFd(file.handle); - - defer { - if (fs.needToCloseFiles()) { - file.close(); - } - } + defer file.close(); const stat = try file.stat(); if (stat.size == 0) { @@ -825,6 +892,7 @@ pub const Loader = struct { var buf = try this.allocator.allocSentinel(u8, stat.size, 0); errdefer this.allocator.free(buf); var contents = try file.readAll(buf); + // always sentinel buf.ptr[contents + 1] = 0; const source = logger.Source.initPathString(base, buf.ptr[0..contents :0]); diff --git a/src/install/install.zig b/src/install/install.zig index 0d0b8243d..40c14d30f 100644 --- a/src/install/install.zig +++ b/src/install/install.zig @@ -5086,7 +5086,7 @@ pub const PackageManager = struct { }; env.loadProcess(); - try env.load(&fs.fs, entries_option.entries, false); + try env.load(&fs.fs, entries_option.entries, .production); if (env.map.get("BUN_INSTALL_VERBOSE") != null) { PackageManager.verbose_install = true; diff --git a/src/install/lockfile.zig b/src/install/lockfile.zig index 8006d85bb..bef058bf1 100644 --- a/src/install/lockfile.zig +++ b/src/install/lockfile.zig @@ -918,7 +918,7 @@ pub const Printer = struct { }; env_loader.loadProcess(); - try env_loader.load(&fs.fs, entries_option.entries, false); + try env_loader.load(&fs.fs, entries_option.entries, .production); var log = logger.Log.init(allocator); try options.load( allocator, diff --git a/src/js_parser.zig b/src/js_parser.zig index a0f4defae..dea2f044f 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -19451,13 +19451,9 @@ fn NewParser_( return false; } - return - // TODO: figure out why this is needed when bundling - // The problem is all the top-level vars are getting removed when they're not actually side effect free - !p.source.index.isRuntime() and - // when there's actually no symbol by that name, we return Ref.None - // If a symbol had already existed by that name, we return .unbound - (result.ref.isNull() or p.symbols.items[result.ref.innerIndex()].kind == .unbound); + // when there's actually no symbol by that name, we return Ref.None + // If a symbol had already existed by that name, we return .unbound + return (result.ref.isNull() or p.symbols.items[result.ref.innerIndex()].kind == .unbound); } }, else => {}, diff --git a/src/options.zig b/src/options.zig index 6953a448f..4133e95f7 100644 --- a/src/options.zig +++ b/src/options.zig @@ -1464,6 +1464,10 @@ pub const BundleOptions = struct { /// So we have a list of packages which we know are safe to do this with. unwrap_commonjs_packages: []const string = &default_unwrap_commonjs_packages, + pub fn isTest(this: *const BundleOptions) bool { + return this.rewrite_jest_for_tests; + } + pub fn setProduction(this: *BundleOptions, value: bool) void { this.production = value; this.jsx.development = !value; @@ -1509,10 +1513,20 @@ pub const BundleOptions = struct { this.target, loader_, env, - if (loader_) |e| - e.map.get("BUN_ENV") orelse e.map.get("NODE_ENV") - else - null, + node_env: { + if (loader_) |e| + if (e.map.get("BUN_ENV") orelse e.map.get("NODE_ENV")) |env_| break :node_env env_; + + if (this.isTest()) { + break :node_env "\"test\""; + } + + if (this.production) { + break :node_env "\"production\""; + } + + break :node_env "\"development\""; + }, ); this.defines_loaded = true; } diff --git a/test/cli/run/env.test.ts b/test/cli/run/env.test.ts new file mode 100644 index 000000000..328162f0b --- /dev/null +++ b/test/cli/run/env.test.ts @@ -0,0 +1,256 @@ +import { describe, expect, test } from "bun:test"; +import os from "os"; +import fs from "fs"; +import path from "path"; +import { bunEnv, bunExe } from "harness"; + +function tempDirWithFiles(basename: string, files: Record<string, string>) { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), basename + "_")); + for (const [name, contents] of Object.entries(files)) { + fs.writeFileSync(path.join(dir, name), contents); + } + return dir; +} + +function bunRun(file: string, env?: Record<string, string>) { + const result = Bun.spawnSync([bunExe(), file], { + cwd: path.dirname(file), + env: { + ...bunEnv, + NODE_ENV: undefined, + ...env, + }, + }); + if (!result.success) throw new Error(result.stderr.toString("utf8")); + return { + stdout: result.stdout.toString("utf8").trim(), + stderr: result.stderr.toString("utf8").trim(), + }; +} +function bunTest(file: string, env?: Record<string, string>) { + const result = Bun.spawnSync([bunExe(), "test", path.basename(file)], { + cwd: path.dirname(file), + env: { + ...bunEnv, + NODE_ENV: undefined, + ...env, + }, + }); + if (!result.success) throw new Error(result.stderr.toString("utf8")); + return { + stdout: result.stdout.toString("utf8").trim(), + stderr: result.stderr.toString("utf8").trim(), + }; +} + +describe(".env file is loaded", () => { + test(".env", () => { + const dir = tempDirWithFiles("dotenv", { + ".env": "FOO=bar\n", + "index.ts": "console.log(process.env.FOO);", + }); + const { stdout } = bunRun(`${dir}/index.ts`); + expect(stdout).toBe("bar"); + }); + test(".env.local", () => { + const dir = tempDirWithFiles("dotenv", { + ".env": "FOO=fail\nBAR=baz\n", + ".env.local": "FOO=bar\n", + "index.ts": "console.log(process.env.FOO, process.env.BAR);", + }); + const { stdout } = bunRun(`${dir}/index.ts`); + expect(stdout).toBe("bar baz"); + }); + test(".env.development (NODE_ENV=undefined)", () => { + const dir = tempDirWithFiles("dotenv", { + ".env": "FOO=fail\nBAR=baz\n", + ".env.development": "FOO=bar\n", + ".env.local": "LOCAL=true\n", + "index.ts": "console.log(process.env.FOO, process.env.BAR, process.env.LOCAL);", + }); + const { stdout } = bunRun(`${dir}/index.ts`); + expect(stdout).toBe("bar baz true"); + }); + test(".env.development (NODE_ENV=development)", () => { + const dir = tempDirWithFiles("dotenv", { + ".env": "FOO=fail\nBAR=baz\n", + ".env.development": "FOO=bar\n", + ".env.local": "LOCAL=true\n", + "index.ts": "console.log(process.env.FOO, process.env.BAR, process.env.LOCAL);", + }); + const { stdout } = bunRun(`${dir}/index.ts`); + expect(stdout).toBe("bar baz true"); + }); + test(".env.production", () => { + const dir = tempDirWithFiles("dotenv", { + ".env": "FOO=fail\nBAR=baz\n", + ".env.production": "FOO=bar\n", + ".env.local": "LOCAL=true\n", + "index.ts": "console.log(process.env.FOO, process.env.BAR, process.env.LOCAL);", + }); + const { stdout } = bunRun(`${dir}/index.ts`, { NODE_ENV: "production" }); + expect(stdout).toBe("bar baz true"); + }); + test(".env.development and .env.test ignored when NODE_ENV=production", () => { + const dir = tempDirWithFiles("dotenv", { + ".env": "FOO=bar\nBAR=baz\n", + ".env.development": "FOO=development\n", + ".env.development.local": "FOO=development.local\n", + ".env.test": "FOO=test\n", + ".env.test.local": "FOO=test.local\n", + ".env.local": "LOCAL=true\n", + "index.ts": "console.log(process.env.FOO, process.env.BAR, process.env.LOCAL);", + }); + const { stdout } = bunRun(`${dir}/index.ts`, { NODE_ENV: "production" }); + expect(stdout).toBe("bar baz true"); + }); + test(".env.production and .env.test ignored when NODE_ENV=development", () => { + const dir = tempDirWithFiles("dotenv", { + ".env": "FOO=bar\nBAR=baz\n", + ".env.production": "FOO=production\n", + ".env.production.local": "FOO=production.local\n", + ".env.test": "FOO=test\n", + ".env.test.local": "FOO=test.local\n", + ".env.local": "LOCAL=true\n", + "index.ts": "console.log(process.env.FOO, process.env.BAR, process.env.LOCAL);", + }); + const { stdout } = bunRun(`${dir}/index.ts`, {}); + expect(stdout).toBe("bar baz true"); + }); + test(".env and .env.test used in testing", () => { + const dir = tempDirWithFiles("dotenv", { + ".env": "A=a\n", + ".env.test.local": "B=b\n", + ".env.test": "C=c\n", + ".env.development": "FAIL=.env.development\n", + ".env.development.local": "FAIL=.env.development.local\n", + ".env.production": "FAIL=.env.production\n", + ".env.production.local": "FAIL=.env.production.local\n", + "index.test.ts": "console.log(process.env.A,process.env.B,process.env.C,process.env.FAIL);", + }); + const { stdout } = bunTest(`${dir}/index.test.ts`, {}); + expect(stdout).toBe("a b c undefined"); + }); + test(".env.local ignored when bun test", () => { + const dir = tempDirWithFiles("dotenv", { + ".env": "FAILED=false\n", + ".env.local": "FAILED=true\n", + "index.test.ts": "console.log(process.env.FAILED, process.env.NODE_ENV);", + }); + const { stdout } = bunTest(`${dir}/index.test.ts`, {}); + expect(stdout).toBe("false test"); + }); + test(".env.development and .env.production ignored when bun test", () => { + const dir = tempDirWithFiles("dotenv", { + ".env": "FAILED=false\n", + ".env.development": "FAILED=development\n", + ".env.development.local": "FAILED=development.local\n", + ".env.production": "FAILED=production\n", + ".env.production.local": "FAILED=production.local\n", + "index.test.ts": "console.log(process.env.FAILED, process.env.NODE_ENV);", + }); + const { stdout } = bunTest(`${dir}/index.test.ts`); + expect(stdout).toBe("false test"); + }); +}); +describe("dotenv priority", () => { + test("process env overrides everything else", () => { + const dir = tempDirWithFiles("dotenv", { + ".env": "FOO=.env\n", + ".env.development": "FOO=.env.development\n", + ".env.development.local": "FOO=.env.development.local\n", + ".env.production": "FOO=.env.production\n", + ".env.production.local": "FOO=.env.production.local\n", + ".env.test.local": "FOO=.env.test.local\n", + ".env.test": "FOO=.env.test\n", + ".env.local": "FOO=.env.local\n", + "index.ts": "console.log(process.env.FOO);", + "index.test.ts": "console.log(process.env.FOO);", + }); + const { stdout } = bunRun(`${dir}/index.ts`, { FOO: "override" }); + expect(stdout).toBe("override"); + + const { stdout: stdout2 } = bunTest(`${dir}/index.test.ts`, { FOO: "override" }); + expect(stdout2).toBe("override"); + }); + test(".env.{NODE_ENV}.local overrides .env.local", () => { + const dir = tempDirWithFiles("dotenv", { + ".env": "FOO=.env\n", + ".env.development": "FOO=.env.development\n", + ".env.development.local": "FOO=.env.development.local\n", + ".env.production": "FOO=.env.production\n", + ".env.production.local": "FOO=.env.production.local\n", + ".env.test.local": "FOO=.env.test.local\n", + ".env.test": "FOO=.env.test\n", + ".env.local": "FOO=.env.local\n", + "index.ts": "console.log(process.env.FOO);", + "index.test.ts": "console.log(process.env.FOO);", + }); + const { stdout: stdout_dev } = bunRun(`${dir}/index.ts`, { NODE_ENV: "development" }); + expect(stdout_dev).toBe(".env.development.local"); + const { stdout: stdout_prod } = bunRun(`${dir}/index.ts`, { NODE_ENV: "production" }); + expect(stdout_prod).toBe(".env.production.local"); + const { stdout: stdout_test } = bunTest(`${dir}/index.test.ts`, {}); + expect(stdout_test).toBe(".env.test.local"); + }); + test(".env.local overrides .env.{NODE_ENV}", () => { + const dir = tempDirWithFiles("dotenv", { + ".env": "FOO=.env\n", + ".env.development": "FOO=.env.development\n", + ".env.production": "FOO=.env.production\n", + ".env.test": "FOO=.env.test\n", + ".env.local": "FOO=.env.local\n", + "index.ts": "console.log(process.env.FOO);", + "index.test.ts": "console.log(process.env.FOO);", + }); + const { stdout: stdout_dev } = bunRun(`${dir}/index.ts`, { NODE_ENV: "development" }); + expect(stdout_dev).toBe(".env.local"); + const { stdout: stdout_prod } = bunRun(`${dir}/index.ts`, { NODE_ENV: "production" }); + expect(stdout_prod).toBe(".env.local"); + // .env.local is "not checked when `NODE_ENV` is `test`" + const { stdout: stdout_test } = bunTest(`${dir}/index.test.ts`, {}); + expect(stdout_test).toBe(".env.test"); + }); + test(".env.{NODE_ENV} overrides .env", () => { + const dir = tempDirWithFiles("dotenv", { + ".env": "FOO=.env\n", + ".env.development": "FOO=.env.development\n", + ".env.production": "FOO=.env.production\n", + ".env.test": "FOO=.env.test\n", + "index.ts": "console.log(process.env.FOO);", + "index.test.ts": "console.log(process.env.FOO);", + }); + const { stdout: stdout_dev } = bunRun(`${dir}/index.ts`, { NODE_ENV: "development" }); + expect(stdout_dev).toBe(".env.development"); + const { stdout: stdout_prod } = bunRun(`${dir}/index.ts`, { NODE_ENV: "production" }); + expect(stdout_prod).toBe(".env.production"); + const { stdout: stdout_test } = bunTest(`${dir}/index.test.ts`, {}); + expect(stdout_test).toBe(".env.test"); + }); +}); + +test.todo(".env space edgecase (issue #411)", () => { + const dir = tempDirWithFiles("dotenv-issue-411", { + ".env": "VARNAME=A B", + "index.ts": "console.log('[' + process.env.VARNAME + ']'); ", + }); + const { stdout } = bunRun(`${dir}/index.ts`); + expect(stdout).toBe("[A B]"); +}); + +test.todo(".env special characters 1 (issue #2823)", () => { + const dir = tempDirWithFiles("dotenv-issue-411", { + ".env": 'A="a$t"\n', + "index.ts": "console.log('[' + process.env.A + ']'); ", + }); + const { stdout } = bunRun(`${dir}/index.ts`); + expect(stdout).toBe("[a$t]"); +}); + +test.todo("env escaped quote (issue #2484)", () => { + const dir = tempDirWithFiles("dotenv-issue-411", { + "index.ts": "console.log(process.env.VALUE, process.env.VALUE2);", + }); + const { stdout } = bunRun(`${dir}/index.ts`, { VALUE: `\\"`, VALUE2: `\\\\"` }); + expect(stdout).toBe('\\" \\\\"'); +}); |