mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 23:18:47 +00:00
Compare commits
12 Commits
dylan/napi
...
dave/env-r
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a3b400a14 | ||
|
|
d934e3ef12 | ||
|
|
51a788cbe4 | ||
|
|
5c9270dbc2 | ||
|
|
96bde2dee1 | ||
|
|
c5fe3a0a44 | ||
|
|
a02a374ef1 | ||
|
|
be9db9dd3d | ||
|
|
45286ef5e1 | ||
|
|
ff9e7571af | ||
|
|
c2beb127c5 | ||
|
|
30969221fb |
Submodule src/bun.js/WebKit updated: 64b2fa7da7...c3712c13dc
@@ -1688,7 +1688,6 @@ pub const Subprocess = struct {
|
||||
}
|
||||
|
||||
if (args != .zero and args.isObject()) {
|
||||
|
||||
// This must run before the stdio parsing happens
|
||||
if (args.getTruthy(globalThis, "ipc")) |val| {
|
||||
if (val.isCell() and val.isCallable(globalThis.vm())) {
|
||||
@@ -1848,9 +1847,8 @@ pub const Subprocess = struct {
|
||||
}
|
||||
|
||||
if (!override_env and env_array.items.len == 0) {
|
||||
env_array.items = jsc_vm.bundler.env.map.createNullDelimitedEnvMap(allocator) catch |err|
|
||||
env_array = jsc_vm.bundler.env.map.createEnvArrayList(allocator) catch |err|
|
||||
return globalThis.handleError(err, "in Bun.spawn");
|
||||
env_array.capacity = env_array.items.len;
|
||||
}
|
||||
|
||||
inline for (0..stdio.len) |fd_index| {
|
||||
@@ -1949,8 +1947,8 @@ pub const Subprocess = struct {
|
||||
|
||||
var spawned = switch (bun.spawn.spawnProcess(
|
||||
&spawn_options,
|
||||
@ptrCast(argv.items.ptr),
|
||||
@ptrCast(env_array.items.ptr),
|
||||
@ptrCast(argv.items[0 .. argv.items.len - 1 :null].ptr),
|
||||
@ptrCast(env_array.items[0 .. env_array.items.len - 1 :null].ptr),
|
||||
) catch |err| {
|
||||
process_allocator.destroy(subprocess);
|
||||
spawn_options.deinit();
|
||||
|
||||
@@ -2172,7 +2172,7 @@ pub const EventLoopHandle = union(enum) {
|
||||
this.loop().unref();
|
||||
}
|
||||
|
||||
pub inline fn createNullDelimitedEnvMap(this: @This(), alloc: Allocator) ![:null]?[*:0]u8 {
|
||||
pub inline fn createNullDelimitedEnvMap(this: @This(), alloc: Allocator) !bun.DotEnv.Map.NullDelimitedEnvMap {
|
||||
return switch (this) {
|
||||
.js => this.js.virtual_machine.bundler.env.map.createNullDelimitedEnvMap(alloc),
|
||||
.mini => this.mini.env.?.map.createNullDelimitedEnvMap(alloc),
|
||||
|
||||
@@ -769,8 +769,8 @@ pub const VirtualMachine = struct {
|
||||
|
||||
if (map.map.fetchSwapRemove("BUN_INTERNAL_IPC_FD")) |kv| {
|
||||
if (Environment.isWindows) {
|
||||
this.initIPCInstance(kv.value.value);
|
||||
} else if (std.fmt.parseInt(i32, kv.value.value, 10) catch null) |fd| {
|
||||
this.initIPCInstance(kv.value);
|
||||
} else if (std.fmt.parseInt(i32, kv.value, 10) catch null) |fd| {
|
||||
this.initIPCInstance(bun.toFD(fd));
|
||||
} else {
|
||||
Output.printErrorln("Failed to parse BUN_INTERNAL_IPC_FD", .{});
|
||||
|
||||
@@ -183,7 +183,7 @@ fn dumpSourceString(specifier: string, written: []const u8) void {
|
||||
},
|
||||
};
|
||||
const dir = std.fs.cwd().makeOpenPath(base_name, .{}) catch |e| {
|
||||
Output.debug("Failed to dump source string: {}", .{e});
|
||||
Output.debugWarn("Failed to dump source string: {}", .{e});
|
||||
return;
|
||||
};
|
||||
BunDebugHolder.dir = dir;
|
||||
@@ -196,12 +196,12 @@ fn dumpSourceString(specifier: string, written: []const u8) void {
|
||||
.windows => bun.path.windowsFilesystemRoot(dir_path).len,
|
||||
};
|
||||
var parent = dir.makeOpenPath(dir_path[root_len..], .{}) catch |e| {
|
||||
Output.debug("Failed to dump source string: makeOpenPath({s}[{d}..]) {}", .{ dir_path, root_len, e });
|
||||
Output.debugWarn("Failed to dump source string: makeOpenPath({s}[{d}..]) {}", .{ dir_path, root_len, e });
|
||||
return;
|
||||
};
|
||||
defer parent.close();
|
||||
parent.writeFile(std.fs.path.basename(specifier), written) catch |e| {
|
||||
Output.debug("Failed to dump source string: writeFile {}", .{e});
|
||||
Output.debugWarn("Failed to dump source string: writeFile {}", .{e});
|
||||
return;
|
||||
};
|
||||
} else {
|
||||
|
||||
@@ -133,7 +133,7 @@ pub const Run = struct {
|
||||
try @import("./bun.js/config.zig").configureTransformOptionsForBunVM(ctx.allocator, ctx.args),
|
||||
null,
|
||||
);
|
||||
try bundle.runEnvLoader();
|
||||
try bundle.runEnvLoader(false);
|
||||
const mini = JSC.MiniEventLoop.initGlobal(bundle.env);
|
||||
mini.top_level_dir = ctx.args.absolute_working_dir orelse "";
|
||||
return try bun.shell.Interpreter.initAndRunFromFile(mini, entry_path);
|
||||
@@ -147,7 +147,7 @@ pub const Run = struct {
|
||||
try bun.CLI.Arguments.loadConfigPath(ctx.allocator, true, "bunfig.toml", &ctx, .RunCommand);
|
||||
}
|
||||
|
||||
if (strings.endsWithComptime(entry_path, comptime if (Environment.isWindows) ".sh" else ".bun.sh")) {
|
||||
if (strings.endsWithComptime(entry_path, ".sh")) {
|
||||
const exit_code = try bootBunShell(&ctx, entry_path);
|
||||
Global.exitWide(exit_code);
|
||||
return;
|
||||
@@ -226,10 +226,7 @@ pub const Run = struct {
|
||||
const node_env_entry = try b.env.map.getOrPutWithoutValue("NODE_ENV");
|
||||
if (!node_env_entry.found_existing) {
|
||||
node_env_entry.key_ptr.* = try b.env.allocator.dupe(u8, node_env_entry.key_ptr.*);
|
||||
node_env_entry.value_ptr.* = .{
|
||||
.value = try b.env.allocator.dupe(u8, "development"),
|
||||
.conditional = false,
|
||||
};
|
||||
node_env_entry.value_ptr.* = try b.env.allocator.dupe(u8, "development");
|
||||
}
|
||||
|
||||
b.configureRouter(false) catch {
|
||||
|
||||
@@ -520,7 +520,7 @@ pub const Bundler = struct {
|
||||
bundler.configureLinkerWithAutoJSX(true);
|
||||
}
|
||||
|
||||
pub fn runEnvLoader(this: *Bundler) !void {
|
||||
pub fn runEnvLoader(this: *Bundler, is_script_runner: bool) !void {
|
||||
switch (this.options.env.behavior) {
|
||||
.prefix, .load_all, .load_all_without_inlining => {
|
||||
// Step 1. Load the project root.
|
||||
@@ -541,11 +541,11 @@ pub const Bundler = struct {
|
||||
}
|
||||
|
||||
if (this.options.isTest() or this.env.isTest()) {
|
||||
try this.env.load(dir, this.options.env.files, .@"test");
|
||||
try this.env.load(dir, this.options.env.files, .@"test", is_script_runner);
|
||||
} else if (this.options.production) {
|
||||
try this.env.load(dir, this.options.env.files, .production);
|
||||
try this.env.load(dir, this.options.env.files, .production, is_script_runner);
|
||||
} else {
|
||||
try this.env.load(dir, this.options.env.files, .development);
|
||||
try this.env.load(dir, this.options.env.files, .development, is_script_runner);
|
||||
}
|
||||
},
|
||||
.disable => {
|
||||
@@ -584,7 +584,7 @@ pub const Bundler = struct {
|
||||
this.options.env.prefix = "BUN_";
|
||||
}
|
||||
|
||||
try this.runEnvLoader();
|
||||
try this.runEnvLoader(false);
|
||||
|
||||
this.options.jsx.setProduction(this.env.isProduction());
|
||||
|
||||
|
||||
@@ -602,10 +602,13 @@ pub const BunxCommand = struct {
|
||||
debug("installing package: {s}", .{bun.fmt.fmtSlice(argv_to_use, " ")});
|
||||
this_bundler.env.map.put("BUN_INTERNAL_BUNX_INSTALL", "true") catch bun.outOfMemory();
|
||||
|
||||
const envp = try this_bundler.env.map.createNullDelimitedEnvMap(bun.default_allocator);
|
||||
defer envp.deinit(bun.default_allocator);
|
||||
|
||||
const spawn_result = switch ((bun.spawnSync(&.{
|
||||
.argv = argv_to_use,
|
||||
|
||||
.envp = try this_bundler.env.map.createNullDelimitedEnvMap(bun.default_allocator),
|
||||
.envp = envp.envp,
|
||||
|
||||
.cwd = bunx_cache_dir,
|
||||
.stderr = .inherit,
|
||||
|
||||
@@ -347,13 +347,14 @@ pub const RunCommand = struct {
|
||||
Output.flush();
|
||||
}
|
||||
|
||||
const envp = try env.map.createNullDelimitedEnvMap(bun.default_allocator);
|
||||
defer envp.deinit(bun.default_allocator);
|
||||
|
||||
const spawn_result = switch ((bun.spawnSync(&.{
|
||||
.argv = &argv,
|
||||
.argv0 = shell_bin.ptr,
|
||||
|
||||
// TODO: remember to free this when we add --filter or --concurrent
|
||||
// in the meantime we don't need to free it.
|
||||
.envp = try env.map.createNullDelimitedEnvMap(bun.default_allocator),
|
||||
.envp = envp.envp,
|
||||
|
||||
.cwd = cwd,
|
||||
.stderr = .inherit,
|
||||
@@ -513,14 +514,15 @@ pub const RunCommand = struct {
|
||||
argv = try array_list.toOwnedSlice();
|
||||
}
|
||||
|
||||
const envp = try env.map.createNullDelimitedEnvMap(bun.default_allocator);
|
||||
defer envp.deinit(bun.default_allocator);
|
||||
|
||||
const silent = ctx.debug.silent;
|
||||
const spawn_result = bun.spawnSync(&.{
|
||||
.argv = argv,
|
||||
.argv0 = executableZ,
|
||||
|
||||
// TODO: remember to free this when we add --filter or --concurrent
|
||||
// in the meantime we don't need to free it.
|
||||
.envp = try env.map.createNullDelimitedEnvMap(bun.default_allocator),
|
||||
.envp = envp.envp,
|
||||
|
||||
.cwd = cwd,
|
||||
.stderr = .inherit,
|
||||
@@ -850,7 +852,7 @@ pub const RunCommand = struct {
|
||||
|
||||
this_bundler.configureLinker();
|
||||
|
||||
var root_dir_info = this_bundler.resolver.readDirInfo(this_bundler.fs.top_level_dir) catch |err| {
|
||||
const root_dir_info = this_bundler.resolver.readDirInfo(this_bundler.fs.top_level_dir) catch |err| {
|
||||
if (!log_errors) return error.CouldntReadCurrentDirectory;
|
||||
if (Output.enable_ansi_colors) {
|
||||
ctx.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), true) catch {};
|
||||
@@ -876,26 +878,10 @@ pub const RunCommand = struct {
|
||||
if (env == null) {
|
||||
this_bundler.env.loadProcess();
|
||||
|
||||
if (this_bundler.env.get("NODE_ENV")) |node_env| {
|
||||
if (strings.eqlComptime(node_env, "production")) {
|
||||
this_bundler.options.production = true;
|
||||
}
|
||||
}
|
||||
if (this_bundler.env.isProduction())
|
||||
this_bundler.options.production = true;
|
||||
|
||||
// TODO: evaluate if we can skip running this in nested calls to bun run
|
||||
// The reason why it's unclear:
|
||||
// - Some scripts may do NODE_ENV=production bun run foo
|
||||
// This would cause potentially a different .env file to be loaded
|
||||
this_bundler.runEnvLoader() catch {};
|
||||
|
||||
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(dir, this_bundler.options.env.files, .production) catch {};
|
||||
} else {
|
||||
this_bundler.env.load(dir, this_bundler.options.env.files, .development) catch {};
|
||||
}
|
||||
}
|
||||
this_bundler.runEnvLoader(true) catch {};
|
||||
}
|
||||
|
||||
this_bundler.env.map.putDefault("npm_config_local_prefix", this_bundler.fs.top_level_dir) catch unreachable;
|
||||
@@ -1055,11 +1041,8 @@ pub const RunCommand = struct {
|
||||
{
|
||||
this_bundler.env.loadProcess();
|
||||
|
||||
if (this_bundler.env.get("NODE_ENV")) |node_env| {
|
||||
if (strings.eqlComptime(node_env, "production")) {
|
||||
this_bundler.options.production = true;
|
||||
}
|
||||
}
|
||||
if (this_bundler.env.isProduction())
|
||||
this_bundler.options.production = true;
|
||||
}
|
||||
|
||||
const ResultList = bun.StringArrayHashMap(void);
|
||||
|
||||
@@ -663,10 +663,7 @@ pub const TestCommand = struct {
|
||||
const node_env_entry = try env_loader.map.getOrPutWithoutValue("NODE_ENV");
|
||||
if (!node_env_entry.found_existing) {
|
||||
node_env_entry.key_ptr.* = try env_loader.allocator.dupe(u8, node_env_entry.key_ptr.*);
|
||||
node_env_entry.value_ptr.* = .{
|
||||
.value = try env_loader.allocator.dupe(u8, "test"),
|
||||
.conditional = false,
|
||||
};
|
||||
node_env_entry.value_ptr.* = try env_loader.allocator.dupe(u8, "test");
|
||||
}
|
||||
|
||||
try vm.bundler.configureDefines();
|
||||
|
||||
@@ -189,7 +189,6 @@ pub const Loader = struct {
|
||||
}
|
||||
|
||||
fn loadCCachePathImpl(this: *Loader, fs: *Fs.FileSystem) !void {
|
||||
|
||||
// if they have ccache installed, put it in env variable `CMAKE_CXX_COMPILER_LAUNCHER` so
|
||||
// cmake can use it to hopefully speed things up
|
||||
var buf: [bun.MAX_PATH_BYTES]u8 = undefined;
|
||||
@@ -204,18 +203,12 @@ pub const Loader = struct {
|
||||
const cxx_gop = try this.map.getOrPutWithoutValue("CMAKE_CXX_COMPILER_LAUNCHER");
|
||||
if (!cxx_gop.found_existing) {
|
||||
cxx_gop.key_ptr.* = try this.allocator.dupe(u8, cxx_gop.key_ptr.*);
|
||||
cxx_gop.value_ptr.* = .{
|
||||
.value = try this.allocator.dupe(u8, ccache_path),
|
||||
.conditional = false,
|
||||
};
|
||||
cxx_gop.value_ptr.* = try this.allocator.dupe(u8, ccache_path);
|
||||
}
|
||||
const c_gop = try this.map.getOrPutWithoutValue("CMAKE_C_COMPILER_LAUNCHER");
|
||||
if (!c_gop.found_existing) {
|
||||
c_gop.key_ptr.* = try this.allocator.dupe(u8, c_gop.key_ptr.*);
|
||||
c_gop.value_ptr.* = .{
|
||||
.value = try this.allocator.dupe(u8, ccache_path),
|
||||
.conditional = false,
|
||||
};
|
||||
c_gop.value_ptr.* = try this.allocator.dupe(u8, ccache_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -335,7 +328,7 @@ pub const Loader = struct {
|
||||
|
||||
if (behavior == .prefix) {
|
||||
while (iter.next()) |entry| {
|
||||
const value: string = entry.value_ptr.value;
|
||||
const value: string = entry.value_ptr.*;
|
||||
|
||||
if (strings.startsWith(entry.key_ptr.*, prefix)) {
|
||||
const key_str = std.fmt.allocPrint(key_allocator, "process.env.{s}", .{entry.key_ptr.*}) catch unreachable;
|
||||
@@ -386,12 +379,11 @@ pub const Loader = struct {
|
||||
}
|
||||
} else {
|
||||
while (iter.next()) |entry| {
|
||||
const value: string = entry.value_ptr.value;
|
||||
const key = std.fmt.allocPrint(key_allocator, "process.env.{s}", .{entry.key_ptr.*}) catch unreachable;
|
||||
|
||||
e_strings[0] = js_ast.E.String{
|
||||
.data = if (entry.value_ptr.value.len > 0)
|
||||
@as([*]u8, @ptrFromInt(@intFromPtr(entry.value_ptr.value.ptr)))[0..value.len]
|
||||
.data = if (entry.value_ptr.len > 0)
|
||||
entry.value_ptr.*
|
||||
else
|
||||
&[_]u8{},
|
||||
};
|
||||
@@ -468,13 +460,14 @@ pub const Loader = struct {
|
||||
dir: *Fs.FileSystem.DirEntry,
|
||||
env_files: []const []const u8,
|
||||
comptime suffix: DotEnvFileSuffix,
|
||||
is_script_runner: bool,
|
||||
) !void {
|
||||
const start = std.time.nanoTimestamp();
|
||||
|
||||
if (env_files.len > 0) {
|
||||
try this.loadExplicitFiles(env_files);
|
||||
} else {
|
||||
try this.loadDefaultFiles(dir, suffix);
|
||||
try this.loadDefaultFiles(dir, suffix, is_script_runner);
|
||||
}
|
||||
|
||||
if (!this.quiet) this.printLoaded(start);
|
||||
@@ -492,8 +485,7 @@ pub const Loader = struct {
|
||||
var iter = std.mem.splitBackwardsScalar(u8, arg_value, ',');
|
||||
while (iter.next()) |file_path| {
|
||||
if (file_path.len > 0) {
|
||||
try this.loadEnvFileDynamic(file_path, false, true);
|
||||
Analytics.Features.dotenv = true;
|
||||
try this.loadEnvFileDynamic(file_path, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -508,61 +500,61 @@ pub const Loader = struct {
|
||||
this: *Loader,
|
||||
dir: *Fs.FileSystem.DirEntry,
|
||||
comptime suffix: DotEnvFileSuffix,
|
||||
/// Only '.env' and '.env.local' are loaded when running a script
|
||||
/// mostly for backwards compatibility with older behavior of bun.
|
||||
/// See https://github.com/oven-sh/bun/pull/9642
|
||||
is_script_runner: bool,
|
||||
) !void {
|
||||
const dir_handle: std.fs.Dir = std.fs.cwd();
|
||||
|
||||
switch (comptime suffix) {
|
||||
.development => {
|
||||
if (dir.hasComptimeQuery(".env.development.local")) {
|
||||
try this.loadEnvFile(dir_handle, ".env.development.local", false, true);
|
||||
Analytics.Features.dotenv = true;
|
||||
}
|
||||
},
|
||||
.production => {
|
||||
if (dir.hasComptimeQuery(".env.production.local")) {
|
||||
try this.loadEnvFile(dir_handle, ".env.production.local", false, true);
|
||||
Analytics.Features.dotenv = true;
|
||||
}
|
||||
},
|
||||
.@"test" => {
|
||||
if (dir.hasComptimeQuery(".env.test.local")) {
|
||||
try this.loadEnvFile(dir_handle, ".env.test.local", false, true);
|
||||
Analytics.Features.dotenv = true;
|
||||
}
|
||||
},
|
||||
if (!is_script_runner) {
|
||||
switch (comptime suffix) {
|
||||
.development => {
|
||||
if (dir.hasComptimeQuery(".env.development.local")) {
|
||||
try this.loadEnvFile(dir_handle, ".env.development.local", false);
|
||||
}
|
||||
},
|
||||
.production => {
|
||||
if (dir.hasComptimeQuery(".env.production.local")) {
|
||||
try this.loadEnvFile(dir_handle, ".env.production.local", false);
|
||||
}
|
||||
},
|
||||
.@"test" => {
|
||||
if (dir.hasComptimeQuery(".env.test.local")) {
|
||||
try this.loadEnvFile(dir_handle, ".env.test.local", false);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (comptime suffix != .@"test") {
|
||||
if (dir.hasComptimeQuery(".env.local")) {
|
||||
try this.loadEnvFile(dir_handle, ".env.local", false, false);
|
||||
Analytics.Features.dotenv = true;
|
||||
try this.loadEnvFile(dir_handle, ".env.local", false);
|
||||
}
|
||||
}
|
||||
|
||||
switch (comptime suffix) {
|
||||
.development => {
|
||||
if (dir.hasComptimeQuery(".env.development")) {
|
||||
try this.loadEnvFile(dir_handle, ".env.development", false, true);
|
||||
Analytics.Features.dotenv = true;
|
||||
}
|
||||
},
|
||||
.production => {
|
||||
if (dir.hasComptimeQuery(".env.production")) {
|
||||
try this.loadEnvFile(dir_handle, ".env.production", false, true);
|
||||
Analytics.Features.dotenv = true;
|
||||
}
|
||||
},
|
||||
.@"test" => {
|
||||
if (dir.hasComptimeQuery(".env.test")) {
|
||||
try this.loadEnvFile(dir_handle, ".env.test", false, true);
|
||||
Analytics.Features.dotenv = true;
|
||||
}
|
||||
},
|
||||
}
|
||||
if (!is_script_runner) {
|
||||
switch (comptime suffix) {
|
||||
.development => {
|
||||
if (dir.hasComptimeQuery(".env.development")) {
|
||||
try this.loadEnvFile(dir_handle, ".env.development", false);
|
||||
}
|
||||
},
|
||||
.production => {
|
||||
if (dir.hasComptimeQuery(".env.production")) {
|
||||
try this.loadEnvFile(dir_handle, ".env.production", false);
|
||||
}
|
||||
},
|
||||
.@"test" => {
|
||||
if (dir.hasComptimeQuery(".env.test")) {
|
||||
try this.loadEnvFile(dir_handle, ".env.test", false);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
if (dir.hasComptimeQuery(".env")) {
|
||||
try this.loadEnvFile(dir_handle, ".env", false, false);
|
||||
Analytics.Features.dotenv = true;
|
||||
if (dir.hasComptimeQuery(".env")) {
|
||||
try this.loadEnvFile(dir_handle, ".env", false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -636,7 +628,6 @@ pub const Loader = struct {
|
||||
dir: std.fs.Dir,
|
||||
comptime base: string,
|
||||
comptime override: bool,
|
||||
comptime conditional: bool,
|
||||
) !void {
|
||||
if (@field(this, base) != null) {
|
||||
return;
|
||||
@@ -714,7 +705,6 @@ pub const Loader = struct {
|
||||
this.map,
|
||||
override,
|
||||
false,
|
||||
conditional,
|
||||
);
|
||||
|
||||
@field(this, base) = source;
|
||||
@@ -724,7 +714,6 @@ pub const Loader = struct {
|
||||
this: *Loader,
|
||||
file_path: []const u8,
|
||||
comptime override: bool,
|
||||
comptime conditional: bool,
|
||||
) !void {
|
||||
if (this.custom_files_loaded.contains(file_path)) {
|
||||
return;
|
||||
@@ -786,7 +775,6 @@ pub const Loader = struct {
|
||||
this.map,
|
||||
override,
|
||||
false,
|
||||
conditional,
|
||||
);
|
||||
|
||||
try this.custom_files_loaded.put(file_path, source);
|
||||
@@ -1011,8 +999,9 @@ const Parser = struct {
|
||||
map: *Map,
|
||||
comptime override: bool,
|
||||
comptime is_process: bool,
|
||||
comptime conditional: bool,
|
||||
) void {
|
||||
Analytics.Features.dotenv = true;
|
||||
|
||||
var count = map.map.count();
|
||||
while (this.pos < this.src.len) {
|
||||
const key = this.parseKey(true) orelse {
|
||||
@@ -1027,25 +1016,19 @@ const Parser = struct {
|
||||
// https://github.com/oven-sh/bun/issues/1262
|
||||
if (comptime !override) continue;
|
||||
} else {
|
||||
allocator.free(entry.value_ptr.value);
|
||||
allocator.free(entry.value_ptr.*);
|
||||
}
|
||||
}
|
||||
entry.value_ptr.* = .{
|
||||
.value = allocator.dupe(u8, value) catch unreachable,
|
||||
.conditional = conditional,
|
||||
};
|
||||
entry.value_ptr.* = allocator.dupe(u8, value) catch bun.outOfMemory();
|
||||
}
|
||||
if (comptime !is_process) {
|
||||
var it = map.iterator();
|
||||
while (it.next()) |entry| {
|
||||
if (count > 0) {
|
||||
count -= 1;
|
||||
} else if (expandValue(map, entry.value_ptr.value)) |value| {
|
||||
allocator.free(entry.value_ptr.value);
|
||||
entry.value_ptr.* = .{
|
||||
.value = allocator.dupe(u8, value) catch unreachable,
|
||||
.conditional = conditional,
|
||||
};
|
||||
} else if (expandValue(map, entry.value_ptr.*)) |value| {
|
||||
allocator.free(entry.value_ptr.*);
|
||||
entry.value_ptr.* = allocator.dupe(u8, value) catch bun.outOfMemory();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1057,61 +1040,148 @@ const Parser = struct {
|
||||
map: *Map,
|
||||
comptime override: bool,
|
||||
comptime is_process: bool,
|
||||
comptime conditional: bool,
|
||||
) void {
|
||||
var parser = Parser{ .src = source.contents };
|
||||
parser._parse(allocator, map, override, is_process, conditional);
|
||||
parser._parse(allocator, map, override, is_process);
|
||||
}
|
||||
};
|
||||
|
||||
pub const Map = struct {
|
||||
const HashTableValue = struct {
|
||||
value: string,
|
||||
conditional: bool,
|
||||
};
|
||||
// On Windows, environment variables are case-insensitive. So we use a case-insensitive hash map.
|
||||
// An issue with this exact implementation is unicode characters can technically appear in these
|
||||
// keys, and we use a simple toLowercase function that only applies to ascii, so this will make
|
||||
// some strings collide.
|
||||
const HashTable = (if (Environment.isWindows) bun.CaseInsensitiveASCIIStringArrayHashMap else bun.StringArrayHashMap)(HashTableValue);
|
||||
const HashTable = (if (Environment.isWindows) bun.CaseInsensitiveASCIIStringArrayHashMap else bun.StringArrayHashMap)(string);
|
||||
|
||||
const GetOrPutResult = HashTable.GetOrPutResult;
|
||||
|
||||
map: HashTable,
|
||||
|
||||
pub fn createNullDelimitedEnvMap(this: *Map, arena: std.mem.Allocator) ![:null]?[*:0]u8 {
|
||||
/// Creates a environment block for use in Posix Spawn APIs.
|
||||
/// The return value is a struct with a field '.envp' which can be passed to a posix api.
|
||||
/// Call .deinit with the same allocator to free the memory.
|
||||
pub fn createNullDelimitedEnvMap(this: *Map, alloc: std.mem.Allocator) !NullDelimitedEnvMap {
|
||||
var env_map = &this.map;
|
||||
|
||||
const envp_count = env_map.count();
|
||||
const envp_buf = try arena.allocSentinel(?[*:0]u8, envp_count, null);
|
||||
var envp_count: usize = 0;
|
||||
var total_bytes: usize = 0;
|
||||
{
|
||||
var it = env_map.iterator();
|
||||
while (it.next()) |pair| {
|
||||
envp_count += 1;
|
||||
// env line is 'KEY=VALUE\x00'
|
||||
total_bytes += (pair.key_ptr.len + pair.value_ptr.len + "=\x00".len);
|
||||
}
|
||||
}
|
||||
total_bytes += (envp_count + 1) * @sizeOf(?[*:0]const u8); // +1 for the null ptr after the pointer list
|
||||
|
||||
// Instead of creating separate allocations for each string, let's allocate
|
||||
// enough bytes for everything. The buffer contains the pointers, and then
|
||||
// all the string bytes afterwards. For this to pass the ptrCast a few
|
||||
// lines later, we have to make a pointer-aligned allocation.
|
||||
const buf = try alloc.alignedAlloc(u8, @alignOf(?[*:0]const u8), total_bytes);
|
||||
comptime std.debug.assert(@TypeOf(buf.ptr) == NullDelimitedEnvMap.BufPtrType);
|
||||
|
||||
const envp = envp: {
|
||||
const p: [*]?[*:0]const u8 = @ptrCast(buf.ptr);
|
||||
p[envp_count] = null;
|
||||
break :envp p[0..envp_count :null];
|
||||
};
|
||||
|
||||
var fba = std.heap.FixedBufferAllocator.init(buf[@sizeOf(usize) * (envp_count + 1) ..]);
|
||||
const string_alloc = fba.allocator();
|
||||
|
||||
{
|
||||
var it = env_map.iterator();
|
||||
var i: usize = 0;
|
||||
while (it.next()) |pair| : (i += 1) {
|
||||
const env_buf = try arena.allocSentinel(u8, pair.key_ptr.len + pair.value_ptr.value.len + 1, 0);
|
||||
bun.copy(u8, env_buf, pair.key_ptr.*);
|
||||
env_buf[pair.key_ptr.len] = '=';
|
||||
bun.copy(u8, env_buf[pair.key_ptr.len + 1 ..], pair.value_ptr.value);
|
||||
envp_buf[i] = env_buf.ptr;
|
||||
while (it.next()) |pair| {
|
||||
const variable_buf = string_alloc.allocSentinel(
|
||||
u8,
|
||||
pair.key_ptr.len + pair.value_ptr.len + 1,
|
||||
0,
|
||||
) catch unreachable; // all bytes were pre-allocated.
|
||||
@memcpy(variable_buf[0..pair.key_ptr.len], pair.key_ptr.*);
|
||||
variable_buf[pair.key_ptr.len] = '=';
|
||||
@memcpy(variable_buf[pair.key_ptr.len + 1 ..], pair.value_ptr.*);
|
||||
envp[i] = variable_buf.ptr;
|
||||
i += 1;
|
||||
}
|
||||
if (comptime Environment.allow_assert) std.debug.assert(i == envp_count);
|
||||
}
|
||||
return envp_buf;
|
||||
|
||||
if (comptime Environment.allow_assert) {
|
||||
std.debug.assert(fba.end_index == fba.buffer.len); // incorrect counting above. every pointer should be byte-aligned
|
||||
std.debug.assert(@intFromPtr(envp.ptr) == @intFromPtr(buf.ptr));
|
||||
}
|
||||
|
||||
return .{
|
||||
.envp = envp,
|
||||
// In order to properly free this, the Zig allocator must be passed
|
||||
// the original slice it was given. The pointer is technically not
|
||||
// enough information.
|
||||
.allocation_size = total_bytes,
|
||||
};
|
||||
}
|
||||
|
||||
/// Creates a environment block for use in Posix Spawn APIs. It is assumed that the caller
|
||||
/// will want to add other strings to this block, **so there is no null terminator** in the
|
||||
/// return value.
|
||||
///
|
||||
/// If passing this into a Posix Spawn API, you will need to append a `null`, and then
|
||||
/// use `list.items[0.. list.items.len - 1 :null]` to get a null-terminated array.
|
||||
///
|
||||
/// Unlike `createNullDelimitedEnvMap`, this does a separate allocation for each string,
|
||||
/// meaning you likely want to use an arena allocator for the input so you can quickly
|
||||
/// free everything.
|
||||
pub fn createEnvArrayList(this: *Map, alloc: std.mem.Allocator) !std.ArrayListUnmanaged(?[*:0]const u8) {
|
||||
const envp_count = this.map.count();
|
||||
// Allocate an extra entry so a caller that wants a null pointer does not have to resize the ArrayList.
|
||||
var list = try std.ArrayListUnmanaged(?[*:0]const u8).initCapacity(alloc, envp_count + 1);
|
||||
errdefer {
|
||||
for (list.items) |item| {
|
||||
alloc.free(bun.span(
|
||||
item orelse unreachable, // no null values are added
|
||||
));
|
||||
}
|
||||
list.deinit(alloc);
|
||||
}
|
||||
|
||||
var it = this.map.iterator();
|
||||
while (it.next()) |pair| {
|
||||
const variable_buf = try alloc.allocSentinel(u8, pair.key_ptr.len + pair.value_ptr.len + 1, 0);
|
||||
@memcpy(variable_buf[0..pair.key_ptr.len], pair.key_ptr.*);
|
||||
variable_buf[pair.key_ptr.len] = '=';
|
||||
@memcpy(variable_buf[pair.key_ptr.len + 1 ..], pair.value_ptr.*);
|
||||
list.appendAssumeCapacity(variable_buf.ptr);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
pub const NullDelimitedEnvMap = struct {
|
||||
envp: [:null]?[*:0]const u8,
|
||||
allocation_size: usize,
|
||||
|
||||
const BufPtrType = [*]align(@alignOf(?[*:0]const u8)) u8;
|
||||
|
||||
pub inline fn deinit(this: NullDelimitedEnvMap, alloc: std.mem.Allocator) void {
|
||||
alloc.free(
|
||||
@as(BufPtrType, @ptrCast(this.envp.ptr))[0..this.allocation_size],
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/// Returns a wrapper around the std.process.EnvMap that does not duplicate the memory of
|
||||
/// the keys and values, but instead points into the memory of the bun env map.
|
||||
///
|
||||
/// To prevent
|
||||
/// To prevent mutation, the return value is a wrapper struct that can only
|
||||
/// return a *const std.process.EnvMap.
|
||||
pub fn stdEnvMap(this: *Map, allocator: std.mem.Allocator) !StdEnvMapWrapper {
|
||||
var env_map = std.process.EnvMap.init(allocator);
|
||||
|
||||
var iter = this.map.iterator();
|
||||
while (iter.next()) |entry| {
|
||||
// Allow var from .env.development or .env.production to be loaded again
|
||||
if (!entry.value_ptr.conditional) {
|
||||
try env_map.hash_map.put(entry.key_ptr.*, entry.value_ptr.value);
|
||||
}
|
||||
try env_map.hash_map.put(entry.key_ptr.*, entry.value_ptr.*);
|
||||
}
|
||||
|
||||
return .{ .unsafe_map = env_map };
|
||||
@@ -1139,7 +1209,7 @@ pub const Map = struct {
|
||||
if (i + 7 >= result.len) return error.TooManyEnvironmentVariables;
|
||||
result[i] = '=';
|
||||
i += 1;
|
||||
i += bun.strings.convertUTF8toUTF16InBuffer(result[i..], pair.value_ptr.*.value).len;
|
||||
i += bun.strings.convertUTF8toUTF16InBuffer(result[i..], pair.value_ptr.*).len;
|
||||
if (i + 5 >= result.len) return error.TooManyEnvironmentVariables;
|
||||
result[i] = 0;
|
||||
i += 1;
|
||||
@@ -1168,18 +1238,12 @@ pub const Map = struct {
|
||||
if (Environment.isWindows and Environment.allow_assert) {
|
||||
std.debug.assert(bun.strings.indexOfChar(key, '\x00') == null);
|
||||
}
|
||||
try this.map.put(key, .{
|
||||
.value = value,
|
||||
.conditional = false,
|
||||
});
|
||||
try this.map.put(key, value);
|
||||
}
|
||||
|
||||
pub inline fn putAllocKeyAndValue(this: *Map, allocator: std.mem.Allocator, key: string, value: string) !void {
|
||||
const gop = try this.map.getOrPut(key);
|
||||
gop.value_ptr.* = .{
|
||||
.value = try allocator.dupe(u8, value),
|
||||
.conditional = false,
|
||||
};
|
||||
gop.value_ptr.* = try allocator.dupe(u8, value);
|
||||
if (!gop.found_existing) {
|
||||
gop.key_ptr.* = try allocator.dupe(u8, key);
|
||||
}
|
||||
@@ -1187,20 +1251,14 @@ pub const Map = struct {
|
||||
|
||||
pub inline fn putAllocKey(this: *Map, allocator: std.mem.Allocator, key: string, value: string) !void {
|
||||
const gop = try this.map.getOrPut(key);
|
||||
gop.value_ptr.* = .{
|
||||
.value = value,
|
||||
.conditional = false,
|
||||
};
|
||||
gop.value_ptr.* = value;
|
||||
if (!gop.found_existing) {
|
||||
gop.key_ptr.* = try allocator.dupe(u8, key);
|
||||
}
|
||||
}
|
||||
|
||||
pub inline fn putAllocValue(this: *Map, allocator: std.mem.Allocator, key: string, value: string) !void {
|
||||
try this.map.put(key, .{
|
||||
.value = try allocator.dupe(u8, value),
|
||||
.conditional = false,
|
||||
});
|
||||
try this.map.put(key, try allocator.dupe(u8, value));
|
||||
}
|
||||
|
||||
pub inline fn getOrPutWithoutValue(this: *Map, key: string) !GetOrPutResult {
|
||||
@@ -1232,21 +1290,11 @@ pub const Map = struct {
|
||||
this: *const Map,
|
||||
key: string,
|
||||
) ?string {
|
||||
return if (this.map.get(key)) |entry| entry.value else null;
|
||||
return this.map.get(key);
|
||||
}
|
||||
|
||||
pub inline fn putDefault(this: *Map, key: string, value: string) !void {
|
||||
_ = try this.map.getOrPutValue(key, .{
|
||||
.value = value,
|
||||
.conditional = false,
|
||||
});
|
||||
}
|
||||
|
||||
pub inline fn getOrPut(this: *Map, key: string, value: string) !void {
|
||||
_ = try this.map.getOrPutValue(key, .{
|
||||
.value = value,
|
||||
.conditional = false,
|
||||
});
|
||||
_ = try this.map.getOrPutValue(key, value);
|
||||
}
|
||||
|
||||
pub fn remove(this: *Map, key: string) void {
|
||||
|
||||
@@ -2112,10 +2112,7 @@ pub const PackageManager = struct {
|
||||
const init_cwd_gop = try this.env.map.getOrPutWithoutValue("INIT_CWD");
|
||||
if (!init_cwd_gop.found_existing) {
|
||||
init_cwd_gop.key_ptr.* = try ctx.allocator.dupe(u8, init_cwd_gop.key_ptr.*);
|
||||
init_cwd_gop.value_ptr.* = .{
|
||||
.value = try ctx.allocator.dupe(u8, FileSystem.instance.top_level_dir),
|
||||
.conditional = false,
|
||||
};
|
||||
init_cwd_gop.value_ptr.* = try ctx.allocator.dupe(u8, FileSystem.instance.top_level_dir);
|
||||
}
|
||||
|
||||
this.env.loadCCachePath(this_bundler.fs);
|
||||
@@ -6672,7 +6669,7 @@ pub const PackageManager = struct {
|
||||
};
|
||||
|
||||
env.loadProcess();
|
||||
try env.load(entries_option.entries, &[_][]u8{}, .production);
|
||||
try env.load(entries_option.entries, &[_][]u8{}, .production, false);
|
||||
|
||||
var cpu_count = @as(u32, @truncate(((try std.Thread.getCpuCount()) + 1)));
|
||||
|
||||
@@ -10324,13 +10321,12 @@ pub const PackageManager = struct {
|
||||
try PATH.appendSlice(original_path);
|
||||
}
|
||||
|
||||
this_bundler.env.map.put("PATH", PATH.items) catch unreachable;
|
||||
|
||||
const envp = try this_bundler.env.map.createNullDelimitedEnvMap(this.allocator);
|
||||
this_bundler.env.map.put("PATH", PATH.items) catch bun.outOfMemory();
|
||||
const env = try this_bundler.env.map.createNullDelimitedEnvMap(this.allocator);
|
||||
try this_bundler.env.map.put("PATH", original_path);
|
||||
PATH.deinit();
|
||||
|
||||
try LifecycleScriptSubprocess.spawnPackageScripts(this, list, envp, log_level);
|
||||
try LifecycleScriptSubprocess.spawnPackageScripts(this, list, env, log_level);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ pub const LifecycleScriptSubprocess = struct {
|
||||
stderr: OutputReader = OutputReader.init(@This()),
|
||||
has_called_process_exit: bool = false,
|
||||
manager: *PackageManager,
|
||||
envp: [:null]?[*:0]u8,
|
||||
env: bun.DotEnv.Map.NullDelimitedEnvMap,
|
||||
|
||||
timer: ?Timer = null,
|
||||
|
||||
@@ -174,7 +174,7 @@ pub const LifecycleScriptSubprocess = struct {
|
||||
};
|
||||
|
||||
this.remaining_fds = 0;
|
||||
var spawned = try (try bun.spawn.spawnProcess(&spawn_options, @ptrCast(&argv), this.envp)).unwrap();
|
||||
var spawned = try (try bun.spawn.spawnProcess(&spawn_options, @ptrCast(&argv), this.env.envp)).unwrap();
|
||||
|
||||
if (comptime Environment.isPosix) {
|
||||
if (spawned.stdout) |stdout| {
|
||||
@@ -391,18 +391,19 @@ pub const LifecycleScriptSubprocess = struct {
|
||||
this.stderr.deinit();
|
||||
}
|
||||
|
||||
this.env.deinit(this.manager.allocator);
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
pub fn spawnPackageScripts(
|
||||
manager: *PackageManager,
|
||||
list: Lockfile.Package.Scripts.List,
|
||||
envp: [:null]?[*:0]u8,
|
||||
env: bun.DotEnv.Map.NullDelimitedEnvMap,
|
||||
comptime log_level: PackageManager.Options.LogLevel,
|
||||
) !void {
|
||||
var lifecycle_subprocess = LifecycleScriptSubprocess.new(.{
|
||||
.manager = manager,
|
||||
.envp = envp,
|
||||
.env = env,
|
||||
.scripts = list,
|
||||
.package_name = list.package_name,
|
||||
});
|
||||
|
||||
@@ -1112,7 +1112,7 @@ pub const Printer = struct {
|
||||
};
|
||||
|
||||
env_loader.loadProcess();
|
||||
try env_loader.load(entries_option.entries, &[_][]u8{}, .production);
|
||||
try env_loader.load(entries_option.entries, &[_][]u8{}, .production, false);
|
||||
var log = logger.Log.init(allocator);
|
||||
try options.load(
|
||||
allocator,
|
||||
|
||||
@@ -568,17 +568,21 @@ pub noinline fn print(comptime fmt: string, args: anytype) callconv(std.builtin.
|
||||
/// BUN_DEBUG_foo=1
|
||||
/// To enable all logs, set the environment variable
|
||||
/// BUN_DEBUG_ALL=1
|
||||
const _log_fn = fn (comptime fmt: string, args: anytype) void;
|
||||
pub fn scoped(comptime tag: anytype, comptime disabled: bool) _log_fn {
|
||||
const LogFunction = fn (comptime fmt: string, args: anytype) void;
|
||||
|
||||
pub fn createScope(comptime tag: anytype, comptime disabled: bool) type {
|
||||
const tagname = switch (@TypeOf(tag)) {
|
||||
@Type(.EnumLiteral) => @tagName(tag),
|
||||
[]const u8 => tag,
|
||||
else => @compileError("Output.scoped expected @Type(.EnumLiteral) or []const u8, you gave: " ++ @typeName(@Type(tag))),
|
||||
else => @compileError("Output.scoped expected @Type(.EnumLiteral) or []const u8, you gave: " ++ @typeName(@TypeOf(tag))),
|
||||
};
|
||||
if (comptime !Environment.isDebug or !Environment.isNative) {
|
||||
return struct {
|
||||
pub fn isVisible() bool {
|
||||
return false;
|
||||
}
|
||||
pub fn log(comptime _: string, _: anytype) void {}
|
||||
}.log;
|
||||
};
|
||||
}
|
||||
|
||||
return struct {
|
||||
@@ -590,6 +594,21 @@ pub fn scoped(comptime tag: anytype, comptime disabled: bool) _log_fn {
|
||||
var evaluated_disable = false;
|
||||
var lock = std.Thread.Mutex{};
|
||||
|
||||
pub fn isVisible() bool {
|
||||
if (!evaluated_disable) {
|
||||
evaluated_disable = true;
|
||||
if (bun.getenvZ("BUN_DEBUG_ALL") != null or
|
||||
bun.getenvZ("BUN_DEBUG_" ++ tagname) != null)
|
||||
{
|
||||
really_disable = false;
|
||||
} else if (bun.getenvZ("BUN_DEBUG_QUIET_LOGS")) |val| {
|
||||
really_disable = really_disable or !strings.eqlComptime(val, "0");
|
||||
}
|
||||
}
|
||||
|
||||
return !really_disable;
|
||||
}
|
||||
|
||||
/// Debug-only logs which should not appear in release mode
|
||||
/// To enable a specific log at runtime, set the environment variable
|
||||
/// BUN_DEBUG_${TAG} to 1
|
||||
@@ -606,18 +625,7 @@ pub fn scoped(comptime tag: anytype, comptime disabled: bool) _log_fn {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!evaluated_disable) {
|
||||
evaluated_disable = true;
|
||||
if (bun.getenvZ("BUN_DEBUG_ALL") != null or
|
||||
bun.getenvZ("BUN_DEBUG_" ++ tagname) != null)
|
||||
{
|
||||
really_disable = false;
|
||||
} else if (bun.getenvZ("BUN_DEBUG_QUIET_LOGS")) |val| {
|
||||
really_disable = really_disable or !strings.eqlComptime(val, "0");
|
||||
}
|
||||
}
|
||||
|
||||
if (really_disable)
|
||||
if (!isVisible())
|
||||
return;
|
||||
|
||||
if (!out_set) {
|
||||
@@ -650,7 +658,11 @@ pub fn scoped(comptime tag: anytype, comptime disabled: bool) _log_fn {
|
||||
};
|
||||
}
|
||||
}
|
||||
}.log;
|
||||
};
|
||||
}
|
||||
|
||||
pub fn scoped(comptime tag: anytype, comptime disabled: bool) LogFunction {
|
||||
return createScope(tag, disabled).log;
|
||||
}
|
||||
|
||||
// Valid "colors":
|
||||
@@ -868,9 +880,11 @@ pub inline fn warn(comptime fmt: []const u8, args: anytype) void {
|
||||
prettyErrorln("<yellow>warn<r><d>:<r> " ++ fmt, args);
|
||||
}
|
||||
|
||||
const debug_scope = Output.createScope(.debug_warn, false);
|
||||
|
||||
/// Print a yellow warning message, only in debug mode
|
||||
pub inline fn debugWarn(comptime fmt: []const u8, args: anytype) void {
|
||||
if (Environment.isDebug) {
|
||||
if (Environment.isDebug and debug_scope.isVisible()) {
|
||||
prettyErrorln("<yellow>debug warn<r><d>:<r> " ++ fmt, args);
|
||||
flush();
|
||||
}
|
||||
|
||||
@@ -1043,7 +1043,7 @@ pub const Interpreter = struct {
|
||||
var iter = env_loader.iterator();
|
||||
|
||||
while (iter.next()) |entry| {
|
||||
const value = EnvStr.initSlice(entry.value_ptr.value);
|
||||
const value = EnvStr.initSlice(entry.value_ptr.*);
|
||||
const key = EnvStr.initSlice(entry.key_ptr.*);
|
||||
export_env.insert(key, value);
|
||||
}
|
||||
@@ -3423,8 +3423,6 @@ pub const Interpreter = struct {
|
||||
var arena = &this.spawn_arena;
|
||||
var arena_allocator = arena.allocator();
|
||||
var spawn_args = Subprocess.SpawnArgs.default(arena, this.base.interpreter.event_loop, false);
|
||||
|
||||
spawn_args.argv = std.ArrayListUnmanaged(?[*:0]const u8){};
|
||||
spawn_args.cmd_parent = this;
|
||||
spawn_args.cwd = this.base.shell.cwdZ();
|
||||
|
||||
|
||||
@@ -610,10 +610,7 @@ pub const ShellSubprocess = struct {
|
||||
cmd_parent: ?*ShellCmd = null,
|
||||
|
||||
override_env: bool = false,
|
||||
env_array: std.ArrayListUnmanaged(?[*:0]const u8) = .{
|
||||
.items = &.{},
|
||||
.capacity = 0,
|
||||
},
|
||||
env_array: std.ArrayListUnmanaged(?[*:0]const u8) = .{},
|
||||
cwd: []const u8,
|
||||
stdio: [3]Stdio = .{
|
||||
.ignore,
|
||||
@@ -686,10 +683,7 @@ pub const ShellSubprocess = struct {
|
||||
.arena = arena,
|
||||
|
||||
.override_env = false,
|
||||
.env_array = .{
|
||||
.items = &.{},
|
||||
.capacity = 0,
|
||||
},
|
||||
.env_array = .{},
|
||||
.cwd = event_loop.topLevelDir(),
|
||||
.stdio = .{
|
||||
.{ .ignore = {} },
|
||||
@@ -698,7 +692,7 @@ pub const ShellSubprocess = struct {
|
||||
},
|
||||
.lazy = false,
|
||||
.PATH = event_loop.env().get("PATH") orelse "",
|
||||
.argv = undefined,
|
||||
.argv = .{},
|
||||
.detached = false,
|
||||
// .ipc_mode = IPCMode.none,
|
||||
// .ipc_callback = .zero,
|
||||
@@ -708,6 +702,7 @@ pub const ShellSubprocess = struct {
|
||||
out.stdio[1] = .{ .pipe = {} };
|
||||
out.stdio[2] = .{ .pipe = {} };
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -752,21 +747,20 @@ pub const ShellSubprocess = struct {
|
||||
pub fn spawnAsync(
|
||||
event_loop: JSC.EventLoopHandle,
|
||||
shellio: *ShellIO,
|
||||
spawn_args_: SpawnArgs,
|
||||
spawn_args: SpawnArgs,
|
||||
out: **@This(),
|
||||
) bun.shell.Result(void) {
|
||||
var cloned_spawn_args = spawn_args;
|
||||
var arena = @import("root").bun.ArenaAllocator.init(bun.default_allocator);
|
||||
defer arena.deinit();
|
||||
|
||||
var spawn_args = spawn_args_;
|
||||
|
||||
_ = switch (spawnMaybeSyncImpl(
|
||||
.{
|
||||
.is_sync = false,
|
||||
},
|
||||
event_loop,
|
||||
arena.allocator(),
|
||||
&spawn_args,
|
||||
&cloned_spawn_args,
|
||||
shellio,
|
||||
out,
|
||||
)) {
|
||||
@@ -789,11 +783,34 @@ pub const ShellSubprocess = struct {
|
||||
) bun.shell.Result(*@This()) {
|
||||
const is_sync = config.is_sync;
|
||||
|
||||
if (!spawn_args.override_env and spawn_args.env_array.items.len == 0) {
|
||||
// spawn_args.env_array.items = jsc_vm.bundler.env.map.createNullDelimitedEnvMap(allocator) catch bun.outOfMemory();
|
||||
spawn_args.env_array.items = event_loop.createNullDelimitedEnvMap(allocator) catch bun.outOfMemory();
|
||||
spawn_args.env_array.capacity = spawn_args.env_array.items.len;
|
||||
}
|
||||
const override_env = !spawn_args.override_env and spawn_args.env_array.items.len == 0;
|
||||
const env: bun.DotEnv.Map.NullDelimitedEnvMap = brk: {
|
||||
if (override_env) {
|
||||
break :brk event_loop.createNullDelimitedEnvMap(allocator) catch {
|
||||
return .{ .err = .{
|
||||
.custom = bun.default_allocator.dupe(u8, "out of memory") catch bun.outOfMemory(),
|
||||
} };
|
||||
};
|
||||
} else {
|
||||
// Make sure this is a null-terminated
|
||||
if (spawn_args.env_array.items.len == 0 or
|
||||
spawn_args.env_array.getLast() != null)
|
||||
{
|
||||
spawn_args.env_array.append(allocator, null) catch {
|
||||
return .{ .err = .{
|
||||
.custom = bun.default_allocator.dupe(u8, "out of memory") catch bun.outOfMemory(),
|
||||
} };
|
||||
};
|
||||
}
|
||||
|
||||
break :brk .{
|
||||
.envp = spawn_args.env_array.items[0 .. spawn_args.env_array.items.len - 1 :null],
|
||||
// undefined is fine because `env.deinit` is only called if the `override_env` path is taken.
|
||||
.allocation_size = undefined,
|
||||
};
|
||||
}
|
||||
};
|
||||
defer if (override_env) env.deinit(allocator);
|
||||
|
||||
var should_close_memfd = Environment.isLinux;
|
||||
|
||||
@@ -845,14 +862,10 @@ pub const ShellSubprocess = struct {
|
||||
return .{ .err = .{ .custom = bun.default_allocator.dupe(u8, "out of memory") catch bun.outOfMemory() } };
|
||||
};
|
||||
|
||||
spawn_args.env_array.append(allocator, null) catch {
|
||||
return .{ .err = .{ .custom = bun.default_allocator.dupe(u8, "out of memory") catch bun.outOfMemory() } };
|
||||
};
|
||||
|
||||
var spawn_result = switch (bun.spawn.spawnProcess(
|
||||
&spawn_options,
|
||||
@ptrCast(spawn_args.argv.items.ptr),
|
||||
@ptrCast(spawn_args.env_array.items.ptr),
|
||||
env.envp,
|
||||
) catch |err| {
|
||||
return .{ .err = .{ .custom = std.fmt.allocPrint(bun.default_allocator, "Failed to spawn process: {s}", .{@errorName(err)}) catch bun.outOfMemory() } };
|
||||
}) {
|
||||
|
||||
@@ -710,6 +710,99 @@ test("NODE_ENV default is not propogated in bun run", () => {
|
||||
expect(bunRunAsScript(tmp, "show-env", {}).stdout).toBe("");
|
||||
});
|
||||
|
||||
for (const shell of ["system", "bun"]) {
|
||||
const isWindowsCMD = isWindows && shell === "system";
|
||||
|
||||
const show_env_script = isWindowsCMD //
|
||||
? "echo ENV_FILE_NAME=%ENV_FILE_NAME%, NODE_ENV=%NODE_ENV%"
|
||||
: "echo ENV_FILE_NAME=$ENV_FILE_NAME, NODE_ENV=$NODE_ENV";
|
||||
|
||||
describe(`script runner with ${shell} shell`, () => {
|
||||
test("does not pass variables from .env files into scripts", () => {
|
||||
const tmp = tempDirWithFiles("script-runner-env", {
|
||||
"package.json": '{"scripts":{"show-env":"' + show_env_script + '"}}',
|
||||
|
||||
".env.development": "ENV_FILE_NAME=.env.development",
|
||||
".env.production": "ENV_FILE_NAME=.env.production",
|
||||
".env.test": "ENV_FILE_NAME=.env.test",
|
||||
".env": "ENV_FILE_NAME=.env",
|
||||
});
|
||||
|
||||
expect(bunRunAsScript(tmp, "show-env", {}, ["--shell=" + shell]).stdout).toBe("ENV_FILE_NAME=, NODE_ENV=");
|
||||
});
|
||||
|
||||
test(".env.local is loaded by the script runner", () => {
|
||||
const tmp = tempDirWithFiles("script-runner-env", {
|
||||
"package.json": '{"scripts":{"show-env":"' + show_env_script + '"}}',
|
||||
|
||||
".env.local": "ENV_FILE_NAME=.env.local",
|
||||
".env.development": "ENV_FILE_NAME=.env.development",
|
||||
".env.production": "ENV_FILE_NAME=.env.production",
|
||||
".env.test": "ENV_FILE_NAME=.env.test",
|
||||
".env": "ENV_FILE_NAME=.env",
|
||||
});
|
||||
expect(bunRunAsScript(tmp, "show-env", {}, ["--shell=" + shell]).stdout).toBe(
|
||||
"ENV_FILE_NAME=.env.local, NODE_ENV=",
|
||||
);
|
||||
});
|
||||
|
||||
for (const { NODE_ENV, expected, env_file } of [
|
||||
{
|
||||
NODE_ENV: "production",
|
||||
expected: "production",
|
||||
env_file: ".env.production",
|
||||
},
|
||||
{
|
||||
NODE_ENV: "development",
|
||||
expected: "development",
|
||||
env_file: ".env.development",
|
||||
},
|
||||
{
|
||||
NODE_ENV: undefined,
|
||||
expected: "",
|
||||
env_file: ".env.development",
|
||||
},
|
||||
]) {
|
||||
test("explicit NODE_ENV=" + NODE_ENV, () => {
|
||||
const tmp = tempDirWithFiles("script-runner-env", {
|
||||
"package.json": '{"scripts":{"show-env":"' + show_env_script + '"}}',
|
||||
|
||||
".env.development": "ENV_FILE_NAME=.env.development",
|
||||
".env.production": "ENV_FILE_NAME=.env.production",
|
||||
".env.test": "ENV_FILE_NAME=.env.test",
|
||||
".env": "ENV_FILE_NAME=.env",
|
||||
});
|
||||
|
||||
expect(bunRunAsScript(tmp, "show-env", { NODE_ENV }, ["--shell=" + shell]).stdout).toBe(
|
||||
"ENV_FILE_NAME=, NODE_ENV=" + expected,
|
||||
);
|
||||
});
|
||||
|
||||
// This is already covered in isolation by the '.env file is loaded' describe
|
||||
// but it is nice to have just a couple e2e tests combining script runner AND the runtime.
|
||||
test("e2e NODE_ENV=" + NODE_ENV, () => {
|
||||
const run_index_script = isWindowsCMD
|
||||
? `set NODE_ENV=${NODE_ENV} && bun run index.ts`
|
||||
: `NODE_ENV=${NODE_ENV} bun run index.ts`;
|
||||
|
||||
const tmp = tempDirWithFiles("script-runner-env", {
|
||||
"package.json": '{"scripts":{"start":"' + run_index_script + '"}}',
|
||||
"index.ts": "console.log(`ENV_FILE_NAME=${process.env.ENV_FILE_NAME}, NODE_ENV=${process.env.NODE_ENV}`);",
|
||||
|
||||
".env.development": "ENV_FILE_NAME=.env.development",
|
||||
".env.production": "ENV_FILE_NAME=.env.production",
|
||||
".env.test": "ENV_FILE_NAME=.env.test",
|
||||
".env": "ENV_FILE_NAME=.env",
|
||||
});
|
||||
|
||||
expect(bunRunAsScript(tmp, "start", {}, ["--shell=" + shell]).stdout).toBe(
|
||||
"ENV_FILE_NAME=" + env_file + ", NODE_ENV=" + NODE_ENV,
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const todoOnPosix = process.platform !== "win32" ? test.todo : test;
|
||||
todoOnPosix("setting process.env coerces the value to a string", () => {
|
||||
// @ts-expect-error
|
||||
|
||||
@@ -168,8 +168,13 @@ export function bunTest(file: string, env?: Record<string, string>) {
|
||||
};
|
||||
}
|
||||
|
||||
export function bunRunAsScript(dir: string, script: string, env?: Record<string, string>) {
|
||||
const result = Bun.spawnSync([bunExe(), `run`, `${script}`], {
|
||||
export function bunRunAsScript(
|
||||
dir: string,
|
||||
script: string,
|
||||
env?: Record<string, string | undefined>,
|
||||
execArgv?: string[],
|
||||
) {
|
||||
const result = Bun.spawnSync([bunExe(), ...(execArgv ?? []), `run`, `${script}`], {
|
||||
cwd: dir,
|
||||
env: {
|
||||
...bunEnv,
|
||||
|
||||
Reference in New Issue
Block a user