Skip to content

Commit 0a57e84

Browse files
committed
refactor(ripgrep): replace createRequire resolver with static candidate paths
1 parent dadc736 commit 0a57e84

2 files changed

Lines changed: 39 additions & 86 deletions

File tree

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

Lines changed: 27 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,7 @@ 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-
2413
const mockFileExists = vi.mocked(fileExistsAtPath)
25-
const mockExistsSync = vi.mocked(fs.existsSync)
26-
const mockCreateRequire = vi.mocked(createRequire)
2714

2815
describe("Ripgrep line truncation", () => {
2916
// The default MAX_LINE_LENGTH is 500 in the implementation
@@ -80,9 +67,6 @@ describe("getBinPath", () => {
8067
beforeEach(() => {
8168
mockFileExists.mockReset()
8269
mockFileExists.mockResolvedValue(false)
83-
mockExistsSync.mockReset()
84-
mockExistsSync.mockReturnValue(false)
85-
mockCreateRequire.mockReset()
8670
})
8771

8872
it("resolves ripgrep from the classic @vscode/ripgrep layout", async () => {
@@ -112,58 +96,43 @@ describe("getBinPath", () => {
11296
expect(await getBinPath(appRoot)).toBeUndefined()
11397
})
11498

115-
it("returns undefined when platform-package resolution fails", async () => {
116-
mockFileExists.mockResolvedValue(false)
117-
mockExistsSync.mockReturnValue(true)
118-
mockCreateRequire.mockReturnValue({
119-
resolve: vi.fn(() => {
120-
throw new Error("module not found")
121-
}),
122-
} as unknown as NodeRequire)
123-
124-
await expect(getBinPath(appRoot)).resolves.toBeUndefined()
125-
})
126-
12799
// Regression test for https://github.com/Zoo-Code-Org/Zoo-Code/issues/1024
128100
// VS Code 1.130+ ships @vscode/ripgrep >=1.18, where the binary lives in a
129-
// platform-specific optional package resolved via the wrapper's package.json.
130-
// None of the six hardcoded candidate paths match this layout, so getBinPath
131-
// returns undefined on affected Windows installs, causing every task to hang.
132-
it("resolves ripgrep via the @vscode/ripgrep >=1.18 platform-package layout", async () => {
133-
const platformBin = `/vscode/ripgrep-${process.platform}-${process.arch}/bin/${binName}`
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)
134107

135-
mockFileExists.mockResolvedValue(false)
136-
mockExistsSync.mockReturnValue(true)
137-
const mockRequireFromWrapper = { resolve: vi.fn().mockReturnValue(platformBin) }
138-
const mockRequireFromApp = { resolve: vi.fn().mockReturnValue("/vscode/ripgrep/index.js") }
139-
mockCreateRequire
140-
.mockReturnValueOnce(mockRequireFromApp as unknown as NodeRequire)
141-
.mockReturnValueOnce(mockRequireFromWrapper as unknown as NodeRequire)
142-
mockFileExists.mockImplementation(async (p: string) => p === platformBin)
143-
144-
expect(await getBinPath(appRoot)).toBe(platformBin)
108+
expect(await getBinPath(appRoot)).toBe(rg)
145109
})
146110

147-
it("respects npm_config_arch override when resolving platform-package", async () => {
148-
const overrideArch = "x64"
149-
const platformBin = `/vscode/ripgrep-${process.platform}-${overrideArch}/bin/${binName}`
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)
150119

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"
151125
const original = process.env.npm_config_arch
152126
process.env.npm_config_arch = overrideArch
153127
try {
154-
mockFileExists.mockResolvedValue(false)
155-
mockExistsSync.mockReturnValue(true)
156-
const mockRequireFromWrapper = { resolve: vi.fn().mockReturnValue(platformBin) }
157-
const mockRequireFromApp = { resolve: vi.fn().mockReturnValue("/vscode/ripgrep/index.js") }
158-
mockCreateRequire
159-
.mockReturnValueOnce(mockRequireFromApp as unknown as NodeRequire)
160-
.mockReturnValueOnce(mockRequireFromWrapper as unknown as NodeRequire)
161-
mockFileExists.mockImplementation(async (p: string) => p === platformBin)
162-
163-
expect(await getBinPath(appRoot)).toBe(platformBin)
164-
expect(mockRequireFromWrapper.resolve).toHaveBeenCalledWith(
165-
`@vscode/ripgrep-${process.platform}-${overrideArch}/bin/${binName}`,
128+
const rg = path.join(
129+
appRoot,
130+
`node_modules/@vscode/ripgrep-${process.platform}-${overrideArch}/bin`,
131+
binName,
166132
)
133+
mockFileExists.mockImplementation(async (p: string) => p === rg)
134+
135+
expect(await getBinPath(appRoot)).toBe(rg)
167136
} finally {
168137
if (original === undefined) {
169138
delete process.env.npm_config_arch

src/services/ripgrep/index.ts

Lines changed: 12 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import * as childProcess from "child_process"
2-
import * as fs from "fs"
32
import * as path from "path"
43
import * as readline from "readline"
5-
import { createRequire } from "module"
64

75
import * as vscode from "vscode"
86

@@ -58,6 +56,12 @@ const binName = isWindows ? "rg.exe" : "rg"
5856
// bin/<platform>-<arch>/ rather than directly in bin/.
5957
const ripgrepUniversalBinDir = `bin/${process.platform}-${process.arch}`
6058

59+
// @vscode/ripgrep >=1.18 ships the binary in a platform-specific optional
60+
// package (e.g. @vscode/ripgrep-win32-x64). Matches the wrapper's own arch
61+
// selection: process.env.npm_config_arch || process.arch.
62+
const platformPkgArch = process.env.npm_config_arch || process.arch
63+
const ripgrepPlatformPkg = `@vscode/ripgrep-${process.platform}-${platformPkgArch}`
64+
6165
interface SearchFileResult {
6266
file: string
6367
searchResults: SearchResult[]
@@ -103,45 +107,25 @@ export function ripgrepCandidatePaths(vscodeAppRoot: string): readonly string[]
103107
`node_modules.asar.unpacked/@vscode/ripgrep-universal/${ripgrepUniversalBinDir}`,
104108
binName,
105109
),
110+
// @vscode/ripgrep >=1.18 (VS Code 1.130+): binary lives in a platform-specific optional package.
111+
path.join(vscodeAppRoot, `node_modules/${ripgrepPlatformPkg}/bin`, binName),
112+
path.join(vscodeAppRoot, `node_modules.asar.unpacked/${ripgrepPlatformPkg}/bin`, binName),
106113
]
107114
}
108115

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-
const arch = process.env.npm_config_arch || process.arch
122-
return requireFromWrapper.resolve(`@vscode/ripgrep-${process.platform}-${arch}/bin/${binName}`)
123-
} catch {
124-
return undefined
125-
}
126-
}
127-
128116
/**
129117
* Get the path to the ripgrep binary shipped inside the VS Code installation.
130118
*
131-
* Checks the long-standing @vscode/ripgrep and @vscode/ripgrep-universal static
132-
* layouts first, then falls back to the @vscode/ripgrep >=1.18 platform-package
133-
* layout used by VS Code 1.130+ (see microsoft/vscode#252063).
119+
* Probes all known layouts: classic @vscode/ripgrep, @vscode/ripgrep-universal
120+
* (VS Code Insiders staged-install), and the @vscode/ripgrep >=1.18
121+
* platform-package layout used by VS Code 1.130+.
134122
*
135123
* Returns `undefined` when ripgrep cannot be located.
136124
*/
137125
export async function getBinPath(vscodeAppRoot: string): Promise<string | undefined> {
138126
for (const candidate of ripgrepCandidatePaths(vscodeAppRoot)) {
139127
if (await fileExistsAtPath(candidate)) return candidate
140128
}
141-
142-
const platformPackagePath = resolvePlatformRipgrepPath(vscodeAppRoot)
143-
if (platformPackagePath && (await fileExistsAtPath(platformPackagePath))) return platformPackagePath
144-
145129
return undefined
146130
}
147131

0 commit comments

Comments
 (0)