const bun = @import("bun"); const string = bun.string; const Output = bun.Output; const Global = bun.Global; const Environment = bun.Environment; const strings = bun.strings; const stringZ = bun.stringZ; const default_allocator = bun.default_allocator; const std = @import("std"); const JSC = bun.JSC; const OOM = bun.OOM; const clap = bun.clap; const CLI = bun.CLI; const Arguments = CLI.Arguments; const Command = CLI.Command; const options = @import("../options.zig"); const Api = @import("../api/schema.zig").Api; const resolve_path = @import("../resolver/resolve_path.zig"); const transpiler = bun.transpiler; const DotEnv = @import("../env_loader.zig"); const which = @import("../which.zig").which; const Run = @import("../bun_js.zig").Run; var path_buf: bun.PathBuffer = undefined; var path_buf2: bun.PathBuffer = undefined; const NpmArgs = struct { // https://github.com/npm/rfcs/blob/main/implemented/0021-reduce-lifecycle-script-environment.md#detailed-explanation pub const package_name: string = "npm_package_name"; pub const package_version: string = "npm_package_version"; }; const PackageJSON = @import("../resolver/package_json.zig").PackageJSON; const yarn_commands = @import("./list-of-yarn-commands.zig").all_yarn_commands; const ShellCompletions = @import("./shell_completions.zig"); const windows = std.os.windows; pub const RunCommand = struct { const shells_to_search = &[_]string{ "bash", "sh", "zsh", }; fn findShellImpl(PATH: string, cwd: string) ?stringZ { if (comptime Environment.isWindows) { return "C:\\Windows\\System32\\cmd.exe"; } inline for (shells_to_search) |shell| { if (which(&path_buf, PATH, cwd, shell)) |shell_| { return shell_; } } const Try = struct { pub fn shell(str: stringZ) bool { return bun.sys.isExecutableFilePath(str); } }; const hardcoded_popular_ones = [_]stringZ{ "/bin/bash", "/usr/bin/bash", "/usr/local/bin/bash", // don't think this is a real one "/bin/sh", "/usr/bin/sh", // don't think this is a real one "/usr/bin/zsh", "/usr/local/bin/zsh", }; inline for (hardcoded_popular_ones) |shell| { if (Try.shell(shell)) { return shell; } } return null; } /// Find the "best" shell to use /// Cached to only run once pub fn findShell(PATH: string, cwd: string) ?stringZ { const Once = struct { var shell_buf: bun.PathBuffer = undefined; pub var once = bun.once(struct { pub fn run(PATH_: string, cwd_: string) ?stringZ { if (findShellImpl(PATH_, cwd_)) |found| { if (found.len < shell_buf.len) { @memcpy(shell_buf[0..found.len], found); shell_buf[found.len] = 0; return shell_buf[0..found.len :0]; } } return null; } }.run); }; return Once.once.call(.{ PATH, cwd }); } 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 // - pnpm run // - npm run // Replace them with "bun run" pub inline fn replacePackageManagerRun( copy_script: *std.ArrayList(u8), script: string, ) OOM!void { var entry_i: usize = 0; var delimiter: u8 = ' '; while (entry_i < script.len) { const start = entry_i; switch (script[entry_i]) { 'y' => { if (delimiter > 0) { const remainder = script[start..]; if (strings.hasPrefixComptime(remainder, "yarn ")) { const next = remainder["yarn ".len..]; // We have yarn // Find the next space if (strings.indexOfChar(next, ' ')) |space| { const yarn_cmd = next[0..space]; if (strings.eqlComptime(yarn_cmd, "run")) { try copy_script.appendSlice(BUN_RUN); entry_i += "yarn run".len; continue; } // yarn npm is a yarn 2 subcommand if (strings.eqlComptime(yarn_cmd, "npm")) { entry_i += "yarn npm ".len; try copy_script.appendSlice("yarn npm "); continue; } if (strings.startsWith(yarn_cmd, "-")) { // Skip the rest of the command entry_i += "yarn ".len + yarn_cmd.len; try copy_script.appendSlice("yarn "); try copy_script.appendSlice(yarn_cmd); continue; } // implicit yarn commands if (!yarn_commands.has(yarn_cmd)) { try copy_script.appendSlice(BUN_RUN); try copy_script.append(' '); try copy_script.appendSlice(yarn_cmd); entry_i += "yarn ".len + yarn_cmd.len; delimiter = 0; continue; } } } } delimiter = 0; }, ' ' => { delimiter = ' '; }, '"' => { delimiter = '"'; }, '\'' => { delimiter = '\''; }, 'n' => { if (delimiter > 0) { if (strings.hasPrefixComptime(script[start..], "npm run ")) { try copy_script.appendSlice(BUN_RUN ++ " "); entry_i += "npm run ".len; delimiter = 0; continue; } if (strings.hasPrefixComptime(script[start..], "npx ")) { try copy_script.appendSlice(BUN_BIN_NAME ++ " x "); entry_i += "npx ".len; delimiter = 0; continue; } } delimiter = 0; }, 'p' => { if (delimiter > 0) { if (strings.hasPrefixComptime(script[start..], "pnpm run ")) { try copy_script.appendSlice(BUN_RUN ++ " "); entry_i += "pnpm run ".len; delimiter = 0; continue; } if (strings.hasPrefixComptime(script[start..], "pnpm dlx ")) { try copy_script.appendSlice(BUN_BIN_NAME ++ " x "); entry_i += "pnpm dlx ".len; delimiter = 0; continue; } if (strings.hasPrefixComptime(script[start..], "pnpx ")) { try copy_script.appendSlice(BUN_BIN_NAME ++ " x "); entry_i += "pnpx ".len; delimiter = 0; continue; } } delimiter = 0; }, else => { delimiter = 0; }, } try copy_script.append(script[entry_i]); entry_i += 1; } } const log = Output.scoped(.RUN, false); pub fn runPackageScriptForeground( ctx: Command.Context, allocator: std.mem.Allocator, original_script: string, name: string, cwd: string, env: *DotEnv.Loader, passthrough: []const string, silent: bool, use_system_shell: bool, ) !void { const shell_bin = findShell(env.get("PATH") orelse "", cwd) orelse return error.MissingShell; env.map.put("npm_lifecycle_event", name) catch unreachable; env.map.put("npm_lifecycle_script", original_script) catch unreachable; var copy_script_capacity: usize = original_script.len; for (passthrough) |part| copy_script_capacity += 1 + part.len; var copy_script = try std.ArrayList(u8).initCapacity(allocator, copy_script_capacity); // We're going to do this slowly. // Find exact matches of yarn, pnpm, npm try replacePackageManagerRun(©_script, original_script); for (passthrough) |part| { try copy_script.append(' '); if (bun.shell.needsEscapeUtf8AsciiLatin1(part)) { try bun.shell.escape8Bit(part, ©_script, true); } else { try copy_script.appendSlice(part); } } log("Script: \"{s}\"", .{copy_script.items}); if (!silent) { Output.command(copy_script.items); Output.flush(); } if (!use_system_shell) { const mini = bun.JSC.MiniEventLoop.initGlobal(env); const code = bun.shell.Interpreter.initAndRunFromSource(ctx, mini, name, copy_script.items, cwd) catch |err| { if (!silent) { Output.prettyErrorln("error: Failed to run script {s} due to error {s}", .{ name, @errorName(err) }); } Global.exit(1); }; if (code > 0) { if (code != 2 and !silent) { Output.prettyErrorln("error: script \"{s}\" exited with code {d}", .{ name, code }); Output.flush(); } Global.exit(code); } return; } const argv = [_]string{ shell_bin, if (Environment.isWindows) "/c" else "-c", copy_script.items, }; const ipc_fd: ?bun.FD = if (!Environment.isWindows) blk: { const node_ipc_fd = bun.getenvZ("NODE_CHANNEL_FD") orelse break :blk null; const fd = std.fmt.parseInt(u31, node_ipc_fd, 10) catch break :blk null; break :blk bun.FD.fromNative(fd); } else null; // TODO: implement on Windows 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), .cwd = cwd, .stderr = .inherit, .stdout = .inherit, .stdin = .inherit, .ipc = ipc_fd, .windows = if (Environment.isWindows) .{ .loop = JSC.EventLoopHandle.init(JSC.MiniEventLoop.initGlobal(env)), }, }) catch |err| { if (!silent) { Output.prettyErrorln("error: Failed to run script {s} due to error {s}", .{ name, @errorName(err) }); } Output.flush(); return; })) { .err => |err| { if (!silent) { Output.prettyErrorln("error: Failed to run script {s} due to error:\n{}", .{ name, err }); } Output.flush(); return; }, .result => |result| result, }; switch (spawn_result.status) { .exited => |exit_code| { if (exit_code.signal.valid() and exit_code.signal != .SIGINT and !silent) { Output.prettyErrorln("error: script \"{s}\" was terminated by signal {}", .{ name, exit_code.signal.fmt(Output.enable_ansi_colors_stderr) }); Output.flush(); Global.raiseIgnoringPanicHandler(exit_code.signal); } if (exit_code.code != 0) { if (exit_code.code != 2 and !silent) { Output.prettyErrorln("error: script \"{s}\" exited with code {d}", .{ name, exit_code.code }); Output.flush(); } Global.exit(exit_code.code); } }, .signaled => |signal| { if (signal.valid() and signal != .SIGINT and !silent) { Output.prettyErrorln("error: script \"{s}\" was terminated by signal {}", .{ name, signal.fmt(Output.enable_ansi_colors_stderr) }); Output.flush(); } Global.raiseIgnoringPanicHandler(signal); }, .err => |err| { if (!silent) { Output.prettyErrorln("error: Failed to run script {s} due to error:\n{}", .{ name, err }); } Output.flush(); return; }, else => {}, } return; } /// When printing error messages from 'bun run', attribute bun overridden node.js to bun /// This prevents '"node" exited with ...' when it was actually bun. /// As of writing this is only used for 'runBinary' fn basenameOrBun(str: []const u8) []const u8 { // The full path is not used here, because on windows it is dependant on the // username. Before windows we checked bun_node_dir, but this is not allowed on Windows. if (strings.hasSuffixComptime(str, "/bun-node/node" ++ bun.exe_suffix) or (Environment.isWindows and strings.hasSuffixComptime(str, "\\bun-node\\node" ++ bun.exe_suffix))) { return "bun"; } return std.fs.path.basename(str); } /// On windows, this checks for a `.bunx` file in the same directory as the /// script If it exists, it will be run instead of the script which is /// assumed to `bun_shim_impl.exe` /// /// This function only returns if an error starting the process is /// encountered, most other errors are handled by printing and exiting. pub fn runBinary( ctx: Command.Context, executable: []const u8, executableZ: [:0]const u8, cwd: string, env: *DotEnv.Loader, passthrough: []const string, original_script_for_bun_run: ?[]const u8, ) !noreturn { // Attempt to find a ".bunx" file on disk, and run it, skipping the // wrapper exe. we build the full exe path even though we could do // a relative lookup, because in the case we do find it, we have to // generate this full path anyways. if (Environment.isWindows and bun.FeatureFlags.windows_bunx_fast_path and bun.strings.hasSuffixComptime(executable, ".exe")) { bun.assert(std.fs.path.isAbsolute(executable)); // Using @constCast is safe because we know that // `direct_launch_buffer` is the data destination that assumption is // backed by the immediate assertion. var wpath = @constCast(bun.strings.toNTPath(&BunXFastPath.direct_launch_buffer, executable)); bun.assert(bun.isSliceInBufferT(u16, wpath, &BunXFastPath.direct_launch_buffer)); bun.assert(wpath.len > bun.windows.nt_object_prefix.len + ".exe".len); wpath.len += ".bunx".len - ".exe".len; @memcpy(wpath[wpath.len - "bunx".len ..], comptime bun.strings.w("bunx")); BunXFastPath.tryLaunch(ctx, wpath, env, passthrough); } try runBinaryWithoutBunxPath( ctx, executable, executableZ, cwd, env, passthrough, original_script_for_bun_run, ); } fn runBinaryGenericError(executable: []const u8, silent: bool, err: bun.sys.Error) noreturn { if (!silent) { Output.prettyErrorln("error: Failed to run \"{s}\" due to:\n{}", .{ basenameOrBun(executable), err.withPath(executable) }); } Global.exit(1); } fn runBinaryWithoutBunxPath( ctx: Command.Context, executable: []const u8, executableZ: [*:0]const u8, cwd: string, env: *DotEnv.Loader, passthrough: []const string, original_script_for_bun_run: ?[]const u8, ) !noreturn { var argv_ = [_]string{executable}; var argv: []const string = &argv_; if (passthrough.len > 0) { var array_list = std.ArrayList(string).init(ctx.allocator); try array_list.append(executable); try array_list.appendSlice(passthrough); argv = try array_list.toOwnedSlice(); } 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), .cwd = cwd, .stderr = .inherit, .stdout = .inherit, .stdin = .inherit, .use_execve_on_macos = silent, .windows = if (Environment.isWindows) .{ .loop = JSC.EventLoopHandle.init(JSC.MiniEventLoop.initGlobal(env)), }, }) catch |err| { bun.handleErrorReturnTrace(err, @errorReturnTrace()); // an error occurred before the process was spawned print_error: { if (!silent) { if (comptime Environment.isPosix) { switch (bun.sys.stat(executable[0.. :0])) { .result => |stat| { if (bun.S.ISDIR(stat.mode)) { Output.prettyErrorln("error: Failed to run directory \"{s}\"\n", .{basenameOrBun(executable)}); break :print_error; } }, .err => |err2| { switch (err2.getErrno()) { .NOENT, .PERM, .NOTDIR => { Output.prettyErrorln("error: Failed to run \"{s}\" due to error:\n{}", .{ basenameOrBun(executable), err2 }); break :print_error; }, else => {}, } }, } } Output.prettyErrorln("error: Failed to run \"{s}\" due to {s}", .{ basenameOrBun(executable), @errorName(err) }); } } Global.exit(1); }; switch (spawn_result) { .err => |err| { // an error occurred while spawning the process runBinaryGenericError(executable, silent, err); }, .result => |result| { switch (result.status) { // An error occurred after the process was spawned. .err => |err| { runBinaryGenericError(executable, silent, err); }, .signaled => |signal| { if (signal.valid() and signal != .SIGINT and !silent) { Output.prettyErrorln("error: Failed to run \"{s}\" due to signal {s}", .{ basenameOrBun(executable), signal.name() orelse "unknown", }); } Global.raiseIgnoringPanicHandler(signal); }, .exited => |exit_code| { // A process can be both signaled and exited if (exit_code.signal.valid()) { if (!silent) { Output.prettyErrorln("error: \"{s}\" exited with signal {s}", .{ basenameOrBun(executable), exit_code.signal.name() orelse "unknown", }); } Global.raiseIgnoringPanicHandler(exit_code.signal); } const code = exit_code.code; if (code != 0) { if (!silent) { const is_probably_trying_to_run_a_pkg_script = original_script_for_bun_run != null and ((code == 1 and bun.strings.eqlComptime(original_script_for_bun_run.?, "test")) or (code == 2 and bun.strings.eqlAnyComptime(original_script_for_bun_run.?, &.{ "install", "kill", "link", }) and ctx.positionals.len == 1)); if (is_probably_trying_to_run_a_pkg_script) { // if you run something like `bun run test`, you get a confusing message because // you don't usually think about your global path, let alone "/bin/test" // // test exits with code 1, the other ones i listed exit with code 2 // // so for these script names, print the entire exe name. Output.errGeneric("\"{s}\" exited with code {d}", .{ executable, code }); Output.note("a package.json script \"{s}\" was not found", .{original_script_for_bun_run.?}); } // 128 + 2 is the exit code of a process killed by SIGINT, which is caused by CTRL + C else if (code > 0 and code != 130) { Output.errGeneric("\"{s}\" exited with code {d}", .{ basenameOrBun(executable), code }); } else { Output.prettyErrorln("error: Failed to run \"{s}\" due to exit code {d}", .{ basenameOrBun(executable), code, }); } } } Global.exit(code); }, .running => @panic("Unexpected state: process is running"), } }, } } pub fn ls(ctx: Command.Context) !void { const args = ctx.args; var this_transpiler = try transpiler.Transpiler.init(ctx.allocator, ctx.log, args, null); this_transpiler.options.env.behavior = Api.DotEnvBehavior.load_all; this_transpiler.options.env.prefix = ""; this_transpiler.resolver.care_about_bin_folder = true; this_transpiler.resolver.care_about_scripts = true; this_transpiler.configureLinker(); } pub const bun_node_dir = switch (Environment.os) { // This path is almost always a path to a user directory. So it cannot be inlined like // our uses of /tmp. You can use one of these functions instead: // - bun.windows.GetTempPathW (native) // - bun.fs.FileSystem.RealFS.platformTempDir (any platform) .windows => @compileError("Do not use RunCommand.bun_node_dir on Windows"), .mac => "/private/tmp", else => "/tmp", } ++ if (!Environment.isDebug) "/bun-node" ++ if (Environment.git_sha_short.len > 0) "-" ++ Environment.git_sha_short else "" else "/bun-node-debug"; pub fn bunNodeFileUtf8(allocator: std.mem.Allocator) ![:0]const u8 { if (!Environment.isWindows) return bun_node_dir; var temp_path_buffer: bun.WPathBuffer = undefined; var target_path_buffer: bun.PathBuffer = undefined; const len = bun.windows.GetTempPathW( temp_path_buffer.len, @ptrCast(&temp_path_buffer), ); if (len == 0) { return error.FailedToGetTempPath; } const converted = try bun.strings.convertUTF16toUTF8InBuffer( &target_path_buffer, temp_path_buffer[0..len], ); const dir_name = "bun-node" ++ if (Environment.git_sha_short.len > 0) "-" ++ Environment.git_sha_short else ""; const file_name = dir_name ++ "\\node.exe"; @memcpy(target_path_buffer[converted.len..][0..file_name.len], file_name); target_path_buffer[converted.len + file_name.len] = 0; return try allocator.dupeZ(u8, target_path_buffer[0 .. converted.len + file_name.len :0]); } pub fn createFakeTemporaryNodeExecutable(PATH: *std.ArrayList(u8), optional_bun_path: *string) !void { // If we are already running as "node", the path should exist if (CLI.pretend_to_be_node) return; if (Environment.isPosix) { var argv0 = @as([*:0]const u8, @ptrCast(optional_bun_path.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 (bun.argv[0][0] == '/') { optional_bun_path.* = bun.argv[0]; argv0 = bun.argv[0]; } else if (optional_bun_path.len == 0) { // otherwise, ask the OS for the absolute path const self = try bun.selfExePath(); if (self.len > 0) { argv0 = self.ptr; optional_bun_path.* = self; } } if (optional_bun_path.len == 0) { argv0 = bun.argv[0]; } if (Environment.isDebug) { std.fs.deleteTreeAbsolute(bun_node_dir) catch {}; } const paths = .{ bun_node_dir ++ "/node", bun_node_dir ++ "/bun" }; inline for (paths) |path| { var retried = false; while (true) { inner: { std.posix.symlinkZ(argv0, path) catch |err| { if (err == error.PathAlreadyExists) break :inner; if (retried) return; std.fs.makeDirAbsoluteZ(bun_node_dir) catch {}; retried = true; continue; }; } break; } } if (PATH.items.len > 0 and PATH.items[PATH.items.len - 1] != std.fs.path.delimiter) { try PATH.append(std.fs.path.delimiter); } // The reason for the extra delim is because we are going to append the system PATH // later on. this is done by the caller, and explains why we are adding bun_node_dir // to the end of the path slice rather than the start. try PATH.appendSlice(bun_node_dir ++ .{std.fs.path.delimiter}); } else if (Environment.isWindows) { var target_path_buffer: bun.WPathBuffer = undefined; const prefix = comptime bun.strings.w("\\??\\"); const len = bun.windows.GetTempPathW( target_path_buffer.len - prefix.len, @ptrCast(&target_path_buffer[prefix.len]), ); if (len == 0) { Output.debug("Failed to create temporary node dir: {s}", .{@tagName(std.os.windows.kernel32.GetLastError())}); return; } @memcpy(target_path_buffer[0..prefix.len], prefix); const dir_name = "bun-node" ++ if (Environment.isDebug) "-debug" else if (Environment.git_sha_short.len > 0) "-" ++ Environment.git_sha_short else ""; @memcpy(target_path_buffer[prefix.len..][len..].ptr, comptime bun.strings.w(dir_name)); const dir_slice = target_path_buffer[0 .. prefix.len + len + dir_name.len]; if (Environment.isDebug) { const dir_slice_u8 = std.unicode.utf16LeToUtf8Alloc(bun.default_allocator, dir_slice) catch @panic("oom"); defer bun.default_allocator.free(dir_slice_u8); std.fs.deleteTreeAbsolute(dir_slice_u8) catch {}; std.fs.makeDirAbsolute(dir_slice_u8) catch @panic("huh?"); } const image_path = bun.windows.exePathW(); inline for (.{ "node.exe", "bun.exe" }) |name| { const file_name = dir_name ++ "\\" ++ name ++ "\x00"; @memcpy(target_path_buffer[len + prefix.len ..][0..file_name.len], comptime bun.strings.w(file_name)); const file_slice = target_path_buffer[0 .. prefix.len + len + file_name.len - "\x00".len]; if (bun.windows.CreateHardLinkW(@ptrCast(file_slice.ptr), image_path.ptr, null) == 0) { switch (std.os.windows.kernel32.GetLastError()) { .ALREADY_EXISTS => {}, else => { { bun.assert(target_path_buffer[dir_slice.len] == '\\'); target_path_buffer[dir_slice.len] = 0; std.posix.mkdirW(target_path_buffer[0..dir_slice.len :0], 0) catch {}; target_path_buffer[dir_slice.len] = '\\'; } if (bun.windows.CreateHardLinkW(@ptrCast(file_slice.ptr), image_path.ptr, null) == 0) { return; } }, } } } if (PATH.items.len > 0 and PATH.items[PATH.items.len - 1] != std.fs.path.delimiter) { try PATH.append(std.fs.path.delimiter); } // The reason for the extra delim is because we are going to append the system PATH // later on. this is done by the caller, and explains why we are adding bun_node_dir // to the end of the path slice rather than the start. try bun.strings.toUTF8AppendToList(PATH, dir_slice[prefix.len..]); try PATH.append(std.fs.path.delimiter); } } 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( ctx: Command.Context, this_transpiler: *transpiler.Transpiler, env: ?*DotEnv.Loader, log_errors: bool, store_root_fd: bool, ) !*DirInfo { const args = ctx.args; this_transpiler.* = try transpiler.Transpiler.init(ctx.allocator, ctx.log, args, env); this_transpiler.options.env.behavior = Api.DotEnvBehavior.load_all; this_transpiler.env.quiet = true; this_transpiler.options.env.prefix = ""; this_transpiler.resolver.care_about_bin_folder = true; this_transpiler.resolver.care_about_scripts = true; this_transpiler.resolver.store_fd = store_root_fd; this_transpiler.resolver.opts.load_tsconfig_json = true; this_transpiler.options.load_tsconfig_json = true; this_transpiler.configureLinker(); const root_dir_info = this_transpiler.resolver.readDirInfo(this_transpiler.fs.top_level_dir) catch |err| { if (!log_errors) return error.CouldntReadCurrentDirectory; ctx.log.print(Output.errorWriter()) catch {}; Output.prettyErrorln("error: {s} loading directory {}", .{ @errorName(err), bun.fmt.QuotedFormatter{ .text = this_transpiler.fs.top_level_dir } }); Output.flush(); return err; } orelse { ctx.log.print(Output.errorWriter()) catch {}; Output.prettyErrorln("error loading current directory", .{}); Output.flush(); return error.CouldntReadCurrentDirectory; }; this_transpiler.resolver.store_fd = false; if (env == null) { this_transpiler.env.loadProcess(); if (this_transpiler.env.get("NODE_ENV")) |node_env| { if (strings.eqlComptime(node_env, "production")) { this_transpiler.options.production = true; } } this_transpiler.runEnvLoader(true) catch {}; } this_transpiler.env.map.put("INIT_CWD", this_transpiler.fs.top_level_dir) catch unreachable; // we have no way of knowing what version they're expecting without running the node executable // running the node executable is too slow // so we will just hardcode it to LTS this_transpiler.env.map.putDefault( "npm_config_user_agent", // the use of npm/? is copying yarn // e.g. // > "yarn/1.22.4 npm/? node/v12.16.3 darwin x64", "bun/" ++ Global.package_json_version ++ " npm/? node/v" ++ Environment.reported_nodejs_version ++ " " ++ Global.os_name ++ " " ++ Global.arch_name, ) catch unreachable; if (this_transpiler.env.get("npm_execpath") == null) { // we don't care if this fails if (bun.selfExePath()) |self_exe_path| { this_transpiler.env.map.putDefault("npm_execpath", self_exe_path) catch unreachable; } else |_| {} } if (root_dir_info.enclosing_package_json) |package_json| { if (package_json.name.len > 0) { if (this_transpiler.env.map.get(NpmArgs.package_name) == null) { this_transpiler.env.map.put(NpmArgs.package_name, package_json.name) catch unreachable; } } this_transpiler.env.map.putDefault("npm_package_json", package_json.source.path.text) catch unreachable; if (package_json.version.len > 0) { if (this_transpiler.env.map.get(NpmArgs.package_version) == null) { this_transpiler.env.map.put(NpmArgs.package_version, package_json.version) catch unreachable; } } if (package_json.config) |config| { try this_transpiler.env.map.ensureUnusedCapacity(config.count()); for (config.keys(), config.values()) |k, v| { const key = try bun.strings.concat(bun.default_allocator, &.{ "npm_package_config_", k }); this_transpiler.env.map.putAssumeCapacity(key, v); } } this_transpiler.env.map.put("npm_config_local_prefix", package_json.source.path.name.dir) catch unreachable; } else { this_transpiler.env.map.put("npm_config_local_prefix", this_transpiler.fs.top_level_dir) catch unreachable; } return root_dir_info; } pub fn configurePathForRunWithPackageJsonDir( ctx: Command.Context, package_json_dir: string, this_transpiler: *transpiler.Transpiler, ORIGINAL_PATH: ?*string, cwd: string, force_using_bun: bool, ) ![]u8 { const PATH = this_transpiler.env.get("PATH") orelse ""; if (ORIGINAL_PATH) |original_path| { original_path.* = PATH; } const bun_node_exe = try bunNodeFileUtf8(ctx.allocator); const bun_node_dir_win = bun.Dirname.dirname(u8, bun_node_exe) orelse return error.FailedToGetTempPath; const found_node = this_transpiler.env.loadNodeJSConfig( this_transpiler.fs, if (force_using_bun) bun_node_exe else "", ) catch false; var needs_to_force_bun = force_using_bun or !found_node; var optional_bun_self_path: string = ""; var new_path_len: usize = PATH.len + 2; if (package_json_dir.len > 0) { new_path_len += package_json_dir.len + 1; } { var remain = cwd; while (strings.lastIndexOfChar(remain, std.fs.path.sep)) |i| { new_path_len += strings.withoutTrailingSlash(remain).len + "node_modules.bin".len + 1 + 2; // +2 for path separators, +1 for path delimiter remain = remain[0..i]; } else { new_path_len += strings.withoutTrailingSlash(remain).len + "node_modules.bin".len + 1 + 2; // +2 for path separators, +1 for path delimiter } } if (needs_to_force_bun) { new_path_len += bun_node_dir_win.len + 1; } 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 bun.outOfMemory(); if (!force_using_bun) { this_transpiler.env.map.put("NODE", bun_node_exe) catch bun.outOfMemory(); this_transpiler.env.map.put("npm_node_execpath", bun_node_exe) catch bun.outOfMemory(); this_transpiler.env.map.put("npm_execpath", optional_bun_self_path) catch bun.outOfMemory(); } needs_to_force_bun = false; } { if (package_json_dir.len > 0) { try new_path.appendSlice(package_json_dir); try new_path.append(std.fs.path.delimiter); } var remain = cwd; while (strings.lastIndexOfChar(remain, std.fs.path.sep)) |i| { try new_path.appendSlice(strings.withoutTrailingSlash(remain)); try new_path.appendSlice(bun.pathLiteral("/node_modules/.bin")); try new_path.append(std.fs.path.delimiter); remain = remain[0..i]; } else { try new_path.appendSlice(strings.withoutTrailingSlash(remain)); try new_path.appendSlice(bun.pathLiteral("/node_modules/.bin")); try new_path.append(std.fs.path.delimiter); } try new_path.appendSlice(PATH); } return new_path.items; } pub fn configurePathForRun( ctx: Command.Context, root_dir_info: *DirInfo, this_transpiler: *transpiler.Transpiler, ORIGINAL_PATH: ?*string, cwd: string, force_using_bun: bool, ) !void { var package_json_dir: string = ""; if (root_dir_info.enclosing_package_json) |package_json| { if (root_dir_info.package_json == null) { // no trailing slash package_json_dir = strings.withoutTrailingSlash(package_json.source.path.name.dir); } } const new_path = try configurePathForRunWithPackageJsonDir(ctx, package_json_dir, this_transpiler, ORIGINAL_PATH, cwd, force_using_bun); this_transpiler.env.map.put("PATH", new_path) catch bun.outOfMemory(); } pub fn completions(ctx: Command.Context, default_completions: ?[]const string, reject_list: []const string, comptime filter: Filter) !ShellCompletions { var shell_out = ShellCompletions{}; if (filter != .script_exclude) { if (default_completions) |defaults| { shell_out.commands = defaults; } } const args = ctx.args; var this_transpiler = transpiler.Transpiler.init(ctx.allocator, ctx.log, args, null) catch return shell_out; this_transpiler.options.env.behavior = Api.DotEnvBehavior.load_all; this_transpiler.options.env.prefix = ""; this_transpiler.env.quiet = true; this_transpiler.resolver.care_about_bin_folder = true; this_transpiler.resolver.care_about_scripts = true; this_transpiler.resolver.store_fd = true; defer { this_transpiler.resolver.care_about_bin_folder = false; this_transpiler.resolver.care_about_scripts = false; } this_transpiler.configureLinker(); const root_dir_info = (this_transpiler.resolver.readDirInfo(this_transpiler.fs.top_level_dir) catch null) orelse return shell_out; { this_transpiler.env.loadProcess(); if (this_transpiler.env.get("NODE_ENV")) |node_env| { if (strings.eqlComptime(node_env, "production")) { this_transpiler.options.production = true; } } } const ResultList = bun.StringArrayHashMap(void); if (this_transpiler.env.get("SHELL")) |shell| { shell_out.shell = ShellCompletions.Shell.fromEnv(@TypeOf(shell), shell); } var results = ResultList.init(ctx.allocator); var descriptions = std.ArrayList(string).init(ctx.allocator); if (filter != .script_exclude) { if (default_completions) |defaults| { try results.ensureUnusedCapacity(defaults.len); for (defaults) |item| { _ = results.getOrPutAssumeCapacity(item); } } } if (filter == Filter.bin or filter == Filter.all or filter == Filter.all_plus_bun_js) { for (this_transpiler.resolver.binDirs()) |bin_path| { if (this_transpiler.resolver.readDirInfo(bin_path) catch null) |bin_dir| { if (bin_dir.getEntriesConst()) |entries| { var iter = entries.data.iterator(); var has_copied = false; var dir_slice: string = ""; while (iter.next()) |entry| { const value = entry.value_ptr.*; if (value.kind(&this_transpiler.fs.fs, true) == .file) { if (!has_copied) { bun.copy(u8, &path_buf, value.dir); dir_slice = path_buf[0..value.dir.len]; if (!strings.endsWithCharOrIsZeroLength(value.dir, std.fs.path.sep)) { dir_slice = path_buf[0 .. value.dir.len + 1]; } has_copied = true; } const base = value.base(); bun.copy(u8, path_buf[dir_slice.len..], base); path_buf[dir_slice.len + base.len] = 0; const slice = path_buf[0 .. dir_slice.len + base.len :0]; if (Environment.isWindows) { @panic("TODO"); } if (!(bun.sys.isExecutableFilePath(slice))) continue; // we need to dupe because the string pay point to a pointer that only exists in the current scope _ = try results.getOrPut(this_transpiler.fs.filename_store.append(@TypeOf(base), base) catch continue); } } } } } } if (filter == Filter.all_plus_bun_js or filter == Filter.bun_js) { if (this_transpiler.resolver.readDirInfo(this_transpiler.fs.top_level_dir) catch null) |dir_info| { if (dir_info.getEntriesConst()) |entries| { var iter = entries.data.iterator(); while (iter.next()) |entry| { const value = entry.value_ptr.*; const name = value.base(); if (name[0] != '.' and this_transpiler.options.loader(std.fs.path.extension(name)).canBeRunByBun() and !strings.contains(name, ".config") and !strings.contains(name, ".d.ts") and !strings.contains(name, ".d.mts") and !strings.contains(name, ".d.cts") and value.kind(&this_transpiler.fs.fs, true) == .file) { _ = try results.getOrPut(this_transpiler.fs.filename_store.append(@TypeOf(name), name) catch continue); } } } } } if (filter == Filter.script_exclude or filter == Filter.script or filter == Filter.all or filter == Filter.all_plus_bun_js or filter == Filter.script_and_descriptions) { if (root_dir_info.enclosing_package_json) |package_json| { if (package_json.scripts) |scripts| { try results.ensureUnusedCapacity(scripts.count()); if (filter == Filter.script_and_descriptions) { try descriptions.ensureUnusedCapacity(scripts.count()); } var max_description_len: usize = 20; if (this_transpiler.env.get("MAX_DESCRIPTION_LEN")) |max| { if (std.fmt.parseInt(usize, max, 10) catch null) |max_len| { max_description_len = max_len; } } const keys = scripts.keys(); var key_i: usize = 0; loop: while (key_i < keys.len) : (key_i += 1) { const key = keys[key_i]; if (filter == Filter.script_exclude) { for (reject_list) |default| { if (std.mem.eql(u8, default, key)) { continue :loop; } } } if (strings.startsWith(key, "post") or strings.startsWith(key, "pre")) { continue :loop; } const entry_item = results.getOrPutAssumeCapacity(key); if (filter == Filter.script_and_descriptions and max_description_len > 0) { var description = scripts.get(key).?; // When the command starts with something like // NODE_OPTIONS='--max-heap-size foo' bar // ^--------------------------------^ trim that // that way, you can see the real command that's being run if (description.len > 0) { trimmer: { if (description.len > 0 and strings.startsWith(description, "NODE_OPTIONS=")) { if (strings.indexOfChar(description, '=')) |i| { const delimiter: u8 = if (description.len > i + 1) @as(u8, switch (description[i + 1]) { '\'' => '\'', '"' => '"', else => ' ', }) else break :trimmer; const delimiter_offset = @as(usize, if (delimiter == ' ') 1 else 2); if (description.len > delimiter_offset + i) { if (strings.indexOfChar(description[delimiter_offset + i ..], delimiter)) |j| { description = std.mem.trim(u8, description[delimiter_offset + i ..][j + 1 ..], " "); } else { break :trimmer; } } else { break :trimmer; } } else { break :trimmer; } } } if (description.len > max_description_len) { description = description[0..max_description_len]; } } try descriptions.insert(entry_item.index, description); } } } } } const all_keys = results.keys(); strings.sortAsc(all_keys); shell_out.commands = all_keys; shell_out.descriptions = try descriptions.toOwnedSlice(); return shell_out; } pub fn printHelp(package_json: ?*PackageJSON) void { const intro_text = \\Usage: bun run [flags] \ ; const examples_text = \\Examples: \\ Run a JavaScript or TypeScript file \\ bun run ./index.js \\ bun run ./index.tsx \\ \\ Run a package.json script \\ bun run dev \\ bun run lint \\ \\Full documentation is available at https://bun.com/docs/cli/run \\ ; Output.pretty(intro_text ++ "\n\n", .{}); Output.pretty("Flags:", .{}); clap.simpleHelp(&Arguments.run_params); Output.pretty("\n\n" ++ examples_text, .{}); if (package_json) |pkg| { if (pkg.scripts) |scripts| { var display_name = pkg.name; if (display_name.len == 0) { display_name = std.fs.path.basename(pkg.source.path.name.dir); } var iterator = scripts.iterator(); if (scripts.count() > 0) { Output.pretty("\npackage.json scripts ({d} found):", .{scripts.count()}); // Output.prettyln("{s} scripts:\n", .{display_name}); while (iterator.next()) |entry| { Output.prettyln("\n", .{}); Output.prettyln(" $ bun run {s}\n", .{entry.key_ptr.*}); Output.prettyln(" {s}\n", .{entry.value_ptr.*}); } // Output.prettyln("\n{d} scripts", .{scripts.count()}); Output.prettyln("\n", .{}); } else { Output.prettyln("\nNo \"scripts\" found in package.json.\n", .{}); } } else { Output.prettyln("\nNo \"scripts\" found in package.json.\n", .{}); } } Output.flush(); } fn _bootAndHandleError(ctx: Command.Context, path: string, loader: ?bun.options.Loader) bool { Global.configureAllocator(.{ .long_running = true }); Run.boot(ctx, ctx.allocator.dupe(u8, path) catch return false, loader) catch |err| { ctx.log.print(Output.errorWriter()) catch {}; Output.prettyErrorln("error: Failed to run {s} due to error {s}", .{ std.fs.path.basename(path), @errorName(err), }); bun.handleErrorReturnTrace(err, @errorReturnTrace()); Global.exit(1); }; return true; } fn maybeOpenWithBunJS(ctx: Command.Context) bool { if (ctx.args.entry_points.len == 0) return false; var script_name_buf: bun.PathBuffer = undefined; const script_name_to_search = ctx.args.entry_points[0]; var absolute_script_path: ?string = null; // TODO: optimize this pass for Windows. we can make better use of system apis available var file_path = script_name_to_search; { const file = bun.FD.fromStdFile((brk: { if (std.fs.path.isAbsolute(script_name_to_search)) { var win_resolver = resolve_path.PosixToWinNormalizer{}; var resolved = win_resolver.resolveCWD(script_name_to_search) catch @panic("Could not resolve path"); if (comptime Environment.isWindows) { resolved = resolve_path.normalizeString(resolved, false, .windows); } break :brk bun.openFile( resolved, .{ .mode = .read_only }, ); } else if (!strings.hasPrefix(script_name_to_search, "..") and script_name_to_search[0] != '~') { const file_pathZ = brk2: { @memcpy(script_name_buf[0..file_path.len], file_path); script_name_buf[file_path.len] = 0; break :brk2 script_name_buf[0..file_path.len :0]; }; break :brk bun.openFileZ(file_pathZ, .{ .mode = .read_only }); } else { var path_buf_2: bun.PathBuffer = undefined; const cwd = bun.getcwd(&path_buf_2) catch return false; path_buf_2[cwd.len] = std.fs.path.sep; var parts = [_]string{script_name_to_search}; file_path = resolve_path.joinAbsStringBuf( path_buf_2[0 .. cwd.len + 1], &script_name_buf, &parts, .auto, ); if (file_path.len == 0) return false; script_name_buf[file_path.len] = 0; const file_pathZ = script_name_buf[0..file_path.len :0]; break :brk bun.openFileZ(file_pathZ, .{ .mode = .read_only }); } }) catch return false).makeLibUVOwnedForSyscall(.open, .close_on_fail).unwrap() catch return false; defer file.close(); switch (bun.sys.fstat(file)) { .result => |stat| { // directories cannot be run. if only there was a faster way to check this if (bun.S.ISDIR(@intCast(stat.mode))) return false; }, .err => return false, } Global.configureAllocator(.{ .long_running = true }); absolute_script_path = brk: { if (comptime !Environment.isWindows) break :brk bun.getFdPath(file, &script_name_buf) catch return false; var fd_path_buf: bun.PathBuffer = undefined; break :brk bun.getFdPath(file, &fd_path_buf) catch return false; }; } if (!ctx.debug.loaded_bunfig) { bun.CLI.Arguments.loadConfigPath(ctx.allocator, true, "bunfig.toml", ctx, .RunCommand) catch {}; } _ = _bootAndHandleError(ctx, absolute_script_path.?, null); return true; } pub fn exec( ctx: Command.Context, cfg: struct { bin_dirs_only: bool, log_errors: bool, allow_fast_run_for_extensions: bool, }, ) !bool { const bin_dirs_only = cfg.bin_dirs_only; const log_errors = cfg.log_errors; // find what to run var positionals = ctx.positionals; if (positionals.len > 0 and strings.eqlComptime(positionals[0], "run")) { positionals = positionals[1..]; } var target_name: string = ""; if (positionals.len > 0) { target_name = positionals[0]; positionals = positionals[1..]; } const passthrough = ctx.passthrough; // unclear why passthrough is an escaped string, it should probably be []const []const u8 and allow its users to escape it. var try_fast_run = false; var skip_script_check = false; if (target_name.len > 0 and target_name[0] == '.') { try_fast_run = true; skip_script_check = true; } else if (std.fs.path.isAbsolute(target_name)) { try_fast_run = true; skip_script_check = true; } else if (cfg.allow_fast_run_for_extensions) { const ext = std.fs.path.extension(target_name); const default_loader = options.defaultLoaders.get(ext); if (default_loader != null and default_loader.?.canBeRunByBun()) { try_fast_run = true; } } // try fast run (check if the file exists and is not a folder, then run it) if (try_fast_run and maybeOpenWithBunJS(ctx)) return true; // setup const force_using_bun = ctx.debug.run_in_bun; var ORIGINAL_PATH: string = ""; var this_transpiler: transpiler.Transpiler = undefined; const root_dir_info = try configureEnvForRun(ctx, &this_transpiler, null, log_errors, false); try configurePathForRun(ctx, root_dir_info, &this_transpiler, &ORIGINAL_PATH, root_dir_info.abs_path, force_using_bun); this_transpiler.env.map.put("npm_command", "run") catch unreachable; if (!ctx.debug.loaded_bunfig) { bun.CLI.Arguments.loadConfigPath(ctx.allocator, true, "bunfig.toml", ctx, .RunCommand) catch {}; } // check for empty command if (target_name.len == 0) { if (root_dir_info.enclosing_package_json) |package_json| { RunCommand.printHelp(package_json); } else { RunCommand.printHelp(null); Output.prettyln("\nNo package.json found.\n", .{}); Output.flush(); } return true; } // check for stdin if (target_name.len == 1 and target_name[0] == '-') { log("Executing from stdin", .{}); // read from stdin var stack_fallback = std.heap.stackFallback(2048, bun.default_allocator); var list = std.ArrayList(u8).init(stack_fallback.get()); errdefer list.deinit(); std.io.getStdIn().reader().readAllArrayList(&list, 1024 * 1024 * 1024) catch return false; ctx.runtime_options.eval.script = list.items; const trigger = bun.pathLiteral("/[stdin]"); var entry_point_buf: [bun.MAX_PATH_BYTES + trigger.len]u8 = undefined; const cwd = try std.posix.getcwd(&entry_point_buf); @memcpy(entry_point_buf[cwd.len..][0..trigger.len], trigger); const entry_path = entry_point_buf[0 .. cwd.len + trigger.len]; var passthrough_list = try std.ArrayList(string).initCapacity(ctx.allocator, ctx.passthrough.len + 1); passthrough_list.appendAssumeCapacity("-"); passthrough_list.appendSliceAssumeCapacity(ctx.passthrough); ctx.passthrough = passthrough_list.items; Run.boot(ctx, ctx.allocator.dupe(u8, entry_path) catch return false, null) catch |err| { ctx.log.print(Output.errorWriter()) catch {}; Output.prettyErrorln("error: Failed to run {s} due to error {s}", .{ std.fs.path.basename(target_name), @errorName(err), }); bun.handleErrorReturnTrace(err, @errorReturnTrace()); Global.exit(1); }; return true; } // run script with matching name if (!skip_script_check) if (root_dir_info.enclosing_package_json) |package_json| { if (package_json.scripts) |scripts| { if (scripts.get(target_name)) |script_content| { log("Found matching script `{s}`", .{script_content}); Global.configureAllocator(.{ .long_running = false }); this_transpiler.env.map.put("npm_lifecycle_event", target_name) catch unreachable; // allocate enough to hold "post${scriptname}" var temp_script_buffer = try std.fmt.allocPrint(ctx.allocator, "\x00pre{s}", .{target_name}); defer ctx.allocator.free(temp_script_buffer); const package_json_path = root_dir_info.enclosing_package_json.?.source.path.text; const package_json_dir = strings.withoutTrailingSlash(strings.withoutSuffixComptime(package_json_path, "package.json")); log("Running in dir `{s}`", .{package_json_dir}); if (scripts.get(temp_script_buffer[1..])) |prescript| { try runPackageScriptForeground( ctx, ctx.allocator, prescript, temp_script_buffer[1..], package_json_dir, this_transpiler.env, &.{}, ctx.debug.silent, ctx.debug.use_system_shell, ); } try runPackageScriptForeground( ctx, ctx.allocator, script_content, target_name, package_json_dir, this_transpiler.env, passthrough, ctx.debug.silent, ctx.debug.use_system_shell, ); temp_script_buffer[0.."post".len].* = "post".*; if (scripts.get(temp_script_buffer)) |postscript| { try runPackageScriptForeground( ctx, ctx.allocator, postscript, temp_script_buffer, package_json_dir, this_transpiler.env, &.{}, ctx.debug.silent, ctx.debug.use_system_shell, ); } return true; } } }; // load module and run that module // TODO: run module resolution here - try the next condition if the module can't be found log("Try resolve `{s}` in `{s}`", .{ target_name, this_transpiler.fs.top_level_dir }); const resolution = brk: { const preserve_symlinks = this_transpiler.resolver.opts.preserve_symlinks; defer this_transpiler.resolver.opts.preserve_symlinks = preserve_symlinks; this_transpiler.resolver.opts.preserve_symlinks = ctx.runtime_options.preserve_symlinks_main or if (bun.getenvZ("NODE_PRESERVE_SYMLINKS_MAIN")) |env| bun.strings.eqlComptime(env, "1") else false; break :brk this_transpiler.resolver.resolve( this_transpiler.fs.top_level_dir, target_name, .entry_point_run, ) catch this_transpiler.resolver.resolve( this_transpiler.fs.top_level_dir, try std.mem.join(ctx.allocator, "", &.{ "./", target_name }), .entry_point_run, ); }; if (resolution) |resolved| { var resolved_mutable = resolved; const path = resolved_mutable.path().?; const loader: bun.options.Loader = this_transpiler.options.loaders.get(path.name.ext) orelse .tsx; if (loader.canBeRunByBun() or loader == .html) { log("Resolved to: `{s}`", .{path.text}); return _bootAndHandleError(ctx, path.text, loader); } else { log("Resolved file `{s}` but ignoring because loader is {s}", .{ path.text, @tagName(loader) }); } } else |_| { // Support globs for HTML entry points. if (strings.hasSuffixComptime(target_name, ".html")) { if (strings.containsChar(target_name, '*')) { return _bootAndHandleError(ctx, target_name, .html); } } } // execute a node_modules/.bin/ command, or (run only) a system command like 'ls' if (Environment.isWindows and bun.FeatureFlags.windows_bunx_fast_path) try_bunx_file: { // Attempt to find a ".bunx" file on disk, and run it, skipping the // wrapper exe. we build the full exe path even though we could do // a relative lookup, because in the case we do find it, we have to // generate this full path anyways. var ptr: []u16 = &BunXFastPath.direct_launch_buffer; const root = comptime bun.strings.w("\\??\\"); @memcpy(ptr[0..root.len], root); ptr = ptr[4..]; const cwd_len = windows.kernel32.GetCurrentDirectoryW( BunXFastPath.direct_launch_buffer.len - 4, ptr.ptr, ); if (cwd_len == 0) break :try_bunx_file; ptr = ptr[cwd_len..]; const prefix = comptime bun.strings.w("\\node_modules\\.bin\\"); @memcpy(ptr[0..prefix.len], prefix); ptr = ptr[prefix.len..]; const encoded = bun.strings.convertUTF8toUTF16InBuffer(ptr[0..], target_name); ptr = ptr[encoded.len..]; const ext = comptime bun.strings.w(".bunx"); @memcpy(ptr[0..ext.len], ext); ptr[ext.len] = 0; const l = root.len + cwd_len + prefix.len + target_name.len + ext.len; const path_to_use = BunXFastPath.direct_launch_buffer[0..l :0]; BunXFastPath.tryLaunch(ctx, path_to_use, this_transpiler.env, ctx.passthrough); } const PATH = this_transpiler.env.get("PATH") orelse ""; var path_for_which = PATH; if (bin_dirs_only) { if (ORIGINAL_PATH.len < PATH.len) { path_for_which = PATH[0 .. PATH.len - (ORIGINAL_PATH.len + 1)]; } else { path_for_which = ""; } } if (path_for_which.len > 0) { if (which(&path_buf, path_for_which, this_transpiler.fs.top_level_dir, target_name)) |destination| { const out = bun.asByteSlice(destination); return try runBinaryWithoutBunxPath( ctx, try this_transpiler.fs.dirname_store.append(@TypeOf(out), out), destination, this_transpiler.fs.top_level_dir, this_transpiler.env, passthrough, target_name, ); } } // failure if (ctx.runtime_options.if_present) { return true; } if (log_errors) { const ext = std.fs.path.extension(target_name); const default_loader = options.defaultLoaders.get(ext); if (default_loader != null and default_loader.?.isJavaScriptLikeOrJSON() or target_name.len > 0 and (target_name[0] == '.' or target_name[0] == '/' or std.fs.path.isAbsolute(target_name))) { Output.prettyError("error: Module not found \"{s}\"\n", .{target_name}); } else if (ext.len > 0) { Output.prettyError("error: File not found \"{s}\"\n", .{target_name}); } else { Output.prettyError("error: Script not found \"{s}\"\n", .{target_name}); } Global.exit(1); } return false; } pub fn execAsIfNode(ctx: Command.Context) !void { bun.assert(CLI.pretend_to_be_node); if (ctx.runtime_options.eval.script.len > 0) { const trigger = bun.pathLiteral("/[eval]"); var entry_point_buf: [bun.MAX_PATH_BYTES + trigger.len]u8 = undefined; const cwd = try std.posix.getcwd(&entry_point_buf); @memcpy(entry_point_buf[cwd.len..][0..trigger.len], trigger); try Run.boot(ctx, entry_point_buf[0 .. cwd.len + trigger.len], null); return; } if (ctx.positionals.len == 0) { Output.errGeneric("Missing script to execute. Bun's provided 'node' cli wrapper does not support a repl.", .{}); Global.exit(1); } // TODO(@paperclover): merge windows branch // var win_resolver = resolve_path.PosixToWinNormalizer{}; const filename = ctx.positionals[0]; const normalized_filename = if (std.fs.path.isAbsolute(filename)) // TODO(@paperclover): merge windows branch // try win_resolver.resolveCWD("/dev/bun/test/etc.js"); filename else brk: { const cwd = try bun.getcwd(&path_buf); path_buf[cwd.len] = std.fs.path.sep_posix; var parts = [_]string{filename}; break :brk resolve_path.joinAbsStringBuf( path_buf[0 .. cwd.len + 1], &path_buf2, &parts, .loose, ); }; Run.boot(ctx, normalized_filename, null) catch |err| { ctx.log.print(Output.errorWriter()) catch {}; Output.err(err, "Failed to run script \"{s}\"", .{std.fs.path.basename(normalized_filename)}); Global.exit(1); }; } }; pub const BunXFastPath = struct { const shim_impl = @import("../install/windows-shim/bun_shim_impl.zig"); const debug = Output.scoped(.BunXFastPath, false); var direct_launch_buffer: bun.WPathBuffer = undefined; var environment_buffer: bun.WPathBuffer = undefined; /// If this returns, it implies the fast path cannot be taken fn tryLaunch(ctx: Command.Context, path_to_use: [:0]u16, env: *DotEnv.Loader, passthrough: []const []const u8) void { if (!bun.FeatureFlags.windows_bunx_fast_path) return; bun.assert(bun.isSliceInBufferT(u16, path_to_use, &BunXFastPath.direct_launch_buffer)); var command_line = BunXFastPath.direct_launch_buffer[path_to_use.len..]; debug("Attempting to find and load bunx file: '{}'", .{bun.fmt.utf16(path_to_use)}); if (Environment.allow_assert) { bun.assert(std.fs.path.isAbsoluteWindowsWTF16(path_to_use)); } const handle = (bun.sys.openFileAtWindows( bun.invalid_fd, // absolute path is given path_to_use, .{ .access_mask = windows.STANDARD_RIGHTS_READ | windows.FILE_READ_DATA | windows.FILE_READ_ATTRIBUTES | windows.FILE_READ_EA | windows.SYNCHRONIZE, .disposition = windows.FILE_OPEN, .options = windows.FILE_NON_DIRECTORY_FILE | windows.FILE_SYNCHRONOUS_IO_NONALERT, }, ).unwrap() catch |err| { debug("Failed to open bunx file: '{}'", .{err}); return; }).cast(); var i: usize = 0; for (passthrough) |str| { command_line[i] = ' '; const result = bun.strings.convertUTF8toUTF16InBuffer(command_line[1 + i ..], str); i += result.len + 1; } ctx.passthrough = passthrough; const run_ctx = shim_impl.FromBunRunContext{ .handle = handle, .base_path = path_to_use[4..], .arguments = command_line[0..i], .force_use_bun = ctx.debug.run_in_bun, .direct_launch_with_bun_js = &directLaunchCallback, .cli_context = ctx, .environment = env.map.writeWindowsEnvBlock(&environment_buffer) catch return, }; if (Environment.isDebug) { debug("run_ctx.handle: '{}'", .{bun.FD.fromSystem(handle)}); debug("run_ctx.base_path: '{}'", .{bun.fmt.utf16(run_ctx.base_path)}); debug("run_ctx.arguments: '{}'", .{bun.fmt.utf16(run_ctx.arguments)}); debug("run_ctx.force_use_bun: '{}'", .{run_ctx.force_use_bun}); } shim_impl.tryStartupFromBunJS(run_ctx); debug("did not start via shim", .{}); } fn directLaunchCallback(wpath: []u16, ctx: Command.Context) void { const utf8 = bun.strings.convertUTF16toUTF8InBuffer( bun.reinterpretSlice(u8, &direct_launch_buffer), wpath, ) catch return; Run.boot(ctx, utf8, null) catch |err| { ctx.log.print(Output.errorWriter()) catch {}; Output.err(err, "Failed to run bin \"{s}\"", .{std.fs.path.basename(utf8)}); Global.exit(1); }; } };