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/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\u3002import\u8DEF\u5F84:${importPath}
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} import\u8DEF\u5F84:${importPath}
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,