qat-cli 0.3.4 → 0.3.5
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 +250 -178
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +163 -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 +163 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -2861,9 +2861,15 @@ ${errorDetails}`;
|
|
|
2861
2861
|
/**
|
|
2862
2862
|
* 压缩源码:保留签名和关键逻辑,剔除注释、空行、样式块
|
|
2863
2863
|
* 目标:在保留准确性的前提下减少 token 消耗
|
|
2864
|
+
* @param code 源码内容
|
|
2865
|
+
* @param maxLength 最大长度
|
|
2866
|
+
* @param importPathRewrites import 路径重写映射(原路径→正确路径)
|
|
2864
2867
|
*/
|
|
2865
|
-
compressSourceCode(code, maxLength = 3e3) {
|
|
2868
|
+
compressSourceCode(code, maxLength = 3e3, importPathRewrites) {
|
|
2866
2869
|
let compressed = code;
|
|
2870
|
+
if (importPathRewrites && importPathRewrites.size > 0) {
|
|
2871
|
+
compressed = this.rewriteImportPaths(compressed, importPathRewrites);
|
|
2872
|
+
}
|
|
2867
2873
|
compressed = compressed.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "");
|
|
2868
2874
|
compressed = compressed.replace(/<template[^>]*>([\s\S]*?)<\/template>/gi, (_match, content) => {
|
|
2869
2875
|
return `<template>${content.replace(/\s*(?:class|style)\s*=\s*["'][^"']*["']/gi, "")}</template>`;
|
|
@@ -2901,7 +2907,8 @@ ${errorDetails}`;
|
|
|
2901
2907
|
}
|
|
2902
2908
|
buildGenerateTestUserPrompt(req) {
|
|
2903
2909
|
const importPath = this.computeTestImportPath(req.type, req.target);
|
|
2904
|
-
let prompt = `\u4E3A${req.target}\u751F\u6210${req.type}\u6D4B\u8BD5\
|
|
2910
|
+
let prompt = `\u4E3A${req.target}\u751F\u6210${req.type}\u6D4B\u8BD5\u3002
|
|
2911
|
+
\u3010\u5F3A\u5236\u3011import\u88AB\u6D4B\u6A21\u5757\u5FC5\u987B\u7528: ${importPath}
|
|
2905
2912
|
`;
|
|
2906
2913
|
if (req.analysis) {
|
|
2907
2914
|
const parts = [];
|
|
@@ -2923,13 +2930,19 @@ ${errorDetails}`;
|
|
|
2923
2930
|
if (req.analysis.computed?.length) {
|
|
2924
2931
|
parts.push(`Computed:${req.analysis.computed.join(",")}`);
|
|
2925
2932
|
}
|
|
2933
|
+
if (req.analysis.importSignatures?.length) {
|
|
2934
|
+
parts.push(`\u4F9D\u8D56\u7B7E\u540D:${req.analysis.importSignatures.map(
|
|
2935
|
+
(imp) => `${imp.source}{${Object.entries(imp.signatures).map(([k, v]) => `${k}:${v}`).join(",")}}`
|
|
2936
|
+
).join(";")}`);
|
|
2937
|
+
}
|
|
2926
2938
|
prompt += parts.join("\n") + "\n";
|
|
2927
2939
|
}
|
|
2928
2940
|
if (req.context) {
|
|
2941
|
+
const importPathRewrites = this.buildImportPathRewrites(req.type, req.target);
|
|
2929
2942
|
prompt += `
|
|
2930
2943
|
\u6E90\u7801:
|
|
2931
2944
|
\`\`\`
|
|
2932
|
-
${this.compressSourceCode(req.context)}
|
|
2945
|
+
${this.compressSourceCode(req.context, 3e3, importPathRewrites)}
|
|
2933
2946
|
\`\`\`
|
|
2934
2947
|
`;
|
|
2935
2948
|
}
|
|
@@ -2964,6 +2977,56 @@ ${this.compressSourceCode(req.context)}
|
|
|
2964
2977
|
}
|
|
2965
2978
|
return `${prefix}${cleanPath}`;
|
|
2966
2979
|
}
|
|
2980
|
+
/**
|
|
2981
|
+
* 构建 import 路径重写映射
|
|
2982
|
+
* 将源码中可能引用自身的各种写法,映射到测试文件中正确的导入路径
|
|
2983
|
+
*/
|
|
2984
|
+
buildImportPathRewrites(testType, targetPath) {
|
|
2985
|
+
const rewrites = /* @__PURE__ */ new Map();
|
|
2986
|
+
if (!targetPath) return rewrites;
|
|
2987
|
+
const correctImportPath = this.computeTestImportPath(testType, targetPath);
|
|
2988
|
+
const variations = /* @__PURE__ */ new Set();
|
|
2989
|
+
variations.add(targetPath);
|
|
2990
|
+
variations.add(targetPath.replace(/^\.\//, ""));
|
|
2991
|
+
const withoutExt = targetPath.replace(/\.(ts|js|tsx|jsx)$/, "");
|
|
2992
|
+
variations.add(withoutExt);
|
|
2993
|
+
const srcDirMatch = targetPath.match(/^(?:\.\/)?(src\/.+)$/);
|
|
2994
|
+
if (srcDirMatch) {
|
|
2995
|
+
variations.add(`@/${srcDirMatch[1]}`);
|
|
2996
|
+
variations.add(`@/${srcDirMatch[1].replace(/\.(ts|js|tsx|jsx)$/, "")}`);
|
|
2997
|
+
}
|
|
2998
|
+
const pathParts = targetPath.replace(/^\.\//, "").split("/");
|
|
2999
|
+
const fileName = pathParts[pathParts.length - 1];
|
|
3000
|
+
const fileNameNoExt = fileName.replace(/\.(ts|js|tsx|jsx|vue)$/, "");
|
|
3001
|
+
variations.add(`./${fileName}`);
|
|
3002
|
+
variations.add(`./${fileNameNoExt}`);
|
|
3003
|
+
for (let i = 1; i < pathParts.length; i++) {
|
|
3004
|
+
const relPath = "../".repeat(i) + pathParts.slice(i).join("/");
|
|
3005
|
+
variations.add(relPath);
|
|
3006
|
+
variations.add(relPath.replace(/\.(ts|js|tsx|jsx)$/, ""));
|
|
3007
|
+
}
|
|
3008
|
+
for (const variant of variations) {
|
|
3009
|
+
if (variant && variant !== correctImportPath) {
|
|
3010
|
+
rewrites.set(variant, correctImportPath);
|
|
3011
|
+
}
|
|
3012
|
+
}
|
|
3013
|
+
return rewrites;
|
|
3014
|
+
}
|
|
3015
|
+
/**
|
|
3016
|
+
* 重写源码中的 import 路径
|
|
3017
|
+
* 将 from 'oldPath' / from "oldPath" 替换为正确的路径
|
|
3018
|
+
*/
|
|
3019
|
+
rewriteImportPaths(code, rewrites) {
|
|
3020
|
+
let result = code;
|
|
3021
|
+
for (const [oldPath, newPath] of rewrites) {
|
|
3022
|
+
const escaped = oldPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3023
|
+
const singleQuoteRegex = new RegExp(`(from\\s+')${escaped}(')`, "g");
|
|
3024
|
+
const doubleQuoteRegex = new RegExp(`(from\\s+")${escaped}(")`, "g");
|
|
3025
|
+
result = result.replace(singleQuoteRegex, `$1${newPath}$2`);
|
|
3026
|
+
result = result.replace(doubleQuoteRegex, `$1${newPath}$2`);
|
|
3027
|
+
}
|
|
3028
|
+
return result;
|
|
3029
|
+
}
|
|
2967
3030
|
parseGenerateTestResponse(content) {
|
|
2968
3031
|
const codeBlockMatch = content.match(/```(?:typescript|ts|javascript|js)?\s*\n([\s\S]*?)```/);
|
|
2969
3032
|
const code = codeBlockMatch ? codeBlockMatch[1].trim() : content.replace(/^(?:```[\s\S]*?\n)?/, "").replace(/\n?```$/, "").trim();
|
|
@@ -2986,12 +3049,14 @@ SUGGESTIONS:- \u5EFA\u8BAE(\u6BCF\u884C\u4E00\u4E2A)`;
|
|
|
2986
3049
|
}
|
|
2987
3050
|
buildReviewTestUserPrompt(req) {
|
|
2988
3051
|
const importPath = this.computeTestImportPath(req.testType, req.target);
|
|
2989
|
-
let prompt = `\u5BA1\u67E5\u6D4B\u8BD5\u4EE3\u7801\u3002\u88AB\u6D4B:${req.target} \u7C7B\u578B:${req.testType}
|
|
3052
|
+
let prompt = `\u5BA1\u67E5\u6D4B\u8BD5\u4EE3\u7801\u3002\u88AB\u6D4B:${req.target} \u7C7B\u578B:${req.testType}
|
|
3053
|
+
\u3010\u5F3A\u5236\u3011import\u88AB\u6D4B\u6A21\u5757\u5FC5\u987B\u7528: ${importPath}
|
|
2990
3054
|
`;
|
|
3055
|
+
const importPathRewrites = this.buildImportPathRewrites(req.testType, req.target);
|
|
2991
3056
|
prompt += `
|
|
2992
3057
|
\u6E90\u7801:
|
|
2993
3058
|
\`\`\`
|
|
2994
|
-
${this.compressSourceCode(req.sourceCode, 2e3)}
|
|
3059
|
+
${this.compressSourceCode(req.sourceCode, 2e3, importPathRewrites)}
|
|
2995
3060
|
\`\`\`
|
|
2996
3061
|
`;
|
|
2997
3062
|
prompt += `
|
|
@@ -3010,6 +3075,11 @@ ${req.testCode}
|
|
|
3010
3075
|
if (req.analysis.emits?.length) {
|
|
3011
3076
|
parts.push(`Emits:${req.analysis.emits.map((e) => `${e.name}(${e.params?.join(",") || ""})`).join(",")}`);
|
|
3012
3077
|
}
|
|
3078
|
+
if (req.analysis.importSignatures?.length) {
|
|
3079
|
+
parts.push(`\u4F9D\u8D56:${req.analysis.importSignatures.map(
|
|
3080
|
+
(imp) => `${imp.source}{${Object.entries(imp.signatures).map(([k, v]) => `${k}:${v}`).join(",")}}`
|
|
3081
|
+
).join(";")}`);
|
|
3082
|
+
}
|
|
3013
3083
|
if (parts.length > 0) {
|
|
3014
3084
|
prompt += `
|
|
3015
3085
|
\u5206\u6790:${parts.join("|")}`;
|
|
@@ -3621,15 +3691,18 @@ var import_node_path10 = __toESM(require("path"), 1);
|
|
|
3621
3691
|
function analyzeFile(filePath) {
|
|
3622
3692
|
const absolutePath = import_node_path10.default.resolve(process.cwd(), filePath);
|
|
3623
3693
|
if (!import_node_fs9.default.existsSync(absolutePath)) {
|
|
3624
|
-
return { filePath, exports: [], apiCalls: [] };
|
|
3694
|
+
return { filePath, exports: [], imports: [], importSignatures: [], apiCalls: [] };
|
|
3625
3695
|
}
|
|
3626
3696
|
const content = import_node_fs9.default.readFileSync(absolutePath, "utf-8");
|
|
3627
3697
|
const ext = import_node_path10.default.extname(filePath);
|
|
3628
3698
|
const result = {
|
|
3629
3699
|
filePath,
|
|
3630
3700
|
exports: extractExports(content, ext),
|
|
3701
|
+
imports: extractImports(content),
|
|
3702
|
+
importSignatures: [],
|
|
3631
3703
|
apiCalls: extractAPICalls(content, filePath)
|
|
3632
3704
|
};
|
|
3705
|
+
result.importSignatures = analyzeImportSignatures(result.imports, import_node_path10.default.dirname(absolutePath));
|
|
3633
3706
|
if (ext === ".vue") {
|
|
3634
3707
|
result.vueAnalysis = analyzeVueComponent(content, import_node_path10.default.basename(filePath, ".vue"));
|
|
3635
3708
|
const scriptMatch = content.match(/<script[^>]*>([\s\S]*?)<\/script>/);
|
|
@@ -4052,6 +4125,90 @@ function generateMockRoutesFromAPICalls(apiCalls) {
|
|
|
4052
4125
|
}
|
|
4053
4126
|
return routes;
|
|
4054
4127
|
}
|
|
4128
|
+
function extractImports(content) {
|
|
4129
|
+
const imports = [];
|
|
4130
|
+
const namedImportRegex = /import\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/g;
|
|
4131
|
+
let match;
|
|
4132
|
+
while ((match = namedImportRegex.exec(content)) !== null) {
|
|
4133
|
+
const names = match[1].split(",").map((n) => {
|
|
4134
|
+
const parts = n.trim().split(/\s+as\s+/);
|
|
4135
|
+
return parts[0].trim();
|
|
4136
|
+
}).filter(Boolean);
|
|
4137
|
+
imports.push({ names, source: match[2], isDefault: false, isNamespace: false });
|
|
4138
|
+
}
|
|
4139
|
+
const defaultImportRegex = /import\s+(\w+)\s+from\s*['"]([^'"]+)['"]/g;
|
|
4140
|
+
while ((match = defaultImportRegex.exec(content)) !== null) {
|
|
4141
|
+
const fullLine = content.slice(Math.max(0, match.index - 5), match.index + match[0].length);
|
|
4142
|
+
if (fullLine.includes("{")) continue;
|
|
4143
|
+
imports.push({ names: [match[1]], source: match[2], isDefault: true, isNamespace: false });
|
|
4144
|
+
}
|
|
4145
|
+
const namespaceImportRegex = /import\s*\*\s*as\s+(\w+)\s*from\s*['"]([^'"]+)['"]/g;
|
|
4146
|
+
while ((match = namespaceImportRegex.exec(content)) !== null) {
|
|
4147
|
+
imports.push({ names: [match[1]], source: match[2], isDefault: false, isNamespace: true });
|
|
4148
|
+
}
|
|
4149
|
+
return imports;
|
|
4150
|
+
}
|
|
4151
|
+
function analyzeImportSignatures(imports, baseDir) {
|
|
4152
|
+
const signatures = [];
|
|
4153
|
+
for (const imp of imports) {
|
|
4154
|
+
if (!imp.source.startsWith(".") && !imp.source.startsWith("@/") && !imp.source.startsWith("~")) {
|
|
4155
|
+
continue;
|
|
4156
|
+
}
|
|
4157
|
+
const resolvedPath = resolveImportPath2(imp.source, baseDir);
|
|
4158
|
+
if (!resolvedPath || !import_node_fs9.default.existsSync(resolvedPath)) continue;
|
|
4159
|
+
try {
|
|
4160
|
+
const content = import_node_fs9.default.readFileSync(resolvedPath, "utf-8");
|
|
4161
|
+
const ext = import_node_path10.default.extname(resolvedPath);
|
|
4162
|
+
const exports2 = extractExports(content, ext);
|
|
4163
|
+
const sigMap = {};
|
|
4164
|
+
for (const name of imp.names) {
|
|
4165
|
+
const exp = exports2.find((e) => e.name === name);
|
|
4166
|
+
if (exp) {
|
|
4167
|
+
const params = exp.params.length > 0 ? `(${exp.params.join(", ")})` : "";
|
|
4168
|
+
const ret = exp.returnType ? `: ${exp.returnType}` : "";
|
|
4169
|
+
const async = exp.isAsync ? "async " : "";
|
|
4170
|
+
sigMap[name] = `${async}${exp.kind}${params}${ret}`;
|
|
4171
|
+
} else if (imp.isDefault) {
|
|
4172
|
+
const defaultExp = exports2.find((e) => e.kind === "default");
|
|
4173
|
+
if (defaultExp) {
|
|
4174
|
+
sigMap[name] = `default[${defaultExp.name || "anonymous"}]`;
|
|
4175
|
+
}
|
|
4176
|
+
} else if (imp.isNamespace) {
|
|
4177
|
+
sigMap[name] = `{${exports2.map((e) => e.name).join(", ")}}`;
|
|
4178
|
+
}
|
|
4179
|
+
}
|
|
4180
|
+
if (ext === ".vue") {
|
|
4181
|
+
const vueAnalysis = analyzeVueComponent(content, import_node_path10.default.basename(resolvedPath, ".vue"));
|
|
4182
|
+
if (vueAnalysis.props.length > 0) {
|
|
4183
|
+
sigMap["__props__"] = vueAnalysis.props.map((p) => `${p.name}:${p.type}${p.required ? "!" : "?"}`).join(",");
|
|
4184
|
+
}
|
|
4185
|
+
}
|
|
4186
|
+
if (Object.keys(sigMap).length > 0) {
|
|
4187
|
+
signatures.push({ source: imp.source, signatures: sigMap });
|
|
4188
|
+
}
|
|
4189
|
+
} catch {
|
|
4190
|
+
}
|
|
4191
|
+
}
|
|
4192
|
+
return signatures;
|
|
4193
|
+
}
|
|
4194
|
+
function resolveImportPath2(importSource, baseDir) {
|
|
4195
|
+
let resolved;
|
|
4196
|
+
if (importSource.startsWith("@/") || importSource.startsWith("~")) {
|
|
4197
|
+
const relativePath = importSource.replace(/^[@~]\//, "src/");
|
|
4198
|
+
resolved = import_node_path10.default.resolve(process.cwd(), relativePath);
|
|
4199
|
+
} else if (importSource.startsWith(".")) {
|
|
4200
|
+
resolved = import_node_path10.default.resolve(baseDir, importSource);
|
|
4201
|
+
} else {
|
|
4202
|
+
return null;
|
|
4203
|
+
}
|
|
4204
|
+
const extensions = [".ts", ".tsx", ".js", ".jsx", ".vue", "/index.ts", "/index.js"];
|
|
4205
|
+
if (import_node_fs9.default.existsSync(resolved)) return resolved;
|
|
4206
|
+
for (const ext of extensions) {
|
|
4207
|
+
const withExt = resolved + ext;
|
|
4208
|
+
if (import_node_fs9.default.existsSync(withExt)) return withExt;
|
|
4209
|
+
}
|
|
4210
|
+
return null;
|
|
4211
|
+
}
|
|
4055
4212
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4056
4213
|
0 && (module.exports = {
|
|
4057
4214
|
AI_PRESET_PROVIDERS,
|