Skip to content

Commit 865951c

Browse files
committed
fix(ripgrep): resolve platform-specific binary for @vscode/ripgrep >=1.18
1 parent 2db2af0 commit 865951c

2 files changed

Lines changed: 68 additions & 3 deletions

File tree

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

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,20 @@ vi.mock("../../../utils/fs", () => ({
1010
fileExistsAtPath: vi.fn(),
1111
}))
1212

13+
vi.mock("fs", () => ({
14+
existsSync: vi.fn(),
15+
}))
16+
17+
vi.mock("module", () => ({
18+
createRequire: vi.fn(),
19+
}))
20+
21+
import * as fs from "fs"
22+
import { createRequire } from "module"
23+
1324
const mockFileExists = vi.mocked(fileExistsAtPath)
25+
const mockExistsSync = vi.mocked(fs.existsSync)
26+
const mockCreateRequire = vi.mocked(createRequire)
1427

1528
describe("Ripgrep line truncation", () => {
1629
// The default MAX_LINE_LENGTH is 500 in the implementation
@@ -67,6 +80,9 @@ describe("getBinPath", () => {
6780
beforeEach(() => {
6881
mockFileExists.mockReset()
6982
mockFileExists.mockResolvedValue(false)
83+
mockExistsSync.mockReset()
84+
mockExistsSync.mockReturnValue(false)
85+
mockCreateRequire.mockReset()
7086
})
7187

7288
it("resolves ripgrep from the classic @vscode/ripgrep layout", async () => {
@@ -95,4 +111,29 @@ describe("getBinPath", () => {
95111

96112
expect(await getBinPath(appRoot)).toBeUndefined()
97113
})
114+
115+
// Regression test for https://github.com/Zoo-Code-Org/Zoo-Code/issues/1024
116+
// VS Code 1.130+ ships @vscode/ripgrep >=1.18, where the binary lives in a
117+
// platform-specific optional package resolved via the wrapper's package.json.
118+
// None of the six hardcoded candidate paths match this layout, so getBinPath
119+
// returns undefined on affected Windows installs, causing every task to hang.
120+
it("resolves ripgrep via the @vscode/ripgrep >=1.18 platform-package layout", async () => {
121+
const platformBin = `/vscode/ripgrep-${process.platform}-${process.arch}/bin/${binName}`
122+
123+
// All static candidates miss.
124+
mockFileExists.mockResolvedValue(false)
125+
126+
// Simulate @vscode/ripgrep wrapper manifest existing and resolving to the platform bin.
127+
mockExistsSync.mockReturnValue(true)
128+
const mockRequireFromWrapper = { resolve: vi.fn().mockReturnValue(platformBin) }
129+
const mockRequireFromApp = { resolve: vi.fn().mockReturnValue("/vscode/ripgrep/index.js") }
130+
mockCreateRequire
131+
.mockReturnValueOnce(mockRequireFromApp as unknown as NodeRequire)
132+
.mockReturnValueOnce(mockRequireFromWrapper as unknown as NodeRequire)
133+
134+
// The resolved platform bin exists on disk.
135+
mockFileExists.mockImplementation(async (p: string) => p === platformBin)
136+
137+
expect(await getBinPath(appRoot)).toBe(platformBin)
138+
})
98139
})

src/services/ripgrep/index.ts

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import * as childProcess from "child_process"
2+
import * as fs from "fs"
23
import * as path from "path"
34
import * as readline from "readline"
5+
import { createRequire } from "module"
46

57
import * as vscode from "vscode"
68

@@ -104,19 +106,41 @@ export function ripgrepCandidatePaths(vscodeAppRoot: string): readonly string[]
104106
]
105107
}
106108

109+
/**
110+
* Resolves ripgrep for @vscode/ripgrep >=1.18, which ships the binary inside a
111+
* platform-specific optional package (e.g. @vscode/ripgrep-win32-x64) rather
112+
* than directly in @vscode/ripgrep/bin/. VS Code 1.130+ uses this layout.
113+
*/
114+
export function resolvePlatformRipgrepPath(vscodeAppRoot: string): string | undefined {
115+
try {
116+
const wrapperManifest = path.join(vscodeAppRoot, "node_modules", "@vscode", "ripgrep", "package.json")
117+
if (!fs.existsSync(wrapperManifest)) return undefined
118+
const requireFromApp = createRequire(path.join(vscodeAppRoot, "package.json"))
119+
const wrapperEntry = requireFromApp.resolve("@vscode/ripgrep")
120+
const requireFromWrapper = createRequire(wrapperEntry)
121+
return requireFromWrapper.resolve(`@vscode/ripgrep-${process.platform}-${process.arch}/bin/${binName}`)
122+
} catch {
123+
return undefined
124+
}
125+
}
126+
107127
/**
108128
* Get the path to the ripgrep binary shipped inside the VS Code installation.
109129
*
110-
* Both the long-standing `@vscode/ripgrep` layout and the newer
111-
* `@vscode/ripgrep-universal` layout are checked — the latter is what VS Code
112-
* Insiders' staged-install builds use (see microsoft/vscode#252063).
130+
* Checks the long-standing @vscode/ripgrep and @vscode/ripgrep-universal static
131+
* layouts first, then falls back to the @vscode/ripgrep >=1.18 platform-package
132+
* layout used by VS Code 1.130+ (see microsoft/vscode#252063).
113133
*
114134
* Returns `undefined` when ripgrep cannot be located.
115135
*/
116136
export async function getBinPath(vscodeAppRoot: string): Promise<string | undefined> {
117137
for (const candidate of ripgrepCandidatePaths(vscodeAppRoot)) {
118138
if (await fileExistsAtPath(candidate)) return candidate
119139
}
140+
141+
const platformPackagePath = resolvePlatformRipgrepPath(vscodeAppRoot)
142+
if (platformPackagePath && (await fileExistsAtPath(platformPackagePath))) return platformPackagePath
143+
120144
return undefined
121145
}
122146

0 commit comments

Comments
 (0)