Skip to content

Commit 3e07e4c

Browse files
0xMinkroomote
authored andcommitted
fix: resolve ripgrep from @vscode/ripgrep-universal and the system PATH (#248)
* fix: resolve ripgrep from @vscode/ripgrep-universal and the system PATH search_files and list_files threw "Could not find ripgrep binary" on VS Code Insiders, whose staged-install builds ship ripgrep as @vscode/ripgrep-universal with the binary nested under bin/<platform>-<arch>/ — a layout getBinPath did not recognize. getBinPath now also checks that layout, and falls back to ripgrep on the system PATH when no copy is found in the VS Code install (covering VS Code forks and headless/CLI hosts). * fix: drop the PATH fallback per review feedback Per #248 review (edelauna): rely only on VS Code's bundled ripgrep — the fix keeps the @vscode/ripgrep-universal/bin/<platform>-<arch>/ resolution that VS Code Insiders' staged-install builds use (the original bug) but drops the system-PATH probe. This also addresses the Copilot trust-model note at the old line 111 (a PATH-resolved rg could be user-controlled) and clears the codecov gap — the uncovered lines were the PATH helper. * fix: resolve ripgrep via @vscode/ripgrep import per review feedback Per #248 review (edelauna): replace the appRoot path-probing with an @vscode/ripgrep import. VS Code's extension host aliases @vscode/ripgrep to its own @vscode/ripgrep-universal (extHostRequireInterceptor.ts L97-101), so ripgrep resolution stays in sync with whatever VS Code ships with — including the Insiders staged-install layout — without maintaining a hardcoded path list across VS Code repackagings. getBinPath shrinks to a try/catch around await import + the .asar → .asar.unpacked substitution from VS Code's own resolver (src/vs/base/node/ripgrep.ts). - @vscode/ripgrep added as a src devDep (types only; the binary is not shipped in the VSIX). - @vscode/ripgrep added to external in src/esbuild.mjs so the require is preserved at runtime for VS Code's interceptor. - Tests cover all four branches: rgPath returned, .asar substitution applies, rgPath undefined, rgPath access throws. * revert: drop @vscode/ripgrep require attempt for now Diagnostic from a Windows VS Code stable 1.121.0 install (see #248 review thread) showed that require("@vscode/ripgrep") throws — VS Code's extHost interceptor aliases the require to @vscode/ripgrep-universal, but that package isn't installed on builds that haven't completed the package-rename migration (which includes current stable). The path-probe fallback in the diagnostic test build was what actually resolved ripgrep on that install. Reverts to the prior shape: hardcoded paths covering both the @vscode/ripgrep and @vscode/ripgrep-universal layouts under vscode.env.appRoot. Also drops the @vscode/ripgrep devDep, the esbuild external entry, the loadRipgrep wrapper file, and the tests that mocked it — all dead with the revert. VS Code's package-rename migration will be tracked in a separate issue; once it lands across stable + Insiders the require approach can be revisited with empirical evidence of which mechanism VS Code expects 3rd-party extensions to use. * docs: drop stale PATH reference from getBinPath header Per CodeRabbit's review of the revert commit: the module-level comment still listed "or on the system PATH" as a resolution source, but the PATH probe was removed two commits back. Updated to match the actual behavior (probe paths under vscode.env.appRoot only). * test: cover the asar.unpacked universal layout CodeRabbit nit on the latest commit: add explicit coverage for the node_modules.asar.unpacked/@vscode/ripgrep-universal/<plat>-<arch>/<rg> candidate path. Mirrors the existing universal-layout test but targets the .asar.unpacked variant — the exact staged-install shape that motivated the universal-layout entries in the first place. --------- Co-authored-by: 0xMink <260166390+0xMink@users.noreply.github.com>
1 parent d63e7bd commit 3e07e4c

2 files changed

Lines changed: 65 additions & 4 deletions

File tree

src/services/ripgrep/__tests__/index.spec.ts

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
// npx vitest run src/services/ripgrep/__tests__/index.spec.ts
22

3-
import { truncateLine } from "../index"
3+
import path from "path"
4+
import { vi, describe, it, expect, beforeEach } from "vitest"
5+
6+
import { truncateLine, getBinPath } from "../index"
7+
import { fileExistsAtPath } from "../../../utils/fs"
8+
9+
vi.mock("../../../utils/fs", () => ({
10+
fileExistsAtPath: vi.fn(),
11+
}))
12+
13+
const mockFileExists = vi.mocked(fileExistsAtPath)
414

515
describe("Ripgrep line truncation", () => {
616
// The default MAX_LINE_LENGTH is 500 in the implementation
@@ -48,3 +58,41 @@ describe("Ripgrep line truncation", () => {
4858
expect(truncated).toContain("[truncated...]")
4959
})
5060
})
61+
62+
describe("getBinPath", () => {
63+
const appRoot = "/fake/vscode/appRoot"
64+
const binName = process.platform.startsWith("win") ? "rg.exe" : "rg"
65+
const platformDir = `${process.platform}-${process.arch}`
66+
67+
beforeEach(() => {
68+
mockFileExists.mockReset()
69+
mockFileExists.mockResolvedValue(false)
70+
})
71+
72+
it("resolves ripgrep from the classic @vscode/ripgrep layout", async () => {
73+
const rg = path.join(appRoot, "node_modules/@vscode/ripgrep/bin", binName)
74+
mockFileExists.mockImplementation(async (p: string) => p === rg)
75+
76+
expect(await getBinPath(appRoot)).toBe(rg)
77+
})
78+
79+
it("resolves ripgrep from the @vscode/ripgrep-universal layout (VS Code Insiders)", async () => {
80+
const rg = path.join(appRoot, "node_modules/@vscode/ripgrep-universal/bin", platformDir, binName)
81+
mockFileExists.mockImplementation(async (p: string) => p === rg)
82+
83+
expect(await getBinPath(appRoot)).toBe(rg)
84+
})
85+
86+
it("resolves ripgrep from the unpacked `@vscode/ripgrep-universal` layout", async () => {
87+
const rg = path.join(appRoot, "node_modules.asar.unpacked/@vscode/ripgrep-universal/bin", platformDir, binName)
88+
mockFileExists.mockImplementation(async (p: string) => p === rg)
89+
90+
expect(await getBinPath(appRoot)).toBe(rg)
91+
})
92+
93+
it("returns undefined when ripgrep cannot be found", async () => {
94+
mockFileExists.mockResolvedValue(false)
95+
96+
expect(await getBinPath(appRoot)).toBeUndefined()
97+
})
98+
})

src/services/ripgrep/index.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ This file provides functionality to perform regex searches on files using ripgre
1111
Inspired by: https://github.com/DiscreteTom/vscode-ripgrep-utils
1212
1313
Key components:
14-
1. getBinPath: Locates the ripgrep binary within the VSCode installation.
14+
1. getBinPath: Locates the ripgrep binary inside the VS Code installation.
1515
2. execRipgrep: Executes the ripgrep command and returns the output.
1616
3. regexSearchFiles: The main function that performs regex searches on files.
1717
- Parameters:
@@ -51,6 +51,11 @@ rel/path/to/helper.ts
5151
const isWindows = process.platform.startsWith("win")
5252
const binName = isWindows ? "rg.exe" : "rg"
5353

54+
// VS Code's @vscode/ripgrep-universal package (used by recent VS Code builds,
55+
// including the Insiders staged-install layout) nests the binary under
56+
// bin/<platform>-<arch>/ rather than directly in bin/.
57+
const ripgrepUniversalBinDir = `bin/${process.platform}-${process.arch}`
58+
5459
interface SearchFileResult {
5560
file: string
5661
searchResults: SearchResult[]
@@ -80,7 +85,13 @@ export function truncateLine(line: string, maxLength: number = MAX_LINE_LENGTH):
8085
return line.length > maxLength ? line.substring(0, maxLength) + " [truncated...]" : line
8186
}
8287
/**
83-
* Get the path to the ripgrep binary within the VSCode installation
88+
* Get the path to the ripgrep binary shipped inside the VS Code installation.
89+
*
90+
* Both the long-standing `@vscode/ripgrep` layout and the newer
91+
* `@vscode/ripgrep-universal` layout are checked — the latter is what VS Code
92+
* Insiders' staged-install builds use (see microsoft/vscode#252063).
93+
*
94+
* Returns `undefined` when ripgrep cannot be located.
8495
*/
8596
export async function getBinPath(vscodeAppRoot: string): Promise<string | undefined> {
8697
const checkPath = async (pkgFolder: string) => {
@@ -92,7 +103,9 @@ export async function getBinPath(vscodeAppRoot: string): Promise<string | undefi
92103
(await checkPath("node_modules/@vscode/ripgrep/bin/")) ||
93104
(await checkPath("node_modules/vscode-ripgrep/bin")) ||
94105
(await checkPath("node_modules.asar.unpacked/vscode-ripgrep/bin/")) ||
95-
(await checkPath("node_modules.asar.unpacked/@vscode/ripgrep/bin/"))
106+
(await checkPath("node_modules.asar.unpacked/@vscode/ripgrep/bin/")) ||
107+
(await checkPath(`node_modules/@vscode/ripgrep-universal/${ripgrepUniversalBinDir}`)) ||
108+
(await checkPath(`node_modules.asar.unpacked/@vscode/ripgrep-universal/${ripgrepUniversalBinDir}`))
96109
)
97110
}
98111

0 commit comments

Comments
 (0)