Skip to content

Commit d27153a

Browse files
fix(ripgrep): support @vscode/ripgrep >=1.18 platform-package layout (Zoo-Code-Org#1024) (Zoo-Code-Org#1032)
* fix(ripgrep): resolve platform-specific binary for @vscode/ripgrep >=1.18 * fix(environment): gracefully handle ripgrep not found during file listing * fix(ripgrep): use npm_config_arch for platform-package resolution * refactor(ripgrep): replace createRequire resolver with static candidate paths * fix(ripgrep): evaluate npm_config_arch at call time in candidate paths --------- Co-authored-by: Naved Merchant <naved.merchant@gmail.com>
1 parent 782e28e commit d27153a

4 files changed

Lines changed: 89 additions & 15 deletions

File tree

src/core/environment/__tests__/getEnvironmentDetails.spec.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,4 +446,22 @@ describe("getEnvironmentDetails", () => {
446446

447447
expect(getGitStatus).toHaveBeenCalledWith(mockCwd, 5)
448448
})
449+
450+
// Regression test for https://github.com/Zoo-Code-Org/Zoo-Code/issues/1024
451+
// When ripgrep cannot be found (e.g. @vscode/ripgrep >=1.18 platform layout on
452+
// Windows), listFiles throws "Could not find ripgrep binary". getEnvironmentDetails
453+
// must not propagate this — the task should proceed to the API call, not hang at 0%.
454+
it("should degrade gracefully when listFiles rejects with an Error", async () => {
455+
;(listFiles as Mock).mockRejectedValue(new Error("Could not find ripgrep binary"))
456+
457+
const result = await getEnvironmentDetails(mockCline as Task, true)
458+
expect(result).toContain("File listing unavailable: Could not find ripgrep binary")
459+
})
460+
461+
it("should degrade gracefully when listFiles rejects with a non-Error value", async () => {
462+
;(listFiles as Mock).mockRejectedValue("unexpected string rejection")
463+
464+
const result = await getEnvironmentDetails(mockCline as Task, true)
465+
expect(result).toContain("File listing unavailable: unexpected string rejection")
466+
})
449467
})

src/core/environment/getEnvironmentDetails.ts

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -241,18 +241,22 @@ export async function getEnvironmentDetails(cline: Task, includeFileDetails: boo
241241
if (maxFiles === 0) {
242242
details += "(Workspace files context disabled. Use list_files to explore if needed.)"
243243
} else {
244-
const [files, didHitLimit] = await listFiles(cline.cwd, true, maxFiles)
245-
const { showRooIgnoredFiles = false } = state ?? {}
246-
247-
const result = formatResponse.formatFilesList(
248-
cline.cwd,
249-
files,
250-
didHitLimit,
251-
cline.rooIgnoreController,
252-
showRooIgnoredFiles,
253-
)
254-
255-
details += result
244+
try {
245+
const [files, didHitLimit] = await listFiles(cline.cwd, true, maxFiles)
246+
const { showRooIgnoredFiles = false } = state ?? {}
247+
248+
const result = formatResponse.formatFilesList(
249+
cline.cwd,
250+
files,
251+
didHitLimit,
252+
cline.rooIgnoreController,
253+
showRooIgnoredFiles,
254+
)
255+
256+
details += result
257+
} catch (error) {
258+
details += `(File listing unavailable: ${error instanceof Error ? error.message : String(error)})`
259+
}
256260
}
257261
}
258262
}

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

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,4 +95,50 @@ describe("getBinPath", () => {
9595

9696
expect(await getBinPath(appRoot)).toBeUndefined()
9797
})
98+
99+
// Regression test for https://github.com/Zoo-Code-Org/Zoo-Code/issues/1024
100+
// VS Code 1.130+ ships @vscode/ripgrep >=1.18, where the binary lives in a
101+
// platform-specific optional package (e.g. @vscode/ripgrep-win32-x64).
102+
// None of the previous candidate paths matched this layout.
103+
it("resolves ripgrep from the @vscode/ripgrep >=1.18 platform-package layout", async () => {
104+
const arch = process.env.npm_config_arch || process.arch
105+
const rg = path.join(appRoot, `node_modules/@vscode/ripgrep-${process.platform}-${arch}/bin`, binName)
106+
mockFileExists.mockImplementation(async (p: string) => p === rg)
107+
108+
expect(await getBinPath(appRoot)).toBe(rg)
109+
})
110+
111+
it("resolves ripgrep from the unpacked @vscode/ripgrep >=1.18 platform-package layout", async () => {
112+
const arch = process.env.npm_config_arch || process.arch
113+
const rg = path.join(
114+
appRoot,
115+
`node_modules.asar.unpacked/@vscode/ripgrep-${process.platform}-${arch}/bin`,
116+
binName,
117+
)
118+
mockFileExists.mockImplementation(async (p: string) => p === rg)
119+
120+
expect(await getBinPath(appRoot)).toBe(rg)
121+
})
122+
123+
it("respects npm_config_arch when selecting the platform package", async () => {
124+
const overrideArch = "x64"
125+
const original = process.env.npm_config_arch
126+
process.env.npm_config_arch = overrideArch
127+
try {
128+
const rg = path.join(
129+
appRoot,
130+
`node_modules/@vscode/ripgrep-${process.platform}-${overrideArch}/bin`,
131+
binName,
132+
)
133+
mockFileExists.mockImplementation(async (p: string) => p === rg)
134+
135+
expect(await getBinPath(appRoot)).toBe(rg)
136+
} finally {
137+
if (original === undefined) {
138+
delete process.env.npm_config_arch
139+
} else {
140+
process.env.npm_config_arch = original
141+
}
142+
}
143+
})
98144
})

src/services/ripgrep/index.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ export function truncateLine(line: string, maxLength: number = MAX_LINE_LENGTH):
9090
* resolution) and the diagnostic command (existence report for all paths).
9191
*/
9292
export function ripgrepCandidatePaths(vscodeAppRoot: string): readonly string[] {
93+
// Read at call time so process.env.npm_config_arch overrides take effect,
94+
// matching @vscode/ripgrep's own arch selection logic.
95+
const platformPkg = `@vscode/ripgrep-${process.platform}-${process.env.npm_config_arch || process.arch}`
9396
return [
9497
path.join(vscodeAppRoot, "node_modules/@vscode/ripgrep/bin/", binName),
9598
path.join(vscodeAppRoot, "node_modules/vscode-ripgrep/bin", binName),
@@ -101,15 +104,18 @@ export function ripgrepCandidatePaths(vscodeAppRoot: string): readonly string[]
101104
`node_modules.asar.unpacked/@vscode/ripgrep-universal/${ripgrepUniversalBinDir}`,
102105
binName,
103106
),
107+
// @vscode/ripgrep >=1.18 (VS Code 1.130+): binary lives in a platform-specific optional package.
108+
path.join(vscodeAppRoot, `node_modules/${platformPkg}/bin`, binName),
109+
path.join(vscodeAppRoot, `node_modules.asar.unpacked/${platformPkg}/bin`, binName),
104110
]
105111
}
106112

107113
/**
108114
* Get the path to the ripgrep binary shipped inside the VS Code installation.
109115
*
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).
116+
* Probes all known layouts: classic @vscode/ripgrep, @vscode/ripgrep-universal
117+
* (VS Code Insiders staged-install), and the @vscode/ripgrep >=1.18
118+
* platform-package layout used by VS Code 1.130+.
113119
*
114120
* Returns `undefined` when ripgrep cannot be located.
115121
*/

0 commit comments

Comments
 (0)