qat-cli 0.3.4 → 0.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cli.js +366 -222
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +165 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +41 -1
- package/dist/index.d.ts +41 -1
- package/dist/index.js +165 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -32,6 +32,11 @@ interface SourceAnalysisSummary {
|
|
|
32
32
|
methods?: string[];
|
|
33
33
|
/** computed 属性 */
|
|
34
34
|
computed?: string[];
|
|
35
|
+
/** import 依赖的一跳签名(仅项目内依赖) */
|
|
36
|
+
importSignatures?: Array<{
|
|
37
|
+
source: string;
|
|
38
|
+
signatures: Record<string, string>;
|
|
39
|
+
}>;
|
|
35
40
|
}
|
|
36
41
|
/** AI 生成测试请求 */
|
|
37
42
|
interface AIGenerateTestRequest {
|
|
@@ -743,6 +748,9 @@ declare class OpenAICompatibleProvider implements AIProvider {
|
|
|
743
748
|
/**
|
|
744
749
|
* 压缩源码:保留签名和关键逻辑,剔除注释、空行、样式块
|
|
745
750
|
* 目标:在保留准确性的前提下减少 token 消耗
|
|
751
|
+
* @param code 源码内容
|
|
752
|
+
* @param maxLength 最大长度
|
|
753
|
+
* @param importPathRewrites import 路径重写映射(原路径→正确路径)
|
|
746
754
|
*/
|
|
747
755
|
private compressSourceCode;
|
|
748
756
|
private buildGenerateTestSystemPrompt;
|
|
@@ -751,6 +759,16 @@ declare class OpenAICompatibleProvider implements AIProvider {
|
|
|
751
759
|
* 根据测试类型和源文件路径,计算从测试文件到源文件的正确相对导入路径
|
|
752
760
|
*/
|
|
753
761
|
private computeTestImportPath;
|
|
762
|
+
/**
|
|
763
|
+
* 构建 import 路径重写映射
|
|
764
|
+
* 将源码中可能引用自身的各种写法,映射到测试文件中正确的导入路径
|
|
765
|
+
*/
|
|
766
|
+
private buildImportPathRewrites;
|
|
767
|
+
/**
|
|
768
|
+
* 重写源码中的 import 路径
|
|
769
|
+
* 将 from 'oldPath' / from "oldPath" 替换为正确的路径
|
|
770
|
+
*/
|
|
771
|
+
private rewriteImportPaths;
|
|
754
772
|
private parseGenerateTestResponse;
|
|
755
773
|
private buildReviewTestSystemPrompt;
|
|
756
774
|
private buildReviewTestUserPrompt;
|
|
@@ -966,12 +984,34 @@ interface APICallInfo {
|
|
|
966
984
|
/** 来源文件 */
|
|
967
985
|
sourceFile: string;
|
|
968
986
|
}
|
|
987
|
+
/** import 语句信息 */
|
|
988
|
+
interface ImportInfo {
|
|
989
|
+
/** 导入的名称列表 */
|
|
990
|
+
names: string[];
|
|
991
|
+
/** 来源模块路径 */
|
|
992
|
+
source: string;
|
|
993
|
+
/** 是否是默认导入 */
|
|
994
|
+
isDefault: boolean;
|
|
995
|
+
/** 是否是命名空间导入 (import * as) */
|
|
996
|
+
isNamespace: boolean;
|
|
997
|
+
}
|
|
998
|
+
/** import 依赖的一跳签名摘要 */
|
|
999
|
+
interface ImportSignature {
|
|
1000
|
+
/** 来源模块路径 */
|
|
1001
|
+
source: string;
|
|
1002
|
+
/** 导入名称 → 签名(类型/参数等) */
|
|
1003
|
+
signatures: Record<string, string>;
|
|
1004
|
+
}
|
|
969
1005
|
/** TS/JS 文件分析结果 */
|
|
970
1006
|
interface ModuleAnalysis {
|
|
971
1007
|
/** 文件路径 */
|
|
972
1008
|
filePath: string;
|
|
973
1009
|
/** 导出项列表 */
|
|
974
1010
|
exports: ExportInfo[];
|
|
1011
|
+
/** import 语句列表 */
|
|
1012
|
+
imports: ImportInfo[];
|
|
1013
|
+
/** import 依赖的一跳签名摘要 */
|
|
1014
|
+
importSignatures: ImportSignature[];
|
|
975
1015
|
/** Vue 组件分析(仅 .vue 文件) */
|
|
976
1016
|
vueAnalysis?: VueComponentAnalysis;
|
|
977
1017
|
/** 发现的 API 调用 */
|
|
@@ -1004,4 +1044,4 @@ interface MockRouteCandidate {
|
|
|
1004
1044
|
sourceFile: string;
|
|
1005
1045
|
}
|
|
1006
1046
|
|
|
1007
|
-
export { type AIAnalyzeResultRequest, type AIAnalyzeResultResponse, type AICapability, type AIConfig, type AIGenerateTestRequest, type AIGenerateTestResponse, type AIPresetProvider, type AIProvider, type AIProviderConstructor, AI_PRESET_PROVIDERS, type APICallInfo, type ComponentTestSetup, type CoverageResult, type CreateOptions, DEFAULT_CONFIG, type DetectContext, type DiffResult, type EmitInfo, type ExportInfo, type ExportKind, type FrameworkDefinition, type FrameworkDetectResult, type FrameworkType, type GlobalOptions, type InitOptions, type LighthouseRunnerOptions, type LoadExternalFrameworksResult, type MockOptions, type MockRoute, type MockRouteCandidate, type MockServerState, type ModuleAnalysis, type MonorepoType, NoopAIProvider, OpenAICompatibleProvider, type PerformanceMetrics, type PlaywrightRunnerOptions, type ProjectInfo, type PropInfo, type QATConfig, type ReportData, type ReportOptions, type RunOptions, type SetupOptions, type SourceAnalysisSummary, type TemplateContext, type TestCaseResult, type TestError, type TestRunResult, type TestStatus, type TestSuiteResult, type TestType, type UILibrary, type VisualOptions, type VitestRunnerOptions, type VueComponentAnalysis, aggregateResults, analyzeFile, analyzeFiles, cleanBaselines, cleanDiffs, clearConfigCache, compareDirectories, compareImages, createBaseline, createDefaultRoutes, defineConfig, detectFramework, detectMonorepo, detectProject, detectUILibrary, discoverAppDirs, discoverUtilityFiles, discoverVueComponents, generateConfigFile, generateHTMLReport, generateMockRouteTemplate, generateMockRoutesFromAPICalls, generateTestFile, getAIProvider, getMockServerState, getRegisteredFrameworks, getRegisteredProviders, initMockRoutesDir, isAIAvailable, listBaselines, listDiffs, loadConfig, loadExternalFrameworks, loadMockRoutes, registerAIProvider, registerFramework, registerTemplate, renderTemplate, resetAIProvider, resetRegistry, runLighthouse, runPlaywright, runVitest, scanAPICalls, startMockServer, stopMockServer, testAIConnection, updateAllBaselines, updateBaseline, validateConfig, writeConfigFile, writeReportToDisk };
|
|
1047
|
+
export { type AIAnalyzeResultRequest, type AIAnalyzeResultResponse, type AICapability, type AIConfig, type AIGenerateTestRequest, type AIGenerateTestResponse, type AIPresetProvider, type AIProvider, type AIProviderConstructor, AI_PRESET_PROVIDERS, type APICallInfo, type ComponentTestSetup, type CoverageResult, type CreateOptions, DEFAULT_CONFIG, type DetectContext, type DiffResult, type EmitInfo, type ExportInfo, type ExportKind, type FrameworkDefinition, type FrameworkDetectResult, type FrameworkType, type GlobalOptions, type ImportInfo, type ImportSignature, type InitOptions, type LighthouseRunnerOptions, type LoadExternalFrameworksResult, type MockOptions, type MockRoute, type MockRouteCandidate, type MockServerState, type ModuleAnalysis, type MonorepoType, NoopAIProvider, OpenAICompatibleProvider, type PerformanceMetrics, type PlaywrightRunnerOptions, type ProjectInfo, type PropInfo, type QATConfig, type ReportData, type ReportOptions, type RunOptions, type SetupOptions, type SourceAnalysisSummary, type TemplateContext, type TestCaseResult, type TestError, type TestRunResult, type TestStatus, type TestSuiteResult, type TestType, type UILibrary, type VisualOptions, type VitestRunnerOptions, type VueComponentAnalysis, aggregateResults, analyzeFile, analyzeFiles, cleanBaselines, cleanDiffs, clearConfigCache, compareDirectories, compareImages, createBaseline, createDefaultRoutes, defineConfig, detectFramework, detectMonorepo, detectProject, detectUILibrary, discoverAppDirs, discoverUtilityFiles, discoverVueComponents, generateConfigFile, generateHTMLReport, generateMockRouteTemplate, generateMockRoutesFromAPICalls, generateTestFile, getAIProvider, getMockServerState, getRegisteredFrameworks, getRegisteredProviders, initMockRoutesDir, isAIAvailable, listBaselines, listDiffs, loadConfig, loadExternalFrameworks, loadMockRoutes, registerAIProvider, registerFramework, registerTemplate, renderTemplate, resetAIProvider, resetRegistry, runLighthouse, runPlaywright, runVitest, scanAPICalls, startMockServer, stopMockServer, testAIConnection, updateAllBaselines, updateBaseline, validateConfig, writeConfigFile, writeReportToDisk };
|
package/dist/index.d.ts
CHANGED
|
@@ -32,6 +32,11 @@ interface SourceAnalysisSummary {
|
|
|
32
32
|
methods?: string[];
|
|
33
33
|
/** computed 属性 */
|
|
34
34
|
computed?: string[];
|
|
35
|
+
/** import 依赖的一跳签名(仅项目内依赖) */
|
|
36
|
+
importSignatures?: Array<{
|
|
37
|
+
source: string;
|
|
38
|
+
signatures: Record<string, string>;
|
|
39
|
+
}>;
|
|
35
40
|
}
|
|
36
41
|
/** AI 生成测试请求 */
|
|
37
42
|
interface AIGenerateTestRequest {
|
|
@@ -743,6 +748,9 @@ declare class OpenAICompatibleProvider implements AIProvider {
|
|
|
743
748
|
/**
|
|
744
749
|
* 压缩源码:保留签名和关键逻辑,剔除注释、空行、样式块
|
|
745
750
|
* 目标:在保留准确性的前提下减少 token 消耗
|
|
751
|
+
* @param code 源码内容
|
|
752
|
+
* @param maxLength 最大长度
|
|
753
|
+
* @param importPathRewrites import 路径重写映射(原路径→正确路径)
|
|
746
754
|
*/
|
|
747
755
|
private compressSourceCode;
|
|
748
756
|
private buildGenerateTestSystemPrompt;
|
|
@@ -751,6 +759,16 @@ declare class OpenAICompatibleProvider implements AIProvider {
|
|
|
751
759
|
* 根据测试类型和源文件路径,计算从测试文件到源文件的正确相对导入路径
|
|
752
760
|
*/
|
|
753
761
|
private computeTestImportPath;
|
|
762
|
+
/**
|
|
763
|
+
* 构建 import 路径重写映射
|
|
764
|
+
* 将源码中可能引用自身的各种写法,映射到测试文件中正确的导入路径
|
|
765
|
+
*/
|
|
766
|
+
private buildImportPathRewrites;
|
|
767
|
+
/**
|
|
768
|
+
* 重写源码中的 import 路径
|
|
769
|
+
* 将 from 'oldPath' / from "oldPath" 替换为正确的路径
|
|
770
|
+
*/
|
|
771
|
+
private rewriteImportPaths;
|
|
754
772
|
private parseGenerateTestResponse;
|
|
755
773
|
private buildReviewTestSystemPrompt;
|
|
756
774
|
private buildReviewTestUserPrompt;
|
|
@@ -966,12 +984,34 @@ interface APICallInfo {
|
|
|
966
984
|
/** 来源文件 */
|
|
967
985
|
sourceFile: string;
|
|
968
986
|
}
|
|
987
|
+
/** import 语句信息 */
|
|
988
|
+
interface ImportInfo {
|
|
989
|
+
/** 导入的名称列表 */
|
|
990
|
+
names: string[];
|
|
991
|
+
/** 来源模块路径 */
|
|
992
|
+
source: string;
|
|
993
|
+
/** 是否是默认导入 */
|
|
994
|
+
isDefault: boolean;
|
|
995
|
+
/** 是否是命名空间导入 (import * as) */
|
|
996
|
+
isNamespace: boolean;
|
|
997
|
+
}
|
|
998
|
+
/** import 依赖的一跳签名摘要 */
|
|
999
|
+
interface ImportSignature {
|
|
1000
|
+
/** 来源模块路径 */
|
|
1001
|
+
source: string;
|
|
1002
|
+
/** 导入名称 → 签名(类型/参数等) */
|
|
1003
|
+
signatures: Record<string, string>;
|
|
1004
|
+
}
|
|
969
1005
|
/** TS/JS 文件分析结果 */
|
|
970
1006
|
interface ModuleAnalysis {
|
|
971
1007
|
/** 文件路径 */
|
|
972
1008
|
filePath: string;
|
|
973
1009
|
/** 导出项列表 */
|
|
974
1010
|
exports: ExportInfo[];
|
|
1011
|
+
/** import 语句列表 */
|
|
1012
|
+
imports: ImportInfo[];
|
|
1013
|
+
/** import 依赖的一跳签名摘要 */
|
|
1014
|
+
importSignatures: ImportSignature[];
|
|
975
1015
|
/** Vue 组件分析(仅 .vue 文件) */
|
|
976
1016
|
vueAnalysis?: VueComponentAnalysis;
|
|
977
1017
|
/** 发现的 API 调用 */
|
|
@@ -1004,4 +1044,4 @@ interface MockRouteCandidate {
|
|
|
1004
1044
|
sourceFile: string;
|
|
1005
1045
|
}
|
|
1006
1046
|
|
|
1007
|
-
export { type AIAnalyzeResultRequest, type AIAnalyzeResultResponse, type AICapability, type AIConfig, type AIGenerateTestRequest, type AIGenerateTestResponse, type AIPresetProvider, type AIProvider, type AIProviderConstructor, AI_PRESET_PROVIDERS, type APICallInfo, type ComponentTestSetup, type CoverageResult, type CreateOptions, DEFAULT_CONFIG, type DetectContext, type DiffResult, type EmitInfo, type ExportInfo, type ExportKind, type FrameworkDefinition, type FrameworkDetectResult, type FrameworkType, type GlobalOptions, type InitOptions, type LighthouseRunnerOptions, type LoadExternalFrameworksResult, type MockOptions, type MockRoute, type MockRouteCandidate, type MockServerState, type ModuleAnalysis, type MonorepoType, NoopAIProvider, OpenAICompatibleProvider, type PerformanceMetrics, type PlaywrightRunnerOptions, type ProjectInfo, type PropInfo, type QATConfig, type ReportData, type ReportOptions, type RunOptions, type SetupOptions, type SourceAnalysisSummary, type TemplateContext, type TestCaseResult, type TestError, type TestRunResult, type TestStatus, type TestSuiteResult, type TestType, type UILibrary, type VisualOptions, type VitestRunnerOptions, type VueComponentAnalysis, aggregateResults, analyzeFile, analyzeFiles, cleanBaselines, cleanDiffs, clearConfigCache, compareDirectories, compareImages, createBaseline, createDefaultRoutes, defineConfig, detectFramework, detectMonorepo, detectProject, detectUILibrary, discoverAppDirs, discoverUtilityFiles, discoverVueComponents, generateConfigFile, generateHTMLReport, generateMockRouteTemplate, generateMockRoutesFromAPICalls, generateTestFile, getAIProvider, getMockServerState, getRegisteredFrameworks, getRegisteredProviders, initMockRoutesDir, isAIAvailable, listBaselines, listDiffs, loadConfig, loadExternalFrameworks, loadMockRoutes, registerAIProvider, registerFramework, registerTemplate, renderTemplate, resetAIProvider, resetRegistry, runLighthouse, runPlaywright, runVitest, scanAPICalls, startMockServer, stopMockServer, testAIConnection, updateAllBaselines, updateBaseline, validateConfig, writeConfigFile, writeReportToDisk };
|
|
1047
|
+
export { type AIAnalyzeResultRequest, type AIAnalyzeResultResponse, type AICapability, type AIConfig, type AIGenerateTestRequest, type AIGenerateTestResponse, type AIPresetProvider, type AIProvider, type AIProviderConstructor, AI_PRESET_PROVIDERS, type APICallInfo, type ComponentTestSetup, type CoverageResult, type CreateOptions, DEFAULT_CONFIG, type DetectContext, type DiffResult, type EmitInfo, type ExportInfo, type ExportKind, type FrameworkDefinition, type FrameworkDetectResult, type FrameworkType, type GlobalOptions, type ImportInfo, type ImportSignature, type InitOptions, type LighthouseRunnerOptions, type LoadExternalFrameworksResult, type MockOptions, type MockRoute, type MockRouteCandidate, type MockServerState, type ModuleAnalysis, type MonorepoType, NoopAIProvider, OpenAICompatibleProvider, type PerformanceMetrics, type PlaywrightRunnerOptions, type ProjectInfo, type PropInfo, type QATConfig, type ReportData, type ReportOptions, type RunOptions, type SetupOptions, type SourceAnalysisSummary, type TemplateContext, type TestCaseResult, type TestError, type TestRunResult, type TestStatus, type TestSuiteResult, type TestType, type UILibrary, type VisualOptions, type VitestRunnerOptions, type VueComponentAnalysis, aggregateResults, analyzeFile, analyzeFiles, cleanBaselines, cleanDiffs, clearConfigCache, compareDirectories, compareImages, createBaseline, createDefaultRoutes, defineConfig, detectFramework, detectMonorepo, detectProject, detectUILibrary, discoverAppDirs, discoverUtilityFiles, discoverVueComponents, generateConfigFile, generateHTMLReport, generateMockRouteTemplate, generateMockRoutesFromAPICalls, generateTestFile, getAIProvider, getMockServerState, getRegisteredFrameworks, getRegisteredProviders, initMockRoutesDir, isAIAvailable, listBaselines, listDiffs, loadConfig, loadExternalFrameworks, loadMockRoutes, registerAIProvider, registerFramework, registerTemplate, renderTemplate, resetAIProvider, resetRegistry, runLighthouse, runPlaywright, runVitest, scanAPICalls, startMockServer, stopMockServer, testAIConnection, updateAllBaselines, updateBaseline, validateConfig, writeConfigFile, writeReportToDisk };
|
package/dist/index.js
CHANGED
|
@@ -2769,9 +2769,15 @@ ${errorDetails}`;
|
|
|
2769
2769
|
/**
|
|
2770
2770
|
* 压缩源码:保留签名和关键逻辑,剔除注释、空行、样式块
|
|
2771
2771
|
* 目标:在保留准确性的前提下减少 token 消耗
|
|
2772
|
+
* @param code 源码内容
|
|
2773
|
+
* @param maxLength 最大长度
|
|
2774
|
+
* @param importPathRewrites import 路径重写映射(原路径→正确路径)
|
|
2772
2775
|
*/
|
|
2773
|
-
compressSourceCode(code, maxLength = 3e3) {
|
|
2776
|
+
compressSourceCode(code, maxLength = 3e3, importPathRewrites) {
|
|
2774
2777
|
let compressed = code;
|
|
2778
|
+
if (importPathRewrites && importPathRewrites.size > 0) {
|
|
2779
|
+
compressed = this.rewriteImportPaths(compressed, importPathRewrites);
|
|
2780
|
+
}
|
|
2775
2781
|
compressed = compressed.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "");
|
|
2776
2782
|
compressed = compressed.replace(/<template[^>]*>([\s\S]*?)<\/template>/gi, (_match, content) => {
|
|
2777
2783
|
return `<template>${content.replace(/\s*(?:class|style)\s*=\s*["'][^"']*["']/gi, "")}</template>`;
|
|
@@ -2809,7 +2815,8 @@ ${errorDetails}`;
|
|
|
2809
2815
|
}
|
|
2810
2816
|
buildGenerateTestUserPrompt(req) {
|
|
2811
2817
|
const importPath = this.computeTestImportPath(req.type, req.target);
|
|
2812
|
-
let prompt = `\u4E3A${req.target}\u751F\u6210${req.type}\u6D4B\u8BD5\
|
|
2818
|
+
let prompt = `\u4E3A${req.target}\u751F\u6210${req.type}\u6D4B\u8BD5\u3002
|
|
2819
|
+
\u3010\u5F3A\u5236\u3011import\u88AB\u6D4B\u6A21\u5757\u5FC5\u987B\u7528: ${importPath}
|
|
2813
2820
|
`;
|
|
2814
2821
|
if (req.analysis) {
|
|
2815
2822
|
const parts = [];
|
|
@@ -2831,13 +2838,19 @@ ${errorDetails}`;
|
|
|
2831
2838
|
if (req.analysis.computed?.length) {
|
|
2832
2839
|
parts.push(`Computed:${req.analysis.computed.join(",")}`);
|
|
2833
2840
|
}
|
|
2841
|
+
if (req.analysis.importSignatures?.length) {
|
|
2842
|
+
parts.push(`\u4F9D\u8D56\u7B7E\u540D:${req.analysis.importSignatures.map(
|
|
2843
|
+
(imp) => `${imp.source}{${Object.entries(imp.signatures).map(([k, v]) => `${k}:${v}`).join(",")}}`
|
|
2844
|
+
).join(";")}`);
|
|
2845
|
+
}
|
|
2834
2846
|
prompt += parts.join("\n") + "\n";
|
|
2835
2847
|
}
|
|
2836
2848
|
if (req.context) {
|
|
2849
|
+
const importPathRewrites = this.buildImportPathRewrites(req.type, req.target);
|
|
2837
2850
|
prompt += `
|
|
2838
2851
|
\u6E90\u7801:
|
|
2839
2852
|
\`\`\`
|
|
2840
|
-
${this.compressSourceCode(req.context)}
|
|
2853
|
+
${this.compressSourceCode(req.context, 3e3, importPathRewrites)}
|
|
2841
2854
|
\`\`\`
|
|
2842
2855
|
`;
|
|
2843
2856
|
}
|
|
@@ -2872,6 +2885,58 @@ ${this.compressSourceCode(req.context)}
|
|
|
2872
2885
|
}
|
|
2873
2886
|
return `${prefix}${cleanPath}`;
|
|
2874
2887
|
}
|
|
2888
|
+
/**
|
|
2889
|
+
* 构建 import 路径重写映射
|
|
2890
|
+
* 将源码中可能引用自身的各种写法,映射到测试文件中正确的导入路径
|
|
2891
|
+
*/
|
|
2892
|
+
buildImportPathRewrites(testType, targetPath) {
|
|
2893
|
+
const rewrites = /* @__PURE__ */ new Map();
|
|
2894
|
+
if (!targetPath) return rewrites;
|
|
2895
|
+
const correctImportPath = this.computeTestImportPath(testType, targetPath);
|
|
2896
|
+
const variations = /* @__PURE__ */ new Set();
|
|
2897
|
+
variations.add(targetPath);
|
|
2898
|
+
variations.add(targetPath.replace(/^\.\//, ""));
|
|
2899
|
+
const withoutExt = targetPath.replace(/\.(ts|js|tsx|jsx)$/, "");
|
|
2900
|
+
variations.add(withoutExt);
|
|
2901
|
+
const srcDirMatch = targetPath.match(/^(?:\.\/)?(src\/.+)$/);
|
|
2902
|
+
if (srcDirMatch) {
|
|
2903
|
+
variations.add(`@/${srcDirMatch[1]}`);
|
|
2904
|
+
variations.add(`@/${srcDirMatch[1].replace(/\.(ts|js|tsx|jsx)$/, "")}`);
|
|
2905
|
+
variations.add(`#/${srcDirMatch[1]}`);
|
|
2906
|
+
variations.add(`#/${srcDirMatch[1].replace(/\.(ts|js|tsx|jsx)$/, "")}`);
|
|
2907
|
+
}
|
|
2908
|
+
const pathParts = targetPath.replace(/^\.\//, "").split("/");
|
|
2909
|
+
const fileName = pathParts[pathParts.length - 1];
|
|
2910
|
+
const fileNameNoExt = fileName.replace(/\.(ts|js|tsx|jsx|vue)$/, "");
|
|
2911
|
+
variations.add(`./${fileName}`);
|
|
2912
|
+
variations.add(`./${fileNameNoExt}`);
|
|
2913
|
+
for (let i = 1; i < pathParts.length; i++) {
|
|
2914
|
+
const relPath = "../".repeat(i) + pathParts.slice(i).join("/");
|
|
2915
|
+
variations.add(relPath);
|
|
2916
|
+
variations.add(relPath.replace(/\.(ts|js|tsx|jsx)$/, ""));
|
|
2917
|
+
}
|
|
2918
|
+
for (const variant of variations) {
|
|
2919
|
+
if (variant && variant !== correctImportPath) {
|
|
2920
|
+
rewrites.set(variant, correctImportPath);
|
|
2921
|
+
}
|
|
2922
|
+
}
|
|
2923
|
+
return rewrites;
|
|
2924
|
+
}
|
|
2925
|
+
/**
|
|
2926
|
+
* 重写源码中的 import 路径
|
|
2927
|
+
* 将 from 'oldPath' / from "oldPath" 替换为正确的路径
|
|
2928
|
+
*/
|
|
2929
|
+
rewriteImportPaths(code, rewrites) {
|
|
2930
|
+
let result = code;
|
|
2931
|
+
for (const [oldPath, newPath] of rewrites) {
|
|
2932
|
+
const escaped = oldPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2933
|
+
const singleQuoteRegex = new RegExp(`(from\\s+')${escaped}(')`, "g");
|
|
2934
|
+
const doubleQuoteRegex = new RegExp(`(from\\s+")${escaped}(")`, "g");
|
|
2935
|
+
result = result.replace(singleQuoteRegex, `$1${newPath}$2`);
|
|
2936
|
+
result = result.replace(doubleQuoteRegex, `$1${newPath}$2`);
|
|
2937
|
+
}
|
|
2938
|
+
return result;
|
|
2939
|
+
}
|
|
2875
2940
|
parseGenerateTestResponse(content) {
|
|
2876
2941
|
const codeBlockMatch = content.match(/```(?:typescript|ts|javascript|js)?\s*\n([\s\S]*?)```/);
|
|
2877
2942
|
const code = codeBlockMatch ? codeBlockMatch[1].trim() : content.replace(/^(?:```[\s\S]*?\n)?/, "").replace(/\n?```$/, "").trim();
|
|
@@ -2894,12 +2959,14 @@ SUGGESTIONS:- \u5EFA\u8BAE(\u6BCF\u884C\u4E00\u4E2A)`;
|
|
|
2894
2959
|
}
|
|
2895
2960
|
buildReviewTestUserPrompt(req) {
|
|
2896
2961
|
const importPath = this.computeTestImportPath(req.testType, req.target);
|
|
2897
|
-
let prompt = `\u5BA1\u67E5\u6D4B\u8BD5\u4EE3\u7801\u3002\u88AB\u6D4B:${req.target} \u7C7B\u578B:${req.testType}
|
|
2962
|
+
let prompt = `\u5BA1\u67E5\u6D4B\u8BD5\u4EE3\u7801\u3002\u88AB\u6D4B:${req.target} \u7C7B\u578B:${req.testType}
|
|
2963
|
+
\u3010\u5F3A\u5236\u3011import\u88AB\u6D4B\u6A21\u5757\u5FC5\u987B\u7528: ${importPath}
|
|
2898
2964
|
`;
|
|
2965
|
+
const importPathRewrites = this.buildImportPathRewrites(req.testType, req.target);
|
|
2899
2966
|
prompt += `
|
|
2900
2967
|
\u6E90\u7801:
|
|
2901
2968
|
\`\`\`
|
|
2902
|
-
${this.compressSourceCode(req.sourceCode, 2e3)}
|
|
2969
|
+
${this.compressSourceCode(req.sourceCode, 2e3, importPathRewrites)}
|
|
2903
2970
|
\`\`\`
|
|
2904
2971
|
`;
|
|
2905
2972
|
prompt += `
|
|
@@ -2918,6 +2985,11 @@ ${req.testCode}
|
|
|
2918
2985
|
if (req.analysis.emits?.length) {
|
|
2919
2986
|
parts.push(`Emits:${req.analysis.emits.map((e) => `${e.name}(${e.params?.join(",") || ""})`).join(",")}`);
|
|
2920
2987
|
}
|
|
2988
|
+
if (req.analysis.importSignatures?.length) {
|
|
2989
|
+
parts.push(`\u4F9D\u8D56:${req.analysis.importSignatures.map(
|
|
2990
|
+
(imp) => `${imp.source}{${Object.entries(imp.signatures).map(([k, v]) => `${k}:${v}`).join(",")}}`
|
|
2991
|
+
).join(";")}`);
|
|
2992
|
+
}
|
|
2921
2993
|
if (parts.length > 0) {
|
|
2922
2994
|
prompt += `
|
|
2923
2995
|
\u5206\u6790:${parts.join("|")}`;
|
|
@@ -3529,15 +3601,18 @@ import path9 from "path";
|
|
|
3529
3601
|
function analyzeFile(filePath) {
|
|
3530
3602
|
const absolutePath = path9.resolve(process.cwd(), filePath);
|
|
3531
3603
|
if (!fs9.existsSync(absolutePath)) {
|
|
3532
|
-
return { filePath, exports: [], apiCalls: [] };
|
|
3604
|
+
return { filePath, exports: [], imports: [], importSignatures: [], apiCalls: [] };
|
|
3533
3605
|
}
|
|
3534
3606
|
const content = fs9.readFileSync(absolutePath, "utf-8");
|
|
3535
3607
|
const ext = path9.extname(filePath);
|
|
3536
3608
|
const result = {
|
|
3537
3609
|
filePath,
|
|
3538
3610
|
exports: extractExports(content, ext),
|
|
3611
|
+
imports: extractImports(content),
|
|
3612
|
+
importSignatures: [],
|
|
3539
3613
|
apiCalls: extractAPICalls(content, filePath)
|
|
3540
3614
|
};
|
|
3615
|
+
result.importSignatures = analyzeImportSignatures(result.imports, path9.dirname(absolutePath));
|
|
3541
3616
|
if (ext === ".vue") {
|
|
3542
3617
|
result.vueAnalysis = analyzeVueComponent(content, path9.basename(filePath, ".vue"));
|
|
3543
3618
|
const scriptMatch = content.match(/<script[^>]*>([\s\S]*?)<\/script>/);
|
|
@@ -3960,6 +4035,90 @@ function generateMockRoutesFromAPICalls(apiCalls) {
|
|
|
3960
4035
|
}
|
|
3961
4036
|
return routes;
|
|
3962
4037
|
}
|
|
4038
|
+
function extractImports(content) {
|
|
4039
|
+
const imports = [];
|
|
4040
|
+
const namedImportRegex = /import\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/g;
|
|
4041
|
+
let match;
|
|
4042
|
+
while ((match = namedImportRegex.exec(content)) !== null) {
|
|
4043
|
+
const names = match[1].split(",").map((n) => {
|
|
4044
|
+
const parts = n.trim().split(/\s+as\s+/);
|
|
4045
|
+
return parts[0].trim();
|
|
4046
|
+
}).filter(Boolean);
|
|
4047
|
+
imports.push({ names, source: match[2], isDefault: false, isNamespace: false });
|
|
4048
|
+
}
|
|
4049
|
+
const defaultImportRegex = /import\s+(\w+)\s+from\s*['"]([^'"]+)['"]/g;
|
|
4050
|
+
while ((match = defaultImportRegex.exec(content)) !== null) {
|
|
4051
|
+
const fullLine = content.slice(Math.max(0, match.index - 5), match.index + match[0].length);
|
|
4052
|
+
if (fullLine.includes("{")) continue;
|
|
4053
|
+
imports.push({ names: [match[1]], source: match[2], isDefault: true, isNamespace: false });
|
|
4054
|
+
}
|
|
4055
|
+
const namespaceImportRegex = /import\s*\*\s*as\s+(\w+)\s*from\s*['"]([^'"]+)['"]/g;
|
|
4056
|
+
while ((match = namespaceImportRegex.exec(content)) !== null) {
|
|
4057
|
+
imports.push({ names: [match[1]], source: match[2], isDefault: false, isNamespace: true });
|
|
4058
|
+
}
|
|
4059
|
+
return imports;
|
|
4060
|
+
}
|
|
4061
|
+
function analyzeImportSignatures(imports, baseDir) {
|
|
4062
|
+
const signatures = [];
|
|
4063
|
+
for (const imp of imports) {
|
|
4064
|
+
if (!imp.source.startsWith(".") && !imp.source.startsWith("@/") && !imp.source.startsWith("#/") && !imp.source.startsWith("~")) {
|
|
4065
|
+
continue;
|
|
4066
|
+
}
|
|
4067
|
+
const resolvedPath = resolveImportPath2(imp.source, baseDir);
|
|
4068
|
+
if (!resolvedPath || !fs9.existsSync(resolvedPath)) continue;
|
|
4069
|
+
try {
|
|
4070
|
+
const content = fs9.readFileSync(resolvedPath, "utf-8");
|
|
4071
|
+
const ext = path9.extname(resolvedPath);
|
|
4072
|
+
const exports = extractExports(content, ext);
|
|
4073
|
+
const sigMap = {};
|
|
4074
|
+
for (const name of imp.names) {
|
|
4075
|
+
const exp = exports.find((e) => e.name === name);
|
|
4076
|
+
if (exp) {
|
|
4077
|
+
const params = exp.params.length > 0 ? `(${exp.params.join(", ")})` : "";
|
|
4078
|
+
const ret = exp.returnType ? `: ${exp.returnType}` : "";
|
|
4079
|
+
const async = exp.isAsync ? "async " : "";
|
|
4080
|
+
sigMap[name] = `${async}${exp.kind}${params}${ret}`;
|
|
4081
|
+
} else if (imp.isDefault) {
|
|
4082
|
+
const defaultExp = exports.find((e) => e.kind === "default");
|
|
4083
|
+
if (defaultExp) {
|
|
4084
|
+
sigMap[name] = `default[${defaultExp.name || "anonymous"}]`;
|
|
4085
|
+
}
|
|
4086
|
+
} else if (imp.isNamespace) {
|
|
4087
|
+
sigMap[name] = `{${exports.map((e) => e.name).join(", ")}}`;
|
|
4088
|
+
}
|
|
4089
|
+
}
|
|
4090
|
+
if (ext === ".vue") {
|
|
4091
|
+
const vueAnalysis = analyzeVueComponent(content, path9.basename(resolvedPath, ".vue"));
|
|
4092
|
+
if (vueAnalysis.props.length > 0) {
|
|
4093
|
+
sigMap["__props__"] = vueAnalysis.props.map((p) => `${p.name}:${p.type}${p.required ? "!" : "?"}`).join(",");
|
|
4094
|
+
}
|
|
4095
|
+
}
|
|
4096
|
+
if (Object.keys(sigMap).length > 0) {
|
|
4097
|
+
signatures.push({ source: imp.source, signatures: sigMap });
|
|
4098
|
+
}
|
|
4099
|
+
} catch {
|
|
4100
|
+
}
|
|
4101
|
+
}
|
|
4102
|
+
return signatures;
|
|
4103
|
+
}
|
|
4104
|
+
function resolveImportPath2(importSource, baseDir) {
|
|
4105
|
+
let resolved;
|
|
4106
|
+
if (importSource.startsWith("@/") || importSource.startsWith("#/") || importSource.startsWith("~")) {
|
|
4107
|
+
const relativePath = importSource.replace(/^[@#~]\//, "src/");
|
|
4108
|
+
resolved = path9.resolve(process.cwd(), relativePath);
|
|
4109
|
+
} else if (importSource.startsWith(".")) {
|
|
4110
|
+
resolved = path9.resolve(baseDir, importSource);
|
|
4111
|
+
} else {
|
|
4112
|
+
return null;
|
|
4113
|
+
}
|
|
4114
|
+
const extensions = [".ts", ".tsx", ".js", ".jsx", ".vue", "/index.ts", "/index.js"];
|
|
4115
|
+
if (fs9.existsSync(resolved)) return resolved;
|
|
4116
|
+
for (const ext of extensions) {
|
|
4117
|
+
const withExt = resolved + ext;
|
|
4118
|
+
if (fs9.existsSync(withExt)) return withExt;
|
|
4119
|
+
}
|
|
4120
|
+
return null;
|
|
4121
|
+
}
|
|
3963
4122
|
export {
|
|
3964
4123
|
AI_PRESET_PROVIDERS,
|
|
3965
4124
|
DEFAULT_CONFIG,
|