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/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\
|
|
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,58 @@ ${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
|
+
variations.add(`#/${srcDirMatch[1]}`);
|
|
1160
|
+
variations.add(`#/${srcDirMatch[1].replace(/\.(ts|js|tsx|jsx)$/, "")}`);
|
|
1161
|
+
}
|
|
1162
|
+
const pathParts = targetPath.replace(/^\.\//, "").split("/");
|
|
1163
|
+
const fileName = pathParts[pathParts.length - 1];
|
|
1164
|
+
const fileNameNoExt = fileName.replace(/\.(ts|js|tsx|jsx|vue)$/, "");
|
|
1165
|
+
variations.add(`./${fileName}`);
|
|
1166
|
+
variations.add(`./${fileNameNoExt}`);
|
|
1167
|
+
for (let i = 1; i < pathParts.length; i++) {
|
|
1168
|
+
const relPath = "../".repeat(i) + pathParts.slice(i).join("/");
|
|
1169
|
+
variations.add(relPath);
|
|
1170
|
+
variations.add(relPath.replace(/\.(ts|js|tsx|jsx)$/, ""));
|
|
1171
|
+
}
|
|
1172
|
+
for (const variant of variations) {
|
|
1173
|
+
if (variant && variant !== correctImportPath) {
|
|
1174
|
+
rewrites.set(variant, correctImportPath);
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
return rewrites;
|
|
1178
|
+
}
|
|
1179
|
+
/**
|
|
1180
|
+
* 重写源码中的 import 路径
|
|
1181
|
+
* 将 from 'oldPath' / from "oldPath" 替换为正确的路径
|
|
1182
|
+
*/
|
|
1183
|
+
rewriteImportPaths(code, rewrites) {
|
|
1184
|
+
let result = code;
|
|
1185
|
+
for (const [oldPath, newPath] of rewrites) {
|
|
1186
|
+
const escaped = oldPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1187
|
+
const singleQuoteRegex = new RegExp(`(from\\s+')${escaped}(')`, "g");
|
|
1188
|
+
const doubleQuoteRegex = new RegExp(`(from\\s+")${escaped}(")`, "g");
|
|
1189
|
+
result = result.replace(singleQuoteRegex, `$1${newPath}$2`);
|
|
1190
|
+
result = result.replace(doubleQuoteRegex, `$1${newPath}$2`);
|
|
1191
|
+
}
|
|
1192
|
+
return result;
|
|
1193
|
+
}
|
|
1194
1194
|
parseGenerateTestResponse(content) {
|
|
1195
1195
|
const codeBlockMatch = content.match(/```(?:typescript|ts|javascript|js)?\s*\n([\s\S]*?)```/);
|
|
1196
1196
|
const code = codeBlockMatch ? codeBlockMatch[1].trim() : content.replace(/^(?:```[\s\S]*?\n)?/, "").replace(/\n?```$/, "").trim();
|
|
@@ -1213,12 +1213,14 @@ SUGGESTIONS:- \u5EFA\u8BAE(\u6BCF\u884C\u4E00\u4E2A)`;
|
|
|
1213
1213
|
}
|
|
1214
1214
|
buildReviewTestUserPrompt(req) {
|
|
1215
1215
|
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}
|
|
1216
|
+
let prompt = `\u5BA1\u67E5\u6D4B\u8BD5\u4EE3\u7801\u3002\u88AB\u6D4B:${req.target} \u7C7B\u578B:${req.testType}
|
|
1217
|
+
\u3010\u5F3A\u5236\u3011import\u88AB\u6D4B\u6A21\u5757\u5FC5\u987B\u7528: ${importPath}
|
|
1217
1218
|
`;
|
|
1219
|
+
const importPathRewrites = this.buildImportPathRewrites(req.testType, req.target);
|
|
1218
1220
|
prompt += `
|
|
1219
1221
|
\u6E90\u7801:
|
|
1220
1222
|
\`\`\`
|
|
1221
|
-
${this.compressSourceCode(req.sourceCode, 2e3)}
|
|
1223
|
+
${this.compressSourceCode(req.sourceCode, 2e3, importPathRewrites)}
|
|
1222
1224
|
\`\`\`
|
|
1223
1225
|
`;
|
|
1224
1226
|
prompt += `
|
|
@@ -1237,6 +1239,11 @@ ${req.testCode}
|
|
|
1237
1239
|
if (req.analysis.emits?.length) {
|
|
1238
1240
|
parts.push(`Emits:${req.analysis.emits.map((e) => `${e.name}(${e.params?.join(",") || ""})`).join(",")}`);
|
|
1239
1241
|
}
|
|
1242
|
+
if (req.analysis.importSignatures?.length) {
|
|
1243
|
+
parts.push(`\u4F9D\u8D56:${req.analysis.importSignatures.map(
|
|
1244
|
+
(imp) => `${imp.source}{${Object.entries(imp.signatures).map(([k, v]) => `${k}:${v}`).join(",")}}`
|
|
1245
|
+
).join(";")}`);
|
|
1246
|
+
}
|
|
1240
1247
|
if (parts.length > 0) {
|
|
1241
1248
|
prompt += `
|
|
1242
1249
|
\u5206\u6790:${parts.join("|")}`;
|
|
@@ -1428,15 +1435,18 @@ import path4 from "path";
|
|
|
1428
1435
|
function analyzeFile(filePath) {
|
|
1429
1436
|
const absolutePath = path4.resolve(process.cwd(), filePath);
|
|
1430
1437
|
if (!fs4.existsSync(absolutePath)) {
|
|
1431
|
-
return { filePath, exports: [], apiCalls: [] };
|
|
1438
|
+
return { filePath, exports: [], imports: [], importSignatures: [], apiCalls: [] };
|
|
1432
1439
|
}
|
|
1433
1440
|
const content = fs4.readFileSync(absolutePath, "utf-8");
|
|
1434
1441
|
const ext = path4.extname(filePath);
|
|
1435
1442
|
const result = {
|
|
1436
1443
|
filePath,
|
|
1437
1444
|
exports: extractExports(content, ext),
|
|
1445
|
+
imports: extractImports(content),
|
|
1446
|
+
importSignatures: [],
|
|
1438
1447
|
apiCalls: extractAPICalls(content, filePath)
|
|
1439
1448
|
};
|
|
1449
|
+
result.importSignatures = analyzeImportSignatures(result.imports, path4.dirname(absolutePath));
|
|
1440
1450
|
if (ext === ".vue") {
|
|
1441
1451
|
result.vueAnalysis = analyzeVueComponent(content, path4.basename(filePath, ".vue"));
|
|
1442
1452
|
const scriptMatch = content.match(/<script[^>]*>([\s\S]*?)<\/script>/);
|
|
@@ -1856,6 +1866,90 @@ function generateMockRoutesFromAPICalls(apiCalls) {
|
|
|
1856
1866
|
}
|
|
1857
1867
|
return routes;
|
|
1858
1868
|
}
|
|
1869
|
+
function extractImports(content) {
|
|
1870
|
+
const imports = [];
|
|
1871
|
+
const namedImportRegex = /import\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/g;
|
|
1872
|
+
let match;
|
|
1873
|
+
while ((match = namedImportRegex.exec(content)) !== null) {
|
|
1874
|
+
const names = match[1].split(",").map((n) => {
|
|
1875
|
+
const parts = n.trim().split(/\s+as\s+/);
|
|
1876
|
+
return parts[0].trim();
|
|
1877
|
+
}).filter(Boolean);
|
|
1878
|
+
imports.push({ names, source: match[2], isDefault: false, isNamespace: false });
|
|
1879
|
+
}
|
|
1880
|
+
const defaultImportRegex = /import\s+(\w+)\s+from\s*['"]([^'"]+)['"]/g;
|
|
1881
|
+
while ((match = defaultImportRegex.exec(content)) !== null) {
|
|
1882
|
+
const fullLine = content.slice(Math.max(0, match.index - 5), match.index + match[0].length);
|
|
1883
|
+
if (fullLine.includes("{")) continue;
|
|
1884
|
+
imports.push({ names: [match[1]], source: match[2], isDefault: true, isNamespace: false });
|
|
1885
|
+
}
|
|
1886
|
+
const namespaceImportRegex = /import\s*\*\s*as\s+(\w+)\s*from\s*['"]([^'"]+)['"]/g;
|
|
1887
|
+
while ((match = namespaceImportRegex.exec(content)) !== null) {
|
|
1888
|
+
imports.push({ names: [match[1]], source: match[2], isDefault: false, isNamespace: true });
|
|
1889
|
+
}
|
|
1890
|
+
return imports;
|
|
1891
|
+
}
|
|
1892
|
+
function analyzeImportSignatures(imports, baseDir) {
|
|
1893
|
+
const signatures = [];
|
|
1894
|
+
for (const imp of imports) {
|
|
1895
|
+
if (!imp.source.startsWith(".") && !imp.source.startsWith("@/") && !imp.source.startsWith("#/") && !imp.source.startsWith("~")) {
|
|
1896
|
+
continue;
|
|
1897
|
+
}
|
|
1898
|
+
const resolvedPath = resolveImportPath(imp.source, baseDir);
|
|
1899
|
+
if (!resolvedPath || !fs4.existsSync(resolvedPath)) continue;
|
|
1900
|
+
try {
|
|
1901
|
+
const content = fs4.readFileSync(resolvedPath, "utf-8");
|
|
1902
|
+
const ext = path4.extname(resolvedPath);
|
|
1903
|
+
const exports = extractExports(content, ext);
|
|
1904
|
+
const sigMap = {};
|
|
1905
|
+
for (const name of imp.names) {
|
|
1906
|
+
const exp = exports.find((e) => e.name === name);
|
|
1907
|
+
if (exp) {
|
|
1908
|
+
const params = exp.params.length > 0 ? `(${exp.params.join(", ")})` : "";
|
|
1909
|
+
const ret = exp.returnType ? `: ${exp.returnType}` : "";
|
|
1910
|
+
const async = exp.isAsync ? "async " : "";
|
|
1911
|
+
sigMap[name] = `${async}${exp.kind}${params}${ret}`;
|
|
1912
|
+
} else if (imp.isDefault) {
|
|
1913
|
+
const defaultExp = exports.find((e) => e.kind === "default");
|
|
1914
|
+
if (defaultExp) {
|
|
1915
|
+
sigMap[name] = `default[${defaultExp.name || "anonymous"}]`;
|
|
1916
|
+
}
|
|
1917
|
+
} else if (imp.isNamespace) {
|
|
1918
|
+
sigMap[name] = `{${exports.map((e) => e.name).join(", ")}}`;
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
if (ext === ".vue") {
|
|
1922
|
+
const vueAnalysis = analyzeVueComponent(content, path4.basename(resolvedPath, ".vue"));
|
|
1923
|
+
if (vueAnalysis.props.length > 0) {
|
|
1924
|
+
sigMap["__props__"] = vueAnalysis.props.map((p) => `${p.name}:${p.type}${p.required ? "!" : "?"}`).join(",");
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
if (Object.keys(sigMap).length > 0) {
|
|
1928
|
+
signatures.push({ source: imp.source, signatures: sigMap });
|
|
1929
|
+
}
|
|
1930
|
+
} catch {
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
return signatures;
|
|
1934
|
+
}
|
|
1935
|
+
function resolveImportPath(importSource, baseDir) {
|
|
1936
|
+
let resolved;
|
|
1937
|
+
if (importSource.startsWith("@/") || importSource.startsWith("#/") || importSource.startsWith("~")) {
|
|
1938
|
+
const relativePath = importSource.replace(/^[@#~]\//, "src/");
|
|
1939
|
+
resolved = path4.resolve(process.cwd(), relativePath);
|
|
1940
|
+
} else if (importSource.startsWith(".")) {
|
|
1941
|
+
resolved = path4.resolve(baseDir, importSource);
|
|
1942
|
+
} else {
|
|
1943
|
+
return null;
|
|
1944
|
+
}
|
|
1945
|
+
const extensions = [".ts", ".tsx", ".js", ".jsx", ".vue", "/index.ts", "/index.js"];
|
|
1946
|
+
if (fs4.existsSync(resolved)) return resolved;
|
|
1947
|
+
for (const ext of extensions) {
|
|
1948
|
+
const withExt = resolved + ext;
|
|
1949
|
+
if (fs4.existsSync(withExt)) return withExt;
|
|
1950
|
+
}
|
|
1951
|
+
return null;
|
|
1952
|
+
}
|
|
1859
1953
|
|
|
1860
1954
|
// src/services/global-config.ts
|
|
1861
1955
|
import fs5 from "fs";
|
|
@@ -1960,41 +2054,13 @@ async function executeInit(options) {
|
|
|
1960
2054
|
}
|
|
1961
2055
|
}
|
|
1962
2056
|
const config = buildProjectConfig(projectInfo);
|
|
1963
|
-
let configPath;
|
|
1964
2057
|
const existingConfigPath = path6.join(process.cwd(), "qat.config.js");
|
|
1965
2058
|
const existingTsPath = path6.join(process.cwd(), "qat.config.ts");
|
|
1966
2059
|
const configExists = fs6.existsSync(existingConfigPath) || fs6.existsSync(existingTsPath);
|
|
1967
|
-
if (configExists
|
|
1968
|
-
|
|
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
|
-
}
|
|
2060
|
+
if (!configExists) {
|
|
2061
|
+
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
2062
|
} else {
|
|
1990
|
-
|
|
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
|
-
}
|
|
2063
|
+
console.log(chalk3.green(` \u2713 \u5DF2\u6709\u914D\u7F6E\u6587\u4EF6: ${path6.basename(fs6.existsSync(existingConfigPath) ? existingConfigPath : existingTsPath)}`));
|
|
1998
2064
|
}
|
|
1999
2065
|
const dirSpinner = ora("\u6B63\u5728\u521B\u5EFA\u6D4B\u8BD5\u76EE\u5F55...").start();
|
|
2000
2066
|
const createdDirs = createTestDirectories(config);
|
|
@@ -2023,7 +2089,7 @@ async function executeInit(options) {
|
|
|
2023
2089
|
console.log(chalk3.cyan(`
|
|
2024
2090
|
\u53D1\u73B0 ${totalFiles} \u4E2A\u53EF\u6D4B\u8BD5\u6587\u4EF6 (${components.length} \u7EC4\u4EF6, ${utilities.length} \u5DE5\u5177/\u670D\u52A1)`));
|
|
2025
2091
|
}
|
|
2026
|
-
displayResult(
|
|
2092
|
+
displayResult(createdDirs, totalFiles, projectInfo);
|
|
2027
2093
|
}
|
|
2028
2094
|
async function promptAIConfig() {
|
|
2029
2095
|
const answers = await inquirer.prompt([
|
|
@@ -2160,12 +2226,8 @@ function createTestDirectories(config) {
|
|
|
2160
2226
|
}
|
|
2161
2227
|
return dirs;
|
|
2162
2228
|
}
|
|
2163
|
-
function displayResult(
|
|
2164
|
-
const relativeConfig = path6.relative(process.cwd(), configPath);
|
|
2229
|
+
function displayResult(createdDirs, totalTestableFiles, projectInfo) {
|
|
2165
2230
|
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
2231
|
if (createdDirs.length > 0) {
|
|
2170
2232
|
console.log(chalk3.white(" \u5DF2\u521B\u5EFA\u76EE\u5F55:"));
|
|
2171
2233
|
for (const dir of createdDirs) {
|
|
@@ -2173,6 +2235,9 @@ function displayResult(configPath, createdDirs, totalTestableFiles, projectInfo)
|
|
|
2173
2235
|
}
|
|
2174
2236
|
console.log();
|
|
2175
2237
|
}
|
|
2238
|
+
console.log(chalk3.white(" \u914D\u7F6E:"));
|
|
2239
|
+
console.log(chalk3.gray(" \u9ED8\u8BA4\u914D\u7F6E\u5DF2\u5185\u7F6E\uFF0C\u9700\u8981\u81EA\u5B9A\u4E49\u65F6\u521B\u5EFA qat.config.js"));
|
|
2240
|
+
console.log();
|
|
2176
2241
|
console.log(chalk3.cyan(" \u4E0B\u4E00\u6B65:"));
|
|
2177
2242
|
if (totalTestableFiles > 0) {
|
|
2178
2243
|
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 +2258,7 @@ import chalk5 from "chalk";
|
|
|
2193
2258
|
import inquirer2 from "inquirer";
|
|
2194
2259
|
import ora3 from "ora";
|
|
2195
2260
|
import fs8 from "fs";
|
|
2196
|
-
import
|
|
2261
|
+
import path9 from "path";
|
|
2197
2262
|
|
|
2198
2263
|
// src/services/template.ts
|
|
2199
2264
|
import fs7 from "fs";
|
|
@@ -2242,7 +2307,7 @@ Handlebars.registerHelper("propTestValue", (prop) => {
|
|
|
2242
2307
|
};
|
|
2243
2308
|
return map[prop.type] || "'test-value'";
|
|
2244
2309
|
});
|
|
2245
|
-
function
|
|
2310
|
+
function resolveImportPath2(testType, targetPath) {
|
|
2246
2311
|
if (!targetPath) return targetPath;
|
|
2247
2312
|
const testDirMap = {
|
|
2248
2313
|
unit: "tests/unit",
|
|
@@ -2268,7 +2333,7 @@ function resolveImportPath(testType, targetPath) {
|
|
|
2268
2333
|
function renderTemplate(type, context) {
|
|
2269
2334
|
const templateContent = loadTemplate(type);
|
|
2270
2335
|
const template = Handlebars.compile(templateContent);
|
|
2271
|
-
const resolvedTarget =
|
|
2336
|
+
const resolvedTarget = resolveImportPath2(type, context.target);
|
|
2272
2337
|
const fullContext = {
|
|
2273
2338
|
vueVersion: 3,
|
|
2274
2339
|
typescript: true,
|
|
@@ -2652,10 +2717,11 @@ function toPascalCase(str) {
|
|
|
2652
2717
|
// src/services/test-reviewer.ts
|
|
2653
2718
|
import chalk4 from "chalk";
|
|
2654
2719
|
import ora2 from "ora";
|
|
2720
|
+
import path8 from "path";
|
|
2655
2721
|
var MAX_RETRIES = 3;
|
|
2656
2722
|
var REVIEW_THRESHOLD = 0.6;
|
|
2657
2723
|
async function generateWithReview(params) {
|
|
2658
|
-
const { testType, targetPath, sourceCode, analysis, aiConfig, framework, onAttempt } = params;
|
|
2724
|
+
const { testType, targetPath, sourceCode, analysis, aiConfig, framework, onAttempt, onProgress } = params;
|
|
2659
2725
|
const generatorProvider = createAIProvider(aiConfig);
|
|
2660
2726
|
const reviewerProvider = createAIProvider(aiConfig);
|
|
2661
2727
|
if (!generatorProvider.capabilities.generateTest) {
|
|
@@ -2667,9 +2733,12 @@ async function generateWithReview(params) {
|
|
|
2667
2733
|
let lastReview = null;
|
|
2668
2734
|
let approved = false;
|
|
2669
2735
|
let attempts = 0;
|
|
2736
|
+
const targetName = path8.basename(targetPath);
|
|
2670
2737
|
for (let i = 0; i < MAX_RETRIES; i++) {
|
|
2671
2738
|
attempts = i + 1;
|
|
2672
2739
|
onAttempt?.(attempts, MAX_RETRIES);
|
|
2740
|
+
const genLabel = attempts > 1 ? `\u27F3 \u91CD\u65B0\u751F\u6210 (${attempts}/${MAX_RETRIES})` : "\u751F\u6210\u6D4B\u8BD5\u4EE3\u7801";
|
|
2741
|
+
onProgress?.(`${genLabel} \u2190 ${targetName}`);
|
|
2673
2742
|
if (process.env.QAT_DEBUG === "true") {
|
|
2674
2743
|
console.error(chalk4.gray(`[DEBUG] \u2500\u2500\u2500 \u751F\u6210\u8005 AI \u7B2C${attempts}\u6B21\u5C1D\u8BD5 \u2500\u2500\u2500`));
|
|
2675
2744
|
}
|
|
@@ -2693,6 +2762,7 @@ ${lastReview.suggestions.map((s) => `- ${s}`).join("\n")}
|
|
|
2693
2762
|
currentCode = generateResponse.code;
|
|
2694
2763
|
currentDescription = generateResponse.description;
|
|
2695
2764
|
currentConfidence = generateResponse.confidence;
|
|
2765
|
+
onProgress?.(`\u5BA1\u8BA1\u5BA1\u67E5 \u2190 ${targetName}`);
|
|
2696
2766
|
if (process.env.QAT_DEBUG === "true") {
|
|
2697
2767
|
console.error(chalk4.gray(`[DEBUG] \u2500\u2500\u2500 \u5BA1\u8BA1\u5458 AI \u5BA1\u67E5 \u2500\u2500\u2500`));
|
|
2698
2768
|
}
|
|
@@ -2846,59 +2916,103 @@ async function executeCreate(options) {
|
|
|
2846
2916
|
]);
|
|
2847
2917
|
useAI = ai;
|
|
2848
2918
|
}
|
|
2919
|
+
const tasks = [];
|
|
2920
|
+
for (const testType of types) {
|
|
2921
|
+
for (const testTarget of targets) {
|
|
2922
|
+
tasks.push({ testType, target: testTarget });
|
|
2923
|
+
}
|
|
2924
|
+
}
|
|
2849
2925
|
const createdFiles = [];
|
|
2850
2926
|
let skippedCount = 0;
|
|
2851
2927
|
const reviewReportEntries = [];
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2928
|
+
if (useAI && tasks.length > 0) {
|
|
2929
|
+
const spinner = ora3(`\u5E76\u884C\u751F\u6210 ${tasks.length} \u4E2A\u6D4B\u8BD5\u7528\u4F8B...`).start();
|
|
2930
|
+
let completed = 0;
|
|
2931
|
+
let approvedCount = 0;
|
|
2932
|
+
let failedCount = 0;
|
|
2933
|
+
const results = await Promise.allSettled(
|
|
2934
|
+
tasks.map(async (task) => {
|
|
2935
|
+
const result = await generateWithAI(task.testType, name, task.target, config, (text) => {
|
|
2936
|
+
completed++;
|
|
2937
|
+
spinner.text = `\u5E76\u884C\u751F\u6210\u4E2D (${completed}/${tasks.length}) ${text}`;
|
|
2938
|
+
});
|
|
2939
|
+
return { ...task, ...result };
|
|
2940
|
+
})
|
|
2941
|
+
);
|
|
2942
|
+
spinner.stop();
|
|
2943
|
+
for (let i = 0; i < results.length; i++) {
|
|
2944
|
+
const r = results[i];
|
|
2945
|
+
const task = tasks[i];
|
|
2946
|
+
if (r.status === "rejected") {
|
|
2947
|
+
console.log(chalk5.red(` \u2717 ${TEST_TYPE_LABELS[task.testType]} - ${path9.basename(task.target)}: ${r.reason instanceof Error ? r.reason.message : String(r.reason)}`));
|
|
2948
|
+
failedCount++;
|
|
2949
|
+
continue;
|
|
2950
|
+
}
|
|
2951
|
+
const { code, reviewEntry } = r.value;
|
|
2952
|
+
const outputDir = getTestOutputDir(task.testType);
|
|
2953
|
+
const filePath = path9.join(outputDir, `${name}.${task.testType === "e2e" ? "spec" : "test"}.ts`);
|
|
2954
|
+
if (fs8.existsSync(filePath)) {
|
|
2955
|
+
console.log(chalk5.yellow(` \u26A0 \u6D4B\u8BD5\u6587\u4EF6\u5DF2\u5B58\u5728: ${path9.relative(process.cwd(), filePath)}`));
|
|
2956
|
+
skippedCount++;
|
|
2957
|
+
continue;
|
|
2958
|
+
}
|
|
2959
|
+
if (!fs8.existsSync(outputDir)) {
|
|
2960
|
+
fs8.mkdirSync(outputDir, { recursive: true });
|
|
2961
|
+
}
|
|
2962
|
+
fs8.writeFileSync(filePath, code, "utf-8");
|
|
2963
|
+
createdFiles.push({ type: task.testType, filePath });
|
|
2964
|
+
if (reviewEntry) {
|
|
2965
|
+
reviewReportEntries.push(reviewEntry);
|
|
2966
|
+
if (reviewEntry.approved) {
|
|
2967
|
+
approvedCount++;
|
|
2863
2968
|
} else {
|
|
2864
|
-
|
|
2865
|
-
content = renderTemplate(testType, {
|
|
2866
|
-
name,
|
|
2867
|
-
target: testTarget || `./${config.project.srcDir}`,
|
|
2868
|
-
framework: projectInfo.framework,
|
|
2869
|
-
vueVersion: projectInfo.vueVersion,
|
|
2870
|
-
typescript: projectInfo.typescript,
|
|
2871
|
-
uiLibrary: projectInfo.uiLibrary,
|
|
2872
|
-
extraImports: projectInfo.componentTestSetup?.extraImports,
|
|
2873
|
-
globalPlugins: projectInfo.componentTestSetup?.globalPlugins,
|
|
2874
|
-
globalStubs: projectInfo.componentTestSetup?.globalStubs,
|
|
2875
|
-
mountOptions: projectInfo.componentTestSetup?.mountOptions,
|
|
2876
|
-
// 注入源码分析结果
|
|
2877
|
-
exports: analysis?.exports.map((e) => ({
|
|
2878
|
-
name: e.name,
|
|
2879
|
-
kind: e.kind,
|
|
2880
|
-
params: e.params,
|
|
2881
|
-
isAsync: e.isAsync,
|
|
2882
|
-
returnType: e.returnType
|
|
2883
|
-
})),
|
|
2884
|
-
props: analysis?.vueAnalysis?.props.map((p) => ({
|
|
2885
|
-
name: p.name,
|
|
2886
|
-
type: p.type,
|
|
2887
|
-
required: p.required,
|
|
2888
|
-
defaultValue: p.defaultValue
|
|
2889
|
-
})),
|
|
2890
|
-
emits: analysis?.vueAnalysis?.emits.map((e) => ({
|
|
2891
|
-
name: e.name,
|
|
2892
|
-
params: e.params
|
|
2893
|
-
})),
|
|
2894
|
-
methods: analysis?.vueAnalysis?.methods,
|
|
2895
|
-
computed: analysis?.vueAnalysis?.computed
|
|
2896
|
-
});
|
|
2969
|
+
failedCount++;
|
|
2897
2970
|
}
|
|
2898
|
-
|
|
2899
|
-
|
|
2971
|
+
}
|
|
2972
|
+
}
|
|
2973
|
+
console.log(chalk5.cyan(`
|
|
2974
|
+
\u5E76\u884C\u751F\u6210\u5B8C\u6210: ${createdFiles.length} \u6210\u529F, ${skippedCount} \u8DF3\u8FC7, ${failedCount} \u5BA1\u8BA1\u672A\u901A\u8FC7`));
|
|
2975
|
+
} else {
|
|
2976
|
+
for (const task of tasks) {
|
|
2977
|
+
const spinner = ora3(`\u6B63\u5728\u751F\u6210 ${TEST_TYPE_LABELS[task.testType]} - ${path9.basename(task.target)}...`).start();
|
|
2978
|
+
try {
|
|
2979
|
+
const analysis = task.target ? analyzeFile(task.target) : void 0;
|
|
2980
|
+
const content = renderTemplate(task.testType, {
|
|
2981
|
+
name,
|
|
2982
|
+
target: task.target || `./${config.project.srcDir}`,
|
|
2983
|
+
framework: projectInfo.framework,
|
|
2984
|
+
vueVersion: projectInfo.vueVersion,
|
|
2985
|
+
typescript: projectInfo.typescript,
|
|
2986
|
+
uiLibrary: projectInfo.uiLibrary,
|
|
2987
|
+
extraImports: projectInfo.componentTestSetup?.extraImports,
|
|
2988
|
+
globalPlugins: projectInfo.componentTestSetup?.globalPlugins,
|
|
2989
|
+
globalStubs: projectInfo.componentTestSetup?.globalStubs,
|
|
2990
|
+
mountOptions: projectInfo.componentTestSetup?.mountOptions,
|
|
2991
|
+
// 注入源码分析结果
|
|
2992
|
+
exports: analysis?.exports.map((e) => ({
|
|
2993
|
+
name: e.name,
|
|
2994
|
+
kind: e.kind,
|
|
2995
|
+
params: e.params,
|
|
2996
|
+
isAsync: e.isAsync,
|
|
2997
|
+
returnType: e.returnType
|
|
2998
|
+
})),
|
|
2999
|
+
props: analysis?.vueAnalysis?.props.map((p) => ({
|
|
3000
|
+
name: p.name,
|
|
3001
|
+
type: p.type,
|
|
3002
|
+
required: p.required,
|
|
3003
|
+
defaultValue: p.defaultValue
|
|
3004
|
+
})),
|
|
3005
|
+
emits: analysis?.vueAnalysis?.emits.map((e) => ({
|
|
3006
|
+
name: e.name,
|
|
3007
|
+
params: e.params
|
|
3008
|
+
})),
|
|
3009
|
+
methods: analysis?.vueAnalysis?.methods,
|
|
3010
|
+
computed: analysis?.vueAnalysis?.computed
|
|
3011
|
+
});
|
|
3012
|
+
const outputDir = getTestOutputDir(task.testType);
|
|
3013
|
+
const filePath = path9.join(outputDir, `${name}.${task.testType === "e2e" ? "spec" : "test"}.ts`);
|
|
2900
3014
|
if (fs8.existsSync(filePath)) {
|
|
2901
|
-
spinner.warn(`\u6D4B\u8BD5\u6587\u4EF6\u5DF2\u5B58\u5728: ${
|
|
3015
|
+
spinner.warn(`\u6D4B\u8BD5\u6587\u4EF6\u5DF2\u5B58\u5728: ${path9.relative(process.cwd(), filePath)}`);
|
|
2902
3016
|
skippedCount++;
|
|
2903
3017
|
continue;
|
|
2904
3018
|
}
|
|
@@ -2906,10 +3020,10 @@ async function executeCreate(options) {
|
|
|
2906
3020
|
fs8.mkdirSync(outputDir, { recursive: true });
|
|
2907
3021
|
}
|
|
2908
3022
|
fs8.writeFileSync(filePath, content, "utf-8");
|
|
2909
|
-
spinner.succeed(`${TEST_TYPE_LABELS[testType]} - ${
|
|
2910
|
-
createdFiles.push({ type: testType, filePath });
|
|
3023
|
+
spinner.succeed(`${TEST_TYPE_LABELS[task.testType]} - ${path9.basename(task.target)}`);
|
|
3024
|
+
createdFiles.push({ type: task.testType, filePath });
|
|
2911
3025
|
} catch (error) {
|
|
2912
|
-
spinner.fail(`${TEST_TYPE_LABELS[testType]} - ${
|
|
3026
|
+
spinner.fail(`${TEST_TYPE_LABELS[task.testType]} - ${path9.basename(task.target)} \u521B\u5EFA\u5931\u8D25`);
|
|
2913
3027
|
console.log(chalk5.gray(` ${error instanceof Error ? error.message : String(error)}`));
|
|
2914
3028
|
}
|
|
2915
3029
|
}
|
|
@@ -2918,6 +3032,34 @@ async function executeCreate(options) {
|
|
|
2918
3032
|
if (reviewReportEntries.length > 0) {
|
|
2919
3033
|
printReviewReport(reviewReportEntries);
|
|
2920
3034
|
}
|
|
3035
|
+
const failedEntries = reviewReportEntries.filter((e) => !e.approved);
|
|
3036
|
+
if (failedEntries.length > 0 && useAI) {
|
|
3037
|
+
const { retry } = await inquirer2.prompt([
|
|
3038
|
+
{
|
|
3039
|
+
type: "confirm",
|
|
3040
|
+
name: "retry",
|
|
3041
|
+
message: `${failedEntries.length} \u4E2A\u6D4B\u8BD5\u7528\u4F8B\u5BA1\u8BA1\u672A\u901A\u8FC7\uFF0C\u662F\u5426\u91CD\u65B0\u751F\u6210\uFF1F`,
|
|
3042
|
+
default: false
|
|
3043
|
+
}
|
|
3044
|
+
]);
|
|
3045
|
+
if (retry) {
|
|
3046
|
+
console.log(chalk5.cyan("\n \u91CD\u65B0\u751F\u6210\u672A\u901A\u8FC7\u7684\u6D4B\u8BD5\u7528\u4F8B...\n"));
|
|
3047
|
+
for (const entry of failedEntries) {
|
|
3048
|
+
const spinner = ora3(`\u91CD\u65B0\u751F\u6210 ${path9.basename(entry.target)}...`).start();
|
|
3049
|
+
try {
|
|
3050
|
+
const aiResult = await generateWithAI(entry.testType, name, entry.target, config, (text) => {
|
|
3051
|
+
spinner.text = text;
|
|
3052
|
+
});
|
|
3053
|
+
const outputDir = getTestOutputDir(entry.testType);
|
|
3054
|
+
const filePath = path9.join(outputDir, `${name}.${entry.testType === "e2e" ? "spec" : "test"}.ts`);
|
|
3055
|
+
fs8.writeFileSync(filePath, aiResult.code, "utf-8");
|
|
3056
|
+
spinner.succeed(`${path9.basename(entry.target)} \u5DF2\u91CD\u65B0\u751F\u6210${aiResult.reviewEntry?.approved ? chalk5.green(" (\u5BA1\u8BA1\u901A\u8FC7)") : chalk5.yellow(" (\u4ECD\u9700\u5BA1\u67E5)")}`);
|
|
3057
|
+
} catch (error) {
|
|
3058
|
+
spinner.fail(`${path9.basename(entry.target)} \u91CD\u65B0\u751F\u6210\u5931\u8D25`);
|
|
3059
|
+
}
|
|
3060
|
+
}
|
|
3061
|
+
}
|
|
3062
|
+
}
|
|
2921
3063
|
}
|
|
2922
3064
|
async function selectTargets(types, projectInfo, srcDir) {
|
|
2923
3065
|
const allTargets = [];
|
|
@@ -2976,22 +3118,22 @@ async function selectTargets(types, projectInfo, srcDir) {
|
|
|
2976
3118
|
}
|
|
2977
3119
|
function generateDefaultName(type, target) {
|
|
2978
3120
|
if (!target) return `${type}-test`;
|
|
2979
|
-
const basename =
|
|
3121
|
+
const basename = path9.basename(target, path9.extname(target));
|
|
2980
3122
|
const cleaned = basename.replace(/[^a-zA-Z0-9]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
2981
3123
|
return cleaned || `${type}-test`;
|
|
2982
3124
|
}
|
|
2983
|
-
async function generateWithAI(type, name, target, config) {
|
|
3125
|
+
async function generateWithAI(type, name, target, config, onProgress) {
|
|
2984
3126
|
const provider = getAIProvider(config.ai);
|
|
2985
3127
|
if (!provider.capabilities.generateTest) {
|
|
2986
3128
|
throw new Error("\u5F53\u524D AI Provider \u4E0D\u652F\u6301\u6D4B\u8BD5\u751F\u6210");
|
|
2987
3129
|
}
|
|
2988
3130
|
let sourceCode = "";
|
|
2989
|
-
const fullPath =
|
|
3131
|
+
const fullPath = path9.resolve(process.cwd(), target);
|
|
2990
3132
|
if (fs8.existsSync(fullPath)) {
|
|
2991
3133
|
sourceCode = fs8.readFileSync(fullPath, "utf-8");
|
|
2992
3134
|
}
|
|
2993
3135
|
const analysis = analyzeFile(target);
|
|
2994
|
-
const analysisSummary = analysis.exports.length > 0 || analysis.vueAnalysis ? {
|
|
3136
|
+
const analysisSummary = analysis.exports.length > 0 || analysis.vueAnalysis || analysis.importSignatures.length > 0 ? {
|
|
2995
3137
|
exports: analysis.exports.map((e) => ({
|
|
2996
3138
|
name: e.name,
|
|
2997
3139
|
kind: e.kind,
|
|
@@ -3008,7 +3150,8 @@ async function generateWithAI(type, name, target, config) {
|
|
|
3008
3150
|
params: e.params
|
|
3009
3151
|
})),
|
|
3010
3152
|
methods: analysis.vueAnalysis?.methods,
|
|
3011
|
-
computed: analysis.vueAnalysis?.computed
|
|
3153
|
+
computed: analysis.vueAnalysis?.computed,
|
|
3154
|
+
importSignatures: analysis.importSignatures.length > 0 ? analysis.importSignatures : void 0
|
|
3012
3155
|
} : void 0;
|
|
3013
3156
|
const reviewResult = await generateWithReview({
|
|
3014
3157
|
testType: type,
|
|
@@ -3016,7 +3159,8 @@ async function generateWithAI(type, name, target, config) {
|
|
|
3016
3159
|
sourceCode,
|
|
3017
3160
|
analysis: analysisSummary,
|
|
3018
3161
|
aiConfig: config.ai,
|
|
3019
|
-
framework: "vue"
|
|
3162
|
+
framework: "vue",
|
|
3163
|
+
onProgress
|
|
3020
3164
|
});
|
|
3021
3165
|
const headerComment = [
|
|
3022
3166
|
`// AI Generated Test - ${name}`,
|
|
@@ -3060,7 +3204,7 @@ function displayCreateResult(createdFiles, skippedCount, usedAI) {
|
|
|
3060
3204
|
}
|
|
3061
3205
|
console.log();
|
|
3062
3206
|
for (const { type, filePath } of createdFiles) {
|
|
3063
|
-
const relativePath =
|
|
3207
|
+
const relativePath = path9.relative(process.cwd(), filePath);
|
|
3064
3208
|
console.log(chalk5.white(` ${chalk5.cyan(TEST_TYPE_LABELS[type])} ${chalk5.gray(relativePath)}`));
|
|
3065
3209
|
}
|
|
3066
3210
|
if (usedAI) {
|
|
@@ -3085,11 +3229,11 @@ import chalk6 from "chalk";
|
|
|
3085
3229
|
import inquirer3 from "inquirer";
|
|
3086
3230
|
import ora4 from "ora";
|
|
3087
3231
|
import fs11 from "fs";
|
|
3088
|
-
import
|
|
3232
|
+
import path13 from "path";
|
|
3089
3233
|
|
|
3090
3234
|
// src/runners/vitest-runner.ts
|
|
3091
3235
|
import { execFile } from "child_process";
|
|
3092
|
-
import
|
|
3236
|
+
import path10 from "path";
|
|
3093
3237
|
import fs9 from "fs";
|
|
3094
3238
|
import os2 from "os";
|
|
3095
3239
|
var isVerbose = () => process.env.QAT_VERBOSE === "true";
|
|
@@ -3171,7 +3315,7 @@ function buildVitestArgs(options) {
|
|
|
3171
3315
|
return args;
|
|
3172
3316
|
}
|
|
3173
3317
|
async function execVitest(args) {
|
|
3174
|
-
const tmpFile =
|
|
3318
|
+
const tmpFile = path10.join(os2.tmpdir(), `qat-vitest-result-${Date.now()}.json`);
|
|
3175
3319
|
const argsWithOutput = [...args, "--outputFile", tmpFile];
|
|
3176
3320
|
return new Promise((resolve, reject) => {
|
|
3177
3321
|
const npx = process.platform === "win32" ? "npx.cmd" : "npx";
|
|
@@ -3250,7 +3394,7 @@ function parseVitestJSON(jsonStr) {
|
|
|
3250
3394
|
for (const fileResult of data.testResults) {
|
|
3251
3395
|
const suiteTests = parseTestResults(fileResult);
|
|
3252
3396
|
suites.push({
|
|
3253
|
-
name:
|
|
3397
|
+
name: path10.basename(fileResult.name || "unknown"),
|
|
3254
3398
|
file: fileResult.name || "unknown",
|
|
3255
3399
|
type: "unit",
|
|
3256
3400
|
status: mapVitestStatus(fileResult.status),
|
|
@@ -3361,7 +3505,7 @@ function parseFromStdout(output) {
|
|
|
3361
3505
|
if (data.testResults && Array.isArray(data.testResults)) {
|
|
3362
3506
|
for (const fileResult of data.testResults) {
|
|
3363
3507
|
suites.push({
|
|
3364
|
-
name:
|
|
3508
|
+
name: path10.basename(fileResult.name || "unknown"),
|
|
3365
3509
|
file: fileResult.name || "unknown",
|
|
3366
3510
|
type: "unit",
|
|
3367
3511
|
status: mapVitestStatus(fileResult.status),
|
|
@@ -3397,7 +3541,7 @@ function parseVitestTextOutput(output, hasError) {
|
|
|
3397
3541
|
if (isPassed) totalPassed += testCount;
|
|
3398
3542
|
else totalFailed += testCount;
|
|
3399
3543
|
suites.push({
|
|
3400
|
-
name:
|
|
3544
|
+
name: path10.basename(file),
|
|
3401
3545
|
file,
|
|
3402
3546
|
type: "unit",
|
|
3403
3547
|
status: isPassed ? "passed" : "failed",
|
|
@@ -3498,7 +3642,7 @@ function extractCoverage(coverageMap) {
|
|
|
3498
3642
|
|
|
3499
3643
|
// src/runners/playwright-runner.ts
|
|
3500
3644
|
import { execFile as execFile2 } from "child_process";
|
|
3501
|
-
import
|
|
3645
|
+
import path11 from "path";
|
|
3502
3646
|
async function runPlaywright(options) {
|
|
3503
3647
|
const startTime = Date.now();
|
|
3504
3648
|
const args = buildPlaywrightArgs(options);
|
|
@@ -3698,7 +3842,7 @@ function parsePlaywrightTextOutput(output, hasError) {
|
|
|
3698
3842
|
let existingSuite = suites.find((s) => s.file === file);
|
|
3699
3843
|
if (!existingSuite) {
|
|
3700
3844
|
existingSuite = {
|
|
3701
|
-
name:
|
|
3845
|
+
name: path11.basename(file),
|
|
3702
3846
|
file,
|
|
3703
3847
|
type: "e2e",
|
|
3704
3848
|
status: "passed",
|
|
@@ -3933,7 +4077,7 @@ function calculateAverageMetrics(results) {
|
|
|
3933
4077
|
|
|
3934
4078
|
// src/services/reporter.ts
|
|
3935
4079
|
import fs10 from "fs";
|
|
3936
|
-
import
|
|
4080
|
+
import path12 from "path";
|
|
3937
4081
|
function aggregateResults(results) {
|
|
3938
4082
|
const summary = {
|
|
3939
4083
|
total: 0,
|
|
@@ -4129,13 +4273,13 @@ function generateMDReport(data) {
|
|
|
4129
4273
|
}
|
|
4130
4274
|
function writeReportToDisk(data, outputDir) {
|
|
4131
4275
|
const md = generateMDReport(data);
|
|
4132
|
-
const dir =
|
|
4276
|
+
const dir = path12.resolve(outputDir);
|
|
4133
4277
|
if (!fs10.existsSync(dir)) {
|
|
4134
4278
|
fs10.mkdirSync(dir, { recursive: true });
|
|
4135
4279
|
}
|
|
4136
|
-
const mdPath =
|
|
4280
|
+
const mdPath = path12.join(dir, "report.md");
|
|
4137
4281
|
fs10.writeFileSync(mdPath, md, "utf-8");
|
|
4138
|
-
const jsonPath =
|
|
4282
|
+
const jsonPath = path12.join(dir, "report.json");
|
|
4139
4283
|
fs10.writeFileSync(jsonPath, JSON.stringify(data, null, 2), "utf-8");
|
|
4140
4284
|
return mdPath;
|
|
4141
4285
|
}
|
|
@@ -4356,7 +4500,7 @@ async function executeRun(options) {
|
|
|
4356
4500
|
const reportData = aggregateResults(results);
|
|
4357
4501
|
const outputDir = config.report.outputDir || "qat-report";
|
|
4358
4502
|
const reportPath = writeReportToDisk(reportData, outputDir);
|
|
4359
|
-
const relativePath =
|
|
4503
|
+
const relativePath = path13.relative(process.cwd(), reportPath);
|
|
4360
4504
|
console.log(chalk6.gray(`
|
|
4361
4505
|
\u62A5\u544A\u5DF2\u751F\u6210: ${chalk6.cyan(relativePath)}`));
|
|
4362
4506
|
console.log();
|
|
@@ -4743,8 +4887,8 @@ async function checkDevServer(config) {
|
|
|
4743
4887
|
}
|
|
4744
4888
|
async function startDevServer(config) {
|
|
4745
4889
|
const { spawn } = await import("child_process");
|
|
4746
|
-
const hasPnpm = fs11.existsSync(
|
|
4747
|
-
const hasYarn = fs11.existsSync(
|
|
4890
|
+
const hasPnpm = fs11.existsSync(path13.join(process.cwd(), "pnpm-lock.yaml"));
|
|
4891
|
+
const hasYarn = fs11.existsSync(path13.join(process.cwd(), "yarn.lock"));
|
|
4748
4892
|
const pkgCmd = hasPnpm ? "pnpm" : hasYarn ? "yarn" : "npm";
|
|
4749
4893
|
console.log(chalk6.cyan(` \u6B63\u5728\u542F\u52A8 dev server (${pkgCmd} run dev) ...`));
|
|
4750
4894
|
const child = spawn(pkgCmd, ["run", "dev"], {
|
|
@@ -4773,13 +4917,13 @@ async function startDevServer(config) {
|
|
|
4773
4917
|
}
|
|
4774
4918
|
function saveRunResults(results) {
|
|
4775
4919
|
if (results.length === 0) return;
|
|
4776
|
-
const resultsPath =
|
|
4920
|
+
const resultsPath = path13.join(process.cwd(), RESULTS_DIR);
|
|
4777
4921
|
if (!fs11.existsSync(resultsPath)) {
|
|
4778
4922
|
fs11.mkdirSync(resultsPath, { recursive: true });
|
|
4779
4923
|
}
|
|
4780
4924
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
4781
4925
|
const fileName = `result-${timestamp}.json`;
|
|
4782
|
-
const filePath =
|
|
4926
|
+
const filePath = path13.join(resultsPath, fileName);
|
|
4783
4927
|
const data = {
|
|
4784
4928
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4785
4929
|
results
|
|
@@ -4789,13 +4933,13 @@ function saveRunResults(results) {
|
|
|
4789
4933
|
const files = fs11.readdirSync(resultsPath).filter((f) => f.startsWith("result-") && f.endsWith(".json")).sort();
|
|
4790
4934
|
while (files.length > 20) {
|
|
4791
4935
|
const oldest = files.shift();
|
|
4792
|
-
fs11.unlinkSync(
|
|
4936
|
+
fs11.unlinkSync(path13.join(resultsPath, oldest));
|
|
4793
4937
|
}
|
|
4794
4938
|
} catch {
|
|
4795
4939
|
}
|
|
4796
4940
|
}
|
|
4797
4941
|
function checkTestDependencies(types) {
|
|
4798
|
-
const pkgPath =
|
|
4942
|
+
const pkgPath = path13.join(process.cwd(), "package.json");
|
|
4799
4943
|
let allDeps = {};
|
|
4800
4944
|
if (fs11.existsSync(pkgPath)) {
|
|
4801
4945
|
try {
|
|
@@ -4821,8 +4965,8 @@ function checkTestDependencies(types) {
|
|
|
4821
4965
|
return missingDeps;
|
|
4822
4966
|
}
|
|
4823
4967
|
async function installTestDependencies(missingDeps) {
|
|
4824
|
-
const hasPnpm = fs11.existsSync(
|
|
4825
|
-
const hasYarn = fs11.existsSync(
|
|
4968
|
+
const hasPnpm = fs11.existsSync(path13.join(process.cwd(), "pnpm-lock.yaml"));
|
|
4969
|
+
const hasYarn = fs11.existsSync(path13.join(process.cwd(), "yarn.lock"));
|
|
4826
4970
|
const pkgManager = hasPnpm ? "pnpm" : hasYarn ? "yarn" : "npm";
|
|
4827
4971
|
const { execFile: execFile5 } = await import("child_process");
|
|
4828
4972
|
let allSuccess = true;
|
|
@@ -4990,7 +5134,7 @@ function showStatus() {
|
|
|
4990
5134
|
import chalk8 from "chalk";
|
|
4991
5135
|
import ora6 from "ora";
|
|
4992
5136
|
import fs12 from "fs";
|
|
4993
|
-
import
|
|
5137
|
+
import path14 from "path";
|
|
4994
5138
|
var RESULTS_DIR2 = ".qat-results";
|
|
4995
5139
|
function registerReportCommand(program2) {
|
|
4996
5140
|
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 +5170,13 @@ async function executeReport(options) {
|
|
|
5026
5170
|
}
|
|
5027
5171
|
function collectResults() {
|
|
5028
5172
|
const results = [];
|
|
5029
|
-
const resultsPath =
|
|
5173
|
+
const resultsPath = path14.join(process.cwd(), RESULTS_DIR2);
|
|
5030
5174
|
if (!fs12.existsSync(resultsPath)) {
|
|
5031
5175
|
return results;
|
|
5032
5176
|
}
|
|
5033
5177
|
const files = fs12.readdirSync(resultsPath).filter((f) => f.endsWith(".json")).sort().reverse();
|
|
5034
5178
|
if (files.length > 0) {
|
|
5035
|
-
const latestFile =
|
|
5179
|
+
const latestFile = path14.join(resultsPath, files[0]);
|
|
5036
5180
|
try {
|
|
5037
5181
|
const data = JSON.parse(fs12.readFileSync(latestFile, "utf-8"));
|
|
5038
5182
|
if (Array.isArray(data)) {
|
|
@@ -5046,22 +5190,22 @@ function collectResults() {
|
|
|
5046
5190
|
return results;
|
|
5047
5191
|
}
|
|
5048
5192
|
function saveResultToHistory(reportData) {
|
|
5049
|
-
const resultsPath =
|
|
5193
|
+
const resultsPath = path14.join(process.cwd(), RESULTS_DIR2);
|
|
5050
5194
|
if (!fs12.existsSync(resultsPath)) {
|
|
5051
5195
|
fs12.mkdirSync(resultsPath, { recursive: true });
|
|
5052
5196
|
}
|
|
5053
5197
|
const timestamp = new Date(reportData.timestamp).toISOString().replace(/[:.]/g, "-");
|
|
5054
5198
|
const fileName = `result-${timestamp}.json`;
|
|
5055
|
-
const filePath =
|
|
5199
|
+
const filePath = path14.join(resultsPath, fileName);
|
|
5056
5200
|
fs12.writeFileSync(filePath, JSON.stringify(reportData, null, 2), "utf-8");
|
|
5057
5201
|
const files = fs12.readdirSync(resultsPath).filter((f) => f.startsWith("result-") && f.endsWith(".json")).sort();
|
|
5058
5202
|
while (files.length > 20) {
|
|
5059
5203
|
const oldest = files.shift();
|
|
5060
|
-
fs12.unlinkSync(
|
|
5204
|
+
fs12.unlinkSync(path14.join(resultsPath, oldest));
|
|
5061
5205
|
}
|
|
5062
5206
|
}
|
|
5063
5207
|
function displayReportResult(reportPath, data) {
|
|
5064
|
-
const relativePath =
|
|
5208
|
+
const relativePath = path14.relative(process.cwd(), reportPath);
|
|
5065
5209
|
const passRate = data.summary.total > 0 ? (data.summary.passed / data.summary.total * 100).toFixed(1) : "0";
|
|
5066
5210
|
console.log();
|
|
5067
5211
|
console.log(chalk8.green(" \u2713 \u6D4B\u8BD5\u62A5\u544A\u5DF2\u751F\u6210"));
|
|
@@ -5119,7 +5263,7 @@ import ora7 from "ora";
|
|
|
5119
5263
|
|
|
5120
5264
|
// src/services/visual.ts
|
|
5121
5265
|
import fs13 from "fs";
|
|
5122
|
-
import
|
|
5266
|
+
import path15 from "path";
|
|
5123
5267
|
import pixelmatch from "pixelmatch";
|
|
5124
5268
|
import { PNG } from "pngjs";
|
|
5125
5269
|
function compareImages(baselinePath, currentPath, diffOutputPath, threshold = 0.1) {
|
|
@@ -5158,7 +5302,7 @@ function compareImages(baselinePath, currentPath, diffOutputPath, threshold = 0.
|
|
|
5158
5302
|
const passed = diffRatio <= threshold;
|
|
5159
5303
|
let diffPath;
|
|
5160
5304
|
if (diffPixels > 0) {
|
|
5161
|
-
const diffDir =
|
|
5305
|
+
const diffDir = path15.dirname(diffOutputPath);
|
|
5162
5306
|
if (!fs13.existsSync(diffDir)) {
|
|
5163
5307
|
fs13.mkdirSync(diffDir, { recursive: true });
|
|
5164
5308
|
}
|
|
@@ -5179,7 +5323,7 @@ function createBaseline(currentPath, baselinePath) {
|
|
|
5179
5323
|
if (!fs13.existsSync(currentPath)) {
|
|
5180
5324
|
throw new Error(`\u5F53\u524D\u622A\u56FE\u4E0D\u5B58\u5728: ${currentPath}`);
|
|
5181
5325
|
}
|
|
5182
|
-
const baselineDir =
|
|
5326
|
+
const baselineDir = path15.dirname(baselinePath);
|
|
5183
5327
|
if (!fs13.existsSync(baselineDir)) {
|
|
5184
5328
|
fs13.mkdirSync(baselineDir, { recursive: true });
|
|
5185
5329
|
}
|
|
@@ -5193,9 +5337,9 @@ function updateAllBaselines(currentDir, baselineDir) {
|
|
|
5193
5337
|
}
|
|
5194
5338
|
const files = fs13.readdirSync(currentDir).filter((f) => f.endsWith(".png"));
|
|
5195
5339
|
for (const file of files) {
|
|
5196
|
-
const currentPath =
|
|
5197
|
-
const baselinePath =
|
|
5198
|
-
const baselineDirAbs =
|
|
5340
|
+
const currentPath = path15.join(currentDir, file);
|
|
5341
|
+
const baselinePath = path15.join(baselineDir, file);
|
|
5342
|
+
const baselineDirAbs = path15.dirname(baselinePath);
|
|
5199
5343
|
if (!fs13.existsSync(baselineDirAbs)) {
|
|
5200
5344
|
fs13.mkdirSync(baselineDirAbs, { recursive: true });
|
|
5201
5345
|
}
|
|
@@ -5211,7 +5355,7 @@ function cleanBaselines(baselineDir) {
|
|
|
5211
5355
|
const files = fs13.readdirSync(baselineDir).filter((f) => f.endsWith(".png"));
|
|
5212
5356
|
let count = 0;
|
|
5213
5357
|
for (const file of files) {
|
|
5214
|
-
fs13.unlinkSync(
|
|
5358
|
+
fs13.unlinkSync(path15.join(baselineDir, file));
|
|
5215
5359
|
count++;
|
|
5216
5360
|
}
|
|
5217
5361
|
return count;
|
|
@@ -5223,7 +5367,7 @@ function cleanDiffs(diffDir) {
|
|
|
5223
5367
|
const files = fs13.readdirSync(diffDir).filter((f) => f.endsWith(".png"));
|
|
5224
5368
|
let count = 0;
|
|
5225
5369
|
for (const file of files) {
|
|
5226
|
-
fs13.unlinkSync(
|
|
5370
|
+
fs13.unlinkSync(path15.join(diffDir, file));
|
|
5227
5371
|
count++;
|
|
5228
5372
|
}
|
|
5229
5373
|
return count;
|
|
@@ -5235,9 +5379,9 @@ function compareDirectories(baselineDir, currentDir, diffDir, threshold = 0.1) {
|
|
|
5235
5379
|
}
|
|
5236
5380
|
const currentFiles = fs13.readdirSync(currentDir).filter((f) => f.endsWith(".png"));
|
|
5237
5381
|
for (const file of currentFiles) {
|
|
5238
|
-
const currentPath =
|
|
5239
|
-
const baselinePath =
|
|
5240
|
-
const diffPath =
|
|
5382
|
+
const currentPath = path15.join(currentDir, file);
|
|
5383
|
+
const baselinePath = path15.join(baselineDir, file);
|
|
5384
|
+
const diffPath = path15.join(diffDir, file);
|
|
5241
5385
|
if (!fs13.existsSync(baselinePath)) {
|
|
5242
5386
|
createBaseline(currentPath, baselinePath);
|
|
5243
5387
|
results.push({
|
|
@@ -5271,7 +5415,7 @@ function compareDirectories(baselineDir, currentDir, diffDir, threshold = 0.1) {
|
|
|
5271
5415
|
|
|
5272
5416
|
// src/commands/visual.ts
|
|
5273
5417
|
import fs14 from "fs";
|
|
5274
|
-
import
|
|
5418
|
+
import path16 from "path";
|
|
5275
5419
|
function registerVisualCommand(program2) {
|
|
5276
5420
|
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
5421
|
try {
|
|
@@ -5346,9 +5490,9 @@ async function runVisualTest(threshold, baselineDir, diffDir, config) {
|
|
|
5346
5490
|
}
|
|
5347
5491
|
function findCurrentScreenshotsDir(baselineDir) {
|
|
5348
5492
|
const possibleDirs = [
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
|
|
5493
|
+
path16.join(process.cwd(), "test-results"),
|
|
5494
|
+
path16.join(process.cwd(), "tests", "visual", "current"),
|
|
5495
|
+
path16.join(process.cwd(), baselineDir, "..", "current")
|
|
5352
5496
|
];
|
|
5353
5497
|
for (const dir of possibleDirs) {
|
|
5354
5498
|
if (fs14.existsSync(dir)) {
|
|
@@ -5364,7 +5508,7 @@ function findPngFiles(dir) {
|
|
|
5364
5508
|
if (!fs14.existsSync(d)) return;
|
|
5365
5509
|
const entries = fs14.readdirSync(d, { withFileTypes: true });
|
|
5366
5510
|
for (const entry of entries) {
|
|
5367
|
-
const fullPath =
|
|
5511
|
+
const fullPath = path16.join(d, entry.name);
|
|
5368
5512
|
if (entry.isDirectory() && entry.name !== "node_modules") {
|
|
5369
5513
|
walk(fullPath);
|
|
5370
5514
|
} else if (entry.name.endsWith(".png")) {
|
|
@@ -5393,7 +5537,7 @@ function displayVisualResults(results, threshold) {
|
|
|
5393
5537
|
console.log();
|
|
5394
5538
|
console.log(chalk9.green(" \u901A\u8FC7\u7684\u622A\u56FE:"));
|
|
5395
5539
|
for (const result of passed) {
|
|
5396
|
-
const name =
|
|
5540
|
+
const name = path16.basename(result.baselinePath);
|
|
5397
5541
|
if (result.totalPixels > 0) {
|
|
5398
5542
|
const diffPct = (result.diffRatio * 100).toFixed(2);
|
|
5399
5543
|
console.log(chalk9.green(` \u2713 ${name} (\u5DEE\u5F02: ${diffPct}%)`));
|
|
@@ -5406,7 +5550,7 @@ function displayVisualResults(results, threshold) {
|
|
|
5406
5550
|
console.log();
|
|
5407
5551
|
console.log(chalk9.red(" \u5931\u8D25\u7684\u622A\u56FE:"));
|
|
5408
5552
|
for (const result of failed) {
|
|
5409
|
-
const name =
|
|
5553
|
+
const name = path16.basename(result.baselinePath);
|
|
5410
5554
|
if (result.diffPixels === -1) {
|
|
5411
5555
|
console.log(chalk9.red(` \u2717 ${name} (\u5C3A\u5BF8\u4E0D\u5339\u914D)`));
|
|
5412
5556
|
} else {
|
|
@@ -5414,7 +5558,7 @@ function displayVisualResults(results, threshold) {
|
|
|
5414
5558
|
console.log(chalk9.red(` \u2717 ${name} (\u5DEE\u5F02: ${diffPct}%)`));
|
|
5415
5559
|
}
|
|
5416
5560
|
if (result.diffPath) {
|
|
5417
|
-
console.log(chalk9.gray(` \u5DEE\u5F02\u56FE: ${
|
|
5561
|
+
console.log(chalk9.gray(` \u5DEE\u5F02\u56FE: ${path16.relative(process.cwd(), result.diffPath)}`));
|
|
5418
5562
|
}
|
|
5419
5563
|
}
|
|
5420
5564
|
console.log();
|
|
@@ -5461,7 +5605,7 @@ import inquirer4 from "inquirer";
|
|
|
5461
5605
|
import ora8 from "ora";
|
|
5462
5606
|
import { execFile as execFile4 } from "child_process";
|
|
5463
5607
|
import fs15 from "fs";
|
|
5464
|
-
import
|
|
5608
|
+
import path17 from "path";
|
|
5465
5609
|
var DEPENDENCY_GROUPS = [
|
|
5466
5610
|
{
|
|
5467
5611
|
name: "Vitest (\u5355\u5143/\u7EC4\u4EF6/API\u6D4B\u8BD5)",
|
|
@@ -5495,7 +5639,7 @@ function registerSetupCommand(program2) {
|
|
|
5495
5639
|
async function executeSetup(options) {
|
|
5496
5640
|
console.log(chalk10.cyan("\n QAT \u4F9D\u8D56\u5B89\u88C5\u5668\n"));
|
|
5497
5641
|
const projectInfo = detectProject();
|
|
5498
|
-
if (!fs15.existsSync(
|
|
5642
|
+
if (!fs15.existsSync(path17.join(process.cwd(), "package.json"))) {
|
|
5499
5643
|
throw new Error("\u672A\u627E\u5230 package.json\uFF0C\u8BF7\u5728\u9879\u76EE\u6839\u76EE\u5F55\u6267\u884C\u6B64\u547D\u4EE4");
|
|
5500
5644
|
}
|
|
5501
5645
|
if (projectInfo.frameworkConfidence > 0) {
|
|
@@ -5529,8 +5673,8 @@ async function executeSetup(options) {
|
|
|
5529
5673
|
}
|
|
5530
5674
|
]);
|
|
5531
5675
|
if (chooseDir !== "root") {
|
|
5532
|
-
installDir =
|
|
5533
|
-
if (!fs15.existsSync(
|
|
5676
|
+
installDir = path17.join(process.cwd(), chooseDir);
|
|
5677
|
+
if (!fs15.existsSync(path17.join(installDir, "package.json"))) {
|
|
5534
5678
|
throw new Error(`${chooseDir} \u4E0B\u6CA1\u6709 package.json`);
|
|
5535
5679
|
}
|
|
5536
5680
|
}
|
|
@@ -5569,7 +5713,7 @@ ${allPackages.map((p) => ` - ${p}`).join("\n")}`,
|
|
|
5569
5713
|
const pm = getPackageManager(projectInfo.packageManager);
|
|
5570
5714
|
const installCmd = pm === "npm" ? "npm install -D" : pm === "yarn" ? "yarn add -D" : pm === "pnpm" ? "pnpm add -D" : "bun add -D";
|
|
5571
5715
|
for (const group of selectedGroups) {
|
|
5572
|
-
const dirHint = installDir !== process.cwd() ? ` (\u5728 ${
|
|
5716
|
+
const dirHint = installDir !== process.cwd() ? ` (\u5728 ${path17.relative(process.cwd(), installDir) || installDir})` : "";
|
|
5573
5717
|
console.log(chalk10.white(` ${installCmd} ${group.packages.join(" ")}${dirHint}`));
|
|
5574
5718
|
if (group.postInstall) {
|
|
5575
5719
|
for (const cmd of group.postInstall) {
|
|
@@ -5850,7 +5994,7 @@ async function executeChange(_options) {
|
|
|
5850
5994
|
}
|
|
5851
5995
|
|
|
5852
5996
|
// src/cli.ts
|
|
5853
|
-
var VERSION = "0.3.
|
|
5997
|
+
var VERSION = "0.3.06";
|
|
5854
5998
|
function printLogo() {
|
|
5855
5999
|
const logo = `
|
|
5856
6000
|
${chalk13.bold.cyan(" ___ _ _ _ _ _____ _ _ ")}
|