diff options
author | 2022-09-23 03:10:56 -0700 | |
---|---|---|
committer | 2022-09-23 03:10:56 -0700 | |
commit | 62e22b29959cc984cf9abfa42c30a5b2925841fd (patch) | |
tree | 7b5d50fcb484677028451e3083f16d3ef279fc65 | |
parent | 2346be55fc5c0fb51de986e55f698debc5dff362 (diff) | |
download | bun-62e22b29959cc984cf9abfa42c30a5b2925841fd.tar.gz bun-62e22b29959cc984cf9abfa42c30a5b2925841fd.tar.zst bun-62e22b29959cc984cf9abfa42c30a5b2925841fd.zip |
Implement `Bun.which`
-rw-r--r-- | src/bun.js/api/bun.zig | 79 | ||||
-rw-r--r-- | test/bun.js/which.test.ts | 52 |
2 files changed, 131 insertions, 0 deletions
diff --git a/src/bun.js/api/bun.zig b/src/bun.js/api/bun.zig index 8a75c3653..f36549485 100644 --- a/src/bun.js/api/bun.zig +++ b/src/bun.js/api/bun.zig @@ -76,6 +76,7 @@ const Transpiler = @import("./transpiler.zig"); const VirtualMachine = @import("../javascript.zig").VirtualMachine; const IOTask = JSC.IOTask; const zlib = @import("../../zlib.zig"); +const Which = @import("../../which.zig"); const is_bindgen = JSC.is_bindgen; const max_addressible_memory = std.math.maxInt(u56); @@ -129,6 +130,80 @@ pub fn getCSSImports() []ZigString { return css_imports_list_strings[0..tail]; } +pub fn which( + // this + _: void, + globalThis: js.JSContextRef, + // function + _: js.JSObjectRef, + // thisObject + _: js.JSObjectRef, + arguments_: []const js.JSValueRef, + exception: js.ExceptionRef, +) js.JSValueRef { + var path_buf: [bun.MAX_PATH_BYTES]u8 = undefined; + var arguments = JSC.Node.ArgumentsSlice.from(globalThis.bunVM(), arguments_); + defer arguments.deinit(); + const path_arg = arguments.nextEat() orelse { + JSC.throwInvalidArguments("which: expected 1 argument, got 0", .{}, globalThis, exception); + return JSC.JSValue.jsUndefined().asObjectRef(); + }; + + var path_str: ZigString.Slice = ZigString.Slice.empty; + var bin_str: ZigString.Slice = ZigString.Slice.empty; + var cwd_str: ZigString.Slice = ZigString.Slice.empty; + defer { + path_str.deinit(); + bin_str.deinit(); + cwd_str.deinit(); + } + + if (path_arg.isEmptyOrUndefinedOrNull()) { + return JSC.JSValue.jsNull().asObjectRef(); + } + + bin_str = path_arg.toSlice(globalThis, globalThis.bunVM().allocator); + + if (bin_str.len >= bun.MAX_PATH_BYTES) { + JSC.throwInvalidArguments("bin path is too long", .{}, globalThis, exception); + return JSC.JSValue.jsUndefined().asObjectRef(); + } + + if (bin_str.len == 0) { + return JSC.JSValue.jsNull().asObjectRef(); + } + + path_str = ZigString.Slice.fromUTF8( + globalThis.bunVM().bundler.env.map.get("PATH") orelse "", + ); + cwd_str = ZigString.Slice.fromUTF8( + 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).asObjectRef(); + } + + return JSC.JSValue.jsNull().asObjectRef(); +} + pub fn inspect( // this _: void, @@ -1116,6 +1191,10 @@ pub const Class = NewClass( .inflateSync = .{ .rfn = JSC.wrapWithHasContainer(JSZlib, "inflateSync", false, false, true), }, + + .which = .{ + .rfn = which, + }, }, .{ .main = .{ diff --git a/test/bun.js/which.test.ts b/test/bun.js/which.test.ts new file mode 100644 index 000000000..7fd4b8400 --- /dev/null +++ b/test/bun.js/which.test.ts @@ -0,0 +1,52 @@ +import { test, expect } from "bun:test"; + +import { which } from "bun"; + +test("which", () => { + writeFixture("/tmp/myscript.sh"); + + // Our cwd is not /tmp + expect(which("myscript.sh")).toBe(null); + + // "bun" is in our PATH + expect(which("bun")?.length > 0).toBe(true); + + expect( + // You can override PATH + which("myscript.sh", { + PATH: "/tmp", + }) + ).toBe("/tmp/myscript.sh"); + + expect( + which("myscript.sh", { + PATH: "/not-tmp", + }) + ).toBe(null); + + expect( + // PATH works like the $PATH environment variable, respecting colons + which("myscript.sh", { + PATH: "/not-tmp:/tmp", + }) + ).toBe("/tmp/myscript.sh"); + + expect( + // cwd is checked first + which("myscript.sh", { + cwd: "/tmp", + }) + ).toBe("/tmp/myscript.sh"); +}); + +function writeFixture(path) { + var fs = require("fs"); + try { + fs.unlinkSync(path); + } catch (e) {} + + var script_name = path; + var script_content = "echo Hello world!"; + fs.writeFileSync(script_name, script_content); + fs.chmodSync(script_name, "755"); +} |