diff options
-rw-r--r-- | src/cli.zig | 20 | ||||
-rw-r--r-- | src/cli/run_command.zig | 264 | ||||
-rw-r--r-- | src/env_loader.zig | 14 | ||||
-rw-r--r-- | src/install/install.zig | 2 |
4 files changed, 222 insertions, 78 deletions
diff --git a/src/cli.zig b/src/cli.zig index 2a9b31091..fac996b5a 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -163,6 +163,7 @@ pub const Arguments = struct { const public_params = [_]ParamType{ clap.parseParam("--use <STR> Choose a framework, e.g. \"--use next\". It checks first for a package named \"bun-framework-packagename\" and then \"packagename\".") catch unreachable, + clap.parseParam("-b, --bun Force a script or package to use Bun.js instead of Node.js (via symlinking node)") catch unreachable, clap.parseParam("--bunfile <STR> Use a .bun file (default: node_modules.bun)") catch unreachable, clap.parseParam("--server-bunfile <STR> Use a .server.bun file (default: node_modules.server.bun)") catch unreachable, clap.parseParam("--cwd <STR> Absolute path to resolve files & entry points from. This just changes the process' cwd.") catch unreachable, @@ -178,7 +179,7 @@ pub const Arguments = struct { clap.parseParam("--main-fields <STR>... Main fields to lookup in package.json. Defaults to --platform dependent") catch unreachable, clap.parseParam("--no-summary Don't print a summary (when generating .bun") catch unreachable, clap.parseParam("-v, --version Print version and exit") catch unreachable, - clap.parseParam("--platform <STR> \"browser\" or \"node\". Defaults to \"browser\"") catch unreachable, + clap.parseParam("--platform <STR> \"bun\" or \"browser\" or \"node\", used when building or bundling") catch unreachable, // clap.parseParam("--production [not implemented] generate production code") catch unreachable, clap.parseParam("--public-dir <STR> Top-level directory for .html files, fonts or anything external. Defaults to \"<cwd>/public\", to match create-react-app and Next.js") catch unreachable, clap.parseParam("--tsconfig-override <STR> Load tsconfig from path instead of cwd/tsconfig.json") catch unreachable, @@ -618,8 +619,12 @@ pub const Arguments = struct { PlatformMatcher.case("bun") => Api.Platform.bun, else => invalidPlatform(&diag, _platform), }; + + ctx.debug.run_in_bun = opts.platform.? == .bun; } + ctx.debug.run_in_bun = args.flag("--bun") or ctx.debug.run_in_bun; + if (jsx_factory != null or jsx_fragment != null or jsx_import_source != null or @@ -815,6 +820,7 @@ pub const Command = struct { hot_reload: bool = false, global_cache: options.GlobalCache = .auto, offline_mode_setting: ?Bunfig.OfflineMode = null, + run_in_bun: bool = false, // technical debt macros: ?MacroMap = null, @@ -1207,12 +1213,21 @@ pub const Command = struct { break :brk null; }; + const force_using_bun = ctx.debug.run_in_bun; + var did_check = false; if (default_loader) |loader| { if (loader.isJavaScriptLike()) { was_js_like = true; if (maybeOpenWithBunJS(&ctx)) { return; } + did_check = true; + } + } + + if (force_using_bun and !did_check) { + if (maybeOpenWithBunJS(&ctx)) { + return; } } @@ -1243,6 +1258,9 @@ pub const Command = struct { } fn maybeOpenWithBunJS(ctx: *const Command.Context) bool { + if (ctx.args.entry_points.len == 0) + return false; + const script_name_to_search = ctx.args.entry_points[0]; var file_path = script_name_to_search; diff --git a/src/cli/run_command.zig b/src/cli/run_command.zig index bbeed40a2..86404c315 100644 --- a/src/cli/run_command.zig +++ b/src/cli/run_command.zig @@ -84,6 +84,8 @@ pub const RunCommand = struct { const BUN_BIN_NAME = if (Environment.isDebug) "bun-debug" else "bun"; const BUN_RUN = std.fmt.comptimePrint("{s} run", .{BUN_BIN_NAME}); + const BUN_RUN_USING_BUN = std.fmt.comptimePrint("{s} --bun run", .{BUN_BIN_NAME}); + // Look for invocations of any: // - yarn run // - yarn $cmdName @@ -91,7 +93,10 @@ pub const RunCommand = struct { // - npm run // Replace them with "bun run" - pub inline fn replacePackageManagerRun(copy_script: *std.ArrayList(u8), script: string) !void { + pub inline fn replacePackageManagerRun( + copy_script: *std.ArrayList(u8), + script: string, + ) !void { var entry_i: usize = 0; var delimiter: u8 = ' '; @@ -110,7 +115,7 @@ pub const RunCommand = struct { if (strings.indexOfChar(next, ' ')) |space| { const yarn_cmd = next[0..space]; if (strings.eqlComptime(yarn_cmd, "run")) { - try copy_script.appendSlice("bun run"); + try copy_script.appendSlice(BUN_RUN); entry_i += "yarn run".len; continue; } @@ -132,7 +137,7 @@ pub const RunCommand = struct { // implicit yarn commands if (std.mem.indexOfScalar(u64, yarn_commands, std.hash.Wyhash.hash(0, yarn_cmd)) == null) { - try copy_script.appendSlice("bun run"); + try copy_script.appendSlice(BUN_RUN); try copy_script.append(' '); try copy_script.appendSlice(yarn_cmd); entry_i += "yarn ".len + yarn_cmd.len; @@ -166,7 +171,7 @@ pub const RunCommand = struct { if (base.len > "npm run ".len) { const remainder = base[0.."npm run ".len]; if (strings.eqlComptimeIgnoreLen(remainder, "npm run ")) { - try copy_script.appendSlice("bun run "); + try copy_script.appendSlice(BUN_RUN ++ " "); entry_i += remainder.len; delimiter = 0; continue; @@ -183,7 +188,7 @@ pub const RunCommand = struct { if (npm_i < script.len) { const remainder = script[start .. npm_i + 1]; if (remainder.len > npm_i and strings.eqlComptimeIgnoreLen(remainder, "pnpm run") and remainder[remainder.len - 1] == delimiter) { - try copy_script.appendSlice("bun run "); + try copy_script.appendSlice(BUN_RUN ++ " "); entry_i += remainder.len; delimiter = 0; continue; @@ -411,6 +416,68 @@ pub const RunCommand = struct { this_bundler.configureLinker(); } + const bun_node_dir = switch (@import("builtin").target.os.tag) { + // TODO: + .windows => "TMPDIR", + + .macos => "/private/tmp", + else => "/tmp", + } ++ if (!Environment.isDebug) + "/bun-node" + else + "/bun-debug-node"; + + var self_exe_bin_path_buf: [bun.MAX_PATH_BYTES + 1]u8 = undefined; + fn createFakeTemporaryNodeExecutable(PATH: *std.ArrayList(u8), optional_bun_path: *string) !void { + var retried = false; + + if (!strings.endsWithComptime(std.mem.span(std.os.argv[0]), "node")) { + var argv0 = std.meta.assumeSentinel(optional_bun_path.*, 0).ptr; + + // if we are already an absolute path, use that + // if the user started the application via a shebang, it's likely that the path is absolute already + if (std.os.argv[0][0] == '/') { + optional_bun_path.* = bun.span(std.os.argv[0]); + argv0 = std.os.argv[0]; + } else if (optional_bun_path.len == 0) { + // otherwise, ask the OS for the absolute path + var self = std.fs.selfExePath(&self_exe_bin_path_buf) catch ""; + if (self.len > 0) { + self.ptr[self.len] = 0; + argv0 = std.meta.assumeSentinel(self, 0).ptr; + optional_bun_path.* = self; + } + } + + if (optional_bun_path.len == 0) { + argv0 = std.os.argv[0]; + } + + while (true) { + inner: { + std.os.symlinkZ(argv0, bun_node_dir ++ "/node") catch |err| { + if (err == error.PathAlreadyExists) break :inner; + if (retried) + return; + + std.fs.makeDirAbsoluteZ(bun_node_dir) catch {}; + + retried = true; + continue; + }; + } + _ = bun.C.chmod(bun_node_dir ++ "/node", 0o777); + break; + } + } + + if (PATH.items.len > 0) { + try PATH.append(':'); + } + + try PATH.appendSlice(bun_node_dir ++ ":"); + } + pub const Filter = enum { script, bin, all, bun_js, all_plus_bun_js, script_and_descriptions, script_exclude }; const DirInfo = @import("../resolver/dir_info.zig"); pub fn configureEnvForRun( @@ -419,6 +486,7 @@ pub const RunCommand = struct { env: ?*DotEnv.Loader, ORIGINAL_PATH: *string, log_errors: bool, + force_using_bun: bool, ) !*DirInfo { var args = ctx.args; args.node_modules_bundle_path = null; @@ -493,6 +561,14 @@ pub const RunCommand = struct { var PATH = this_bundler.env.map.get("PATH") orelse ""; ORIGINAL_PATH.* = PATH; + const found_node = this_bundler.env.loadNodeJSConfig( + this_bundler.fs, + if (force_using_bun) bun_node_dir ++ "/node" else "", + ) catch false; + + var needs_to_force_bun = force_using_bun or !found_node; + var optional_bun_self_path: string = ""; + if (bin_dirs.len > 0 or package_json_dir.len > 0) { var new_path_len: usize = PATH.len + 2; for (bin_dirs) |bin| { @@ -503,8 +579,21 @@ pub const RunCommand = struct { new_path_len += package_json_dir.len + 1; } + new_path_len += if (needs_to_force_bun) bun_node_dir.len + 1 else 0; + var new_path = try std.ArrayList(u8).initCapacity(ctx.allocator, new_path_len); + if (needs_to_force_bun) { + createFakeTemporaryNodeExecutable(&new_path, &optional_bun_self_path) catch unreachable; + if (!force_using_bun) { + this_bundler.env.map.put("NODE", bun_node_dir ++ "/node") catch unreachable; + this_bundler.env.map.put("npm_node_execpath", bun_node_dir ++ "/node") catch unreachable; + this_bundler.env.map.put("npm_execpath", optional_bun_self_path) catch unreachable; + } + + needs_to_force_bun = false; + } + { var needs_colon = false; if (package_json_dir.len > 0) { @@ -537,7 +626,25 @@ pub const RunCommand = struct { PATH = new_path.items; } - this_bundler.env.loadNodeJSConfig(this_bundler.fs) catch {}; + if (needs_to_force_bun) { + needs_to_force_bun = false; + + var new_path = try std.ArrayList(u8).initCapacity(ctx.allocator, PATH.len); + createFakeTemporaryNodeExecutable(&new_path, &optional_bun_self_path) catch unreachable; + if (new_path.items.len > 0) + try new_path.append(':'); + try new_path.appendSlice(PATH); + + this_bundler.env.map.put("PATH", new_path.items) catch unreachable; + + if (!force_using_bun) { + this_bundler.env.map.put("NODE", bun_node_dir ++ "/node") catch unreachable; + this_bundler.env.map.put("npm_node_execpath", bun_node_dir ++ "/node") catch unreachable; + this_bundler.env.map.put("npm_execpath", optional_bun_self_path) catch unreachable; + } + PATH = new_path.items; + } + this_bundler.env.map.putDefault("npm_config_local_prefix", this_bundler.fs.top_level_dir) catch unreachable; // we have no way of knowing what version they're expecting without running the node executable @@ -793,75 +900,84 @@ pub const RunCommand = struct { } const passthrough = ctx.passthrough; + const force_using_bun = ctx.debug.run_in_bun; - if (comptime log_errors) { + if (log_errors or force_using_bun) { if (script_name_to_search.len > 0) { possibly_open_with_bun_js: { - if (options.defaultLoaders.get(std.fs.path.extension(script_name_to_search))) |loader| { - if (loader.isJavaScriptLike()) { - var file_path = script_name_to_search; - const file_: std.fs.File.OpenError!std.fs.File = brk: { - if (script_name_to_search[0] == std.fs.path.sep) { - break :brk std.fs.openFileAbsolute(script_name_to_search, .{ .mode = .read_only }); - } else { - const cwd = std.os.getcwd(&path_buf) catch break :possibly_open_with_bun_js; - path_buf[cwd.len] = std.fs.path.sep; - var parts = [_]string{script_name_to_search}; - file_path = resolve_path.joinAbsStringBuf( - path_buf[0 .. cwd.len + 1], - &path_buf2, - &parts, - .auto, - ); - if (file_path.len == 0) break :possibly_open_with_bun_js; - path_buf2[file_path.len] = 0; - var file_pathZ = path_buf2[0..file_path.len :0]; - break :brk std.fs.openFileAbsoluteZ(file_pathZ, .{ .mode = .read_only }); - } - }; - - const file = file_ catch break :possibly_open_with_bun_js; - // "White space after #! is optional." - var shebang_buf: [64]u8 = undefined; - const shebang_size = file.pread(&shebang_buf, 0) catch |err| { - Output.prettyErrorln("<r><red>error<r>: Failed to read file <b>{s}<r> due to error <b>{s}<r>", .{ file_path, @errorName(err) }); - Global.exit(1); - }; - - var shebang: string = shebang_buf[0..shebang_size]; - shebang = std.mem.trim(u8, shebang, " \r\n\t"); - if (shebang.len == 0) break :possibly_open_with_bun_js; - - if (shebang.len > 2 and strings.eqlComptimeIgnoreLen(shebang[0..2], "#!")) { - const first_arg: string = if (std.os.argv.len > 0) bun.span(std.os.argv[0]) else ""; - const filename = std.fs.path.basename(first_arg); - // are we attempting to run the script with bun? - if (!strings.contains(shebang, filename)) { - break :possibly_open_with_bun_js; - } - } - Global.configureAllocator(.{ .long_running = true }); - - Run.boot(ctx, file, ctx.allocator.dupe(u8, file_path) catch unreachable) catch |err| { - if (Output.enable_ansi_colors) { - ctx.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), true) catch {}; - } else { - ctx.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), false) catch {}; - } + if (!force_using_bun) { + if (options.defaultLoaders.get(std.fs.path.extension(script_name_to_search))) |load| { + if (!load.isJavaScriptLike()) + break :possibly_open_with_bun_js; + } else { + break :possibly_open_with_bun_js; + } + } - Output.prettyErrorln("<r><red>error<r>: Failed to run <b>{s}<r> due to error <b>{s}<r>", .{ - std.fs.path.basename(file_path), - @errorName(err), - }); - Global.exit(1); - }; + var file_path = script_name_to_search; - return true; + const file_: std.fs.File.OpenError!std.fs.File = brk: { + if (script_name_to_search[0] == std.fs.path.sep) { + break :brk std.fs.openFileAbsolute(script_name_to_search, .{ .mode = .read_only }); + } else { + const cwd = std.os.getcwd(&path_buf) catch break :possibly_open_with_bun_js; + path_buf[cwd.len] = std.fs.path.sep; + var parts = [_]string{script_name_to_search}; + file_path = resolve_path.joinAbsStringBuf( + path_buf[0 .. cwd.len + 1], + &path_buf2, + &parts, + .auto, + ); + if (file_path.len == 0) break :possibly_open_with_bun_js; + path_buf2[file_path.len] = 0; + var file_pathZ = path_buf2[0..file_path.len :0]; + break :brk std.fs.openFileAbsoluteZ(file_pathZ, .{ .mode = .read_only }); + } + }; - // If we get here, then we run it with bun.js + const file = file_ catch break :possibly_open_with_bun_js; + // ignore the shebang if they explicitly passed `--bun` + if (!force_using_bun) { + // "White space after #! is optional." + var shebang_buf: [64]u8 = undefined; + const shebang_size = file.pread(&shebang_buf, 0) catch |err| { + Output.prettyErrorln("<r><red>error<r>: Failed to read file <b>{s}<r> due to error <b>{s}<r>", .{ file_path, @errorName(err) }); + Global.exit(1); + }; + + var shebang: string = shebang_buf[0..shebang_size]; + shebang = std.mem.trim(u8, shebang, " \r\n\t"); + if (shebang.len == 0) break :possibly_open_with_bun_js; + + if (shebang.len > 2 and strings.eqlComptimeIgnoreLen(shebang[0..2], "#!")) { + const first_arg: string = if (std.os.argv.len > 0) bun.span(std.os.argv[0]) else ""; + const filename = std.fs.path.basename(first_arg); + // are we attempting to run the script with bun? + if (!strings.contains(shebang, filename)) { + break :possibly_open_with_bun_js; + } } } + + Global.configureAllocator(.{ .long_running = true }); + + Run.boot(ctx, file, ctx.allocator.dupe(u8, file_path) catch unreachable) catch |err| { + if (Output.enable_ansi_colors) { + ctx.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), true) catch {}; + } else { + ctx.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), false) catch {}; + } + + Output.prettyErrorln("<r><red>error<r>: Failed to run <b>{s}<r> due to error <b>{s}<r>", .{ + std.fs.path.basename(file_path), + @errorName(err), + }); + Global.exit(1); + }; + + return true; } } } @@ -871,7 +987,7 @@ pub const RunCommand = struct { var did_print = false; var ORIGINAL_PATH: string = ""; var this_bundler: bundler.Bundler = undefined; - var root_dir_info = try configureEnvForRun(ctx, &this_bundler, null, &ORIGINAL_PATH, log_errors); + var root_dir_info = try configureEnvForRun(ctx, &this_bundler, null, &ORIGINAL_PATH, log_errors, force_using_bun); if (root_dir_info.enclosing_package_json) |package_json| { if (package_json.scripts) |scripts| { switch (script_name_to_search.len) { @@ -908,6 +1024,7 @@ pub const RunCommand = struct { else => { if (scripts.get(script_name_to_search)) |script_content| { // allocate enough to hold "post${scriptname}" + var temp_script_buffer = try std.fmt.allocPrint(ctx.allocator, "ppre{s}", .{script_name_to_search}); if (scripts.get(temp_script_buffer[1..])) |prescript| { @@ -924,7 +1041,10 @@ pub const RunCommand = struct { } } - if (!try runPackageScript( + std.mem.copy(u8, temp_script_buffer, "post"); + const postscript_ = scripts.get(temp_script_buffer); + + if (!try runPackageScriptWithIsLast( ctx.allocator, script_content, script_name_to_search, @@ -932,12 +1052,11 @@ pub const RunCommand = struct { this_bundler.env, passthrough, ctx.debug.silent, + postscript_ == null, )) return false; - std.mem.copy(u8, temp_script_buffer, "post"); - - if (scripts.get(temp_script_buffer)) |postscript| { - if (!try runPackageScript( + if (postscript_) |postscript| { + if (!try runPackageScriptWithIsLast( ctx.allocator, postscript, temp_script_buffer, @@ -945,6 +1064,7 @@ pub const RunCommand = struct { this_bundler.env, passthrough, ctx.debug.silent, + true, )) { return false; } diff --git a/src/env_loader.zig b/src/env_loader.zig index 222d98910..b2ee6815a 100644 --- a/src/env_loader.zig +++ b/src/env_loader.zig @@ -431,13 +431,17 @@ pub const Loader = struct { this.map.get("bamboo.buildKey")) != null; } - pub fn loadNodeJSConfig(this: *Loader, fs: *Fs.FileSystem) !void { + pub fn loadNodeJSConfig(this: *Loader, fs: *Fs.FileSystem, override_node: []const u8) !bool { var buf: Fs.PathBuffer = undefined; - var node = this.getNodePath(fs, &buf) orelse return; - var cloned = try fs.dirname_store.append([]const u8, std.mem.span(node)); - try this.map.put("NODE", cloned); - try this.map.put("npm_node_execpath", cloned); + var node_path_to_use = override_node; + if (node_path_to_use.len == 0) { + var node = this.getNodePath(fs, &buf) orelse return false; + node_path_to_use = try fs.dirname_store.append([]const u8, std.mem.span(node)); + } + try this.map.put("NODE", node_path_to_use); + try this.map.put("npm_node_execpath", node_path_to_use); + return true; } pub fn get(this: *const Loader, key: string) ?string { diff --git a/src/install/install.zig b/src/install/install.zig index 592eaf14b..758585015 100644 --- a/src/install/install.zig +++ b/src/install/install.zig @@ -6549,6 +6549,7 @@ pub const PackageManager = struct { manager.env, &ORIGINAL_PATH, log_level != .silent, + false, ); } @@ -6676,6 +6677,7 @@ pub const PackageManager = struct { manager.env, &ORIGINAL_PATH, log_level != .silent, + false, ); } else { // bun install may have installed new bins, so we need to update the PATH |