diff --git a/README.md b/README.md index d005a3b..d8936fb 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,9 @@ Options: - `stderr` — writable stream to use instead of `process.stderr`. With `nodeWasi`, only streams with a numeric `fd` property are supported. - `buffer` — when `true`, capture stdout and stderr and return them as strings in the result (`{ code, stdout, stderr }`). Custom `stdout`/`stderr` streams take precedence over buffering for the corresponding channel (default: `false`). - `env` — environment variables passed to the WASI instance (default: `process.env`). -- `preopens` — WASI preopened directories mapping guest paths to host paths (default: `{ ".": process.cwd() }`). Absolute paths passed as args are auto-added as preopens. +- `preopens` — WASI preopened directories mapping guest paths to host paths (default: `{ ".": process.cwd() }`, except when `cwd` is `/` or `\\` in which case it's `{}`). Absolute paths passed as args are auto-added as preopens. + + **Note:** When your app is launched from a GUI (macOS Finder, Windows Explorer, etc.) without a terminal, `process.cwd()` may be `/` (root). In this case, the default preopens excludes `"."` to avoid permission/sandbox issues. You should provide explicit absolute paths in the `preopens` option or as arguments when running in GUI environments. - `returnOnExit` — when `true`, `proc_exit` returns the exit code instead of terminating the process (default: `true`). - `nodeWasi` — use Node's built-in `node:wasi` instead of the bundled WASI shim. Enabled by default on Node.js for best performance; automatically disabled on Bun and Deno where `node:wasi` is not available, falling back to the bundled shim. Can also be forced on via `RIPGREP_NODE_WASI=1`. diff --git a/lib/index.mjs b/lib/index.mjs index bcee43d..984042b 100644 --- a/lib/index.mjs +++ b/lib/index.mjs @@ -1,15 +1,35 @@ import { fileURLToPath } from "node:url"; -import { isAbsolute, resolve } from "node:path"; +import { isAbsolute, resolve, parse } from "node:path"; export const rgPath = fileURLToPath(new URL("./rg.mjs", import.meta.url)); +function isRootPath(cwd) { + // Unix: "/" + // Windows: "C:\\", "D:\\", "C:/", or just "\\" + if (cwd === "/" || cwd === "\\") return true; + const parsed = parse(cwd); + return parsed.root === cwd; +} + +function getDefaultPreopens() { + const cwd = process.cwd(); + // Skip mapping "." to cwd when it's root - this commonly happens + // when apps are launched from GUI (macOS Finder, Windows Explorer, etc.) + // without a terminal. In these cases, "/" or "C:\\" is rarely the intended + // search directory and can cause permission/sandbox issues. + if (isRootPath(cwd)) { + return {}; + } + return { ".": cwd }; +} + export async function ripgrep(args = [], options = {}) { let { stdout, stderr, buffer = false, env = process.env, - preopens = { ".": process.cwd() }, + preopens = getDefaultPreopens(), returnOnExit = true, nodeWasi = getDefaultNodeWasi(), } = options; diff --git a/test/ripgrep.test.mjs b/test/ripgrep.test.mjs index a43de25..652af5c 100644 --- a/test/ripgrep.test.mjs +++ b/test/ripgrep.test.mjs @@ -128,6 +128,30 @@ describe("ripgrep", () => { expect(res.stdout).toMatch(/^ripgrep \d+/); }); + it("works when cwd is root (regression test for GUI launcher issue)", async () => { + // When apps are launched from GUI (Finder, Explorer, etc.), cwd may be "/" + // This should not break ripgrep - it should require explicit paths + const originalCwd = process.cwd(); + const absHello = originalCwd + "/" + HELLO; + + // Only run this test if we can chdir to root + try { + process.chdir("/"); + } catch { + // Skip if we can't chdir to root (permissions) + return; + } + + try { + // With cwd=/, relative paths won't work, but absolute paths should + const res = await ripgrep(["hello", absHello], { buffer: true }); + expect(res.code).toBe(0); + expect(res.stdout).toContain("hello ripgrep world"); + } finally { + process.chdir(originalCwd); + } + }); + it("searches with explicit --color=never", async () => { const res = await ripgrep(["--color=never", "hello", HELLO], { buffer: true,