From 4869ebff24e9bbcbc782c63c5c70153325fcf3e9 Mon Sep 17 00:00:00 2001 From: dave caruso Date: Fri, 29 Mar 2024 16:42:17 -0700 Subject: [PATCH] fix!: do not lookup cwd in which (#9691) * do not lookup cwd in which * fix webkit submodule * fix compilation on linux * feedback --- packages/bun-types/bun.d.ts | 3 +- src/StandaloneModuleGraph.zig | 1 - src/bun.js/api/BunObject.zig | 8 ----- src/bun.js/api/bun/subprocess.zig | 4 +-- src/cli/bunx_command.zig | 6 ---- src/cli/create_command.zig | 4 +-- src/cli/install_completions_command.zig | 2 +- src/cli/run_command.zig | 12 +++---- src/cli/upgrade_command.zig | 2 +- src/env_loader.zig | 13 ++++---- src/install/install.zig | 4 +-- src/install/lifecycle_script_runner.zig | 2 +- src/open.zig | 18 +++++------ src/shell/interpreter.zig | 6 ++-- src/which.zig | 34 ++++++------------- test/js/bun/util/which.test.ts | 43 +++++++++++++++++-------- 16 files changed, 74 insertions(+), 88 deletions(-) diff --git a/packages/bun-types/bun.d.ts b/packages/bun-types/bun.d.ts index 418c7f3b6c..2bb70f3971 100644 --- a/packages/bun-types/bun.d.ts +++ b/packages/bun-types/bun.d.ts @@ -43,9 +43,8 @@ declare module "bun" { * * @param {string} command The name of the executable or script * @param {string} options.PATH Overrides the PATH environment variable - * @param {string} options.cwd Limits the search to a particular directory in which to searc */ - function which(command: string, options?: { PATH?: string; cwd?: string }): string | null; + function which(command: string, options?: { PATH?: string }): string | null; /** * Get the column count of a string as it would be displayed in a terminal. diff --git a/src/StandaloneModuleGraph.zig b/src/StandaloneModuleGraph.zig index a33aaa725a..d658f771cf 100644 --- a/src/StandaloneModuleGraph.zig +++ b/src/StandaloneModuleGraph.zig @@ -716,7 +716,6 @@ pub const StandaloneModuleGraph = struct { if (bun.which( &whichbuf, bun.getenvZ("PATH") orelse return error.FileNotFound, - "", bun.argv()[0], )) |path| { return bun.toFD((try std.fs.cwd().openFileZ(path, .{})).handle); diff --git a/src/bun.js/api/BunObject.zig b/src/bun.js/api/BunObject.zig index 8ca1d6495f..61b1766b9d 100644 --- a/src/bun.js/api/BunObject.zig +++ b/src/bun.js/api/BunObject.zig @@ -735,26 +735,18 @@ pub fn which( path_str = ZigString.Slice.fromUTF8NeverFree( globalThis.bunVM().bundler.env.get("PATH") orelse "", ); - cwd_str = ZigString.Slice.fromUTF8NeverFree( - globalThis.bunVM().bundler.fs.top_level_dir, - ); if (arguments.nextEat()) |arg| { if (!arg.isEmptyOrUndefinedOrNull() and arg.isObject()) { if (arg.get(globalThis, "PATH")) |str_| { path_str = str_.toSlice(globalThis, globalThis.bunVM().allocator); } - - if (arg.get(globalThis, "cwd")) |str_| { - cwd_str = str_.toSlice(globalThis, globalThis.bunVM().allocator); - } } } if (Which.which( &path_buf, path_str.slice(), - cwd_str.slice(), bin_str.slice(), )) |bin_path| { return ZigString.init(bin_path).withEncoding().toValueGC(globalThis); diff --git a/src/bun.js/api/bun/subprocess.zig b/src/bun.js/api/bun/subprocess.zig index f4aa21aae9..ea7f9ec808 100644 --- a/src/bun.js/api/bun/subprocess.zig +++ b/src/bun.js/api/bun/subprocess.zig @@ -1642,7 +1642,7 @@ pub const Subprocess = struct { if (argv0 == null) { var path_buf: [bun.MAX_PATH_BYTES]u8 = undefined; - const resolved = Which.which(&path_buf, PATH, cwd, arg0.slice()) orelse { + const resolved = Which.which(&path_buf, PATH, arg0.slice()) orelse { globalThis.throwInvalidArguments("Executable not found in $PATH: \"{s}\"", .{arg0.slice()}); return .zero; }; @@ -1652,7 +1652,7 @@ pub const Subprocess = struct { }; } else { var path_buf: [bun.MAX_PATH_BYTES]u8 = undefined; - const resolved = Which.which(&path_buf, PATH, cwd, bun.sliceTo(argv0.?, 0)) orelse { + const resolved = Which.which(&path_buf, PATH, bun.sliceTo(argv0.?, 0)) orelse { globalThis.throwInvalidArguments("Executable not found in $PATH: \"{s}\"", .{arg0.slice()}); return .zero; }; diff --git a/src/cli/bunx_command.zig b/src/cli/bunx_command.zig index cc277bd891..c77495c8ec 100644 --- a/src/cli/bunx_command.zig +++ b/src/cli/bunx_command.zig @@ -449,7 +449,6 @@ pub const BunxCommand = struct { destination_ = bun.which( &path_buf, PATH_FOR_BIN_DIRS, - if (ignore_cwd.len > 0) "" else this_bundler.fs.top_level_dir, initial_bin_name, ); } @@ -460,7 +459,6 @@ pub const BunxCommand = struct { if (destination_ orelse bun.which( &path_buf, bunx_cache_dir, - if (ignore_cwd.len > 0) "" else this_bundler.fs.top_level_dir, absolute_in_cache_dir, )) |destination| { const out = bun.asByteSlice(destination); @@ -531,7 +529,6 @@ pub const BunxCommand = struct { destination_ = bun.which( &path_buf, bunx_cache_dir, - if (ignore_cwd.len > 0) "" else this_bundler.fs.top_level_dir, package_name_for_bin, ); } @@ -539,7 +536,6 @@ pub const BunxCommand = struct { if (destination_ orelse bun.which( &path_buf, bunx_cache_dir, - if (ignore_cwd.len > 0) "" else this_bundler.fs.top_level_dir, absolute_in_cache_dir, )) |destination| { const out = bun.asByteSlice(destination); @@ -655,7 +651,6 @@ pub const BunxCommand = struct { if (bun.which( &path_buf, bunx_cache_dir, - if (ignore_cwd.len > 0) "" else this_bundler.fs.top_level_dir, absolute_in_cache_dir, )) |destination| { const out = bun.asByteSlice(destination); @@ -680,7 +675,6 @@ pub const BunxCommand = struct { if (bun.which( &path_buf, bunx_cache_dir, - if (ignore_cwd.len > 0) "" else this_bundler.fs.top_level_dir, absolute_in_cache_dir, )) |destination| { const out = bun.asByteSlice(destination); diff --git a/src/cli/create_command.zig b/src/cli/create_command.zig index 40d0cada3a..98ebac6a2e 100644 --- a/src/cli/create_command.zig +++ b/src/cli/create_command.zig @@ -1564,7 +1564,7 @@ pub const CreateCommand = struct { Output.flush(); if (create_options.open) { - if (which(&bun_path_buf, PATH, destination, "bun")) |bin| { + if (which(&bun_path_buf, PATH, "bun")) |bin| { var argv = [_]string{bun.asByteSlice(bin)}; var child = std.ChildProcess.init(&argv, ctx.allocator); child.cwd = destination; @@ -2289,7 +2289,7 @@ const GitHandler = struct { // Time (mean ± σ): 306.7 ms ± 6.1 ms [User: 31.7 ms, System: 269.8 ms] // Range (min … max): 299.5 ms … 318.8 ms 10 runs - if (which(&bun_path_buf, PATH, destination, "git")) |git| { + if (which(&bun_path_buf, PATH, "git")) |git| { const git_commands = .{ &[_]string{ git, "init", "--quiet" }, &[_]string{ git, "add", destination, "--ignore-errors" }, diff --git a/src/cli/install_completions_command.zig b/src/cli/install_completions_command.zig index 3611ad32a2..d10d2f0874 100644 --- a/src/cli/install_completions_command.zig +++ b/src/cli/install_completions_command.zig @@ -50,7 +50,7 @@ pub const InstallCompletionsCommand = struct { var buf: [bun.MAX_PATH_BYTES]u8 = undefined; // don't install it if it's already there - if (bun.which(&buf, bun.getenvZ("PATH") orelse cwd, cwd, bunx_name) != null) + if (bun.which(&buf, bun.getenvZ("PATH") orelse cwd, bunx_name) != null) return; // first try installing the symlink into the same directory as the bun executable diff --git a/src/cli/run_command.zig b/src/cli/run_command.zig index 63da7c9434..457d9c5b34 100644 --- a/src/cli/run_command.zig +++ b/src/cli/run_command.zig @@ -64,13 +64,13 @@ pub const RunCommand = struct { "zsh", }; - fn findShellImpl(PATH: string, cwd: string) ?stringZ { + fn findShellImpl(PATH: 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_| { + if (which(&path_buf, PATH, shell)) |shell_| { return shell_; } } @@ -101,7 +101,7 @@ pub const RunCommand = struct { /// Find the "best" shell to use /// Cached to only run once - pub fn findShell(PATH: string, cwd: string) ?stringZ { + pub fn findShell(PATH: string) ?stringZ { const bufs = struct { pub var shell_buf_once: [bun.MAX_PATH_BYTES]u8 = undefined; pub var found_shell: [:0]const u8 = ""; @@ -110,7 +110,7 @@ pub const RunCommand = struct { return bufs.found_shell; } - if (findShellImpl(PATH, cwd)) |found| { + if (findShellImpl(PATH)) |found| { if (found.len < bufs.shell_buf_once.len) { @memcpy(bufs.shell_buf_once[0..found.len], found); bufs.shell_buf_once[found.len] = 0; @@ -275,7 +275,7 @@ pub const RunCommand = struct { silent: bool, use_system_shell: bool, ) !bool { - const shell_bin = findShell(env.get("PATH") orelse "", cwd) orelse return error.MissingShell; + const shell_bin = findShell(env.get("PATH") orelse "") orelse return error.MissingShell; const script = original_script; var copy_script = try std.ArrayList(u8).initCapacity(allocator, script.len); @@ -1577,7 +1577,7 @@ pub const RunCommand = struct { } if (path_for_which.len > 0) { - if (which(&path_buf, path_for_which, this_bundler.fs.top_level_dir, script_name_to_search)) |destination| { + if (which(&path_buf, path_for_which, script_name_to_search)) |destination| { const out = bun.asByteSlice(destination); return try runBinaryWithoutBunxPath( ctx, diff --git a/src/cli/upgrade_command.zig b/src/cli/upgrade_command.zig index 6ed111acab..ee56728ea2 100644 --- a/src/cli/upgrade_command.zig +++ b/src/cli/upgrade_command.zig @@ -612,7 +612,7 @@ pub const UpgradeCommand = struct { } if (comptime Environment.isPosix) { - const unzip_exe = which(&unzip_path_buf, env_loader.map.get("PATH") orelse "", filesystem.top_level_dir, "unzip") orelse { + const unzip_exe = which(&unzip_path_buf, env_loader.map.get("PATH") orelse "", "unzip") orelse { save_dir.deleteFileZ(tmpname) catch {}; Output.prettyErrorln("error: Failed to locate \"unzip\" in PATH. bun upgrade needs \"unzip\" to work.", .{}); Global.exit(1); diff --git a/src/env_loader.zig b/src/env_loader.zig index 31ae661f4a..443581b887 100644 --- a/src/env_loader.zig +++ b/src/env_loader.zig @@ -66,14 +66,14 @@ pub const Loader = struct { return strings.eqlComptime(env, "test"); } - pub fn getNodePath(this: *Loader, fs: *Fs.FileSystem, buf: *bun.PathBuffer) ?[:0]const u8 { + pub fn getNodePath(this: *Loader, buf: *bun.PathBuffer) ?[:0]const u8 { if (this.get("NODE") orelse this.get("npm_node_execpath")) |node| { @memcpy(buf[0..node.len], node); buf[node.len] = 0; return buf[0..node.len :0]; } - if (which(buf, this.get("PATH") orelse return null, fs.top_level_dir, "node")) |node| { + if (which(buf, this.get("PATH") orelse return null, "node")) |node| { return node; } @@ -180,15 +180,15 @@ pub const Loader = struct { var did_load_ccache_path: bool = false; - pub fn loadCCachePath(this: *Loader, fs: *Fs.FileSystem) void { + pub fn loadCCachePath(this: *Loader) void { if (did_load_ccache_path) { return; } did_load_ccache_path = true; - loadCCachePathImpl(this, fs) catch {}; + loadCCachePathImpl(this) catch {}; } - fn loadCCachePathImpl(this: *Loader, fs: *Fs.FileSystem) !void { + fn loadCCachePathImpl(this: *Loader) !void { // if they have ccache installed, put it in env variable `CMAKE_CXX_COMPILER_LAUNCHER` so // cmake can use it to hopefully speed things up @@ -196,7 +196,6 @@ pub const Loader = struct { const ccache_path = bun.which( &buf, this.get("PATH") orelse return, - fs.top_level_dir, "ccache", ) orelse ""; @@ -229,7 +228,7 @@ pub const Loader = struct { if (node_path_to_use_set_once.len > 0) { node_path_to_use = node_path_to_use_set_once; } else { - const node = this.getNodePath(fs, &buf) orelse return false; + const node = this.getNodePath(&buf) orelse return false; node_path_to_use = try fs.dirname_store.append([]const u8, bun.asByteSlice(node)); } } diff --git a/src/install/install.zig b/src/install/install.zig index a4cbe56f9d..f4724f3113 100644 --- a/src/install/install.zig +++ b/src/install/install.zig @@ -2118,11 +2118,11 @@ pub const PackageManager = struct { }; } - this.env.loadCCachePath(this_bundler.fs); + this.env.loadCCachePath(); { var node_path: [bun.MAX_PATH_BYTES]u8 = undefined; - if (this.env.getNodePath(this_bundler.fs, &node_path)) |node_pathZ| { + if (this.env.getNodePath(&node_path)) |node_pathZ| { _ = try this.env.loadNodeJSConfig(this_bundler.fs, bun.default_allocator.dupe(u8, node_pathZ) catch bun.outOfMemory()); } else brk: { const current_path = this.env.get("PATH") orelse ""; diff --git a/src/install/lifecycle_script_runner.zig b/src/install/lifecycle_script_runner.zig index 9698e4137f..5a54e71edf 100644 --- a/src/install/lifecycle_script_runner.zig +++ b/src/install/lifecycle_script_runner.zig @@ -125,7 +125,7 @@ pub const LifecycleScriptSubprocess = struct { this.current_script_index = next_script_index; this.has_called_process_exit = false; - const shell_bin = bun.CLI.RunCommand.findShell(env.get("PATH") orelse "", cwd) orelse return error.MissingShell; + const shell_bin = bun.CLI.RunCommand.findShell(env.get("PATH") orelse "") orelse return error.MissingShell; var copy_script = try std.ArrayList(u8).initCapacity(manager.allocator, original_script.script.len + 1); defer copy_script.deinit(); diff --git a/src/open.zig b/src/open.zig index ea2bd6d64c..b4ee5f6a72 100644 --- a/src/open.zig +++ b/src/open.zig @@ -106,12 +106,12 @@ pub const Editor = enum(u8) { } const which = @import("./which.zig").which; - pub fn byPATH(env: *DotEnv.Loader, buf: *[bun.MAX_PATH_BYTES]u8, cwd: string, out: *[]const u8) ?Editor { + pub fn byPATH(env: *DotEnv.Loader, buf: *[bun.MAX_PATH_BYTES]u8, out: *[]const u8) ?Editor { const PATH = env.get("PATH") orelse return null; inline for (default_preference_list) |editor| { if (bin_name.get(editor)) |path| { - if (which(buf, PATH, cwd, path)) |bin| { + if (which(buf, PATH, path)) |bin| { out.* = bun.asByteSlice(bin); return editor; } @@ -121,12 +121,12 @@ pub const Editor = enum(u8) { return null; } - pub fn byPATHForEditor(env: *DotEnv.Loader, editor: Editor, buf: *[bun.MAX_PATH_BYTES]u8, cwd: string, out: *[]const u8) bool { + pub fn byPATHForEditor(env: *DotEnv.Loader, editor: Editor, buf: *[bun.MAX_PATH_BYTES]u8, out: *[]const u8) bool { const PATH = env.get("PATH") orelse return false; if (bin_name.get(editor)) |path| { if (path.len > 0) { - if (which(buf, PATH, cwd, path)) |bin| { + if (which(buf, PATH, path)) |bin| { out.* = bun.asByteSlice(bin); return true; } @@ -152,9 +152,9 @@ pub const Editor = enum(u8) { return false; } - pub fn byFallback(env: *DotEnv.Loader, buf: *[bun.MAX_PATH_BYTES]u8, cwd: string, out: *[]const u8) ?Editor { + pub fn byFallback(env: *DotEnv.Loader, buf: *[bun.MAX_PATH_BYTES]u8, out: *[]const u8) ?Editor { inline for (default_preference_list) |editor| { - if (byPATHForEditor(env, editor, buf, cwd, out)) { + if (byPATHForEditor(env, editor, buf, out)) { return editor; } @@ -405,7 +405,7 @@ pub const EditorContext = struct { // "vscode" if (Editor.byName(std.fs.path.basename(this.name))) |editor_| { - if (Editor.byPATHForEditor(env, editor_, &buf, Fs.FileSystem.instance.top_level_dir, &out)) { + if (Editor.byPATHForEditor(env, editor_, &buf, &out)) { this.editor = editor_; this.path = Fs.FileSystem.instance.dirname_store.append(string, out) catch unreachable; return; @@ -422,7 +422,7 @@ pub const EditorContext = struct { // EDITOR=code if (Editor.detect(env)) |editor_| { - if (Editor.byPATHForEditor(env, editor_, &buf, Fs.FileSystem.instance.top_level_dir, &out)) { + if (Editor.byPATHForEditor(env, editor_, &buf, &out)) { this.editor = editor_; this.path = Fs.FileSystem.instance.dirname_store.append(string, out) catch unreachable; return; @@ -437,7 +437,7 @@ pub const EditorContext = struct { } // Don't know, so we will just guess based on what exists - if (Editor.byFallback(env, &buf, Fs.FileSystem.instance.top_level_dir, &out)) |editor_| { + if (Editor.byFallback(env, &buf, &out)) |editor_| { this.editor = editor_; this.path = Fs.FileSystem.instance.dirname_store.append(string, out) catch unreachable; return; diff --git a/src/shell/interpreter.zig b/src/shell/interpreter.zig index c56fb76dc0..2cc05161a4 100644 --- a/src/shell/interpreter.zig +++ b/src/shell/interpreter.zig @@ -3597,7 +3597,7 @@ pub const Interpreter = struct { } var path_buf: [bun.MAX_PATH_BYTES]u8 = undefined; - const resolved = which(&path_buf, spawn_args.PATH, spawn_args.cwd, first_arg[0..first_arg_len]) orelse { + const resolved = which(&path_buf, spawn_args.PATH, first_arg[0..first_arg_len]) orelse { this.writeFailingError("bun: command not found: {s}\n", .{first_arg}); return; }; @@ -5895,7 +5895,7 @@ pub const Interpreter = struct { var had_not_found = false; for (args) |arg_raw| { const arg = arg_raw[0..std.mem.len(arg_raw)]; - const resolved = which(&path_buf, PATH.slice(), this.bltn.parentCmd().base.shell.cwdZ(), arg) orelse { + const resolved = which(&path_buf, PATH.slice(), arg) orelse { had_not_found = true; const buf = this.bltn.fmtErrorArena(.which, "{s} not found\n", .{arg}); _ = this.bltn.writeNoIO(.stdout, buf); @@ -5933,7 +5933,7 @@ pub const Interpreter = struct { var path_buf: [bun.MAX_PATH_BYTES]u8 = undefined; const PATH = this.bltn.parentCmd().base.shell.export_env.get(EnvStr.initSlice("PATH")) orelse EnvStr.initSlice(""); - const resolved = which(&path_buf, PATH.slice(), this.bltn.parentCmd().base.shell.cwdZ(), arg) orelse { + const resolved = which(&path_buf, PATH.slice(), arg) orelse { multiargs.had_not_found = true; if (!this.bltn.stdout.needsIO()) { const buf = this.bltn.fmtErrorArena(null, "{s} not found\n", .{arg}); diff --git a/src/which.zig b/src/which.zig index 32dffb8d59..a11e0b8405 100644 --- a/src/which.zig +++ b/src/which.zig @@ -2,6 +2,8 @@ const std = @import("std"); const bun = @import("root").bun; const PosixToWinNormalizer = bun.path.PosixToWinNormalizer; +const debug = bun.Output.scoped(.which, true); + fn isValid(buf: *bun.PathBuffer, segment: []const u8, bin: []const u8) ?u16 { bun.copy(u8, buf, segment); buf[segment.len] = std.fs.path.sep; @@ -14,15 +16,17 @@ fn isValid(buf: *bun.PathBuffer, segment: []const u8, bin: []const u8) ?u16 { // Like /usr/bin/which but without needing to exec a child process // Remember to resolve the symlink if necessary -pub fn which(buf: *bun.PathBuffer, path: []const u8, cwd: []const u8, bin: []const u8) ?[:0]const u8 { +pub fn which(buf: *bun.PathBuffer, path: []const u8, bin: []const u8) ?[:0]const u8 { + debug("which({s} in {s})", .{ bin, path }); if (bun.Environment.os == .windows) { var convert_buf: bun.WPathBuffer = undefined; - const result = whichWin(&convert_buf, path, cwd, bin) orelse return null; + const result = whichWin(&convert_buf, path, bin) orelse return null; const result_converted = bun.strings.convertUTF16toUTF8InBuffer(buf, result) catch unreachable; buf[result_converted.len] = 0; std.debug.assert(result_converted.ptr == buf.ptr); return buf[0..result_converted.len :0]; } + if (bin.len == 0) return null; // handle absolute paths @@ -37,11 +41,7 @@ pub fn which(buf: *bun.PathBuffer, path: []const u8, cwd: []const u8, bin: []con // /foo/bar/baz as a path and you're in /home/jarred? } - if (cwd.len > 0) { - if (isValid(buf, std.mem.trimRight(u8, cwd, std.fs.path.sep_str), bin)) |len| { - return buf[0..len :0]; - } - } + if (path.len == 0) return null; var path_iter = std.mem.tokenizeScalar(u8, path, std.fs.path.delimiter); while (path_iter.next()) |segment| { @@ -117,9 +117,9 @@ fn searchBinInPath(buf: *bun.WPathBuffer, path_buf: *[bun.MAX_PATH_BYTES]u8, pat } /// This is the windows version of `which`. -/// It operates on wide strings. +/// It returns a wide string. /// It is similar to Get-Command in powershell. -pub fn whichWin(buf: *bun.WPathBuffer, path: []const u8, cwd: []const u8, bin: []const u8) ?[:0]const u16 { +fn whichWin(buf: *bun.WPathBuffer, path: []const u8, bin: []const u8) ?[:0]const u16 { if (bin.len == 0) return null; var path_buf: [bun.MAX_PATH_BYTES]u8 = undefined; @@ -133,10 +133,7 @@ pub fn whichWin(buf: *bun.WPathBuffer, path: []const u8, cwd: []const u8, bin: [ return searchBin(buf, bin_utf16.len, check_windows_extensions); } - // check if bin is in cwd - if (searchBinInPath(buf, &path_buf, cwd, bin, check_windows_extensions)) |bin_path| { - return bin_path; - } + if (path.len == 0) return null; // iterate over system path delimiter var path_iter = std.mem.tokenizeScalar(u8, path, ';'); @@ -148,14 +145,3 @@ pub fn whichWin(buf: *bun.WPathBuffer, path: []const u8, cwd: []const u8, bin: [ return null; } - -test "which" { - var buf: bun.fs.PathBuffer = undefined; - const realpath = bun.getenvZ("PATH") orelse unreachable; - const whichbin = which(&buf, realpath, try bun.getcwdAlloc(std.heap.c_allocator), "which"); - try std.testing.expectEqualStrings(whichbin orelse return std.debug.assert(false), "/usr/bin/which"); - try std.testing.expect(null == which(&buf, realpath, try bun.getcwdAlloc(std.heap.c_allocator), "baconnnnnn")); - try std.testing.expect(null != which(&buf, realpath, try bun.getcwdAlloc(std.heap.c_allocator), "zig")); - try std.testing.expect(null == which(&buf, realpath, try bun.getcwdAlloc(std.heap.c_allocator), "bin")); - try std.testing.expect(null == which(&buf, realpath, try bun.getcwdAlloc(std.heap.c_allocator), "usr")); -} diff --git a/test/js/bun/util/which.test.ts b/test/js/bun/util/which.test.ts index f1d4b675bb..c5ec4e13b2 100644 --- a/test/js/bun/util/which.test.ts +++ b/test/js/bun/util/which.test.ts @@ -1,11 +1,18 @@ import { test, expect } from "bun:test"; -import { which } from "bun"; +import { $, which } from "bun"; import { rmSync, chmodSync, mkdirSync, realpathSync } from "node:fs"; import { join, basename } from "node:path"; import { tmpdir } from "node:os"; -import { cpSync, rmdirSync } from "js/node/fs/export-star-from"; -import { isIntelMacOS, isWindows } from "../../../harness"; +import { rmdirSync } from "js/node/fs/export-star-from"; +import { isIntelMacOS, isWindows, tempDirWithFiles } from "harness"; + +{ + const delim = isWindows ? ";" : ":"; + if (`${delim}${process.env.PATH}${delim}`.includes(`${delim}.${delim}`)) { + throw new Error("$PATH includes . which will break `Bun.which` tests. This is an environment configuration issue."); + } +} function writeFixture(path: string) { var fs = require("fs"); @@ -52,10 +59,8 @@ if (isWindows) { basedir = realpathSync(basedir); process.chdir(basedir); - // Our cwd is not /tmp - expect(which("myscript.sh")).toBe(abs); + expect(which("myscript.sh")).toBe(null); - const orig = process.cwd(); process.chdir(tmpdir()); try { rmdirSync("myscript.sh"); @@ -96,16 +101,28 @@ if (isWindows) { PATH: "/not-tmp", }), ).toBe(null); - - expect( - // cwd is checked first - which("myscript.sh", { - cwd: basedir, - }), - ).toBe(abs); } finally { process.chdir(origDir); rmSync(basedir, { recursive: true, force: true }); } }); } + +test("Bun.which does not look in the current directory", async () => { + const cwd = process.cwd(); + const dir = tempDirWithFiles("which", { + "some_program_name": "#!/usr/bin/env sh\necho FAIL\nexit 0\n", + "some_program_name.cmd": "@echo FAIL\n@exit 0\n", + }); + process.chdir(dir); + try { + if (!isWindows) { + await $`chmod +x ./some_program_name`; + } + + expect(which("some_program_name")).toBe(null); + expect((await $`some_program_name`).exitCode).not.toBe(0); + } finally { + process.chdir(cwd); + } +});