diff --git a/src/cli.zig b/src/cli.zig index 0bb6dbf966f777..a6e1791b9324bc 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -1168,15 +1168,12 @@ pub const Command = struct { } }; - const exe_suffix = if (Environment.isWindows) ".exe" else ""; - pub fn isBunX(argv0: []const u8) bool { - return strings.endsWithComptime(argv0, "bunx" ++ exe_suffix) or - (Environment.isDebug and strings.endsWithComptime(argv0, "bunx-debug" ++ exe_suffix)); + return strings.endsWithComptime(argv0, "bunx") or (Environment.isDebug and strings.endsWithComptime(argv0, "bunx-debug")); } pub fn isNode(argv0: []const u8) bool { - return strings.endsWithComptime(argv0, "node" ++ exe_suffix); + return strings.endsWithComptime(argv0, "node"); } pub fn which() Tag { @@ -1184,10 +1181,15 @@ pub const Command = struct { const argv0 = args_iter.next() orelse return .HelpCommand; + const without_exe = if (Environment.isWindows) + strings.withoutSuffixComptime(argv0, ".exe") + else + argv0; + // symlink is argv[0] - if (isBunX(argv0)) return .BunxCommand; + if (isBunX(without_exe)) return .BunxCommand; - if (isNode(argv0)) { + if (isNode(without_exe)) { @import("./deps/zig-clap/clap/streaming.zig").warn_on_unrecognized_flag = false; pretend_to_be_node = true; return .RunAsNodeCommand; @@ -1626,7 +1628,7 @@ pub const Command = struct { const ctx = try Command.Context.create(allocator, log, .RunCommand); if (ctx.positionals.len > 0) { - if (try RunCommand.exec(ctx, false, true)) { + if (try RunCommand.exec(ctx, false, true, false)) { return; } @@ -1743,7 +1745,7 @@ pub const Command = struct { } if (ctx.positionals.len > 0 and extension.len == 0) { - if (try RunCommand.exec(ctx, true, false)) { + if (try RunCommand.exec(ctx, true, false, true)) { return; } @@ -1789,61 +1791,71 @@ pub const Command = struct { 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_: anyerror!std.fs.File = brk: { - if (std.fs.path.isAbsoluteWindows(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, true, .windows); - } - absolute_script_path = resolved; - 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); + { + const file = bun.toLibUVOwnedFD(((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, true, .windows); + } + absolute_script_path = resolved; + 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: [bun.MAX_PATH_BYTES]u8 = undefined; + const cwd = bun.getcwd(&path_buf) catch return false; + path_buf[cwd.len] = std.fs.path.sep; + var parts = [_]string{script_name_to_search}; + file_path = resolve_path.joinAbsStringBuf( + path_buf[0 .. cwd.len + 1], + &script_name_buf, + &parts, + .auto, + ); + if (file_path.len == 0) return false; script_name_buf[file_path.len] = 0; - break :brk2 script_name_buf[0..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).handle); + defer _ = bun.sys.close(file); - break :brk bun.openFileZ(file_pathZ, .{ .mode = .read_only }); - } else { - var path_buf: [bun.MAX_PATH_BYTES]u8 = undefined; - const cwd = bun.getcwd(&path_buf) catch return false; - path_buf[cwd.len] = std.fs.path.sep; - var parts = [_]string{script_name_to_search}; - file_path = resolve_path.joinAbsStringBuf( - path_buf[0 .. cwd.len + 1], - &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 }); + 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, } - }; - const file = file_ catch return false; + Global.configureAllocator(.{ .long_running = true }); - Global.configureAllocator(.{ .long_running = true }); + // the case where this doesn't work is if the script name on disk doesn't end with a known JS-like file extension + absolute_script_path = absolute_script_path orelse brk: { + if (comptime !Environment.isWindows) break :brk bun.getFdPath(file, &script_name_buf) catch return false; - // the case where this doesn't work is if the script name on disk doesn't end with a known JS-like file extension - absolute_script_path = absolute_script_path orelse brk: { - if (comptime !Environment.isWindows) break :brk bun.getFdPath(file.handle, &script_name_buf) catch return false; - - var fd_path_buf: bun.PathBuffer = undefined; - const path = bun.getFdPath(file.handle, &fd_path_buf) catch return false; - break :brk resolve_path.normalizeString( - resolve_path.PosixToWinNormalizer.resolveCWDWithExternalBufZ(&script_name_buf, path) catch @panic("Could not resolve path"), - true, - .windows, - ); - }; + var fd_path_buf: bun.PathBuffer = undefined; + const path = bun.getFdPath(file, &fd_path_buf) catch return false; + break :brk resolve_path.normalizeString( + resolve_path.PosixToWinNormalizer.resolveCWDWithExternalBufZ(&script_name_buf, path) catch @panic("Could not resolve path"), + true, + .windows, + ); + }; + } if (!ctx.debug.loaded_bunfig) { bun.CLI.Arguments.loadConfigPath(ctx.allocator, true, "bunfig.toml", ctx, .RunCommand) catch {}; diff --git a/src/cli/run_command.zig b/src/cli/run_command.zig index 3466b2e10f9f7c..963500fcb117c3 100644 --- a/src/cli/run_command.zig +++ b/src/cli/run_command.zig @@ -1098,7 +1098,12 @@ pub const RunCommand = struct { } } - pub fn exec(ctx_: Command.Context, comptime bin_dirs_only: bool, comptime log_errors: bool) !bool { + pub fn exec( + ctx_: Command.Context, + comptime bin_dirs_only: bool, + comptime log_errors: bool, + comptime did_try_open_with_bun_js: bool, + ) !bool { var ctx = ctx_; // Step 1. Figure out what we're trying to run var positionals = ctx.positionals; @@ -1138,7 +1143,7 @@ pub const RunCommand = struct { return true; } - if (log_errors or force_using_bun) { + if (!did_try_open_with_bun_js and (log_errors or force_using_bun)) { if (script_name_to_search.len > 0) { possibly_open_with_bun_js: { const ext = std.fs.path.extension(script_name_to_search); diff --git a/src/install/windows-shim/bun_shim_impl.exe b/src/install/windows-shim/bun_shim_impl.exe index e2e560b5d03ff0..5a81cbcafbc4b8 100755 Binary files a/src/install/windows-shim/bun_shim_impl.exe and b/src/install/windows-shim/bun_shim_impl.exe differ diff --git a/src/install/windows-shim/bun_shim_impl.zig b/src/install/windows-shim/bun_shim_impl.zig index ef918cfc72c757..278beeef27b677 100644 --- a/src/install/windows-shim/bun_shim_impl.zig +++ b/src/install/windows-shim/bun_shim_impl.zig @@ -609,9 +609,9 @@ fn launcher(bun_ctx: anytype) noreturn { // Copy the filename in. There is no leading " but there is a trailing " // BUF1: '\??\C:\Users\dave\project\node_modules\my-cli\src\app.js"#node #####!!!!!!!!!!' - // ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^ ^ read_ptr + // ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^ ^ read_ptr // BUF2: 'node "C:\Users\dave\project\node_modules\my-cli\src\app.js"!!!!!!!!!!!!!!!!!!!!' - const length_of_filename_u8 = (@intFromPtr(read_ptr) - (2 * "\x00".len)) - @intFromPtr(buf1_u8); + const length_of_filename_u8 = @intFromPtr(read_ptr) - @intFromPtr(buf1_u8) - nt_object_prefix.len - 6; @memcpy( buf2_u8[shebang_arg_len_u8 + 2 * "\"".len ..][0..length_of_filename_u8], buf1_u8[2 * nt_object_prefix.len ..][0..length_of_filename_u8], @@ -623,10 +623,10 @@ fn launcher(bun_ctx: anytype) noreturn { // | |filename_len where the user args go // | the quote // shebang_arg_len - read_ptr = @ptrFromInt(@intFromPtr(buf2_u8) + shebang_arg_len_u8 + length_of_filename_u8 + 2 * "\"".len); + read_ptr = @ptrFromInt(@intFromPtr(buf2_u8) + length_of_filename_u8 + 2 * "\"\"".len + 2 * nt_object_prefix.len); if (user_arguments_u8.len > 0) { @memcpy(@as([*]u8, @ptrCast(read_ptr)), user_arguments_u8); - read_ptr += user_arguments_u8.len; + read_ptr = @ptrFromInt(@intFromPtr(read_ptr) + user_arguments_u8.len); } // BUF2: 'node "C:\Users\dave\project\node_modules\my-cli\src\app.js" --flags#!!!!!!!!!!' diff --git a/src/string_immutable.zig b/src/string_immutable.zig index abf716e0f7e524..8ec698fc1e95c2 100644 --- a/src/string_immutable.zig +++ b/src/string_immutable.zig @@ -5948,3 +5948,10 @@ pub inline fn indexOfScalar(input: anytype, scalar: std.meta.Child(@TypeOf(input pub fn containsScalar(input: anytype, item: std.meta.Child(@TypeOf(input))) bool { return indexOfScalar(input, item) != null; } + +pub fn withoutSuffixComptime(input: []const u8, comptime suffix: []const u8) []const u8 { + if (hasSuffixComptime(input, suffix)) { + return input[0 .. input.len - suffix.len]; + } + return input; +}