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/cli.js CHANGED
@@ -524,71 +524,6 @@ function validateConfig(config) {
524
524
  }
525
525
  return merged;
526
526
  }
527
- function generateConfigFile(overrides = {}) {
528
- const config = validateConfig(overrides);
529
- return `// @ts-check
530
- /**
531
- * QAT \u914D\u7F6E\u6587\u4EF6 - Quick Auto Testing
532
- * \u4FEE\u6539\u540E\u65E0\u9700\u91CD\u542F\uFF0C\u4E0B\u6B21\u8FD0\u884C qat \u547D\u4EE4\u65F6\u81EA\u52A8\u751F\u6548
533
- *
534
- * AI \u6A21\u578B\u914D\u7F6E\u8BF7\u8FD0\u884C: qat change
535
- * AI \u72B6\u6001\u67E5\u770B\u8BF7\u8FD0\u884C: qat status
536
- */
537
- import { defineConfig } from 'qat-cli';
538
-
539
- export default defineConfig({
540
- project: {
541
- framework: '${config.project.framework}',
542
- vite: ${config.project.vite ?? true},
543
- srcDir: '${config.project.srcDir}',
544
- },
545
- vitest: {
546
- enabled: ${config.vitest.enabled},
547
- coverage: ${config.vitest.coverage},
548
- globals: ${config.vitest.globals},
549
- environment: '${config.vitest.environment}',
550
- },
551
- playwright: {
552
- enabled: ${config.playwright.enabled},
553
- browsers: [${config.playwright.browsers.map((b) => `'${b}'`).join(", ")}],
554
- baseURL: '${config.playwright.baseURL}',
555
- screenshot: '${config.playwright.screenshot}',
556
- },
557
- visual: {
558
- enabled: ${config.visual.enabled},
559
- threshold: ${config.visual.threshold},
560
- baselineDir: '${config.visual.baselineDir}',
561
- diffDir: '${config.visual.diffDir}',
562
- },
563
- lighthouse: {
564
- enabled: ${config.lighthouse.enabled},
565
- urls: [${config.lighthouse.urls.map((u) => `'${u}'`).join(", ")}],
566
- runs: ${config.lighthouse.runs},
567
- thresholds: {${Object.entries(config.lighthouse.thresholds).map(([k, v]) => `
568
- ${k}: ${v},`).join("")}
569
- },
570
- },
571
- mock: {
572
- enabled: ${config.mock.enabled},
573
- port: ${config.mock.port},
574
- routesDir: '${config.mock.routesDir}',
575
- },
576
- report: {
577
- outputDir: '${config.report.outputDir}',
578
- open: ${config.report.open},
579
- },
580
- });
581
- `;
582
- }
583
- async function writeConfigFile(cwd, overrides = {}, force = false) {
584
- const configPath = path2.join(cwd, "qat.config.js");
585
- if (fs2.existsSync(configPath) && !force) {
586
- throw new Error(`\u914D\u7F6E\u6587\u4EF6\u5DF2\u5B58\u5728: ${configPath}\uFF0C\u4F7F\u7528 --force \u8986\u76D6`);
587
- }
588
- const content = generateConfigFile(overrides);
589
- fs2.writeFileSync(configPath, content, "utf-8");
590
- return configPath;
591
- }
592
527
  function isFileNotFoundError(error) {
593
528
  if (error instanceof Error) {
594
529
  return error.message.includes("Cannot find") || error.message.includes("ENOENT") || error.message.includes("\u65E0\u6CD5\u52A0\u8F7D\u914D\u7F6E\u6587\u4EF6");
@@ -1088,9 +1023,15 @@ ${errorDetails}`;
1088
1023
  /**
1089
1024
  * 压缩源码:保留签名和关键逻辑,剔除注释、空行、样式块
1090
1025
  * 目标:在保留准确性的前提下减少 token 消耗
1026
+ * @param code 源码内容
1027
+ * @param maxLength 最大长度
1028
+ * @param importPathRewrites import 路径重写映射(原路径→正确路径)
1091
1029
  */
1092
- compressSourceCode(code, maxLength = 3e3) {
1030
+ compressSourceCode(code, maxLength = 3e3, importPathRewrites) {
1093
1031
  let compressed = code;
1032
+ if (importPathRewrites && importPathRewrites.size > 0) {
1033
+ compressed = this.rewriteImportPaths(compressed, importPathRewrites);
1034
+ }
1094
1035
  compressed = compressed.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "");
1095
1036
  compressed = compressed.replace(/<template[^>]*>([\s\S]*?)<\/template>/gi, (_match, content) => {
1096
1037
  return `<template>${content.replace(/\s*(?:class|style)\s*=\s*["'][^"']*["']/gi, "")}</template>`;
@@ -1128,7 +1069,8 @@ ${errorDetails}`;
1128
1069
  }
1129
1070
  buildGenerateTestUserPrompt(req) {
1130
1071
  const importPath = this.computeTestImportPath(req.type, req.target);
1131
- let prompt = `\u4E3A${req.target}\u751F\u6210${req.type}\u6D4B\u8BD5\u3002import\u8DEF\u5F84:${importPath}
1072
+ let prompt = `\u4E3A${req.target}\u751F\u6210${req.type}\u6D4B\u8BD5\u3002
1073
+ \u3010\u5F3A\u5236\u3011import\u88AB\u6D4B\u6A21\u5757\u5FC5\u987B\u7528: ${importPath}
1132
1074
  `;
1133
1075
  if (req.analysis) {
1134
1076
  const parts = [];
@@ -1150,13 +1092,19 @@ ${errorDetails}`;
1150
1092
  if (req.analysis.computed?.length) {
1151
1093
  parts.push(`Computed:${req.analysis.computed.join(",")}`);
1152
1094
  }
1095
+ if (req.analysis.importSignatures?.length) {
1096
+ parts.push(`\u4F9D\u8D56\u7B7E\u540D:${req.analysis.importSignatures.map(
1097
+ (imp) => `${imp.source}{${Object.entries(imp.signatures).map(([k, v]) => `${k}:${v}`).join(",")}}`
1098
+ ).join(";")}`);
1099
+ }
1153
1100
  prompt += parts.join("\n") + "\n";
1154
1101
  }
1155
1102
  if (req.context) {
1103
+ const importPathRewrites = this.buildImportPathRewrites(req.type, req.target);
1156
1104
  prompt += `
1157
1105
  \u6E90\u7801:
1158
1106
  \`\`\`
1159
- ${this.compressSourceCode(req.context)}
1107
+ ${this.compressSourceCode(req.context, 3e3, importPathRewrites)}
1160
1108
  \`\`\`
1161
1109
  `;
1162
1110
  }
@@ -1191,6 +1139,56 @@ ${this.compressSourceCode(req.context)}
1191
1139
  }
1192
1140
  return `${prefix}${cleanPath}`;
1193
1141
  }
1142
+ /**
1143
+ * 构建 import 路径重写映射
1144
+ * 将源码中可能引用自身的各种写法,映射到测试文件中正确的导入路径
1145
+ */
1146
+ buildImportPathRewrites(testType, targetPath) {
1147
+ const rewrites = /* @__PURE__ */ new Map();
1148
+ if (!targetPath) return rewrites;
1149
+ const correctImportPath = this.computeTestImportPath(testType, targetPath);
1150
+ const variations = /* @__PURE__ */ new Set();
1151
+ variations.add(targetPath);
1152
+ variations.add(targetPath.replace(/^\.\//, ""));
1153
+ const withoutExt = targetPath.replace(/\.(ts|js|tsx|jsx)$/, "");
1154
+ variations.add(withoutExt);
1155
+ const srcDirMatch = targetPath.match(/^(?:\.\/)?(src\/.+)$/);
1156
+ if (srcDirMatch) {
1157
+ variations.add(`@/${srcDirMatch[1]}`);
1158
+ variations.add(`@/${srcDirMatch[1].replace(/\.(ts|js|tsx|jsx)$/, "")}`);
1159
+ }
1160
+ const pathParts = targetPath.replace(/^\.\//, "").split("/");
1161
+ const fileName = pathParts[pathParts.length - 1];
1162
+ const fileNameNoExt = fileName.replace(/\.(ts|js|tsx|jsx|vue)$/, "");
1163
+ variations.add(`./${fileName}`);
1164
+ variations.add(`./${fileNameNoExt}`);
1165
+ for (let i = 1; i < pathParts.length; i++) {
1166
+ const relPath = "../".repeat(i) + pathParts.slice(i).join("/");
1167
+ variations.add(relPath);
1168
+ variations.add(relPath.replace(/\.(ts|js|tsx|jsx)$/, ""));
1169
+ }
1170
+ for (const variant of variations) {
1171
+ if (variant && variant !== correctImportPath) {
1172
+ rewrites.set(variant, correctImportPath);
1173
+ }
1174
+ }
1175
+ return rewrites;
1176
+ }
1177
+ /**
1178
+ * 重写源码中的 import 路径
1179
+ * 将 from 'oldPath' / from "oldPath" 替换为正确的路径
1180
+ */
1181
+ rewriteImportPaths(code, rewrites) {
1182
+ let result = code;
1183
+ for (const [oldPath, newPath] of rewrites) {
1184
+ const escaped = oldPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1185
+ const singleQuoteRegex = new RegExp(`(from\\s+')${escaped}(')`, "g");
1186
+ const doubleQuoteRegex = new RegExp(`(from\\s+")${escaped}(")`, "g");
1187
+ result = result.replace(singleQuoteRegex, `$1${newPath}$2`);
1188
+ result = result.replace(doubleQuoteRegex, `$1${newPath}$2`);
1189
+ }
1190
+ return result;
1191
+ }
1194
1192
  parseGenerateTestResponse(content) {
1195
1193
  const codeBlockMatch = content.match(/```(?:typescript|ts|javascript|js)?\s*\n([\s\S]*?)```/);
1196
1194
  const code = codeBlockMatch ? codeBlockMatch[1].trim() : content.replace(/^(?:```[\s\S]*?\n)?/, "").replace(/\n?```$/, "").trim();
@@ -1213,12 +1211,14 @@ SUGGESTIONS:- \u5EFA\u8BAE(\u6BCF\u884C\u4E00\u4E2A)`;
1213
1211
  }
1214
1212
  buildReviewTestUserPrompt(req) {
1215
1213
  const importPath = this.computeTestImportPath(req.testType, req.target);
1216
- let prompt = `\u5BA1\u67E5\u6D4B\u8BD5\u4EE3\u7801\u3002\u88AB\u6D4B:${req.target} \u7C7B\u578B:${req.testType} import\u8DEF\u5F84:${importPath}
1214
+ let prompt = `\u5BA1\u67E5\u6D4B\u8BD5\u4EE3\u7801\u3002\u88AB\u6D4B:${req.target} \u7C7B\u578B:${req.testType}
1215
+ \u3010\u5F3A\u5236\u3011import\u88AB\u6D4B\u6A21\u5757\u5FC5\u987B\u7528: ${importPath}
1217
1216
  `;
1217
+ const importPathRewrites = this.buildImportPathRewrites(req.testType, req.target);
1218
1218
  prompt += `
1219
1219
  \u6E90\u7801:
1220
1220
  \`\`\`
1221
- ${this.compressSourceCode(req.sourceCode, 2e3)}
1221
+ ${this.compressSourceCode(req.sourceCode, 2e3, importPathRewrites)}
1222
1222
  \`\`\`
1223
1223
  `;
1224
1224
  prompt += `
@@ -1237,6 +1237,11 @@ ${req.testCode}
1237
1237
  if (req.analysis.emits?.length) {
1238
1238
  parts.push(`Emits:${req.analysis.emits.map((e) => `${e.name}(${e.params?.join(",") || ""})`).join(",")}`);
1239
1239
  }
1240
+ if (req.analysis.importSignatures?.length) {
1241
+ parts.push(`\u4F9D\u8D56:${req.analysis.importSignatures.map(
1242
+ (imp) => `${imp.source}{${Object.entries(imp.signatures).map(([k, v]) => `${k}:${v}`).join(",")}}`
1243
+ ).join(";")}`);
1244
+ }
1240
1245
  if (parts.length > 0) {
1241
1246
  prompt += `
1242
1247
  \u5206\u6790:${parts.join("|")}`;
@@ -1428,15 +1433,18 @@ import path4 from "path";
1428
1433
  function analyzeFile(filePath) {
1429
1434
  const absolutePath = path4.resolve(process.cwd(), filePath);
1430
1435
  if (!fs4.existsSync(absolutePath)) {
1431
- return { filePath, exports: [], apiCalls: [] };
1436
+ return { filePath, exports: [], imports: [], importSignatures: [], apiCalls: [] };
1432
1437
  }
1433
1438
  const content = fs4.readFileSync(absolutePath, "utf-8");
1434
1439
  const ext = path4.extname(filePath);
1435
1440
  const result = {
1436
1441
  filePath,
1437
1442
  exports: extractExports(content, ext),
1443
+ imports: extractImports(content),
1444
+ importSignatures: [],
1438
1445
  apiCalls: extractAPICalls(content, filePath)
1439
1446
  };
1447
+ result.importSignatures = analyzeImportSignatures(result.imports, path4.dirname(absolutePath));
1440
1448
  if (ext === ".vue") {
1441
1449
  result.vueAnalysis = analyzeVueComponent(content, path4.basename(filePath, ".vue"));
1442
1450
  const scriptMatch = content.match(/<script[^>]*>([\s\S]*?)<\/script>/);
@@ -1856,6 +1864,90 @@ function generateMockRoutesFromAPICalls(apiCalls) {
1856
1864
  }
1857
1865
  return routes;
1858
1866
  }
1867
+ function extractImports(content) {
1868
+ const imports = [];
1869
+ const namedImportRegex = /import\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/g;
1870
+ let match;
1871
+ while ((match = namedImportRegex.exec(content)) !== null) {
1872
+ const names = match[1].split(",").map((n) => {
1873
+ const parts = n.trim().split(/\s+as\s+/);
1874
+ return parts[0].trim();
1875
+ }).filter(Boolean);
1876
+ imports.push({ names, source: match[2], isDefault: false, isNamespace: false });
1877
+ }
1878
+ const defaultImportRegex = /import\s+(\w+)\s+from\s*['"]([^'"]+)['"]/g;
1879
+ while ((match = defaultImportRegex.exec(content)) !== null) {
1880
+ const fullLine = content.slice(Math.max(0, match.index - 5), match.index + match[0].length);
1881
+ if (fullLine.includes("{")) continue;
1882
+ imports.push({ names: [match[1]], source: match[2], isDefault: true, isNamespace: false });
1883
+ }
1884
+ const namespaceImportRegex = /import\s*\*\s*as\s+(\w+)\s*from\s*['"]([^'"]+)['"]/g;
1885
+ while ((match = namespaceImportRegex.exec(content)) !== null) {
1886
+ imports.push({ names: [match[1]], source: match[2], isDefault: false, isNamespace: true });
1887
+ }
1888
+ return imports;
1889
+ }
1890
+ function analyzeImportSignatures(imports, baseDir) {
1891
+ const signatures = [];
1892
+ for (const imp of imports) {
1893
+ if (!imp.source.startsWith(".") && !imp.source.startsWith("@/") && !imp.source.startsWith("~")) {
1894
+ continue;
1895
+ }
1896
+ const resolvedPath = resolveImportPath(imp.source, baseDir);
1897
+ if (!resolvedPath || !fs4.existsSync(resolvedPath)) continue;
1898
+ try {
1899
+ const content = fs4.readFileSync(resolvedPath, "utf-8");
1900
+ const ext = path4.extname(resolvedPath);
1901
+ const exports = extractExports(content, ext);
1902
+ const sigMap = {};
1903
+ for (const name of imp.names) {
1904
+ const exp = exports.find((e) => e.name === name);
1905
+ if (exp) {
1906
+ const params = exp.params.length > 0 ? `(${exp.params.join(", ")})` : "";
1907
+ const ret = exp.returnType ? `: ${exp.returnType}` : "";
1908
+ const async = exp.isAsync ? "async " : "";
1909
+ sigMap[name] = `${async}${exp.kind}${params}${ret}`;
1910
+ } else if (imp.isDefault) {
1911
+ const defaultExp = exports.find((e) => e.kind === "default");
1912
+ if (defaultExp) {
1913
+ sigMap[name] = `default[${defaultExp.name || "anonymous"}]`;
1914
+ }
1915
+ } else if (imp.isNamespace) {
1916
+ sigMap[name] = `{${exports.map((e) => e.name).join(", ")}}`;
1917
+ }
1918
+ }
1919
+ if (ext === ".vue") {
1920
+ const vueAnalysis = analyzeVueComponent(content, path4.basename(resolvedPath, ".vue"));
1921
+ if (vueAnalysis.props.length > 0) {
1922
+ sigMap["__props__"] = vueAnalysis.props.map((p) => `${p.name}:${p.type}${p.required ? "!" : "?"}`).join(",");
1923
+ }
1924
+ }
1925
+ if (Object.keys(sigMap).length > 0) {
1926
+ signatures.push({ source: imp.source, signatures: sigMap });
1927
+ }
1928
+ } catch {
1929
+ }
1930
+ }
1931
+ return signatures;
1932
+ }
1933
+ function resolveImportPath(importSource, baseDir) {
1934
+ let resolved;
1935
+ if (importSource.startsWith("@/") || importSource.startsWith("~")) {
1936
+ const relativePath = importSource.replace(/^[@~]\//, "src/");
1937
+ resolved = path4.resolve(process.cwd(), relativePath);
1938
+ } else if (importSource.startsWith(".")) {
1939
+ resolved = path4.resolve(baseDir, importSource);
1940
+ } else {
1941
+ return null;
1942
+ }
1943
+ const extensions = [".ts", ".tsx", ".js", ".jsx", ".vue", "/index.ts", "/index.js"];
1944
+ if (fs4.existsSync(resolved)) return resolved;
1945
+ for (const ext of extensions) {
1946
+ const withExt = resolved + ext;
1947
+ if (fs4.existsSync(withExt)) return withExt;
1948
+ }
1949
+ return null;
1950
+ }
1859
1951
 
1860
1952
  // src/services/global-config.ts
1861
1953
  import fs5 from "fs";
@@ -1960,41 +2052,13 @@ async function executeInit(options) {
1960
2052
  }
1961
2053
  }
1962
2054
  const config = buildProjectConfig(projectInfo);
1963
- let configPath;
1964
2055
  const existingConfigPath = path6.join(process.cwd(), "qat.config.js");
1965
2056
  const existingTsPath = path6.join(process.cwd(), "qat.config.ts");
1966
2057
  const configExists = fs6.existsSync(existingConfigPath) || fs6.existsSync(existingTsPath);
1967
- if (configExists && !options.force) {
1968
- const { overwrite } = await inquirer.prompt([
1969
- {
1970
- type: "confirm",
1971
- name: "overwrite",
1972
- message: "\u914D\u7F6E\u6587\u4EF6 qat.config.js \u5DF2\u5B58\u5728\uFF0C\u662F\u5426\u8986\u76D6\uFF1F",
1973
- default: true
1974
- }
1975
- ]);
1976
- if (!overwrite) {
1977
- console.log(chalk3.gray(" \u4FDD\u7559\u73B0\u6709\u914D\u7F6E\u6587\u4EF6\uFF0C\u7EE7\u7EED\u540E\u7EED\u6B65\u9AA4..."));
1978
- configPath = existingConfigPath;
1979
- } else {
1980
- const fileSpinner = ora("\u6B63\u5728\u8986\u76D6\u914D\u7F6E\u6587\u4EF6...").start();
1981
- try {
1982
- configPath = await writeConfigFile(process.cwd(), config, true);
1983
- fileSpinner.succeed("\u914D\u7F6E\u6587\u4EF6\u5DF2\u8986\u76D6");
1984
- } catch (error) {
1985
- fileSpinner.fail("\u914D\u7F6E\u6587\u4EF6\u8986\u76D6\u5931\u8D25");
1986
- throw error;
1987
- }
1988
- }
2058
+ if (!configExists) {
2059
+ console.log(chalk3.gray(" \u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6\uFF0C\u4F7F\u7528\u9ED8\u8BA4\u914D\u7F6E\uFF08\u9700\u8981\u81EA\u5B9A\u4E49\u65F6\u53EF\u521B\u5EFA qat.config.js\uFF09"));
1989
2060
  } else {
1990
- const fileSpinner = ora("\u6B63\u5728\u751F\u6210\u914D\u7F6E\u6587\u4EF6...").start();
1991
- try {
1992
- configPath = await writeConfigFile(process.cwd(), config, options.force);
1993
- fileSpinner.succeed("\u914D\u7F6E\u6587\u4EF6\u5DF2\u751F\u6210");
1994
- } catch (error) {
1995
- fileSpinner.fail("\u914D\u7F6E\u6587\u4EF6\u751F\u6210\u5931\u8D25");
1996
- throw error;
1997
- }
2061
+ console.log(chalk3.green(` \u2713 \u5DF2\u6709\u914D\u7F6E\u6587\u4EF6: ${path6.basename(fs6.existsSync(existingConfigPath) ? existingConfigPath : existingTsPath)}`));
1998
2062
  }
1999
2063
  const dirSpinner = ora("\u6B63\u5728\u521B\u5EFA\u6D4B\u8BD5\u76EE\u5F55...").start();
2000
2064
  const createdDirs = createTestDirectories(config);
@@ -2023,7 +2087,7 @@ async function executeInit(options) {
2023
2087
  console.log(chalk3.cyan(`
2024
2088
  \u53D1\u73B0 ${totalFiles} \u4E2A\u53EF\u6D4B\u8BD5\u6587\u4EF6 (${components.length} \u7EC4\u4EF6, ${utilities.length} \u5DE5\u5177/\u670D\u52A1)`));
2025
2089
  }
2026
- displayResult(configPath, createdDirs, totalFiles, projectInfo);
2090
+ displayResult(createdDirs, totalFiles, projectInfo);
2027
2091
  }
2028
2092
  async function promptAIConfig() {
2029
2093
  const answers = await inquirer.prompt([
@@ -2160,12 +2224,8 @@ function createTestDirectories(config) {
2160
2224
  }
2161
2225
  return dirs;
2162
2226
  }
2163
- function displayResult(configPath, createdDirs, totalTestableFiles, projectInfo) {
2164
- const relativeConfig = path6.relative(process.cwd(), configPath);
2227
+ function displayResult(createdDirs, totalTestableFiles, projectInfo) {
2165
2228
  console.log(chalk3.green("\n \u2713 \u9879\u76EE\u521D\u59CB\u5316\u5B8C\u6210!\n"));
2166
- console.log(chalk3.white(" \u5DF2\u751F\u6210\u914D\u7F6E:"));
2167
- console.log(chalk3.gray(` ${relativeConfig}`));
2168
- console.log();
2169
2229
  if (createdDirs.length > 0) {
2170
2230
  console.log(chalk3.white(" \u5DF2\u521B\u5EFA\u76EE\u5F55:"));
2171
2231
  for (const dir of createdDirs) {
@@ -2173,6 +2233,9 @@ function displayResult(configPath, createdDirs, totalTestableFiles, projectInfo)
2173
2233
  }
2174
2234
  console.log();
2175
2235
  }
2236
+ console.log(chalk3.white(" \u914D\u7F6E:"));
2237
+ console.log(chalk3.gray(" \u9ED8\u8BA4\u914D\u7F6E\u5DF2\u5185\u7F6E\uFF0C\u9700\u8981\u81EA\u5B9A\u4E49\u65F6\u521B\u5EFA qat.config.js"));
2238
+ console.log();
2176
2239
  console.log(chalk3.cyan(" \u4E0B\u4E00\u6B65:"));
2177
2240
  if (totalTestableFiles > 0) {
2178
2241
  console.log(chalk3.gray(` 1. qat create \u9009\u62E9\u6587\u4EF6\u5E76\u751F\u6210\u6D4B\u8BD5\u7528\u4F8B (\u53D1\u73B0 ${totalTestableFiles} \u4E2A\u53EF\u6D4B\u8BD5\u6587\u4EF6)`));
@@ -2193,7 +2256,7 @@ import chalk5 from "chalk";
2193
2256
  import inquirer2 from "inquirer";
2194
2257
  import ora3 from "ora";
2195
2258
  import fs8 from "fs";
2196
- import path8 from "path";
2259
+ import path9 from "path";
2197
2260
 
2198
2261
  // src/services/template.ts
2199
2262
  import fs7 from "fs";
@@ -2242,7 +2305,7 @@ Handlebars.registerHelper("propTestValue", (prop) => {
2242
2305
  };
2243
2306
  return map[prop.type] || "'test-value'";
2244
2307
  });
2245
- function resolveImportPath(testType, targetPath) {
2308
+ function resolveImportPath2(testType, targetPath) {
2246
2309
  if (!targetPath) return targetPath;
2247
2310
  const testDirMap = {
2248
2311
  unit: "tests/unit",
@@ -2268,7 +2331,7 @@ function resolveImportPath(testType, targetPath) {
2268
2331
  function renderTemplate(type, context) {
2269
2332
  const templateContent = loadTemplate(type);
2270
2333
  const template = Handlebars.compile(templateContent);
2271
- const resolvedTarget = resolveImportPath(type, context.target);
2334
+ const resolvedTarget = resolveImportPath2(type, context.target);
2272
2335
  const fullContext = {
2273
2336
  vueVersion: 3,
2274
2337
  typescript: true,
@@ -2652,10 +2715,11 @@ function toPascalCase(str) {
2652
2715
  // src/services/test-reviewer.ts
2653
2716
  import chalk4 from "chalk";
2654
2717
  import ora2 from "ora";
2718
+ import path8 from "path";
2655
2719
  var MAX_RETRIES = 3;
2656
2720
  var REVIEW_THRESHOLD = 0.6;
2657
2721
  async function generateWithReview(params) {
2658
- const { testType, targetPath, sourceCode, analysis, aiConfig, framework, onAttempt } = params;
2722
+ const { testType, targetPath, sourceCode, analysis, aiConfig, framework, onAttempt, onProgress } = params;
2659
2723
  const generatorProvider = createAIProvider(aiConfig);
2660
2724
  const reviewerProvider = createAIProvider(aiConfig);
2661
2725
  if (!generatorProvider.capabilities.generateTest) {
@@ -2667,9 +2731,12 @@ async function generateWithReview(params) {
2667
2731
  let lastReview = null;
2668
2732
  let approved = false;
2669
2733
  let attempts = 0;
2734
+ const targetName = path8.basename(targetPath);
2670
2735
  for (let i = 0; i < MAX_RETRIES; i++) {
2671
2736
  attempts = i + 1;
2672
2737
  onAttempt?.(attempts, MAX_RETRIES);
2738
+ const genLabel = attempts > 1 ? `\u27F3 \u91CD\u65B0\u751F\u6210 (${attempts}/${MAX_RETRIES})` : "\u751F\u6210\u6D4B\u8BD5\u4EE3\u7801";
2739
+ onProgress?.(`${genLabel} \u2190 ${targetName}`);
2673
2740
  if (process.env.QAT_DEBUG === "true") {
2674
2741
  console.error(chalk4.gray(`[DEBUG] \u2500\u2500\u2500 \u751F\u6210\u8005 AI \u7B2C${attempts}\u6B21\u5C1D\u8BD5 \u2500\u2500\u2500`));
2675
2742
  }
@@ -2693,6 +2760,7 @@ ${lastReview.suggestions.map((s) => `- ${s}`).join("\n")}
2693
2760
  currentCode = generateResponse.code;
2694
2761
  currentDescription = generateResponse.description;
2695
2762
  currentConfidence = generateResponse.confidence;
2763
+ onProgress?.(`\u5BA1\u8BA1\u5BA1\u67E5 \u2190 ${targetName}`);
2696
2764
  if (process.env.QAT_DEBUG === "true") {
2697
2765
  console.error(chalk4.gray(`[DEBUG] \u2500\u2500\u2500 \u5BA1\u8BA1\u5458 AI \u5BA1\u67E5 \u2500\u2500\u2500`));
2698
2766
  }
@@ -2851,11 +2919,13 @@ async function executeCreate(options) {
2851
2919
  const reviewReportEntries = [];
2852
2920
  for (const testType of types) {
2853
2921
  for (const testTarget of targets) {
2854
- const spinner = ora3(`\u6B63\u5728\u751F\u6210 ${TEST_TYPE_LABELS[testType]} - ${path8.basename(testTarget)}...`).start();
2922
+ const spinner = ora3(`\u6B63\u5728\u751F\u6210 ${TEST_TYPE_LABELS[testType]} - ${path9.basename(testTarget)}...`).start();
2855
2923
  try {
2856
2924
  let content;
2857
2925
  if (useAI && testTarget) {
2858
- const aiResult = await generateWithAI(testType, name, testTarget, config);
2926
+ const aiResult = await generateWithAI(testType, name, testTarget, config, (text) => {
2927
+ spinner.text = `${text}`;
2928
+ });
2859
2929
  content = aiResult.code;
2860
2930
  if (aiResult.reviewEntry) {
2861
2931
  reviewReportEntries.push(aiResult.reviewEntry);
@@ -2896,9 +2966,9 @@ async function executeCreate(options) {
2896
2966
  });
2897
2967
  }
2898
2968
  const outputDir = getTestOutputDir(testType);
2899
- const filePath = path8.join(outputDir, `${name}.${testType === "e2e" ? "spec" : "test"}.ts`);
2969
+ const filePath = path9.join(outputDir, `${name}.${testType === "e2e" ? "spec" : "test"}.ts`);
2900
2970
  if (fs8.existsSync(filePath)) {
2901
- spinner.warn(`\u6D4B\u8BD5\u6587\u4EF6\u5DF2\u5B58\u5728: ${path8.relative(process.cwd(), filePath)}`);
2971
+ spinner.warn(`\u6D4B\u8BD5\u6587\u4EF6\u5DF2\u5B58\u5728: ${path9.relative(process.cwd(), filePath)}`);
2902
2972
  skippedCount++;
2903
2973
  continue;
2904
2974
  }
@@ -2906,10 +2976,10 @@ async function executeCreate(options) {
2906
2976
  fs8.mkdirSync(outputDir, { recursive: true });
2907
2977
  }
2908
2978
  fs8.writeFileSync(filePath, content, "utf-8");
2909
- spinner.succeed(`${TEST_TYPE_LABELS[testType]} - ${path8.basename(testTarget)}`);
2979
+ spinner.succeed(`${TEST_TYPE_LABELS[testType]} - ${path9.basename(testTarget)}`);
2910
2980
  createdFiles.push({ type: testType, filePath });
2911
2981
  } catch (error) {
2912
- spinner.fail(`${TEST_TYPE_LABELS[testType]} - ${path8.basename(testTarget)} \u521B\u5EFA\u5931\u8D25`);
2982
+ spinner.fail(`${TEST_TYPE_LABELS[testType]} - ${path9.basename(testTarget)} \u521B\u5EFA\u5931\u8D25`);
2913
2983
  console.log(chalk5.gray(` ${error instanceof Error ? error.message : String(error)}`));
2914
2984
  }
2915
2985
  }
@@ -2976,22 +3046,22 @@ async function selectTargets(types, projectInfo, srcDir) {
2976
3046
  }
2977
3047
  function generateDefaultName(type, target) {
2978
3048
  if (!target) return `${type}-test`;
2979
- const basename = path8.basename(target, path8.extname(target));
3049
+ const basename = path9.basename(target, path9.extname(target));
2980
3050
  const cleaned = basename.replace(/[^a-zA-Z0-9]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
2981
3051
  return cleaned || `${type}-test`;
2982
3052
  }
2983
- async function generateWithAI(type, name, target, config) {
3053
+ async function generateWithAI(type, name, target, config, onProgress) {
2984
3054
  const provider = getAIProvider(config.ai);
2985
3055
  if (!provider.capabilities.generateTest) {
2986
3056
  throw new Error("\u5F53\u524D AI Provider \u4E0D\u652F\u6301\u6D4B\u8BD5\u751F\u6210");
2987
3057
  }
2988
3058
  let sourceCode = "";
2989
- const fullPath = path8.resolve(process.cwd(), target);
3059
+ const fullPath = path9.resolve(process.cwd(), target);
2990
3060
  if (fs8.existsSync(fullPath)) {
2991
3061
  sourceCode = fs8.readFileSync(fullPath, "utf-8");
2992
3062
  }
2993
3063
  const analysis = analyzeFile(target);
2994
- const analysisSummary = analysis.exports.length > 0 || analysis.vueAnalysis ? {
3064
+ const analysisSummary = analysis.exports.length > 0 || analysis.vueAnalysis || analysis.importSignatures.length > 0 ? {
2995
3065
  exports: analysis.exports.map((e) => ({
2996
3066
  name: e.name,
2997
3067
  kind: e.kind,
@@ -3008,7 +3078,8 @@ async function generateWithAI(type, name, target, config) {
3008
3078
  params: e.params
3009
3079
  })),
3010
3080
  methods: analysis.vueAnalysis?.methods,
3011
- computed: analysis.vueAnalysis?.computed
3081
+ computed: analysis.vueAnalysis?.computed,
3082
+ importSignatures: analysis.importSignatures.length > 0 ? analysis.importSignatures : void 0
3012
3083
  } : void 0;
3013
3084
  const reviewResult = await generateWithReview({
3014
3085
  testType: type,
@@ -3016,7 +3087,8 @@ async function generateWithAI(type, name, target, config) {
3016
3087
  sourceCode,
3017
3088
  analysis: analysisSummary,
3018
3089
  aiConfig: config.ai,
3019
- framework: "vue"
3090
+ framework: "vue",
3091
+ onProgress
3020
3092
  });
3021
3093
  const headerComment = [
3022
3094
  `// AI Generated Test - ${name}`,
@@ -3060,7 +3132,7 @@ function displayCreateResult(createdFiles, skippedCount, usedAI) {
3060
3132
  }
3061
3133
  console.log();
3062
3134
  for (const { type, filePath } of createdFiles) {
3063
- const relativePath = path8.relative(process.cwd(), filePath);
3135
+ const relativePath = path9.relative(process.cwd(), filePath);
3064
3136
  console.log(chalk5.white(` ${chalk5.cyan(TEST_TYPE_LABELS[type])} ${chalk5.gray(relativePath)}`));
3065
3137
  }
3066
3138
  if (usedAI) {
@@ -3085,11 +3157,11 @@ import chalk6 from "chalk";
3085
3157
  import inquirer3 from "inquirer";
3086
3158
  import ora4 from "ora";
3087
3159
  import fs11 from "fs";
3088
- import path12 from "path";
3160
+ import path13 from "path";
3089
3161
 
3090
3162
  // src/runners/vitest-runner.ts
3091
3163
  import { execFile } from "child_process";
3092
- import path9 from "path";
3164
+ import path10 from "path";
3093
3165
  import fs9 from "fs";
3094
3166
  import os2 from "os";
3095
3167
  var isVerbose = () => process.env.QAT_VERBOSE === "true";
@@ -3171,7 +3243,7 @@ function buildVitestArgs(options) {
3171
3243
  return args;
3172
3244
  }
3173
3245
  async function execVitest(args) {
3174
- const tmpFile = path9.join(os2.tmpdir(), `qat-vitest-result-${Date.now()}.json`);
3246
+ const tmpFile = path10.join(os2.tmpdir(), `qat-vitest-result-${Date.now()}.json`);
3175
3247
  const argsWithOutput = [...args, "--outputFile", tmpFile];
3176
3248
  return new Promise((resolve, reject) => {
3177
3249
  const npx = process.platform === "win32" ? "npx.cmd" : "npx";
@@ -3250,7 +3322,7 @@ function parseVitestJSON(jsonStr) {
3250
3322
  for (const fileResult of data.testResults) {
3251
3323
  const suiteTests = parseTestResults(fileResult);
3252
3324
  suites.push({
3253
- name: path9.basename(fileResult.name || "unknown"),
3325
+ name: path10.basename(fileResult.name || "unknown"),
3254
3326
  file: fileResult.name || "unknown",
3255
3327
  type: "unit",
3256
3328
  status: mapVitestStatus(fileResult.status),
@@ -3361,7 +3433,7 @@ function parseFromStdout(output) {
3361
3433
  if (data.testResults && Array.isArray(data.testResults)) {
3362
3434
  for (const fileResult of data.testResults) {
3363
3435
  suites.push({
3364
- name: path9.basename(fileResult.name || "unknown"),
3436
+ name: path10.basename(fileResult.name || "unknown"),
3365
3437
  file: fileResult.name || "unknown",
3366
3438
  type: "unit",
3367
3439
  status: mapVitestStatus(fileResult.status),
@@ -3397,7 +3469,7 @@ function parseVitestTextOutput(output, hasError) {
3397
3469
  if (isPassed) totalPassed += testCount;
3398
3470
  else totalFailed += testCount;
3399
3471
  suites.push({
3400
- name: path9.basename(file),
3472
+ name: path10.basename(file),
3401
3473
  file,
3402
3474
  type: "unit",
3403
3475
  status: isPassed ? "passed" : "failed",
@@ -3498,7 +3570,7 @@ function extractCoverage(coverageMap) {
3498
3570
 
3499
3571
  // src/runners/playwright-runner.ts
3500
3572
  import { execFile as execFile2 } from "child_process";
3501
- import path10 from "path";
3573
+ import path11 from "path";
3502
3574
  async function runPlaywright(options) {
3503
3575
  const startTime = Date.now();
3504
3576
  const args = buildPlaywrightArgs(options);
@@ -3698,7 +3770,7 @@ function parsePlaywrightTextOutput(output, hasError) {
3698
3770
  let existingSuite = suites.find((s) => s.file === file);
3699
3771
  if (!existingSuite) {
3700
3772
  existingSuite = {
3701
- name: path10.basename(file),
3773
+ name: path11.basename(file),
3702
3774
  file,
3703
3775
  type: "e2e",
3704
3776
  status: "passed",
@@ -3933,7 +4005,7 @@ function calculateAverageMetrics(results) {
3933
4005
 
3934
4006
  // src/services/reporter.ts
3935
4007
  import fs10 from "fs";
3936
- import path11 from "path";
4008
+ import path12 from "path";
3937
4009
  function aggregateResults(results) {
3938
4010
  const summary = {
3939
4011
  total: 0,
@@ -4129,13 +4201,13 @@ function generateMDReport(data) {
4129
4201
  }
4130
4202
  function writeReportToDisk(data, outputDir) {
4131
4203
  const md = generateMDReport(data);
4132
- const dir = path11.resolve(outputDir);
4204
+ const dir = path12.resolve(outputDir);
4133
4205
  if (!fs10.existsSync(dir)) {
4134
4206
  fs10.mkdirSync(dir, { recursive: true });
4135
4207
  }
4136
- const mdPath = path11.join(dir, "report.md");
4208
+ const mdPath = path12.join(dir, "report.md");
4137
4209
  fs10.writeFileSync(mdPath, md, "utf-8");
4138
- const jsonPath = path11.join(dir, "report.json");
4210
+ const jsonPath = path12.join(dir, "report.json");
4139
4211
  fs10.writeFileSync(jsonPath, JSON.stringify(data, null, 2), "utf-8");
4140
4212
  return mdPath;
4141
4213
  }
@@ -4356,7 +4428,7 @@ async function executeRun(options) {
4356
4428
  const reportData = aggregateResults(results);
4357
4429
  const outputDir = config.report.outputDir || "qat-report";
4358
4430
  const reportPath = writeReportToDisk(reportData, outputDir);
4359
- const relativePath = path12.relative(process.cwd(), reportPath);
4431
+ const relativePath = path13.relative(process.cwd(), reportPath);
4360
4432
  console.log(chalk6.gray(`
4361
4433
  \u62A5\u544A\u5DF2\u751F\u6210: ${chalk6.cyan(relativePath)}`));
4362
4434
  console.log();
@@ -4743,8 +4815,8 @@ async function checkDevServer(config) {
4743
4815
  }
4744
4816
  async function startDevServer(config) {
4745
4817
  const { spawn } = await import("child_process");
4746
- const hasPnpm = fs11.existsSync(path12.join(process.cwd(), "pnpm-lock.yaml"));
4747
- const hasYarn = fs11.existsSync(path12.join(process.cwd(), "yarn.lock"));
4818
+ const hasPnpm = fs11.existsSync(path13.join(process.cwd(), "pnpm-lock.yaml"));
4819
+ const hasYarn = fs11.existsSync(path13.join(process.cwd(), "yarn.lock"));
4748
4820
  const pkgCmd = hasPnpm ? "pnpm" : hasYarn ? "yarn" : "npm";
4749
4821
  console.log(chalk6.cyan(` \u6B63\u5728\u542F\u52A8 dev server (${pkgCmd} run dev) ...`));
4750
4822
  const child = spawn(pkgCmd, ["run", "dev"], {
@@ -4773,13 +4845,13 @@ async function startDevServer(config) {
4773
4845
  }
4774
4846
  function saveRunResults(results) {
4775
4847
  if (results.length === 0) return;
4776
- const resultsPath = path12.join(process.cwd(), RESULTS_DIR);
4848
+ const resultsPath = path13.join(process.cwd(), RESULTS_DIR);
4777
4849
  if (!fs11.existsSync(resultsPath)) {
4778
4850
  fs11.mkdirSync(resultsPath, { recursive: true });
4779
4851
  }
4780
4852
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
4781
4853
  const fileName = `result-${timestamp}.json`;
4782
- const filePath = path12.join(resultsPath, fileName);
4854
+ const filePath = path13.join(resultsPath, fileName);
4783
4855
  const data = {
4784
4856
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
4785
4857
  results
@@ -4789,13 +4861,13 @@ function saveRunResults(results) {
4789
4861
  const files = fs11.readdirSync(resultsPath).filter((f) => f.startsWith("result-") && f.endsWith(".json")).sort();
4790
4862
  while (files.length > 20) {
4791
4863
  const oldest = files.shift();
4792
- fs11.unlinkSync(path12.join(resultsPath, oldest));
4864
+ fs11.unlinkSync(path13.join(resultsPath, oldest));
4793
4865
  }
4794
4866
  } catch {
4795
4867
  }
4796
4868
  }
4797
4869
  function checkTestDependencies(types) {
4798
- const pkgPath = path12.join(process.cwd(), "package.json");
4870
+ const pkgPath = path13.join(process.cwd(), "package.json");
4799
4871
  let allDeps = {};
4800
4872
  if (fs11.existsSync(pkgPath)) {
4801
4873
  try {
@@ -4821,8 +4893,8 @@ function checkTestDependencies(types) {
4821
4893
  return missingDeps;
4822
4894
  }
4823
4895
  async function installTestDependencies(missingDeps) {
4824
- const hasPnpm = fs11.existsSync(path12.join(process.cwd(), "pnpm-lock.yaml"));
4825
- const hasYarn = fs11.existsSync(path12.join(process.cwd(), "yarn.lock"));
4896
+ const hasPnpm = fs11.existsSync(path13.join(process.cwd(), "pnpm-lock.yaml"));
4897
+ const hasYarn = fs11.existsSync(path13.join(process.cwd(), "yarn.lock"));
4826
4898
  const pkgManager = hasPnpm ? "pnpm" : hasYarn ? "yarn" : "npm";
4827
4899
  const { execFile: execFile5 } = await import("child_process");
4828
4900
  let allSuccess = true;
@@ -4990,7 +5062,7 @@ function showStatus() {
4990
5062
  import chalk8 from "chalk";
4991
5063
  import ora6 from "ora";
4992
5064
  import fs12 from "fs";
4993
- import path13 from "path";
5065
+ import path14 from "path";
4994
5066
  var RESULTS_DIR2 = ".qat-results";
4995
5067
  function registerReportCommand(program2) {
4996
5068
  program2.command("report").description("\u751F\u6210\u6D4B\u8BD5\u62A5\u544A - \u805A\u5408\u6240\u6709\u6D4B\u8BD5\u7ED3\u679C\u5E76\u8F93\u51FAHTML").option("-o, --output <dir>", "\u62A5\u544A\u8F93\u51FA\u76EE\u5F55").option("--open", "\u751F\u6210\u540E\u81EA\u52A8\u6253\u5F00\u62A5\u544A", false).action(async (options) => {
@@ -5026,13 +5098,13 @@ async function executeReport(options) {
5026
5098
  }
5027
5099
  function collectResults() {
5028
5100
  const results = [];
5029
- const resultsPath = path13.join(process.cwd(), RESULTS_DIR2);
5101
+ const resultsPath = path14.join(process.cwd(), RESULTS_DIR2);
5030
5102
  if (!fs12.existsSync(resultsPath)) {
5031
5103
  return results;
5032
5104
  }
5033
5105
  const files = fs12.readdirSync(resultsPath).filter((f) => f.endsWith(".json")).sort().reverse();
5034
5106
  if (files.length > 0) {
5035
- const latestFile = path13.join(resultsPath, files[0]);
5107
+ const latestFile = path14.join(resultsPath, files[0]);
5036
5108
  try {
5037
5109
  const data = JSON.parse(fs12.readFileSync(latestFile, "utf-8"));
5038
5110
  if (Array.isArray(data)) {
@@ -5046,22 +5118,22 @@ function collectResults() {
5046
5118
  return results;
5047
5119
  }
5048
5120
  function saveResultToHistory(reportData) {
5049
- const resultsPath = path13.join(process.cwd(), RESULTS_DIR2);
5121
+ const resultsPath = path14.join(process.cwd(), RESULTS_DIR2);
5050
5122
  if (!fs12.existsSync(resultsPath)) {
5051
5123
  fs12.mkdirSync(resultsPath, { recursive: true });
5052
5124
  }
5053
5125
  const timestamp = new Date(reportData.timestamp).toISOString().replace(/[:.]/g, "-");
5054
5126
  const fileName = `result-${timestamp}.json`;
5055
- const filePath = path13.join(resultsPath, fileName);
5127
+ const filePath = path14.join(resultsPath, fileName);
5056
5128
  fs12.writeFileSync(filePath, JSON.stringify(reportData, null, 2), "utf-8");
5057
5129
  const files = fs12.readdirSync(resultsPath).filter((f) => f.startsWith("result-") && f.endsWith(".json")).sort();
5058
5130
  while (files.length > 20) {
5059
5131
  const oldest = files.shift();
5060
- fs12.unlinkSync(path13.join(resultsPath, oldest));
5132
+ fs12.unlinkSync(path14.join(resultsPath, oldest));
5061
5133
  }
5062
5134
  }
5063
5135
  function displayReportResult(reportPath, data) {
5064
- const relativePath = path13.relative(process.cwd(), reportPath);
5136
+ const relativePath = path14.relative(process.cwd(), reportPath);
5065
5137
  const passRate = data.summary.total > 0 ? (data.summary.passed / data.summary.total * 100).toFixed(1) : "0";
5066
5138
  console.log();
5067
5139
  console.log(chalk8.green(" \u2713 \u6D4B\u8BD5\u62A5\u544A\u5DF2\u751F\u6210"));
@@ -5119,7 +5191,7 @@ import ora7 from "ora";
5119
5191
 
5120
5192
  // src/services/visual.ts
5121
5193
  import fs13 from "fs";
5122
- import path14 from "path";
5194
+ import path15 from "path";
5123
5195
  import pixelmatch from "pixelmatch";
5124
5196
  import { PNG } from "pngjs";
5125
5197
  function compareImages(baselinePath, currentPath, diffOutputPath, threshold = 0.1) {
@@ -5158,7 +5230,7 @@ function compareImages(baselinePath, currentPath, diffOutputPath, threshold = 0.
5158
5230
  const passed = diffRatio <= threshold;
5159
5231
  let diffPath;
5160
5232
  if (diffPixels > 0) {
5161
- const diffDir = path14.dirname(diffOutputPath);
5233
+ const diffDir = path15.dirname(diffOutputPath);
5162
5234
  if (!fs13.existsSync(diffDir)) {
5163
5235
  fs13.mkdirSync(diffDir, { recursive: true });
5164
5236
  }
@@ -5179,7 +5251,7 @@ function createBaseline(currentPath, baselinePath) {
5179
5251
  if (!fs13.existsSync(currentPath)) {
5180
5252
  throw new Error(`\u5F53\u524D\u622A\u56FE\u4E0D\u5B58\u5728: ${currentPath}`);
5181
5253
  }
5182
- const baselineDir = path14.dirname(baselinePath);
5254
+ const baselineDir = path15.dirname(baselinePath);
5183
5255
  if (!fs13.existsSync(baselineDir)) {
5184
5256
  fs13.mkdirSync(baselineDir, { recursive: true });
5185
5257
  }
@@ -5193,9 +5265,9 @@ function updateAllBaselines(currentDir, baselineDir) {
5193
5265
  }
5194
5266
  const files = fs13.readdirSync(currentDir).filter((f) => f.endsWith(".png"));
5195
5267
  for (const file of files) {
5196
- const currentPath = path14.join(currentDir, file);
5197
- const baselinePath = path14.join(baselineDir, file);
5198
- const baselineDirAbs = path14.dirname(baselinePath);
5268
+ const currentPath = path15.join(currentDir, file);
5269
+ const baselinePath = path15.join(baselineDir, file);
5270
+ const baselineDirAbs = path15.dirname(baselinePath);
5199
5271
  if (!fs13.existsSync(baselineDirAbs)) {
5200
5272
  fs13.mkdirSync(baselineDirAbs, { recursive: true });
5201
5273
  }
@@ -5211,7 +5283,7 @@ function cleanBaselines(baselineDir) {
5211
5283
  const files = fs13.readdirSync(baselineDir).filter((f) => f.endsWith(".png"));
5212
5284
  let count = 0;
5213
5285
  for (const file of files) {
5214
- fs13.unlinkSync(path14.join(baselineDir, file));
5286
+ fs13.unlinkSync(path15.join(baselineDir, file));
5215
5287
  count++;
5216
5288
  }
5217
5289
  return count;
@@ -5223,7 +5295,7 @@ function cleanDiffs(diffDir) {
5223
5295
  const files = fs13.readdirSync(diffDir).filter((f) => f.endsWith(".png"));
5224
5296
  let count = 0;
5225
5297
  for (const file of files) {
5226
- fs13.unlinkSync(path14.join(diffDir, file));
5298
+ fs13.unlinkSync(path15.join(diffDir, file));
5227
5299
  count++;
5228
5300
  }
5229
5301
  return count;
@@ -5235,9 +5307,9 @@ function compareDirectories(baselineDir, currentDir, diffDir, threshold = 0.1) {
5235
5307
  }
5236
5308
  const currentFiles = fs13.readdirSync(currentDir).filter((f) => f.endsWith(".png"));
5237
5309
  for (const file of currentFiles) {
5238
- const currentPath = path14.join(currentDir, file);
5239
- const baselinePath = path14.join(baselineDir, file);
5240
- const diffPath = path14.join(diffDir, file);
5310
+ const currentPath = path15.join(currentDir, file);
5311
+ const baselinePath = path15.join(baselineDir, file);
5312
+ const diffPath = path15.join(diffDir, file);
5241
5313
  if (!fs13.existsSync(baselinePath)) {
5242
5314
  createBaseline(currentPath, baselinePath);
5243
5315
  results.push({
@@ -5271,7 +5343,7 @@ function compareDirectories(baselineDir, currentDir, diffDir, threshold = 0.1) {
5271
5343
 
5272
5344
  // src/commands/visual.ts
5273
5345
  import fs14 from "fs";
5274
- import path15 from "path";
5346
+ import path16 from "path";
5275
5347
  function registerVisualCommand(program2) {
5276
5348
  program2.command("visual").description("\u89C6\u89C9\u56DE\u5F52\u6D4B\u8BD5 - \u622A\u56FE\u6BD4\u5BF9\u4E0E\u57FA\u7EBF\u7BA1\u7406").argument("<action>", "\u64CD\u4F5C\u7C7B\u578B (test|approve|clean)").option("--threshold <number>", "\u50CF\u7D20\u5DEE\u5F02\u9608\u503C (0-1)", "0.1").action(async (action, options) => {
5277
5349
  try {
@@ -5346,9 +5418,9 @@ async function runVisualTest(threshold, baselineDir, diffDir, config) {
5346
5418
  }
5347
5419
  function findCurrentScreenshotsDir(baselineDir) {
5348
5420
  const possibleDirs = [
5349
- path15.join(process.cwd(), "test-results"),
5350
- path15.join(process.cwd(), "tests", "visual", "current"),
5351
- path15.join(process.cwd(), baselineDir, "..", "current")
5421
+ path16.join(process.cwd(), "test-results"),
5422
+ path16.join(process.cwd(), "tests", "visual", "current"),
5423
+ path16.join(process.cwd(), baselineDir, "..", "current")
5352
5424
  ];
5353
5425
  for (const dir of possibleDirs) {
5354
5426
  if (fs14.existsSync(dir)) {
@@ -5364,7 +5436,7 @@ function findPngFiles(dir) {
5364
5436
  if (!fs14.existsSync(d)) return;
5365
5437
  const entries = fs14.readdirSync(d, { withFileTypes: true });
5366
5438
  for (const entry of entries) {
5367
- const fullPath = path15.join(d, entry.name);
5439
+ const fullPath = path16.join(d, entry.name);
5368
5440
  if (entry.isDirectory() && entry.name !== "node_modules") {
5369
5441
  walk(fullPath);
5370
5442
  } else if (entry.name.endsWith(".png")) {
@@ -5393,7 +5465,7 @@ function displayVisualResults(results, threshold) {
5393
5465
  console.log();
5394
5466
  console.log(chalk9.green(" \u901A\u8FC7\u7684\u622A\u56FE:"));
5395
5467
  for (const result of passed) {
5396
- const name = path15.basename(result.baselinePath);
5468
+ const name = path16.basename(result.baselinePath);
5397
5469
  if (result.totalPixels > 0) {
5398
5470
  const diffPct = (result.diffRatio * 100).toFixed(2);
5399
5471
  console.log(chalk9.green(` \u2713 ${name} (\u5DEE\u5F02: ${diffPct}%)`));
@@ -5406,7 +5478,7 @@ function displayVisualResults(results, threshold) {
5406
5478
  console.log();
5407
5479
  console.log(chalk9.red(" \u5931\u8D25\u7684\u622A\u56FE:"));
5408
5480
  for (const result of failed) {
5409
- const name = path15.basename(result.baselinePath);
5481
+ const name = path16.basename(result.baselinePath);
5410
5482
  if (result.diffPixels === -1) {
5411
5483
  console.log(chalk9.red(` \u2717 ${name} (\u5C3A\u5BF8\u4E0D\u5339\u914D)`));
5412
5484
  } else {
@@ -5414,7 +5486,7 @@ function displayVisualResults(results, threshold) {
5414
5486
  console.log(chalk9.red(` \u2717 ${name} (\u5DEE\u5F02: ${diffPct}%)`));
5415
5487
  }
5416
5488
  if (result.diffPath) {
5417
- console.log(chalk9.gray(` \u5DEE\u5F02\u56FE: ${path15.relative(process.cwd(), result.diffPath)}`));
5489
+ console.log(chalk9.gray(` \u5DEE\u5F02\u56FE: ${path16.relative(process.cwd(), result.diffPath)}`));
5418
5490
  }
5419
5491
  }
5420
5492
  console.log();
@@ -5461,7 +5533,7 @@ import inquirer4 from "inquirer";
5461
5533
  import ora8 from "ora";
5462
5534
  import { execFile as execFile4 } from "child_process";
5463
5535
  import fs15 from "fs";
5464
- import path16 from "path";
5536
+ import path17 from "path";
5465
5537
  var DEPENDENCY_GROUPS = [
5466
5538
  {
5467
5539
  name: "Vitest (\u5355\u5143/\u7EC4\u4EF6/API\u6D4B\u8BD5)",
@@ -5495,7 +5567,7 @@ function registerSetupCommand(program2) {
5495
5567
  async function executeSetup(options) {
5496
5568
  console.log(chalk10.cyan("\n QAT \u4F9D\u8D56\u5B89\u88C5\u5668\n"));
5497
5569
  const projectInfo = detectProject();
5498
- if (!fs15.existsSync(path16.join(process.cwd(), "package.json"))) {
5570
+ if (!fs15.existsSync(path17.join(process.cwd(), "package.json"))) {
5499
5571
  throw new Error("\u672A\u627E\u5230 package.json\uFF0C\u8BF7\u5728\u9879\u76EE\u6839\u76EE\u5F55\u6267\u884C\u6B64\u547D\u4EE4");
5500
5572
  }
5501
5573
  if (projectInfo.frameworkConfidence > 0) {
@@ -5529,8 +5601,8 @@ async function executeSetup(options) {
5529
5601
  }
5530
5602
  ]);
5531
5603
  if (chooseDir !== "root") {
5532
- installDir = path16.join(process.cwd(), chooseDir);
5533
- if (!fs15.existsSync(path16.join(installDir, "package.json"))) {
5604
+ installDir = path17.join(process.cwd(), chooseDir);
5605
+ if (!fs15.existsSync(path17.join(installDir, "package.json"))) {
5534
5606
  throw new Error(`${chooseDir} \u4E0B\u6CA1\u6709 package.json`);
5535
5607
  }
5536
5608
  }
@@ -5569,7 +5641,7 @@ ${allPackages.map((p) => ` - ${p}`).join("\n")}`,
5569
5641
  const pm = getPackageManager(projectInfo.packageManager);
5570
5642
  const installCmd = pm === "npm" ? "npm install -D" : pm === "yarn" ? "yarn add -D" : pm === "pnpm" ? "pnpm add -D" : "bun add -D";
5571
5643
  for (const group of selectedGroups) {
5572
- const dirHint = installDir !== process.cwd() ? ` (\u5728 ${path16.relative(process.cwd(), installDir) || installDir})` : "";
5644
+ const dirHint = installDir !== process.cwd() ? ` (\u5728 ${path17.relative(process.cwd(), installDir) || installDir})` : "";
5573
5645
  console.log(chalk10.white(` ${installCmd} ${group.packages.join(" ")}${dirHint}`));
5574
5646
  if (group.postInstall) {
5575
5647
  for (const cmd of group.postInstall) {
@@ -5850,7 +5922,7 @@ async function executeChange(_options) {
5850
5922
  }
5851
5923
 
5852
5924
  // src/cli.ts
5853
- var VERSION = "0.3.04";
5925
+ var VERSION = "0.3.05";
5854
5926
  function printLogo() {
5855
5927
  const logo = `
5856
5928
  ${chalk13.bold.cyan(" ___ _ _ _ _ _____ _ _ ")}