Fix: Improve ripgrep binary detection and cross-platform compatibility (#1060)

This commit is contained in:
tanzhenxin
2025-11-18 19:38:30 +08:00
committed by GitHub
parent f0bbeac04a
commit 71646490f1
5 changed files with 203 additions and 204 deletions

View File

@@ -22,12 +22,12 @@ import type { Config } from '../config/config.js';
import { createMockWorkspaceContext } from '../test-utils/mockWorkspaceContext.js';
import type { ChildProcess } from 'node:child_process';
import { spawn } from 'node:child_process';
import { ensureRipgrepPath } from '../utils/ripgrepUtils.js';
import { getRipgrepCommand } from '../utils/ripgrepUtils.js';
import { DEFAULT_FILE_FILTERING_OPTIONS } from '../config/constants.js';
// Mock ripgrepUtils
vi.mock('../utils/ripgrepUtils.js', () => ({
ensureRipgrepPath: vi.fn(),
getRipgrepCommand: vi.fn(),
}));
// Mock child_process for ripgrep calls
@@ -109,7 +109,7 @@ describe('RipGrepTool', () => {
beforeEach(async () => {
vi.clearAllMocks();
(ensureRipgrepPath as Mock).mockResolvedValue('/mock/path/to/rg');
(getRipgrepCommand as Mock).mockResolvedValue('/mock/path/to/rg');
mockSpawn.mockReset();
tempRootDir = await fs.mkdtemp(path.join(os.tmpdir(), 'grep-tool-root-'));
fileExclusionsMock = {
@@ -588,18 +588,15 @@ describe('RipGrepTool', () => {
});
it('should throw an error if ripgrep is not available', async () => {
// Make ensureRipgrepBinary throw
(ensureRipgrepPath as Mock).mockRejectedValue(
new Error('Ripgrep binary not found'),
);
(getRipgrepCommand as Mock).mockResolvedValue(null);
const params: RipGrepToolParams = { pattern: 'world' };
const invocation = grepTool.build(params);
expect(await invocation.execute(abortSignal)).toStrictEqual({
llmContent:
'Error during grep search operation: Ripgrep binary not found',
returnDisplay: 'Error: Ripgrep binary not found',
'Error during grep search operation: ripgrep binary not found.',
returnDisplay: 'Error: ripgrep binary not found.',
});
});
});

View File

@@ -6,7 +6,6 @@
import fs from 'node:fs';
import path from 'node:path';
import { EOL } from 'node:os';
import { spawn } from 'node:child_process';
import type { ToolInvocation, ToolResult } from './tools.js';
import { BaseDeclarativeTool, BaseToolInvocation, Kind } from './tools.js';
@@ -14,7 +13,7 @@ import { ToolNames } from './tool-names.js';
import { resolveAndValidatePath } from '../utils/paths.js';
import { getErrorMessage } from '../utils/errors.js';
import type { Config } from '../config/config.js';
import { ensureRipgrepPath } from '../utils/ripgrepUtils.js';
import { getRipgrepCommand } from '../utils/ripgrepUtils.js';
import { SchemaValidator } from '../utils/schemaValidator.js';
import type { FileFilteringOptions } from '../config/constants.js';
import { DEFAULT_FILE_FILTERING_OPTIONS } from '../config/constants.js';
@@ -88,7 +87,7 @@ class GrepToolInvocation extends BaseToolInvocation<
}
// Split into lines and count total matches
const allLines = rawOutput.split(EOL).filter((line) => line.trim());
const allLines = rawOutput.split('\n').filter((line) => line.trim());
const totalMatches = allLines.length;
const matchTerm = totalMatches === 1 ? 'match' : 'matches';
@@ -159,7 +158,7 @@ class GrepToolInvocation extends BaseToolInvocation<
returnDisplay: displayMessage,
};
} catch (error) {
console.error(`Error during GrepLogic execution: ${error}`);
console.error(`Error during ripgrep search operation: ${error}`);
const errorMessage = getErrorMessage(error);
return {
llmContent: `Error during grep search operation: ${errorMessage}`,
@@ -210,11 +209,15 @@ class GrepToolInvocation extends BaseToolInvocation<
rgArgs.push(absolutePath);
try {
const rgPath = this.config.getUseBuiltinRipgrep()
? await ensureRipgrepPath()
: 'rg';
const rgCommand = await getRipgrepCommand(
this.config.getUseBuiltinRipgrep(),
);
if (!rgCommand) {
throw new Error('ripgrep binary not found.');
}
const output = await new Promise<string>((resolve, reject) => {
const child = spawn(rgPath, rgArgs, {
const child = spawn(rgCommand, rgArgs, {
windowsHide: true,
});
@@ -234,7 +237,7 @@ class GrepToolInvocation extends BaseToolInvocation<
child.on('error', (err) => {
options.signal.removeEventListener('abort', cleanup);
reject(new Error(`Failed to start ripgrep: ${err.message}.`));
reject(new Error(`failed to start ripgrep: ${err.message}.`));
});
child.on('close', (code) => {
@@ -256,7 +259,7 @@ class GrepToolInvocation extends BaseToolInvocation<
return output;
} catch (error: unknown) {
console.error(`GrepLogic: ripgrep failed: ${getErrorMessage(error)}`);
console.error(`Ripgrep failed: ${getErrorMessage(error)}`);
throw error;
}
}