Replace spawn with execFile for memory-safe command execution (#1068)

This commit is contained in:
tanzhenxin
2025-11-20 15:04:00 +08:00
committed by GitHub
parent a15b84e2a1
commit 442a9aed58
20 changed files with 620 additions and 969 deletions

View File

@@ -19,10 +19,10 @@ import * as path from 'node:path';
import * as fs from 'node:fs/promises';
import * as os from 'node:os';
import { getProjectHash, QWEN_DIR } from '../utils/paths.js';
import { spawnAsync } from '../utils/shell-utils.js';
import { isCommandAvailable } from '../utils/shell-utils.js';
vi.mock('../utils/shell-utils.js', () => ({
spawnAsync: vi.fn(),
isCommandAvailable: vi.fn(),
}));
const hoistedMockEnv = vi.hoisted(() => vi.fn());
@@ -76,10 +76,7 @@ describe('GitService', () => {
vi.clearAllMocks();
hoistedIsGitRepositoryMock.mockReturnValue(true);
(spawnAsync as Mock).mockResolvedValue({
stdout: 'git version 2.0.0',
stderr: '',
});
(isCommandAvailable as Mock).mockReturnValue({ available: true });
hoistedMockHomedir.mockReturnValue(homedir);
@@ -119,23 +116,9 @@ describe('GitService', () => {
});
});
describe('verifyGitAvailability', () => {
it('should resolve true if git --version command succeeds', async () => {
const service = new GitService(projectRoot, storage);
await expect(service.verifyGitAvailability()).resolves.toBe(true);
expect(spawnAsync).toHaveBeenCalledWith('git', ['--version']);
});
it('should resolve false if git --version command fails', async () => {
(spawnAsync as Mock).mockRejectedValue(new Error('git not found'));
const service = new GitService(projectRoot, storage);
await expect(service.verifyGitAvailability()).resolves.toBe(false);
});
});
describe('initialize', () => {
it('should throw an error if Git is not available', async () => {
(spawnAsync as Mock).mockRejectedValue(new Error('git not found'));
(isCommandAvailable as Mock).mockReturnValue({ available: false });
const service = new GitService(projectRoot, storage);
await expect(service.initialize()).rejects.toThrow(
'Checkpointing is enabled, but Git is not installed. Please install Git or disable checkpointing to continue.',

View File

@@ -6,7 +6,7 @@
import * as fs from 'node:fs/promises';
import * as path from 'node:path';
import { spawnAsync } from '../utils/shell-utils.js';
import { isCommandAvailable } from '../utils/shell-utils.js';
import type { SimpleGit } from 'simple-git';
import { simpleGit, CheckRepoActions } from 'simple-git';
import type { Storage } from '../config/storage.js';
@@ -26,7 +26,7 @@ export class GitService {
}
async initialize(): Promise<void> {
const gitAvailable = await this.verifyGitAvailability();
const { available: gitAvailable } = isCommandAvailable('git');
if (!gitAvailable) {
throw new Error(
'Checkpointing is enabled, but Git is not installed. Please install Git or disable checkpointing to continue.',
@@ -41,15 +41,6 @@ export class GitService {
}
}
async verifyGitAvailability(): Promise<boolean> {
try {
await spawnAsync('git', ['--version']);
return true;
} catch (_error) {
return false;
}
}
/**
* Creates a hidden git repository in the project root.
* The Git repository is used to support checkpointing.