mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
Fix crash when encountering an included directory which doesn't exist (#6497)
Co-authored-by: Gal Zahavi <38544478+galz10@users.noreply.github.com>
This commit is contained in:
0
packages/cli/src/config/config.ts
Normal file → Executable file
0
packages/cli/src/config/config.ts
Normal file → Executable file
@@ -468,7 +468,8 @@ export const SETTINGS_SCHEMA = {
|
||||
category: 'General',
|
||||
requiresRestart: false,
|
||||
default: [] as string[],
|
||||
description: 'Additional directories to include in the workspace context.',
|
||||
description:
|
||||
'Additional directories to include in the workspace context. Missing directories will be skipped with a warning.',
|
||||
showInDialog: false,
|
||||
},
|
||||
loadMemoryFromIncludeDirectories: {
|
||||
|
||||
@@ -27,6 +27,7 @@ vi.mock('node:process', () => {
|
||||
const mockProcess: Partial<NodeJS.Process> = {
|
||||
exit: mockProcessExit,
|
||||
platform: 'sunos',
|
||||
cwd: () => '/fake/dir',
|
||||
} as unknown as NodeJS.Process;
|
||||
return {
|
||||
...mockProcess,
|
||||
|
||||
@@ -48,13 +48,6 @@ describe('WorkspaceContext with real filesystem', () => {
|
||||
expect(directories).toEqual([cwd, otherDir]);
|
||||
});
|
||||
|
||||
it('should reject non-existent directories', () => {
|
||||
const nonExistentDir = path.join(tempDir, 'does-not-exist');
|
||||
expect(() => {
|
||||
new WorkspaceContext(cwd, [nonExistentDir]);
|
||||
}).toThrow('Directory does not exist');
|
||||
});
|
||||
|
||||
it('should handle empty initialization', () => {
|
||||
const workspaceContext = new WorkspaceContext(cwd, []);
|
||||
const directories = workspaceContext.getDirectories();
|
||||
@@ -81,15 +74,6 @@ describe('WorkspaceContext with real filesystem', () => {
|
||||
expect(directories).toEqual([cwd, otherDir]);
|
||||
});
|
||||
|
||||
it('should reject non-existent directories', () => {
|
||||
const nonExistentDir = path.join(tempDir, 'does-not-exist');
|
||||
const workspaceContext = new WorkspaceContext(cwd);
|
||||
|
||||
expect(() => {
|
||||
workspaceContext.addDirectory(nonExistentDir);
|
||||
}).toThrow('Directory does not exist');
|
||||
});
|
||||
|
||||
it('should prevent duplicate directories', () => {
|
||||
const workspaceContext = new WorkspaceContext(cwd);
|
||||
workspaceContext.addDirectory(otherDir);
|
||||
@@ -387,3 +371,52 @@ describe('WorkspaceContext with real filesystem', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('WorkspaceContext with optional directories', () => {
|
||||
let tempDir: string;
|
||||
let cwd: string;
|
||||
let existingDir1: string;
|
||||
let existingDir2: string;
|
||||
let nonExistentDir: string;
|
||||
|
||||
beforeEach(() => {
|
||||
tempDir = fs.realpathSync(
|
||||
fs.mkdtempSync(path.join(os.tmpdir(), 'workspace-context-optional-')),
|
||||
);
|
||||
cwd = path.join(tempDir, 'project');
|
||||
existingDir1 = path.join(tempDir, 'existing-dir-1');
|
||||
existingDir2 = path.join(tempDir, 'existing-dir-2');
|
||||
nonExistentDir = path.join(tempDir, 'non-existent-dir');
|
||||
|
||||
fs.mkdirSync(cwd, { recursive: true });
|
||||
fs.mkdirSync(existingDir1, { recursive: true });
|
||||
fs.mkdirSync(existingDir2, { recursive: true });
|
||||
|
||||
vi.spyOn(console, 'warn').mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should skip a missing optional directory and log a warning', () => {
|
||||
const workspaceContext = new WorkspaceContext(cwd, [
|
||||
nonExistentDir,
|
||||
existingDir1,
|
||||
]);
|
||||
const directories = workspaceContext.getDirectories();
|
||||
expect(directories).toEqual([cwd, existingDir1]);
|
||||
expect(console.warn).toHaveBeenCalledTimes(1);
|
||||
expect(console.warn).toHaveBeenCalledWith(
|
||||
`[WARN] Skipping unreadable directory: ${nonExistentDir} (Directory does not exist: ${nonExistentDir})`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should include an existing optional directory', () => {
|
||||
const workspaceContext = new WorkspaceContext(cwd, [existingDir1]);
|
||||
const directories = workspaceContext.getDirectories();
|
||||
expect(directories).toEqual([cwd, existingDir1]);
|
||||
expect(console.warn).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
18
packages/core/src/utils/workspaceContext.ts
Normal file → Executable file
18
packages/core/src/utils/workspaceContext.ts
Normal file → Executable file
@@ -7,6 +7,7 @@
|
||||
import { isNodeError } from '../utils/errors.js';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as process from 'process';
|
||||
|
||||
export type Unsubscribe = () => void;
|
||||
|
||||
@@ -30,7 +31,6 @@ export class WorkspaceContext {
|
||||
for (const additionalDirectory of additionalDirectories) {
|
||||
this.addDirectory(additionalDirectory);
|
||||
}
|
||||
|
||||
this.initialDirectories = new Set(this.directories);
|
||||
}
|
||||
|
||||
@@ -64,12 +64,18 @@ export class WorkspaceContext {
|
||||
* @param basePath Optional base path for resolving relative paths (defaults to cwd)
|
||||
*/
|
||||
addDirectory(directory: string, basePath: string = process.cwd()): void {
|
||||
const resolved = this.resolveAndValidateDir(directory, basePath);
|
||||
if (this.directories.has(resolved)) {
|
||||
return;
|
||||
try {
|
||||
const resolved = this.resolveAndValidateDir(directory, basePath);
|
||||
if (this.directories.has(resolved)) {
|
||||
return;
|
||||
}
|
||||
this.directories.add(resolved);
|
||||
this.notifyDirectoriesChanged();
|
||||
} catch (err) {
|
||||
console.warn(
|
||||
`[WARN] Skipping unreadable directory: ${directory} (${err instanceof Error ? err.message : String(err)})`,
|
||||
);
|
||||
}
|
||||
this.directories.add(resolved);
|
||||
this.notifyDirectoriesChanged();
|
||||
}
|
||||
|
||||
private resolveAndValidateDir(
|
||||
|
||||
Reference in New Issue
Block a user