Compare commits

...

3 Commits

Author SHA1 Message Date
github-actions[bot]
bbbc4c6342 chore(release): v0.8.0-preview.0 2026-01-21 12:25:07 +00:00
tanzhenxin
21b26a400a Merge pull request #1563 from QwenLM/fix/issue-1549-skip-enoent-import
fix: skip non-existent file imports instead of warning (ENOENT)
2026-01-21 20:11:00 +08:00
LaZzyMan
1562780393 fix: skip non-existent file imports instead of warning (ENOENT) 2026-01-21 14:13:20 +08:00
8 changed files with 110 additions and 19 deletions

12
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "@qwen-code/qwen-code",
"version": "0.8.0",
"version": "0.8.0-preview.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@qwen-code/qwen-code",
"version": "0.8.0",
"version": "0.8.0-preview.0",
"workspaces": [
"packages/*"
],
@@ -17304,7 +17304,7 @@
},
"packages/cli": {
"name": "@qwen-code/qwen-code",
"version": "0.8.0",
"version": "0.8.0-preview.0",
"dependencies": {
"@google/genai": "1.30.0",
"@iarna/toml": "^2.2.5",
@@ -17941,7 +17941,7 @@
},
"packages/core": {
"name": "@qwen-code/qwen-code-core",
"version": "0.8.0",
"version": "0.8.0-preview.0",
"hasInstallScript": true,
"dependencies": {
"@anthropic-ai/sdk": "^0.36.1",
@@ -21400,7 +21400,7 @@
},
"packages/test-utils": {
"name": "@qwen-code/qwen-code-test-utils",
"version": "0.8.0",
"version": "0.8.0-preview.0",
"dev": true,
"license": "Apache-2.0",
"devDependencies": {
@@ -21412,7 +21412,7 @@
},
"packages/vscode-ide-companion": {
"name": "qwen-code-vscode-ide-companion",
"version": "0.8.0",
"version": "0.8.0-preview.0",
"license": "LICENSE",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.25.1",

View File

@@ -1,6 +1,6 @@
{
"name": "@qwen-code/qwen-code",
"version": "0.8.0",
"version": "0.8.0-preview.0",
"engines": {
"node": ">=20.0.0"
},
@@ -13,7 +13,7 @@
"url": "git+https://github.com/QwenLM/qwen-code.git"
},
"config": {
"sandboxImageUri": "ghcr.io/qwenlm/qwen-code:0.8.0"
"sandboxImageUri": "ghcr.io/qwenlm/qwen-code:0.8.0-preview.0"
},
"scripts": {
"start": "cross-env node scripts/start.js",

View File

@@ -1,6 +1,6 @@
{
"name": "@qwen-code/qwen-code",
"version": "0.8.0",
"version": "0.8.0-preview.0",
"description": "Qwen Code",
"repository": {
"type": "git",
@@ -33,7 +33,7 @@
"dist"
],
"config": {
"sandboxImageUri": "ghcr.io/qwenlm/qwen-code:0.8.0"
"sandboxImageUri": "ghcr.io/qwenlm/qwen-code:0.8.0-preview.0"
},
"dependencies": {
"@google/genai": "1.30.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@qwen-code/qwen-code-core",
"version": "0.8.0",
"version": "0.8.0-preview.0",
"description": "Qwen Code Core",
"repository": {
"type": "git",

View File

@@ -92,11 +92,15 @@ const findCodeBlocks = (
describe('memoryImportProcessor', () => {
beforeEach(() => {
vi.clearAllMocks();
vi.resetAllMocks(); // Use resetAllMocks to clear mock implementations
// Mock console methods
console.warn = vi.fn();
console.error = vi.fn();
console.debug = vi.fn();
// Default mock for lstat (used by findProjectRoot)
mockedFs.lstat.mockRejectedValue(
Object.assign(new Error('ENOENT'), { code: 'ENOENT' }),
);
});
afterEach(() => {
@@ -204,20 +208,43 @@ describe('memoryImportProcessor', () => {
);
});
it('should handle file not found errors', async () => {
it('should silently preserve content when file not found (ENOENT)', async () => {
const content = 'Content @./nonexistent.md more content';
const basePath = testPath('test', 'path');
mockedFs.access.mockRejectedValue(new Error('File not found'));
// Mock ENOENT error (file not found)
mockedFs.access.mockRejectedValue(
Object.assign(new Error('ENOENT: no such file or directory'), {
code: 'ENOENT',
}),
);
const result = await processImports(content, basePath, true);
// Content should be preserved as-is when file doesn't exist
expect(result.content).toBe(content);
// No error should be logged for ENOENT
expect(console.error).not.toHaveBeenCalled();
});
it('should log error for non-ENOENT file access errors', async () => {
const content = 'Content @./permission-denied.md more content';
const basePath = testPath('test', 'path');
// Mock a permission denied error (not ENOENT)
mockedFs.access.mockRejectedValue(
Object.assign(new Error('Permission denied'), { code: 'EACCES' }),
);
const result = await processImports(content, basePath, true);
// Should show error comment for non-ENOENT errors
expect(result.content).toContain(
'<!-- Import failed: ./nonexistent.md - File not found -->',
'<!-- Import failed: ./permission-denied.md - Permission denied -->',
);
expect(console.error).toHaveBeenCalledWith(
'[ERROR] [ImportProcessor]',
'Failed to import ./nonexistent.md: File not found',
'Failed to import ./permission-denied.md: Permission denied',
);
});
@@ -448,6 +475,50 @@ describe('memoryImportProcessor', () => {
expect(result.importTree.imports).toBeUndefined();
});
it('should still import valid paths while ignoring non-existent paths', async () => {
const content = '使用 @./valid.md 文件和 @中文路径 注解';
const basePath = testPath('test', 'path');
const importedContent = 'Valid imported content';
// Mock: valid.md exists, 中文路径 doesn't exist
mockedFs.access
.mockResolvedValueOnce(undefined) // ./valid.md exists
.mockRejectedValueOnce(
Object.assign(new Error('ENOENT'), { code: 'ENOENT' }),
); // 中文路径 doesn't exist
mockedFs.readFile.mockResolvedValue(importedContent);
const result = await processImports(content, basePath, true);
// Should import valid.md
expect(result.content).toContain(importedContent);
expect(result.content).toContain('<!-- Imported from: ./valid.md -->');
// The non-existent path should remain as-is
expect(result.content).toContain('@中文路径');
});
it('should import Chinese file names if they exist', async () => {
const content = '导入 @./中文文档.md 文件';
const projectRoot = testPath('test', 'project');
const basePath = testPath(projectRoot, 'src');
const importedContent = '这是中文文档的内容';
mockedFs.access.mockResolvedValue(undefined);
mockedFs.readFile.mockResolvedValue(importedContent);
const result = await processImports(
content,
basePath,
true,
undefined,
projectRoot,
);
// Should successfully import the Chinese-named file
expect(result.content).toContain(importedContent);
expect(result.content).toContain('<!-- Imported from: ./中文文档.md -->');
});
it('should allow imports from parent and subdirectories within project root', async () => {
const content =
'Parent import: @../parent.md Subdir import: @./components/sub.md';

View File

@@ -150,6 +150,18 @@ function isLetter(char: string): boolean {
); // a-z
}
/**
* Checks if an error is a "file not found" error (ENOENT)
*/
function isFileNotFoundError(err: unknown): boolean {
return (
typeof err === 'object' &&
err !== null &&
'code' in err &&
(err as { code: unknown }).code === 'ENOENT'
);
}
function findCodeRegions(content: string): Array<[number, number]> {
const regions: Array<[number, number]> = [];
const tokens = marked.lexer(content);
@@ -292,7 +304,9 @@ export async function processImports(
depth + 1,
);
} catch (error) {
if (debugMode) {
// If file doesn't exist, silently skip this import (it's not a real import)
// Only log warnings for other types of errors
if (!isFileNotFoundError(error) && debugMode) {
logger.warn(
`Failed to import ${fullPath}: ${hasMessage(error) ? error.message : 'Unknown error'}`,
);
@@ -371,6 +385,12 @@ export async function processImports(
result += `<!-- Imported from: ${importPath} -->\n${imported.content}\n<!-- End of import from: ${importPath} -->`;
imports.push(imported.importTree);
} catch (err: unknown) {
// If file doesn't exist, preserve the original @path text (it's not a real import)
if (isFileNotFoundError(err)) {
result += `@${importPath}`;
continue;
}
// For other errors, log and add error comment
let message = 'Unknown error';
if (hasMessage(err)) {
message = err.message;

View File

@@ -1,6 +1,6 @@
{
"name": "@qwen-code/qwen-code-test-utils",
"version": "0.8.0",
"version": "0.8.0-preview.0",
"private": true,
"main": "src/index.ts",
"license": "Apache-2.0",

View File

@@ -2,7 +2,7 @@
"name": "qwen-code-vscode-ide-companion",
"displayName": "Qwen Code Companion",
"description": "Enable Qwen Code with direct access to your VS Code workspace.",
"version": "0.8.0",
"version": "0.8.0-preview.0",
"publisher": "qwenlm",
"icon": "assets/icon.png",
"repository": {