mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 18:38:55 +00:00
1746 lines
73 KiB
Zig
1746 lines
73 KiB
Zig
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("<r><red>error<r>: Failed to run script <b>{s}<r> due to error <b>{s}<r>", .{ name, @errorName(err) });
|
|
}
|
|
|
|
Global.exit(1);
|
|
};
|
|
|
|
if (code > 0) {
|
|
if (code != 2 and !silent) {
|
|
Output.prettyErrorln("<r><red>error<r><d>:<r> script <b>\"{s}\"<r> exited with code {d}<r>", .{ 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("<r><red>error<r>: Failed to run script <b>{s}<r> due to error <b>{s}<r>", .{ name, @errorName(err) });
|
|
}
|
|
|
|
Output.flush();
|
|
return;
|
|
})) {
|
|
.err => |err| {
|
|
if (!silent) {
|
|
Output.prettyErrorln("<r><red>error<r>: Failed to run script <b>{s}<r> 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("<r><red>error<r><d>:<r> script <b>\"{s}\"<r> was terminated by signal {}<r>", .{ 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("<r><red>error<r><d>:<r> script <b>\"{s}\"<r> exited with code {d}<r>", .{ name, exit_code.code });
|
|
Output.flush();
|
|
}
|
|
|
|
Global.exit(exit_code.code);
|
|
}
|
|
},
|
|
|
|
.signaled => |signal| {
|
|
if (signal.valid() and signal != .SIGINT and !silent) {
|
|
Output.prettyErrorln("<r><red>error<r><d>:<r> script <b>\"{s}\"<r> was terminated by signal {}<r>", .{ name, signal.fmt(Output.enable_ansi_colors_stderr) });
|
|
Output.flush();
|
|
}
|
|
Global.raiseIgnoringPanicHandler(signal);
|
|
},
|
|
|
|
.err => |err| {
|
|
if (!silent) {
|
|
Output.prettyErrorln("<r><red>error<r>: Failed to run script <b>{s}<r> 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("<r><red>error<r>: Failed to run \"<b>{s}<r>\" 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("<r><red>error<r>: Failed to run directory \"<b>{s}<r>\"\n", .{basenameOrBun(executable)});
|
|
break :print_error;
|
|
}
|
|
},
|
|
.err => |err2| {
|
|
switch (err2.getErrno()) {
|
|
.NOENT, .PERM, .NOTDIR => {
|
|
Output.prettyErrorln("<r><red>error<r>: Failed to run \"<b>{s}<r>\" due to error:\n{}", .{ basenameOrBun(executable), err2 });
|
|
break :print_error;
|
|
},
|
|
else => {},
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
Output.prettyErrorln("<r><red>error<r>: Failed to run \"<b>{s}<r>\" due to <r><red>{s}<r>", .{ 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("<r><red>error<r>: Failed to run \"<b>{s}<r>\" due to signal <b>{s}<r>", .{
|
|
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("<r><red>error<r>: \"<b>{s}<r>\" exited with signal <b>{s}<r>", .{
|
|
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("\"<b>{s}<r>\" 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("\"<b>{s}<r>\" exited with code {d}", .{ basenameOrBun(executable), code });
|
|
} else {
|
|
Output.prettyErrorln("<r><red>error<r>: Failed to run \"<b>{s}<r>\" due to exit code <b>{d}<r>", .{
|
|
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("<r><red>error<r><d>:<r> <b>{s}<r> 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.putDefault("npm_config_local_prefix", 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
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 =
|
|
\\<b>Usage<r>: <b><green>bun run<r> <cyan>[flags]<r> \<file or script\>
|
|
;
|
|
|
|
const examples_text =
|
|
\\<b>Examples:<r>
|
|
\\ <d>Run a JavaScript or TypeScript file<r>
|
|
\\ <b><green>bun run<r> <blue>./index.js<r>
|
|
\\ <b><green>bun run<r> <blue>./index.tsx<r>
|
|
\\
|
|
\\ <d>Run a package.json script<r>
|
|
\\ <b><green>bun run<r> <blue>dev<r>
|
|
\\ <b><green>bun run<r> <blue>lint<r>
|
|
\\
|
|
\\Full documentation is available at <magenta>https://bun.com/docs/cli/run<r>
|
|
\\
|
|
;
|
|
|
|
Output.pretty(intro_text ++ "\n\n", .{});
|
|
|
|
Output.pretty("<b>Flags:<r>", .{});
|
|
|
|
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("\n<b>package.json scripts ({d} found):<r>", .{scripts.count()});
|
|
// Output.prettyln("<r><blue><b>{s}<r> scripts:<r>\n", .{display_name});
|
|
while (iterator.next()) |entry| {
|
|
Output.prettyln("\n", .{});
|
|
Output.prettyln(" <d>$</r> bun run<r> <blue>{s}<r>\n", .{entry.key_ptr.*});
|
|
Output.prettyln(" <d> {s}<r>\n", .{entry.value_ptr.*});
|
|
}
|
|
|
|
// Output.prettyln("\n<d>{d} scripts<r>", .{scripts.count()});
|
|
|
|
Output.prettyln("\n", .{});
|
|
} else {
|
|
Output.prettyln("\n<r><yellow>No \"scripts\" found in package.json.<r>\n", .{});
|
|
}
|
|
} else {
|
|
Output.prettyln("\n<r><yellow>No \"scripts\" found in package.json.<r>\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("<r><red>error<r>: Failed to run <b>{s}<r> due to error <b>{s}<r>", .{
|
|
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-script") 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("\n<r><yellow>No package.json found.<r>\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("<r><red>error<r>: Failed to run <b>{s}<r> due to error <b>{s}<r>", .{
|
|
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/<X> 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("<r><red>error<r><d>:<r> <b>Module not found \"<b>{s}<r>\"\n", .{target_name});
|
|
} else if (ext.len > 0) {
|
|
Output.prettyError("<r><red>error<r><d>:<r> <b>File not found \"<b>{s}<r>\"\n", .{target_name});
|
|
} else {
|
|
Output.prettyError("<r><red>error<r><d>:<r> <b>Script not found \"<b>{s}<r>\"\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 \"<b>{s}<r>\"", .{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 \"<b>{s}<r>\"", .{std.fs.path.basename(utf8)});
|
|
Global.exit(1);
|
|
};
|
|
}
|
|
};
|