aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2023-05-24 18:52:50 -0700
committerGravatar GitHub <noreply@github.com> 2023-05-24 18:52:50 -0700
commit63740a382bbf65fc20fb9c1f1211fbc9285bd9e5 (patch)
tree7cdf3bc65e47ccc62196504798512f28e1d79562
parented1f62ffffdd02db761513e8ac2561afa4b5a8dc (diff)
downloadbun-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.zig2
-rw-r--r--src/bundler.zig33
-rw-r--r--src/bundler/bundle_v2.zig21
-rw-r--r--src/cli/run_command.zig4
-rw-r--r--src/cli/test_command.zig4
-rw-r--r--src/defines.zig1
-rw-r--r--src/env_loader.zig106
-rw-r--r--src/install/install.zig2
-rw-r--r--src/install/lockfile.zig2
-rw-r--r--src/js_parser.zig10
-rw-r--r--src/options.zig22
-rw-r--r--test/cli/run/env.test.ts256
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('\\" \\\\"');
+});