From 7d6bb96f78e13d8a335404ec72cdb4ba408f5ef7 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 9 Dec 2025 08:24:48 +0000 Subject: [PATCH] Add cross-runtime which wrapper utility Add internal/which.ts that uses Bun.which when running on Bun, and falls back to spawning the `which` command on other runtimes like Node.js. This enables projects to work in both environments without spawning unnecessary processes when Bun's native which is available. --- src/js/internal-for-testing.ts | 1 + src/js/internal/which.ts | 27 +++++++++++++++++++++++++ test/js/bun/util/internal-which.test.ts | 27 +++++++++++++++++++++++++ 3 files changed, 55 insertions(+) create mode 100644 src/js/internal/which.ts create mode 100644 test/js/bun/util/internal-which.test.ts diff --git a/src/js/internal-for-testing.ts b/src/js/internal-for-testing.ts index 35b7cd0916..b52c271cdc 100644 --- a/src/js/internal-for-testing.ts +++ b/src/js/internal-for-testing.ts @@ -165,6 +165,7 @@ export const bindgen = $zig("bindgen_test.zig", "getBindgenTestFunctions") as { export const noOpForTesting = $cpp("NoOpForTesting.cpp", "createNoOpForTesting"); export const Dequeue = require("internal/fifo"); +export const which = require("internal/which").which; export const fs = require("node:fs/promises").$data; diff --git a/src/js/internal/which.ts b/src/js/internal/which.ts new file mode 100644 index 0000000000..460cf167ce --- /dev/null +++ b/src/js/internal/which.ts @@ -0,0 +1,27 @@ +/** + * Cross-runtime `which` utility. + * Uses Bun.which when running on Bun, spawns `which` command otherwise. + */ + +declare const Bun: typeof import("bun") | undefined; + +function which(command: string): string | null { + if (typeof Bun !== "undefined") { + return Bun.which(command); + } + + // Fallback for Node.js or other runtimes + const { execSync } = require("node:child_process"); + try { + const result = execSync(`which ${command}`, { + encoding: "utf-8", + stdio: ["ignore", "pipe", "ignore"], + }); + return result.trim() || null; + } catch { + return null; + } +} + +export default which; +export { which }; diff --git a/test/js/bun/util/internal-which.test.ts b/test/js/bun/util/internal-which.test.ts new file mode 100644 index 0000000000..756c6220b1 --- /dev/null +++ b/test/js/bun/util/internal-which.test.ts @@ -0,0 +1,27 @@ +import { expect, test } from "bun:test"; +import { which } from "bun:internal-for-testing"; +import { isWindows } from "harness"; + +test("which finds executables in PATH", () => { + // "bun" should always be in PATH during tests + const bunPath = which("bun"); + expect(bunPath).not.toBe(null); + expect(bunPath!.length).toBeGreaterThan(0); +}); + +test("which returns null for non-existent commands", () => { + const result = which("this-command-definitely-does-not-exist-12345"); + expect(result).toBe(null); +}); + +if (!isWindows) { + test("which finds common system utilities", () => { + // At least one of these should exist on any POSIX system + const ls = which("ls"); + const cat = which("cat"); + const sh = which("sh"); + + const foundAtLeastOne = ls !== null || cat !== null || sh !== null; + expect(foundAtLeastOne).toBe(true); + }); +}