Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.

Expand Down
24 changes: 22 additions & 2 deletions lib/index.mjs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
24 changes: 24 additions & 0 deletions test/ripgrep.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down