qat-cli 0.3.2 → 0.3.4
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 +572 -379
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +320 -134
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +133 -120
- package/dist/index.d.ts +133 -120
- package/dist/index.js +320 -134
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -8,10 +8,10 @@ import {
|
|
|
8
8
|
|
|
9
9
|
// src/cli.ts
|
|
10
10
|
import { Command } from "commander";
|
|
11
|
-
import
|
|
11
|
+
import chalk13 from "chalk";
|
|
12
12
|
|
|
13
13
|
// src/commands/init.ts
|
|
14
|
-
import
|
|
14
|
+
import chalk3 from "chalk";
|
|
15
15
|
import inquirer from "inquirer";
|
|
16
16
|
import ora from "ora";
|
|
17
17
|
import fs6 from "fs";
|
|
@@ -843,6 +843,15 @@ var NoopAIProvider = class {
|
|
|
843
843
|
};
|
|
844
844
|
|
|
845
845
|
// src/ai/openai-provider.ts
|
|
846
|
+
import chalk2 from "chalk";
|
|
847
|
+
function isDebug() {
|
|
848
|
+
return process.env.QAT_DEBUG === "true";
|
|
849
|
+
}
|
|
850
|
+
function debugLog(tag, ...args) {
|
|
851
|
+
if (!isDebug()) return;
|
|
852
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(11, 19);
|
|
853
|
+
console.error(chalk2.gray(`[DEBUG ${timestamp}] [${tag}]`), ...args);
|
|
854
|
+
}
|
|
846
855
|
var OpenAICompatibleProvider = class {
|
|
847
856
|
constructor(config) {
|
|
848
857
|
this.capabilities = {
|
|
@@ -856,25 +865,20 @@ var OpenAICompatibleProvider = class {
|
|
|
856
865
|
this.baseUrl = config.baseUrl || this.getDefaultBaseUrl(config.provider);
|
|
857
866
|
}
|
|
858
867
|
async generateTest(req) {
|
|
868
|
+
debugLog("GENERATE", `type=${req.type} target=${req.target}`);
|
|
859
869
|
const systemPrompt = this.buildGenerateTestSystemPrompt(req);
|
|
860
870
|
const userPrompt = this.buildGenerateTestUserPrompt(req);
|
|
861
871
|
const content = await this.chat(systemPrompt, userPrompt);
|
|
862
|
-
|
|
872
|
+
const result = this.parseGenerateTestResponse(content);
|
|
873
|
+
debugLog("GENERATE", `done code=${result.code.length}chars confidence=${result.confidence}`);
|
|
874
|
+
return result;
|
|
863
875
|
}
|
|
864
876
|
async analyzeResult(req) {
|
|
865
|
-
const systemPrompt = `\
|
|
866
|
-
\u8F93\u51FA\
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
const
|
|
870
|
-
const failed = r.suites.flatMap((s) => s.tests.filter((t) => t.status === "failed"));
|
|
871
|
-
return `\u7C7B\u578B: ${r.type}, \u72B6\u6001: ${r.status}, \u8017\u65F6: ${r.duration}ms, \u5931\u8D25\u7528\u4F8B: ${failed.length}`;
|
|
872
|
-
}).join("\n");
|
|
873
|
-
const errorDetails = req.errorLogs?.join("\n") || req.testResults.flatMap((r) => r.suites.flatMap((s) => s.tests.filter((t) => t.status === "failed" && t.error))).map((t) => `[${t.name}] ${t.error?.message}`).join("\n") || "\u65E0\u9519\u8BEF\u8BE6\u60C5";
|
|
874
|
-
const userPrompt = `\u6D4B\u8BD5\u7ED3\u679C:
|
|
875
|
-
${resultSummary}
|
|
876
|
-
|
|
877
|
-
\u9519\u8BEF\u8BE6\u60C5:
|
|
877
|
+
const systemPrompt = `\u6D4B\u8BD5\u5206\u6790\u4E13\u5BB6\u3002\u627E\u95EE\u9898\u6839\u56E0\uFF0C\u7ED9\u53EF\u64CD\u4F5C\u5EFA\u8BAE\u3002
|
|
878
|
+
\u8F93\u51FA:1.\u6458\u8981(1-3\u53E5) 2.\u5EFA\u8BAE\u5217\u8868`;
|
|
879
|
+
const failed = req.testResults.flatMap((r) => r.suites.flatMap((s) => s.tests.filter((t) => t.status === "failed")));
|
|
880
|
+
const errorDetails = req.errorLogs?.join("\n") || failed.map((t) => `[${t.name}] ${t.error?.message}`).join("\n") || "\u65E0";
|
|
881
|
+
const userPrompt = `\u5931\u8D25:${failed.length}
|
|
878
882
|
${errorDetails}`;
|
|
879
883
|
const content = await this.chat(systemPrompt, userPrompt);
|
|
880
884
|
return {
|
|
@@ -884,23 +888,22 @@ ${errorDetails}`;
|
|
|
884
888
|
};
|
|
885
889
|
}
|
|
886
890
|
async suggestFix(error) {
|
|
887
|
-
const systemPrompt = `\
|
|
888
|
-
\
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
const userPrompt = `\u9519\u8BEF\u4FE1\u606F: ${error.message}
|
|
893
|
-
${error.stack ? `\u5806\u6808: ${error.stack}` : ""}
|
|
894
|
-
${error.expected ? `\u671F\u671B\u503C: ${error.expected}` : ""}
|
|
895
|
-
${error.actual ? `\u5B9E\u9645\u503C: ${error.actual}` : ""}`;
|
|
891
|
+
const systemPrompt = `\u4EE3\u7801\u4FEE\u590D\u4E13\u5BB6\u3002\u7ED9\u51FA:1.\u95EE\u9898\u5B9A\u4F4D 2.\u4FEE\u590D\u65B9\u6848 3.\u793A\u4F8B\u4EE3\u7801`;
|
|
892
|
+
const userPrompt = `\u9519\u8BEF:${error.message}${error.stack ? `
|
|
893
|
+
\u5806\u6808:${error.stack}` : ""}${error.expected ? `
|
|
894
|
+
\u671F\u671B:${error.expected}` : ""}${error.actual ? `
|
|
895
|
+
\u5B9E\u9645:${error.actual}` : ""}`;
|
|
896
896
|
const content = await this.chat(systemPrompt, userPrompt);
|
|
897
897
|
return content.split("\n").filter((l) => l.trim().startsWith("-") || l.trim().startsWith("\u2022") || l.trim().match(/^\d+\./)).map((l) => l.replace(/^[-•\d.]+\s*/, "").trim()).filter(Boolean);
|
|
898
898
|
}
|
|
899
899
|
async reviewTest(req) {
|
|
900
|
+
debugLog("REVIEW", `target=${req.target} type=${req.testType}`);
|
|
900
901
|
const systemPrompt = this.buildReviewTestSystemPrompt(req);
|
|
901
902
|
const userPrompt = this.buildReviewTestUserPrompt(req);
|
|
902
903
|
const content = await this.chat(systemPrompt, userPrompt);
|
|
903
|
-
|
|
904
|
+
const result = this.parseReviewTestResponse(content);
|
|
905
|
+
debugLog("REVIEW", `approved=${result.approved} score=${result.score} feedback=${result.feedback}`);
|
|
906
|
+
return result;
|
|
904
907
|
}
|
|
905
908
|
// ─── 内部方法 ──────────────────────────────────────────────
|
|
906
909
|
/**
|
|
@@ -964,8 +967,9 @@ ${error.actual ? `\u5B9E\u9645\u503C: ${error.actual}` : ""}`;
|
|
|
964
967
|
return { ok: false, message: `\u8FDE\u63A5\u5931\u8D25: ${error instanceof Error ? error.message : String(error)}`, latencyMs };
|
|
965
968
|
}
|
|
966
969
|
}
|
|
967
|
-
async chat(systemPrompt, userPrompt) {
|
|
970
|
+
async chat(systemPrompt, userPrompt, retries = 2) {
|
|
968
971
|
const url = `${this.baseUrl}/chat/completions`;
|
|
972
|
+
const useStream = isDebug();
|
|
969
973
|
const body = {
|
|
970
974
|
model: this.model,
|
|
971
975
|
messages: [
|
|
@@ -975,99 +979,218 @@ ${error.actual ? `\u5B9E\u9645\u503C: ${error.actual}` : ""}`;
|
|
|
975
979
|
temperature: 0.3,
|
|
976
980
|
max_tokens: 4096
|
|
977
981
|
};
|
|
982
|
+
if (useStream) {
|
|
983
|
+
body.stream = true;
|
|
984
|
+
}
|
|
978
985
|
const headers = {
|
|
979
986
|
"Content-Type": "application/json"
|
|
980
987
|
};
|
|
981
988
|
if (this.apiKey) {
|
|
982
989
|
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
983
990
|
}
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
991
|
+
debugLog("REQUEST", `POST ${url}`);
|
|
992
|
+
debugLog("REQUEST", `model=${this.model} stream=${useStream}`);
|
|
993
|
+
debugLog("SYSTEM", systemPrompt.length > 500 ? `${systemPrompt.slice(0, 500)}...` : systemPrompt);
|
|
994
|
+
debugLog("USER", userPrompt.length > 1e3 ? `${userPrompt.slice(0, 1e3)}...` : userPrompt);
|
|
995
|
+
let lastError = null;
|
|
996
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
997
|
+
try {
|
|
998
|
+
if (attempt > 0) {
|
|
999
|
+
debugLog("RETRY", `\u7B2C${attempt}\u6B21\u91CD\u8BD5...`);
|
|
1000
|
+
}
|
|
1001
|
+
const response = await fetch(url, {
|
|
1002
|
+
method: "POST",
|
|
1003
|
+
headers,
|
|
1004
|
+
body: JSON.stringify(body),
|
|
1005
|
+
signal: AbortSignal.timeout(12e4)
|
|
1006
|
+
// 120s timeout
|
|
1007
|
+
});
|
|
1008
|
+
if (!response.ok) {
|
|
1009
|
+
const text = await response.text().catch(() => "");
|
|
1010
|
+
if ((response.status === 429 || response.status >= 500) && attempt < retries) {
|
|
1011
|
+
const delay = Math.min(1e3 * Math.pow(2, attempt), 8e3);
|
|
1012
|
+
debugLog("RETRY", `HTTP ${response.status}, ${delay}ms\u540E\u91CD\u8BD5`);
|
|
1013
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
1014
|
+
lastError = new Error(`AI API \u8BF7\u6C42\u5931\u8D25 (${response.status}): ${text.slice(0, 200)}`);
|
|
1015
|
+
continue;
|
|
1016
|
+
}
|
|
1017
|
+
throw new Error(`AI API \u8BF7\u6C42\u5931\u8D25 (${response.status}): ${text.slice(0, 500)}`);
|
|
1018
|
+
}
|
|
1019
|
+
if (useStream && response.body) {
|
|
1020
|
+
const content = await this.readStream(response.body);
|
|
1021
|
+
debugLog("RESPONSE", content.length > 500 ? `${content.slice(0, 500)}...` : content);
|
|
1022
|
+
return content;
|
|
1023
|
+
}
|
|
1024
|
+
const data = await response.json();
|
|
1025
|
+
if (!data.choices?.[0]?.message?.content) {
|
|
1026
|
+
throw new Error("AI API \u8FD4\u56DE\u7A7A\u54CD\u5E94");
|
|
1027
|
+
}
|
|
1028
|
+
debugLog("RESPONSE", `tokens: prompt=${data.usage?.prompt_tokens} completion=${data.usage?.completion_tokens} total=${data.usage?.total_tokens}`);
|
|
1029
|
+
debugLog("RESPONSE", data.choices[0].message.content.length > 500 ? `${data.choices[0].message.content.slice(0, 500)}...` : data.choices[0].message.content);
|
|
1030
|
+
return data.choices[0].message.content;
|
|
1031
|
+
} catch (error) {
|
|
1032
|
+
if (error instanceof Error && error.name === "TimeoutError" && attempt < retries) {
|
|
1033
|
+
debugLog("TIMEOUT", `\u7B2C${attempt}\u6B21\u8D85\u65F6\uFF0C\u91CD\u8BD5\u4E2D...`);
|
|
1034
|
+
lastError = error;
|
|
1035
|
+
continue;
|
|
1036
|
+
}
|
|
1037
|
+
throw error;
|
|
1038
|
+
}
|
|
994
1039
|
}
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
1040
|
+
throw lastError || new Error("AI API \u8BF7\u6C42\u5931\u8D25");
|
|
1041
|
+
}
|
|
1042
|
+
/**
|
|
1043
|
+
* 读取 SSE 流式响应,实时输出内容
|
|
1044
|
+
*/
|
|
1045
|
+
async readStream(body) {
|
|
1046
|
+
const chunks = [];
|
|
1047
|
+
const decoder = new TextDecoder();
|
|
1048
|
+
const reader = body.getReader();
|
|
1049
|
+
let buffer = "";
|
|
1050
|
+
let lineCount = 0;
|
|
1051
|
+
try {
|
|
1052
|
+
while (true) {
|
|
1053
|
+
const { done, value } = await reader.read();
|
|
1054
|
+
if (done) break;
|
|
1055
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1056
|
+
const lines = buffer.split("\n");
|
|
1057
|
+
buffer = lines.pop() || "";
|
|
1058
|
+
for (const line of lines) {
|
|
1059
|
+
const trimmed = line.trim();
|
|
1060
|
+
if (!trimmed || trimmed === "data: [DONE]") continue;
|
|
1061
|
+
if (!trimmed.startsWith("data: ")) continue;
|
|
1062
|
+
try {
|
|
1063
|
+
const json = JSON.parse(trimmed.slice(6));
|
|
1064
|
+
const delta = json.choices?.[0]?.delta?.content;
|
|
1065
|
+
if (delta) {
|
|
1066
|
+
chunks.push(delta);
|
|
1067
|
+
lineCount++;
|
|
1068
|
+
process.stderr.write(chalk2.gray(delta));
|
|
1069
|
+
if (lineCount % 20 === 0) {
|
|
1070
|
+
process.stderr.write("\n");
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
if (json.choices?.[0]?.finish_reason === "stop") {
|
|
1074
|
+
debugLog("STREAM", "\u5B8C\u6210");
|
|
1075
|
+
}
|
|
1076
|
+
} catch {
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
} finally {
|
|
1081
|
+
reader.releaseLock();
|
|
1082
|
+
}
|
|
1083
|
+
if (chunks.length > 0) {
|
|
1084
|
+
process.stderr.write("\n");
|
|
1085
|
+
}
|
|
1086
|
+
return chunks.join("");
|
|
1087
|
+
}
|
|
1088
|
+
/**
|
|
1089
|
+
* 压缩源码:保留签名和关键逻辑,剔除注释、空行、样式块
|
|
1090
|
+
* 目标:在保留准确性的前提下减少 token 消耗
|
|
1091
|
+
*/
|
|
1092
|
+
compressSourceCode(code, maxLength = 3e3) {
|
|
1093
|
+
let compressed = code;
|
|
1094
|
+
compressed = compressed.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "");
|
|
1095
|
+
compressed = compressed.replace(/<template[^>]*>([\s\S]*?)<\/template>/gi, (_match, content) => {
|
|
1096
|
+
return `<template>${content.replace(/\s*(?:class|style)\s*=\s*["'][^"']*["']/gi, "")}</template>`;
|
|
1097
|
+
});
|
|
1098
|
+
compressed = compressed.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
1099
|
+
compressed = compressed.replace(/(^|[^:])(\/\/.*$)/gm, "$1");
|
|
1100
|
+
compressed = compressed.replace(/\n\s*\n\s*\n/g, "\n\n");
|
|
1101
|
+
compressed = compressed.split("\n").map((line) => line.trimEnd()).join("\n").trim();
|
|
1102
|
+
if (compressed.length > maxLength) {
|
|
1103
|
+
const scriptMatch = compressed.match(/<script[^>]*>([\s\S]*?)<\/script>/i);
|
|
1104
|
+
const templateMatch = compressed.match(/<template[^>]*>([\s\S]*?)<\/template>/i);
|
|
1105
|
+
if (scriptMatch) {
|
|
1106
|
+
let scriptPart = scriptMatch[1].trim();
|
|
1107
|
+
scriptPart = compressFunctionBodies(scriptPart, maxLength * 0.7);
|
|
1108
|
+
const templatePart = templateMatch ? `<template>${templateMatch[1].replace(/\s+/g, " ").trim().slice(0, 300)}...</template>` : "";
|
|
1109
|
+
compressed = `${templatePart}
|
|
1110
|
+
<script${scriptMatch[0].match(/<script[^>]*>/)?.[0]?.slice(7) || ">"}>${scriptPart}</script>`;
|
|
1111
|
+
} else {
|
|
1112
|
+
compressed = compressFunctionBodies(compressed, maxLength);
|
|
1113
|
+
}
|
|
998
1114
|
}
|
|
999
|
-
return
|
|
1115
|
+
return compressed;
|
|
1000
1116
|
}
|
|
1001
1117
|
buildGenerateTestSystemPrompt(req) {
|
|
1002
1118
|
const typeMap = {
|
|
1003
|
-
unit: "\u5355\u5143\u6D4B\u8BD5
|
|
1004
|
-
component: "\u7EC4\u4EF6\u6D4B\u8BD5
|
|
1005
|
-
e2e: "E2E\
|
|
1006
|
-
api: "API\
|
|
1007
|
-
visual: "\u89C6\u89C9\u56DE\u5F52\u6D4B\u8BD5
|
|
1008
|
-
performance: "\u6027\u80FD\u6D4B\u8BD5
|
|
1119
|
+
unit: "\u5355\u5143\u6D4B\u8BD5(Vitest)",
|
|
1120
|
+
component: "\u7EC4\u4EF6\u6D4B\u8BD5(Vitest+@vue/test-utils)",
|
|
1121
|
+
e2e: "E2E\u6D4B\u8BD5(Playwright)",
|
|
1122
|
+
api: "API\u6D4B\u8BD5(Vitest+fetch)",
|
|
1123
|
+
visual: "\u89C6\u89C9\u56DE\u5F52\u6D4B\u8BD5(Playwright)",
|
|
1124
|
+
performance: "\u6027\u80FD\u6D4B\u8BD5(Playwright)"
|
|
1009
1125
|
};
|
|
1010
|
-
return `\u4F60\u662F\
|
|
1011
|
-
\
|
|
1012
|
-
1. \u53EA\u8F93\u51FA\u6D4B\u8BD5\u4EE3\u7801\uFF0C\u4E0D\u8981\u591A\u4F59\u7684\u89E3\u91CA
|
|
1013
|
-
2. \u4EE3\u7801\u5FC5\u987B\u53EF\u76F4\u63A5\u8FD0\u884C\uFF0C\u5305\u542B\u6240\u6709\u5FC5\u8981\u7684 import
|
|
1014
|
-
3. \u6D4B\u8BD5\u7528\u4F8B\u8986\u76D6\uFF1A\u6B63\u5E38\u8DEF\u5F84\u3001\u8FB9\u754C\u6761\u4EF6\u3001\u9519\u8BEF\u5904\u7406
|
|
1015
|
-
4. \u4F7F\u7528\u4E2D\u6587\u63CF\u8FF0 it/test \u5757\u540D\u79F0
|
|
1016
|
-
5. Vue \u7EC4\u4EF6\u6D4B\u8BD5\u4F7F\u7528 @vue/test-utils \u7684 mount
|
|
1017
|
-
6. \u5982\u679C\u6709 props/emits \u4FE1\u606F\uFF0C\u52A1\u5FC5\u9488\u5BF9\u6BCF\u4E2A prop \u548C emit \u751F\u6210\u6D4B\u8BD5`;
|
|
1126
|
+
return `\u4F60\u662F\u524D\u7AEF\u6D4B\u8BD5\u5DE5\u7A0B\u5E08\uFF0C\u7F16\u5199${typeMap[req.type] || req.type}\u3002
|
|
1127
|
+
\u89C4\u5219:1.\u53EA\u8F93\u51FA\u6D4B\u8BD5\u4EE3\u7801 2.\u5305\u542B\u6240\u6709import 3.\u8986\u76D6\u6B63\u5E38/\u8FB9\u754C/\u9519\u8BEF 4.it\u540D\u7528\u4E2D\u6587 5.Vue\u7528mount 6.\u5FC5\u6D4B\u6BCF\u4E2Aprop\u548Cemit`;
|
|
1018
1128
|
}
|
|
1019
1129
|
buildGenerateTestUserPrompt(req) {
|
|
1020
|
-
|
|
1021
|
-
|
|
1130
|
+
const importPath = this.computeTestImportPath(req.type, req.target);
|
|
1131
|
+
let prompt = `\u4E3A${req.target}\u751F\u6210${req.type}\u6D4B\u8BD5\u3002import\u8DEF\u5F84:${importPath}
|
|
1022
1132
|
`;
|
|
1023
1133
|
if (req.analysis) {
|
|
1024
|
-
|
|
1134
|
+
const parts = [];
|
|
1025
1135
|
if (req.analysis.exports?.length > 0) {
|
|
1026
|
-
|
|
1027
|
-
${
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
return ` - ${asyncFlag}${e.name}${params} [${e.kind}]`;
|
|
1031
|
-
}).join("\n")}
|
|
1032
|
-
`;
|
|
1136
|
+
parts.push(`\u5BFC\u51FA:${req.analysis.exports.map((e) => {
|
|
1137
|
+
const p = e.params?.length ? `(${e.params.join(",")})` : "";
|
|
1138
|
+
return `${e.isAsync ? "async " : ""}${e.name}${p}[${e.kind}]`;
|
|
1139
|
+
}).join(",")}`);
|
|
1033
1140
|
}
|
|
1034
1141
|
if (req.analysis.props?.length) {
|
|
1035
|
-
|
|
1036
|
-
${req.analysis.props.map(
|
|
1037
|
-
(p) => ` - ${p.name}: ${p.type}${p.required ? " (\u5FC5\u586B)" : " (\u53EF\u9009)"}`
|
|
1038
|
-
).join("\n")}
|
|
1039
|
-
`;
|
|
1142
|
+
parts.push(`Props:${req.analysis.props.map((p) => `${p.name}:${p.type}${p.required ? "!" : "?"}`).join(",")}`);
|
|
1040
1143
|
}
|
|
1041
1144
|
if (req.analysis.emits?.length) {
|
|
1042
|
-
|
|
1043
|
-
${req.analysis.emits.map(
|
|
1044
|
-
(e) => ` - ${e.name}${e.params?.length ? `(${e.params.join(", ")})` : ""}`
|
|
1045
|
-
).join("\n")}
|
|
1046
|
-
`;
|
|
1145
|
+
parts.push(`Emits:${req.analysis.emits.map((e) => `${e.name}(${e.params?.join(",") || ""})`).join(",")}`);
|
|
1047
1146
|
}
|
|
1048
1147
|
if (req.analysis.methods?.length) {
|
|
1049
|
-
|
|
1050
|
-
`;
|
|
1148
|
+
parts.push(`Methods:${req.analysis.methods.join(",")}`);
|
|
1051
1149
|
}
|
|
1052
1150
|
if (req.analysis.computed?.length) {
|
|
1053
|
-
|
|
1054
|
-
`;
|
|
1151
|
+
parts.push(`Computed:${req.analysis.computed.join(",")}`);
|
|
1055
1152
|
}
|
|
1153
|
+
prompt += parts.join("\n") + "\n";
|
|
1056
1154
|
}
|
|
1057
1155
|
if (req.context) {
|
|
1058
1156
|
prompt += `
|
|
1059
|
-
\u6E90\u7801
|
|
1060
|
-
\`\`\`
|
|
1061
|
-
${req.context}
|
|
1157
|
+
\u6E90\u7801:
|
|
1158
|
+
\`\`\`
|
|
1159
|
+
${this.compressSourceCode(req.context)}
|
|
1062
1160
|
\`\`\`
|
|
1063
1161
|
`;
|
|
1064
1162
|
}
|
|
1065
1163
|
if (req.framework) {
|
|
1066
|
-
prompt +=
|
|
1067
|
-
\u6846\u67B6: ${req.framework}`;
|
|
1164
|
+
prompt += `\u6846\u67B6:${req.framework}`;
|
|
1068
1165
|
}
|
|
1069
1166
|
return prompt;
|
|
1070
1167
|
}
|
|
1168
|
+
/**
|
|
1169
|
+
* 根据测试类型和源文件路径,计算从测试文件到源文件的正确相对导入路径
|
|
1170
|
+
*/
|
|
1171
|
+
computeTestImportPath(testType, targetPath) {
|
|
1172
|
+
if (!targetPath) return targetPath;
|
|
1173
|
+
const testDirMap = {
|
|
1174
|
+
unit: "tests/unit",
|
|
1175
|
+
component: "tests/component",
|
|
1176
|
+
e2e: "tests/e2e",
|
|
1177
|
+
api: "tests/api",
|
|
1178
|
+
visual: "tests/visual",
|
|
1179
|
+
performance: "tests/e2e"
|
|
1180
|
+
};
|
|
1181
|
+
const testDir = testDirMap[testType];
|
|
1182
|
+
if (!testDir) return targetPath;
|
|
1183
|
+
if (targetPath.startsWith("../") || targetPath.startsWith("./../")) {
|
|
1184
|
+
return targetPath;
|
|
1185
|
+
}
|
|
1186
|
+
const depth = testDir.split("/").length;
|
|
1187
|
+
const prefix = "../".repeat(depth);
|
|
1188
|
+
let cleanPath = targetPath.replace(/^\.\//, "");
|
|
1189
|
+
if (!cleanPath.endsWith(".vue")) {
|
|
1190
|
+
cleanPath = cleanPath.replace(/\.(ts|js|tsx|jsx)$/, "");
|
|
1191
|
+
}
|
|
1192
|
+
return `${prefix}${cleanPath}`;
|
|
1193
|
+
}
|
|
1071
1194
|
parseGenerateTestResponse(content) {
|
|
1072
1195
|
const codeBlockMatch = content.match(/```(?:typescript|ts|javascript|js)?\s*\n([\s\S]*?)```/);
|
|
1073
1196
|
const code = codeBlockMatch ? codeBlockMatch[1].trim() : content.replace(/^(?:```[\s\S]*?\n)?/, "").replace(/\n?```$/, "").trim();
|
|
@@ -1079,67 +1202,49 @@ ${req.context}
|
|
|
1079
1202
|
};
|
|
1080
1203
|
}
|
|
1081
1204
|
buildReviewTestSystemPrompt(_req) {
|
|
1082
|
-
return `\u4F60\u662F\
|
|
1083
|
-
|
|
1084
|
-
\
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
\u8F93\u51FA\u683C\u5F0F\uFF08\u4E25\u683C\u9075\u5B88\uFF09\uFF1A
|
|
1092
|
-
APPROVED: true \u6216 false
|
|
1093
|
-
SCORE: 0.0 \u5230 1.0 \u4E4B\u95F4\u7684\u8BC4\u5206
|
|
1094
|
-
FEEDBACK: \u4E00\u53E5\u8BDD\u5BA1\u8BA1\u610F\u89C1
|
|
1095
|
-
ISSUES: \u95EE\u9898\u5217\u8868\uFF08\u6BCF\u884C\u4E00\u4E2A\uFF0C\u683C\u5F0F "- \u95EE\u9898\u63CF\u8FF0"\uFF09
|
|
1096
|
-
SUGGESTIONS: \u6539\u8FDB\u5EFA\u8BAE\u5217\u8868\uFF08\u6BCF\u884C\u4E00\u4E2A\uFF0C\u683C\u5F0F "- \u5EFA\u8BAE\u63CF\u8FF0"\uFF09`;
|
|
1205
|
+
return `\u4F60\u662F\u6D4B\u8BD5\u5BA1\u8BA1\u4E13\u5BB6\u3002\u5BA1\u67E5\u6D4B\u8BD5\u4EE3\u7801\u662F\u5426\u51C6\u786E\u3002
|
|
1206
|
+
\u6807\u51C6:1.\u7C7B\u578B\u5339\u914D 2.\u8986\u76D6\u6838\u5FC3\u5BFC\u51FA/props/emits 3.\u65AD\u8A00\u6709\u6548(\u975E\u6C38\u771F) 4.import\u6B63\u786E 5.\u53EF\u8FD0\u884C
|
|
1207
|
+
\u8F93\u51FA:
|
|
1208
|
+
APPROVED:true\u6216false
|
|
1209
|
+
SCORE:0.0-1.0
|
|
1210
|
+
FEEDBACK:\u4E00\u53E5\u8BDD
|
|
1211
|
+
ISSUES:- \u95EE\u9898(\u6BCF\u884C\u4E00\u4E2A)
|
|
1212
|
+
SUGGESTIONS:- \u5EFA\u8BAE(\u6BCF\u884C\u4E00\u4E2A)`;
|
|
1097
1213
|
}
|
|
1098
1214
|
buildReviewTestUserPrompt(req) {
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
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} import\u8DEF\u5F84:${importPath}
|
|
1217
|
+
`;
|
|
1218
|
+
prompt += `
|
|
1219
|
+
\u6E90\u7801:
|
|
1220
|
+
\`\`\`
|
|
1221
|
+
${this.compressSourceCode(req.sourceCode, 2e3)}
|
|
1222
|
+
\`\`\`
|
|
1223
|
+
`;
|
|
1224
|
+
prompt += `
|
|
1225
|
+
\u6D4B\u8BD5\u4EE3\u7801:
|
|
1107
1226
|
\`\`\`
|
|
1108
|
-
|
|
1109
|
-
--- \u751F\u6210\u7684\u6D4B\u8BD5\u4EE3\u7801 ---
|
|
1110
|
-
\`\`\`typescript
|
|
1111
1227
|
${req.testCode}
|
|
1112
1228
|
\`\`\``;
|
|
1113
1229
|
if (req.analysis) {
|
|
1114
|
-
|
|
1230
|
+
const parts = [];
|
|
1115
1231
|
if (req.analysis.exports?.length > 0) {
|
|
1116
|
-
|
|
1117
|
-
\u5BFC\u51FA\u9879:
|
|
1118
|
-
${req.analysis.exports.map((e) => {
|
|
1119
|
-
const params = e.params?.length ? `(${e.params.join(", ")})` : "";
|
|
1120
|
-
const asyncFlag = e.isAsync ? "async " : "";
|
|
1121
|
-
return ` - ${asyncFlag}${e.name}${params} [${e.kind}]`;
|
|
1122
|
-
}).join("\n")}`;
|
|
1232
|
+
parts.push(`\u5BFC\u51FA:${req.analysis.exports.map((e) => `${e.isAsync ? "async " : ""}${e.name}(${e.params?.join(",") || ""})[${e.kind}]`).join(",")}`);
|
|
1123
1233
|
}
|
|
1124
1234
|
if (req.analysis.props?.length) {
|
|
1125
|
-
|
|
1126
|
-
Props:
|
|
1127
|
-
${req.analysis.props.map(
|
|
1128
|
-
(p) => ` - ${p.name}: ${p.type}${p.required ? " (\u5FC5\u586B)" : " (\u53EF\u9009)"}`
|
|
1129
|
-
).join("\n")}`;
|
|
1235
|
+
parts.push(`Props:${req.analysis.props.map((p) => `${p.name}:${p.type}${p.required ? "!" : "?"}`).join(",")}`);
|
|
1130
1236
|
}
|
|
1131
1237
|
if (req.analysis.emits?.length) {
|
|
1238
|
+
parts.push(`Emits:${req.analysis.emits.map((e) => `${e.name}(${e.params?.join(",") || ""})`).join(",")}`);
|
|
1239
|
+
}
|
|
1240
|
+
if (parts.length > 0) {
|
|
1132
1241
|
prompt += `
|
|
1133
|
-
|
|
1134
|
-
${req.analysis.emits.map(
|
|
1135
|
-
(e) => ` - ${e.name}${e.params?.length ? `(${e.params.join(", ")})` : ""}`
|
|
1136
|
-
).join("\n")}`;
|
|
1242
|
+
\u5206\u6790:${parts.join("|")}`;
|
|
1137
1243
|
}
|
|
1138
1244
|
}
|
|
1139
1245
|
if (req.generationDescription) {
|
|
1140
1246
|
prompt += `
|
|
1141
|
-
|
|
1142
|
-
\u751F\u6210\u8005\u8BF4\u660E: ${req.generationDescription}`;
|
|
1247
|
+
\u8BF4\u660E:${req.generationDescription}`;
|
|
1143
1248
|
}
|
|
1144
1249
|
return prompt;
|
|
1145
1250
|
}
|
|
@@ -1199,6 +1304,61 @@ ${req.analysis.emits.map(
|
|
|
1199
1304
|
return urlMap[provider] || "https://api.openai.com/v1";
|
|
1200
1305
|
}
|
|
1201
1306
|
};
|
|
1307
|
+
function compressFunctionBodies(code, maxLength) {
|
|
1308
|
+
const lines = code.split("\n");
|
|
1309
|
+
const result = [];
|
|
1310
|
+
let depth = 0;
|
|
1311
|
+
let fnDepth = 0;
|
|
1312
|
+
let inFunction = false;
|
|
1313
|
+
let braceBalance = 0;
|
|
1314
|
+
let capturedLines = 0;
|
|
1315
|
+
for (const line of lines) {
|
|
1316
|
+
const trimmed = line.trim();
|
|
1317
|
+
for (const ch of trimmed) {
|
|
1318
|
+
if (ch === "{") {
|
|
1319
|
+
braceBalance++;
|
|
1320
|
+
depth++;
|
|
1321
|
+
}
|
|
1322
|
+
if (ch === "}") {
|
|
1323
|
+
braceBalance--;
|
|
1324
|
+
depth = Math.max(0, depth - 1);
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
const isFnSignature = /^(export\s+)?(async\s+)?function\s|=>\s*\{|=>\s*$/m.test(trimmed) || /^(const|let|var)\s+\w+\s*=\s*(async\s+)?(\([^)]*\)|[^=])\s*=>/.test(trimmed);
|
|
1328
|
+
if (isFnSignature && !inFunction) {
|
|
1329
|
+
inFunction = true;
|
|
1330
|
+
fnDepth = depth;
|
|
1331
|
+
result.push(line);
|
|
1332
|
+
capturedLines++;
|
|
1333
|
+
continue;
|
|
1334
|
+
}
|
|
1335
|
+
if (inFunction) {
|
|
1336
|
+
const isReturn = trimmed.startsWith("return ");
|
|
1337
|
+
const isBranch = /^(if|else|switch|case|try|catch|finally|for|while)/.test(trimmed);
|
|
1338
|
+
const isClosing = trimmed === "}" && depth <= fnDepth - 1;
|
|
1339
|
+
if (isClosing) {
|
|
1340
|
+
result.push(line);
|
|
1341
|
+
inFunction = false;
|
|
1342
|
+
capturedLines++;
|
|
1343
|
+
} else if (isReturn || isBranch) {
|
|
1344
|
+
result.push(trimmed.length > 120 ? `${trimmed.slice(0, 120)}...` : line);
|
|
1345
|
+
capturedLines++;
|
|
1346
|
+
} else if (trimmed.startsWith("//") || trimmed === "") {
|
|
1347
|
+
} else if (capturedLines < maxLength / 30) {
|
|
1348
|
+
result.push(trimmed.length > 100 ? `${trimmed.slice(0, 100)}...` : line);
|
|
1349
|
+
capturedLines++;
|
|
1350
|
+
}
|
|
1351
|
+
} else {
|
|
1352
|
+
result.push(line);
|
|
1353
|
+
capturedLines++;
|
|
1354
|
+
}
|
|
1355
|
+
if (result.join("\n").length > maxLength) {
|
|
1356
|
+
result.push("// ... (truncated)");
|
|
1357
|
+
break;
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
return result.join("\n");
|
|
1361
|
+
}
|
|
1202
1362
|
|
|
1203
1363
|
// src/ai/provider.ts
|
|
1204
1364
|
var providerRegistry = /* @__PURE__ */ new Map();
|
|
@@ -1747,7 +1907,7 @@ function registerInitCommand(program2) {
|
|
|
1747
1907
|
try {
|
|
1748
1908
|
await executeInit(options);
|
|
1749
1909
|
} catch (error) {
|
|
1750
|
-
console.error(
|
|
1910
|
+
console.error(chalk3.red(`
|
|
1751
1911
|
\u2717 ${error instanceof Error ? error.message : String(error)}
|
|
1752
1912
|
`));
|
|
1753
1913
|
process.exit(1);
|
|
@@ -1769,19 +1929,19 @@ async function executeInit(options) {
|
|
|
1769
1929
|
}
|
|
1770
1930
|
]);
|
|
1771
1931
|
if (!proceed) {
|
|
1772
|
-
console.log(
|
|
1932
|
+
console.log(chalk3.gray("\n \u5DF2\u53D6\u6D88\u521D\u59CB\u5316\n"));
|
|
1773
1933
|
return;
|
|
1774
1934
|
}
|
|
1775
1935
|
}
|
|
1776
1936
|
let globalAI = loadGlobalAIConfig();
|
|
1777
1937
|
if (!globalAI) {
|
|
1778
|
-
console.log(
|
|
1938
|
+
console.log(chalk3.cyan(" AI \u6A21\u578B\u914D\u7F6E (\u9996\u6B21\u4F7F\u7528\u9700\u914D\u7F6E\uFF0C\u4E4B\u540E\u53EF\u901A\u8FC7 qat change \u4FEE\u6539)\n"));
|
|
1779
1939
|
globalAI = await promptAIConfig();
|
|
1780
1940
|
saveGlobalAIConfig(globalAI);
|
|
1781
|
-
console.log(
|
|
1941
|
+
console.log(chalk3.gray(` \u914D\u7F6E\u5DF2\u4FDD\u5B58\u81F3 ${getAIConfigPath()}
|
|
1782
1942
|
`));
|
|
1783
1943
|
} else {
|
|
1784
|
-
console.log(
|
|
1944
|
+
console.log(chalk3.green(` \u2713 \u5F53\u524D AI \u6A21\u578B: ${chalk3.white(globalAI.model)} @ ${chalk3.gray(globalAI.baseUrl)} (${maskApiKey(globalAI.apiKey)})
|
|
1785
1945
|
`));
|
|
1786
1946
|
}
|
|
1787
1947
|
const aiConfig = toAIConfig(globalAI);
|
|
@@ -1790,10 +1950,10 @@ async function executeInit(options) {
|
|
|
1790
1950
|
try {
|
|
1791
1951
|
const result = await testAIConnection(aiConfig);
|
|
1792
1952
|
if (result.ok) {
|
|
1793
|
-
testSpinner.succeed(`AI \u8FDE\u901A\u6B63\u5E38 ${
|
|
1953
|
+
testSpinner.succeed(`AI \u8FDE\u901A\u6B63\u5E38 ${chalk3.gray(`${globalAI.model} (${result.latencyMs}ms)`)}`);
|
|
1794
1954
|
} else {
|
|
1795
1955
|
testSpinner.fail(`AI \u8FDE\u901A\u5F02\u5E38: ${result.message}`);
|
|
1796
|
-
console.log(
|
|
1956
|
+
console.log(chalk3.yellow(" \u53EF\u8FD0\u884C qat change \u4FEE\u6539 AI \u914D\u7F6E\u3002"));
|
|
1797
1957
|
}
|
|
1798
1958
|
} catch (error) {
|
|
1799
1959
|
testSpinner.fail(`AI \u8FDE\u901A\u6D4B\u8BD5\u5931\u8D25: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -1814,7 +1974,7 @@ async function executeInit(options) {
|
|
|
1814
1974
|
}
|
|
1815
1975
|
]);
|
|
1816
1976
|
if (!overwrite) {
|
|
1817
|
-
console.log(
|
|
1977
|
+
console.log(chalk3.gray(" \u4FDD\u7559\u73B0\u6709\u914D\u7F6E\u6587\u4EF6\uFF0C\u7EE7\u7EED\u540E\u7EED\u6B65\u9AA4..."));
|
|
1818
1978
|
configPath = existingConfigPath;
|
|
1819
1979
|
} else {
|
|
1820
1980
|
const fileSpinner = ora("\u6B63\u5728\u8986\u76D6\u914D\u7F6E\u6587\u4EF6...").start();
|
|
@@ -1849,10 +2009,10 @@ async function executeInit(options) {
|
|
|
1849
2009
|
const mockFilePath = path6.join(process.cwd(), mockDir, "auto-generated.json");
|
|
1850
2010
|
if (!fs6.existsSync(mockFilePath)) {
|
|
1851
2011
|
fs6.writeFileSync(mockFilePath, JSON.stringify(mockRoutes, null, 2), "utf-8");
|
|
1852
|
-
console.log(
|
|
2012
|
+
console.log(chalk3.green(` \u81EA\u52A8\u53D1\u73B0 ${apiCalls.length} \u4E2A API \u63A5\u53E3\uFF0C\u5DF2\u751F\u6210 Mock \u8DEF\u7531`));
|
|
1853
2013
|
}
|
|
1854
2014
|
} else {
|
|
1855
|
-
console.log(
|
|
2015
|
+
console.log(chalk3.gray(" \u672A\u53D1\u73B0 API \u8C03\u7528\uFF0C\u5DF2\u751F\u6210\u793A\u4F8B Mock \u8DEF\u7531"));
|
|
1856
2016
|
}
|
|
1857
2017
|
}
|
|
1858
2018
|
const srcDir = config.project?.srcDir || "src";
|
|
@@ -1860,7 +2020,7 @@ async function executeInit(options) {
|
|
|
1860
2020
|
const utilities = discoverUtilityFiles(process.cwd(), srcDir);
|
|
1861
2021
|
const totalFiles = components.length + utilities.length;
|
|
1862
2022
|
if (totalFiles > 0) {
|
|
1863
|
-
console.log(
|
|
2023
|
+
console.log(chalk3.cyan(`
|
|
1864
2024
|
\u53D1\u73B0 ${totalFiles} \u4E2A\u53EF\u6D4B\u8BD5\u6587\u4EF6 (${components.length} \u7EC4\u4EF6, ${utilities.length} \u5DE5\u5177/\u670D\u52A1)`));
|
|
1865
2025
|
}
|
|
1866
2026
|
displayResult(configPath, createdDirs, totalFiles, projectInfo);
|
|
@@ -1947,8 +2107,8 @@ function buildProjectConfig(projectInfo) {
|
|
|
1947
2107
|
};
|
|
1948
2108
|
}
|
|
1949
2109
|
function displayProjectInfo(info) {
|
|
1950
|
-
console.log(
|
|
1951
|
-
console.log(
|
|
2110
|
+
console.log(chalk3.cyan("\n \u9879\u76EE\u68C0\u6D4B\u7ED3\u679C:"));
|
|
2111
|
+
console.log(chalk3.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
1952
2112
|
const items = [
|
|
1953
2113
|
["\u9879\u76EE\u540D\u79F0", info.name],
|
|
1954
2114
|
["\u6846\u67B6", info.frameworkConfidence > 0.5 ? `${info.frameworkDisplayName} (\u7F6E\u4FE1\u5EA6 ${Math.round(info.frameworkConfidence * 100)}%)` : info.frameworkDisplayName],
|
|
@@ -1970,8 +2130,8 @@ function displayProjectInfo(info) {
|
|
|
1970
2130
|
items.push(["\u7EC4\u4EF6\u76EE\u5F55", info.componentDirs.join(", ")]);
|
|
1971
2131
|
}
|
|
1972
2132
|
for (const [label, value] of items) {
|
|
1973
|
-
const displayValue = value === true ?
|
|
1974
|
-
console.log(` ${
|
|
2133
|
+
const displayValue = value === true ? chalk3.green("\u2713") : value === false ? chalk3.red("\u2717") : String(value);
|
|
2134
|
+
console.log(` ${chalk3.white(label.padEnd(12))} ${displayValue}`);
|
|
1975
2135
|
}
|
|
1976
2136
|
console.log();
|
|
1977
2137
|
}
|
|
@@ -2002,34 +2162,34 @@ function createTestDirectories(config) {
|
|
|
2002
2162
|
}
|
|
2003
2163
|
function displayResult(configPath, createdDirs, totalTestableFiles, projectInfo) {
|
|
2004
2164
|
const relativeConfig = path6.relative(process.cwd(), configPath);
|
|
2005
|
-
console.log(
|
|
2006
|
-
console.log(
|
|
2007
|
-
console.log(
|
|
2165
|
+
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}`));
|
|
2008
2168
|
console.log();
|
|
2009
2169
|
if (createdDirs.length > 0) {
|
|
2010
|
-
console.log(
|
|
2170
|
+
console.log(chalk3.white(" \u5DF2\u521B\u5EFA\u76EE\u5F55:"));
|
|
2011
2171
|
for (const dir of createdDirs) {
|
|
2012
|
-
console.log(
|
|
2172
|
+
console.log(chalk3.gray(` ${dir}/`));
|
|
2013
2173
|
}
|
|
2014
2174
|
console.log();
|
|
2015
2175
|
}
|
|
2016
|
-
console.log(
|
|
2176
|
+
console.log(chalk3.cyan(" \u4E0B\u4E00\u6B65:"));
|
|
2017
2177
|
if (totalTestableFiles > 0) {
|
|
2018
|
-
console.log(
|
|
2178
|
+
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)`));
|
|
2019
2179
|
} else {
|
|
2020
|
-
console.log(
|
|
2180
|
+
console.log(chalk3.gray(" 1. qat create \u521B\u5EFA\u6D4B\u8BD5\u7528\u4F8B"));
|
|
2021
2181
|
}
|
|
2022
|
-
console.log(
|
|
2023
|
-
console.log(
|
|
2182
|
+
console.log(chalk3.gray(" 2. qat run \u6267\u884C\u6D4B\u8BD5"));
|
|
2183
|
+
console.log(chalk3.gray(" 3. qat status \u67E5\u770B AI \u6A21\u578B\u72B6\u6001"));
|
|
2024
2184
|
if (projectInfo.testFrameworks.length === 0) {
|
|
2025
2185
|
console.log();
|
|
2026
|
-
console.log(
|
|
2186
|
+
console.log(chalk3.yellow(" \u26A0 \u672A\u68C0\u6D4B\u5230\u6D4B\u8BD5\u6846\u67B6\u4F9D\u8D56\uFF0C\u8FD0\u884C qat run \u65F6\u4F1A\u63D0\u793A\u5B89\u88C5"));
|
|
2027
2187
|
}
|
|
2028
2188
|
console.log();
|
|
2029
2189
|
}
|
|
2030
2190
|
|
|
2031
2191
|
// src/commands/create.ts
|
|
2032
|
-
import
|
|
2192
|
+
import chalk5 from "chalk";
|
|
2033
2193
|
import inquirer2 from "inquirer";
|
|
2034
2194
|
import ora3 from "ora";
|
|
2035
2195
|
import fs8 from "fs";
|
|
@@ -2082,9 +2242,33 @@ Handlebars.registerHelper("propTestValue", (prop) => {
|
|
|
2082
2242
|
};
|
|
2083
2243
|
return map[prop.type] || "'test-value'";
|
|
2084
2244
|
});
|
|
2245
|
+
function resolveImportPath(testType, targetPath) {
|
|
2246
|
+
if (!targetPath) return targetPath;
|
|
2247
|
+
const testDirMap = {
|
|
2248
|
+
unit: "tests/unit",
|
|
2249
|
+
component: "tests/component",
|
|
2250
|
+
e2e: "tests/e2e",
|
|
2251
|
+
api: "tests/api",
|
|
2252
|
+
visual: "tests/visual",
|
|
2253
|
+
performance: "tests/e2e"
|
|
2254
|
+
};
|
|
2255
|
+
const testDir = testDirMap[testType];
|
|
2256
|
+
if (!testDir) return targetPath;
|
|
2257
|
+
if (targetPath.startsWith("../") || targetPath.startsWith("./../")) {
|
|
2258
|
+
return targetPath;
|
|
2259
|
+
}
|
|
2260
|
+
const depth = testDir.split("/").length;
|
|
2261
|
+
const prefix = "../".repeat(depth);
|
|
2262
|
+
let cleanPath = targetPath.replace(/^\.\//, "");
|
|
2263
|
+
if (!cleanPath.endsWith(".vue")) {
|
|
2264
|
+
cleanPath = cleanPath.replace(/\.(ts|js|tsx|jsx)$/, "");
|
|
2265
|
+
}
|
|
2266
|
+
return `${prefix}${cleanPath}`;
|
|
2267
|
+
}
|
|
2085
2268
|
function renderTemplate(type, context) {
|
|
2086
2269
|
const templateContent = loadTemplate(type);
|
|
2087
2270
|
const template = Handlebars.compile(templateContent);
|
|
2271
|
+
const resolvedTarget = resolveImportPath(type, context.target);
|
|
2088
2272
|
const fullContext = {
|
|
2089
2273
|
vueVersion: 3,
|
|
2090
2274
|
typescript: true,
|
|
@@ -2105,6 +2289,8 @@ function renderTemplate(type, context) {
|
|
|
2105
2289
|
requiredProps: [],
|
|
2106
2290
|
optionalProps: [],
|
|
2107
2291
|
...context,
|
|
2292
|
+
// 使用计算后的正确路径
|
|
2293
|
+
target: resolvedTarget,
|
|
2108
2294
|
framework: context.framework || "vue",
|
|
2109
2295
|
camelName: context.camelName || toCamelCase(context.name),
|
|
2110
2296
|
pascalName: context.pascalName || toPascalCase(context.name)
|
|
@@ -2464,7 +2650,7 @@ function toPascalCase(str) {
|
|
|
2464
2650
|
}
|
|
2465
2651
|
|
|
2466
2652
|
// src/services/test-reviewer.ts
|
|
2467
|
-
import
|
|
2653
|
+
import chalk4 from "chalk";
|
|
2468
2654
|
import ora2 from "ora";
|
|
2469
2655
|
var MAX_RETRIES = 3;
|
|
2470
2656
|
var REVIEW_THRESHOLD = 0.6;
|
|
@@ -2484,6 +2670,9 @@ async function generateWithReview(params) {
|
|
|
2484
2670
|
for (let i = 0; i < MAX_RETRIES; i++) {
|
|
2485
2671
|
attempts = i + 1;
|
|
2486
2672
|
onAttempt?.(attempts, MAX_RETRIES);
|
|
2673
|
+
if (process.env.QAT_DEBUG === "true") {
|
|
2674
|
+
console.error(chalk4.gray(`[DEBUG] \u2500\u2500\u2500 \u751F\u6210\u8005 AI \u7B2C${attempts}\u6B21\u5C1D\u8BD5 \u2500\u2500\u2500`));
|
|
2675
|
+
}
|
|
2487
2676
|
const generationPrompt = i === 0 ? void 0 : `\u4E0A\u6B21\u751F\u6210\u7684\u6D4B\u8BD5\u672A\u901A\u8FC7\u5BA1\u8BA1\uFF0C\u8BF7\u6839\u636E\u4EE5\u4E0B\u53CD\u9988\u91CD\u65B0\u751F\u6210\uFF1A
|
|
2488
2677
|
\u5BA1\u8BA1\u8BC4\u5206: ${(lastReview.score * 100).toFixed(0)}%
|
|
2489
2678
|
\u5BA1\u8BA1\u610F\u89C1: ${lastReview.feedback}
|
|
@@ -2496,16 +2685,17 @@ ${lastReview.suggestions.map((s) => `- ${s}`).join("\n")}
|
|
|
2496
2685
|
const generateResponse = await generatorProvider.generateTest({
|
|
2497
2686
|
type: testType,
|
|
2498
2687
|
target: targetPath,
|
|
2499
|
-
context
|
|
2500
|
-
|
|
2501
|
-
--- \u5BA1\u8BA1\u53CD\u9988 ---
|
|
2502
|
-
${generationPrompt}`,
|
|
2688
|
+
// 重试时:源码已压缩在分析摘要中,context只传审计反馈以减少token
|
|
2689
|
+
context: i === 0 ? sourceCode : generationPrompt,
|
|
2503
2690
|
framework: framework || void 0,
|
|
2504
2691
|
analysis
|
|
2505
2692
|
});
|
|
2506
2693
|
currentCode = generateResponse.code;
|
|
2507
2694
|
currentDescription = generateResponse.description;
|
|
2508
2695
|
currentConfidence = generateResponse.confidence;
|
|
2696
|
+
if (process.env.QAT_DEBUG === "true") {
|
|
2697
|
+
console.error(chalk4.gray(`[DEBUG] \u2500\u2500\u2500 \u5BA1\u8BA1\u5458 AI \u5BA1\u67E5 \u2500\u2500\u2500`));
|
|
2698
|
+
}
|
|
2509
2699
|
const reviewRequest = {
|
|
2510
2700
|
target: targetPath,
|
|
2511
2701
|
sourceCode,
|
|
@@ -2537,20 +2727,20 @@ function printReviewReport(report) {
|
|
|
2537
2727
|
const approved = report.filter((r) => r.approved);
|
|
2538
2728
|
const failed = report.filter((r) => !r.approved);
|
|
2539
2729
|
console.log();
|
|
2540
|
-
console.log(
|
|
2541
|
-
console.log(
|
|
2730
|
+
console.log(chalk4.cyan(" \u5BA1\u8BA1\u62A5\u544A:"));
|
|
2731
|
+
console.log(chalk4.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
2542
2732
|
for (const entry of approved) {
|
|
2543
|
-
console.log(` ${
|
|
2733
|
+
console.log(` ${chalk4.green("\u2713")} ${chalk4.gray(entry.target)} ${chalk4.green(`(${(entry.score * 100).toFixed(0)}%`)}${entry.attempts > 1 ? chalk4.yellow(` ${entry.attempts}\u6B21\u5C1D\u8BD5`) : ""})`);
|
|
2544
2734
|
}
|
|
2545
2735
|
for (const entry of failed) {
|
|
2546
|
-
console.log(` ${
|
|
2736
|
+
console.log(` ${chalk4.red("\u2717")} ${chalk4.gray(entry.target)} ${chalk4.red(`(${(entry.score * 100).toFixed(0)}%)`)}`);
|
|
2547
2737
|
if (entry.issues.length > 0) {
|
|
2548
2738
|
for (const issue of entry.issues.slice(0, 3)) {
|
|
2549
|
-
console.log(
|
|
2739
|
+
console.log(chalk4.gray(` - ${issue}`));
|
|
2550
2740
|
}
|
|
2551
2741
|
}
|
|
2552
2742
|
if (entry.feedback) {
|
|
2553
|
-
console.log(
|
|
2743
|
+
console.log(chalk4.gray(` \u53CD\u9988: ${entry.feedback}`));
|
|
2554
2744
|
}
|
|
2555
2745
|
}
|
|
2556
2746
|
console.log();
|
|
@@ -2578,7 +2768,7 @@ function registerCreateCommand(program2) {
|
|
|
2578
2768
|
try {
|
|
2579
2769
|
await executeCreate(options);
|
|
2580
2770
|
} catch (error) {
|
|
2581
|
-
console.error(
|
|
2771
|
+
console.error(chalk5.red(`
|
|
2582
2772
|
\u2717 ${error instanceof Error ? error.message : String(error)}
|
|
2583
2773
|
`));
|
|
2584
2774
|
process.exit(1);
|
|
@@ -2603,7 +2793,7 @@ async function executeCreate(options) {
|
|
|
2603
2793
|
name: "types",
|
|
2604
2794
|
message: "\u9009\u62E9\u6D4B\u8BD5\u7C7B\u578B (\u7A7A\u683C\u9009\u62E9/\u53D6\u6D88\uFF0C\u56DE\u8F66\u786E\u8BA4):",
|
|
2605
2795
|
choices: Object.entries(TEST_TYPE_LABELS).map(([value, label]) => ({
|
|
2606
|
-
name: `${label} - ${
|
|
2796
|
+
name: `${label} - ${chalk5.gray(TEST_TYPE_DESCRIPTIONS[value])}`,
|
|
2607
2797
|
value,
|
|
2608
2798
|
short: label,
|
|
2609
2799
|
checked: value === "unit" || value === "component"
|
|
@@ -2720,7 +2910,7 @@ async function executeCreate(options) {
|
|
|
2720
2910
|
createdFiles.push({ type: testType, filePath });
|
|
2721
2911
|
} catch (error) {
|
|
2722
2912
|
spinner.fail(`${TEST_TYPE_LABELS[testType]} - ${path8.basename(testTarget)} \u521B\u5EFA\u5931\u8D25`);
|
|
2723
|
-
console.log(
|
|
2913
|
+
console.log(chalk5.gray(` ${error instanceof Error ? error.message : String(error)}`));
|
|
2724
2914
|
}
|
|
2725
2915
|
}
|
|
2726
2916
|
}
|
|
@@ -2734,13 +2924,13 @@ async function selectTargets(types, projectInfo, srcDir) {
|
|
|
2734
2924
|
if (types.includes("unit")) {
|
|
2735
2925
|
const utils = discoverUtilityFiles(process.cwd(), srcDir);
|
|
2736
2926
|
for (const f of utils.slice(0, 30)) {
|
|
2737
|
-
allTargets.push({ name: `${
|
|
2927
|
+
allTargets.push({ name: `${chalk5.gray("[unit]")} ${f}`, value: f });
|
|
2738
2928
|
}
|
|
2739
2929
|
}
|
|
2740
2930
|
if (types.includes("component")) {
|
|
2741
2931
|
const components = discoverVueComponents(process.cwd(), srcDir);
|
|
2742
2932
|
for (const f of components.slice(0, 30)) {
|
|
2743
|
-
allTargets.push({ name: `${
|
|
2933
|
+
allTargets.push({ name: `${chalk5.gray("[comp]")} ${f}`, value: f });
|
|
2744
2934
|
}
|
|
2745
2935
|
}
|
|
2746
2936
|
if (allTargets.length === 0) {
|
|
@@ -2761,7 +2951,7 @@ async function selectTargets(types, projectInfo, srcDir) {
|
|
|
2761
2951
|
message: "\u9009\u62E9\u88AB\u6D4B\u76EE\u6807 (\u7A7A\u683C\u9009\u62E9/\u53D6\u6D88\uFF0C\u56DE\u8F66\u786E\u8BA4):",
|
|
2762
2952
|
choices: [
|
|
2763
2953
|
...allTargets,
|
|
2764
|
-
{ name:
|
|
2954
|
+
{ name: chalk5.gray("\u624B\u52A8\u8F93\u5165\u8DEF\u5F84"), value: "__manual__" }
|
|
2765
2955
|
],
|
|
2766
2956
|
validate: (input) => {
|
|
2767
2957
|
if (input.length === 0) return "\u8BF7\u81F3\u5C11\u9009\u62E9\u4E00\u4E2A\u76EE\u6807";
|
|
@@ -2860,38 +3050,38 @@ function getTestOutputDir(type) {
|
|
|
2860
3050
|
}
|
|
2861
3051
|
function displayCreateResult(createdFiles, skippedCount, usedAI) {
|
|
2862
3052
|
if (createdFiles.length === 0 && skippedCount === 0) {
|
|
2863
|
-
console.log(
|
|
3053
|
+
console.log(chalk5.yellow("\n \u6CA1\u6709\u521B\u5EFA\u4EFB\u4F55\u6D4B\u8BD5\u6587\u4EF6\n"));
|
|
2864
3054
|
return;
|
|
2865
3055
|
}
|
|
2866
|
-
console.log(
|
|
3056
|
+
console.log(chalk5.green(`
|
|
2867
3057
|
\u2713 \u5DF2\u521B\u5EFA ${createdFiles.length} \u4E2A\u6D4B\u8BD5\u6587\u4EF6`));
|
|
2868
3058
|
if (skippedCount > 0) {
|
|
2869
|
-
console.log(
|
|
3059
|
+
console.log(chalk5.yellow(` \u26A0 \u8DF3\u8FC7 ${skippedCount} \u4E2A\u5DF2\u5B58\u5728\u7684\u6587\u4EF6`));
|
|
2870
3060
|
}
|
|
2871
3061
|
console.log();
|
|
2872
3062
|
for (const { type, filePath } of createdFiles) {
|
|
2873
3063
|
const relativePath = path8.relative(process.cwd(), filePath);
|
|
2874
|
-
console.log(
|
|
3064
|
+
console.log(chalk5.white(` ${chalk5.cyan(TEST_TYPE_LABELS[type])} ${chalk5.gray(relativePath)}`));
|
|
2875
3065
|
}
|
|
2876
3066
|
if (usedAI) {
|
|
2877
3067
|
console.log();
|
|
2878
|
-
console.log(
|
|
3068
|
+
console.log(chalk5.magenta(" \u751F\u6210\u65B9\u5F0F: AI \u8F85\u52A9"));
|
|
2879
3069
|
}
|
|
2880
3070
|
console.log();
|
|
2881
|
-
console.log(
|
|
3071
|
+
console.log(chalk5.gray(" \u8FD0\u884C\u6D4B\u8BD5:"));
|
|
2882
3072
|
const uniqueTypes = [...new Set(createdFiles.map((f) => f.type))];
|
|
2883
3073
|
if (uniqueTypes.length === 1) {
|
|
2884
|
-
console.log(
|
|
3074
|
+
console.log(chalk5.cyan(` qat run -t ${uniqueTypes[0]}`));
|
|
2885
3075
|
} else {
|
|
2886
|
-
console.log(
|
|
2887
|
-
console.log(
|
|
2888
|
-
console.log(
|
|
3076
|
+
console.log(chalk5.cyan(` qat run -t ${uniqueTypes.join(" -t ")}`));
|
|
3077
|
+
console.log(chalk5.gray(" # \u6216\u8FD0\u884C\u5168\u90E8"));
|
|
3078
|
+
console.log(chalk5.cyan(" qat run"));
|
|
2889
3079
|
}
|
|
2890
3080
|
console.log();
|
|
2891
3081
|
}
|
|
2892
3082
|
|
|
2893
3083
|
// src/commands/run.ts
|
|
2894
|
-
import
|
|
3084
|
+
import chalk6 from "chalk";
|
|
2895
3085
|
import inquirer3 from "inquirer";
|
|
2896
3086
|
import ora4 from "ora";
|
|
2897
3087
|
import fs11 from "fs";
|
|
@@ -2959,14 +3149,14 @@ function buildVitestArgs(options) {
|
|
|
2959
3149
|
if (options.files && options.files.length > 0) {
|
|
2960
3150
|
args.push(...options.files);
|
|
2961
3151
|
} else {
|
|
2962
|
-
const
|
|
2963
|
-
unit:
|
|
2964
|
-
component:
|
|
2965
|
-
api:
|
|
3152
|
+
const pathMap = {
|
|
3153
|
+
unit: "tests/unit/**/*.test.ts",
|
|
3154
|
+
component: "tests/component/**/*.test.ts",
|
|
3155
|
+
api: "tests/api/**/*.test.ts"
|
|
2966
3156
|
};
|
|
2967
|
-
const
|
|
2968
|
-
if (
|
|
2969
|
-
args.push(
|
|
3157
|
+
const testPattern = pathMap[options.type];
|
|
3158
|
+
if (testPattern) {
|
|
3159
|
+
args.push(testPattern);
|
|
2970
3160
|
}
|
|
2971
3161
|
}
|
|
2972
3162
|
if (options.coverage) {
|
|
@@ -3982,7 +4172,7 @@ function registerRunCommand(program2) {
|
|
|
3982
4172
|
try {
|
|
3983
4173
|
await executeRun(options);
|
|
3984
4174
|
} catch (error) {
|
|
3985
|
-
console.error(
|
|
4175
|
+
console.error(chalk6.red(`
|
|
3986
4176
|
\u2717 ${error instanceof Error ? error.message : String(error)}
|
|
3987
4177
|
`));
|
|
3988
4178
|
process.exit(1);
|
|
@@ -3990,7 +4180,7 @@ function registerRunCommand(program2) {
|
|
|
3990
4180
|
});
|
|
3991
4181
|
}
|
|
3992
4182
|
async function executeRun(options) {
|
|
3993
|
-
const { type, file,
|
|
4183
|
+
const { type, file, watch, parallel } = options;
|
|
3994
4184
|
const config = await loadConfig(options.config);
|
|
3995
4185
|
const globalAI = loadGlobalAIConfig();
|
|
3996
4186
|
if (globalAI) {
|
|
@@ -4002,15 +4192,15 @@ async function executeRun(options) {
|
|
|
4002
4192
|
}
|
|
4003
4193
|
let typesToRun = await determineTypesToRun(type, file, config);
|
|
4004
4194
|
if (typesToRun.length === 0) {
|
|
4005
|
-
console.log(
|
|
4195
|
+
console.log(chalk6.yellow("\n \u6CA1\u6709\u53EF\u8FD0\u884C\u7684\u6D4B\u8BD5\u7C7B\u578B\uFF08\u8BF7\u5728 qat.config.ts \u4E2D\u542F\u7528\uFF09\n"));
|
|
4006
4196
|
return;
|
|
4007
4197
|
}
|
|
4008
4198
|
const missingDeps = checkTestDependencies(typesToRun);
|
|
4009
4199
|
if (missingDeps.length > 0) {
|
|
4010
|
-
console.log(
|
|
4200
|
+
console.log(chalk6.yellow("\n \u26A0 \u4EE5\u4E0B\u6D4B\u8BD5\u6846\u67B6\u4F9D\u8D56\u672A\u5B89\u88C5:\n"));
|
|
4011
4201
|
for (const dep of missingDeps) {
|
|
4012
|
-
console.log(
|
|
4013
|
-
console.log(
|
|
4202
|
+
console.log(chalk6.white(` ${dep.runner} (${dep.pkg})`));
|
|
4203
|
+
console.log(chalk6.gray(` \u5B89\u88C5\u547D\u4EE4: ${chalk6.cyan(dep.installCmd)}`));
|
|
4014
4204
|
}
|
|
4015
4205
|
console.log();
|
|
4016
4206
|
const { action } = await inquirer3.prompt([
|
|
@@ -4027,13 +4217,13 @@ async function executeRun(options) {
|
|
|
4027
4217
|
}
|
|
4028
4218
|
]);
|
|
4029
4219
|
if (action === "cancel") {
|
|
4030
|
-
console.log(
|
|
4220
|
+
console.log(chalk6.gray("\n \u5DF2\u53D6\u6D88\u8FD0\u884C\n"));
|
|
4031
4221
|
return;
|
|
4032
4222
|
}
|
|
4033
4223
|
if (action === "install") {
|
|
4034
4224
|
const installed = await installTestDependencies(missingDeps);
|
|
4035
4225
|
if (!installed) {
|
|
4036
|
-
console.log(
|
|
4226
|
+
console.log(chalk6.yellow(" \u90E8\u5206\u4F9D\u8D56\u5B89\u88C5\u5931\u8D25\uFF0C\u5C06\u8DF3\u8FC7\u5BF9\u5E94\u7684\u6D4B\u8BD5\u7C7B\u578B"));
|
|
4037
4227
|
}
|
|
4038
4228
|
const stillMissing = checkTestDependencies(typesToRun);
|
|
4039
4229
|
if (stillMissing.length > 0) {
|
|
@@ -4043,7 +4233,7 @@ async function executeRun(options) {
|
|
|
4043
4233
|
return !dep || !missingRunners.has(dep.pkg);
|
|
4044
4234
|
});
|
|
4045
4235
|
if (typesToRun.length === 0) {
|
|
4046
|
-
console.log(
|
|
4236
|
+
console.log(chalk6.yellow("\n \u6CA1\u6709\u53EF\u8FD0\u884C\u7684\u6D4B\u8BD5\u7C7B\u578B\uFF08\u4F9D\u8D56\u672A\u5B89\u88C5\uFF09\n"));
|
|
4047
4237
|
return;
|
|
4048
4238
|
}
|
|
4049
4239
|
}
|
|
@@ -4055,7 +4245,7 @@ async function executeRun(options) {
|
|
|
4055
4245
|
return !dep || !missingRunners.has(dep.pkg);
|
|
4056
4246
|
});
|
|
4057
4247
|
if (typesToRun.length === 0) {
|
|
4058
|
-
console.log(
|
|
4248
|
+
console.log(chalk6.yellow("\n \u6CA1\u6709\u53EF\u8FD0\u884C\u7684\u6D4B\u8BD5\u7C7B\u578B\n"));
|
|
4059
4249
|
return;
|
|
4060
4250
|
}
|
|
4061
4251
|
}
|
|
@@ -4078,13 +4268,13 @@ async function executeRun(options) {
|
|
|
4078
4268
|
}
|
|
4079
4269
|
]);
|
|
4080
4270
|
if (action === "cancel") {
|
|
4081
|
-
console.log(
|
|
4271
|
+
console.log(chalk6.gray("\n \u5DF2\u53D6\u6D88\u8FD0\u884C\n"));
|
|
4082
4272
|
return;
|
|
4083
4273
|
}
|
|
4084
4274
|
if (action === "skip") {
|
|
4085
4275
|
typesToRun = typesToRun.filter((t) => !SERVER_REQUIRED_TYPES.includes(t));
|
|
4086
4276
|
if (typesToRun.length === 0) {
|
|
4087
|
-
console.log(
|
|
4277
|
+
console.log(chalk6.yellow("\n \u6CA1\u6709\u53EF\u8FD0\u884C\u7684\u6D4B\u8BD5\u7C7B\u578B\n"));
|
|
4088
4278
|
return;
|
|
4089
4279
|
}
|
|
4090
4280
|
}
|
|
@@ -4104,12 +4294,12 @@ async function executeRun(options) {
|
|
|
4104
4294
|
}
|
|
4105
4295
|
]);
|
|
4106
4296
|
if (fallback === "cancel") {
|
|
4107
|
-
console.log(
|
|
4297
|
+
console.log(chalk6.gray("\n \u5DF2\u53D6\u6D88\u8FD0\u884C\n"));
|
|
4108
4298
|
return;
|
|
4109
4299
|
}
|
|
4110
4300
|
typesToRun = typesToRun.filter((t) => !SERVER_REQUIRED_TYPES.includes(t));
|
|
4111
4301
|
if (typesToRun.length === 0) {
|
|
4112
|
-
console.log(
|
|
4302
|
+
console.log(chalk6.yellow("\n \u6CA1\u6709\u53EF\u8FD0\u884C\u7684\u6D4B\u8BD5\u7C7B\u578B\n"));
|
|
4113
4303
|
return;
|
|
4114
4304
|
}
|
|
4115
4305
|
}
|
|
@@ -4167,8 +4357,8 @@ async function executeRun(options) {
|
|
|
4167
4357
|
const outputDir = config.report.outputDir || "qat-report";
|
|
4168
4358
|
const reportPath = writeReportToDisk(reportData, outputDir);
|
|
4169
4359
|
const relativePath = path12.relative(process.cwd(), reportPath);
|
|
4170
|
-
console.log(
|
|
4171
|
-
\u62A5\u544A\u5DF2\u751F\u6210: ${
|
|
4360
|
+
console.log(chalk6.gray(`
|
|
4361
|
+
\u62A5\u544A\u5DF2\u751F\u6210: ${chalk6.cyan(relativePath)}`));
|
|
4172
4362
|
console.log();
|
|
4173
4363
|
const hasFailures = results.some((r) => r.status === "failed");
|
|
4174
4364
|
if (hasFailures && isAIAvailable(config.ai)) {
|
|
@@ -4177,7 +4367,7 @@ async function executeRun(options) {
|
|
|
4177
4367
|
}
|
|
4178
4368
|
function printDryRunCommands(types, options, config) {
|
|
4179
4369
|
console.log();
|
|
4180
|
-
console.log(
|
|
4370
|
+
console.log(chalk6.cyan(" \u53EF\u6267\u884C\u7684\u6D4B\u8BD5\u547D\u4EE4:\n"));
|
|
4181
4371
|
for (const testType of types) {
|
|
4182
4372
|
const label = TYPE_LABELS2[testType];
|
|
4183
4373
|
const runner = TYPE_RUNNERS[testType];
|
|
@@ -4192,7 +4382,7 @@ function printDryRunCommands(types, options, config) {
|
|
|
4192
4382
|
api: "tests/api"
|
|
4193
4383
|
};
|
|
4194
4384
|
const dir = dirMap[testType];
|
|
4195
|
-
if (dir) cmd += `
|
|
4385
|
+
if (dir) cmd += ` '${dir}/**/*.test.ts'`;
|
|
4196
4386
|
if (config.vitest.coverage) cmd += " --coverage";
|
|
4197
4387
|
}
|
|
4198
4388
|
break;
|
|
@@ -4214,17 +4404,17 @@ function printDryRunCommands(types, options, config) {
|
|
|
4214
4404
|
default:
|
|
4215
4405
|
cmd = `# ${label}: \u672A\u77E5\u8FD0\u884C\u5668`;
|
|
4216
4406
|
}
|
|
4217
|
-
console.log(
|
|
4218
|
-
console.log(
|
|
4407
|
+
console.log(chalk6.white(` ${label}:`));
|
|
4408
|
+
console.log(chalk6.cyan(` ${cmd}`));
|
|
4219
4409
|
console.log();
|
|
4220
4410
|
}
|
|
4221
|
-
console.log(
|
|
4411
|
+
console.log(chalk6.gray(" \u6216\u4F7F\u7528 QAT \u7EDF\u4E00\u8FD0\u884C:"));
|
|
4222
4412
|
if (types.length === 1) {
|
|
4223
|
-
console.log(
|
|
4413
|
+
console.log(chalk6.cyan(` qat run -t ${types[0]}`));
|
|
4224
4414
|
} else {
|
|
4225
|
-
console.log(
|
|
4226
|
-
console.log(
|
|
4227
|
-
console.log(
|
|
4415
|
+
console.log(chalk6.cyan(` qat run -t ${types.join(" -t ")}`));
|
|
4416
|
+
console.log(chalk6.gray(" # \u5E76\u884C\u8FD0\u884C"));
|
|
4417
|
+
console.log(chalk6.cyan(" qat run -p"));
|
|
4228
4418
|
}
|
|
4229
4419
|
console.log();
|
|
4230
4420
|
}
|
|
@@ -4247,7 +4437,7 @@ async function determineTypesToRun(type, file, config) {
|
|
|
4247
4437
|
name: "selectedTypes",
|
|
4248
4438
|
message: "\u9009\u62E9\u8981\u8FD0\u884C\u7684\u6D4B\u8BD5\u7C7B\u578B (\u7A7A\u683C\u9009\u62E9/\u53D6\u6D88\uFF0C\u56DE\u8F66\u786E\u8BA4):",
|
|
4249
4439
|
choices: enabledTypes.map((t) => ({
|
|
4250
|
-
name: `${TYPE_LABELS2[t]} (${
|
|
4440
|
+
name: `${TYPE_LABELS2[t]} (${chalk6.gray(TYPE_RUNNERS[t])})`,
|
|
4251
4441
|
value: t,
|
|
4252
4442
|
checked: true
|
|
4253
4443
|
})),
|
|
@@ -4306,8 +4496,8 @@ async function runTestType(testType, config, options) {
|
|
|
4306
4496
|
throw new Error(`\u672A\u77E5\u7684\u8FD0\u884C\u5668: ${runner}`);
|
|
4307
4497
|
}
|
|
4308
4498
|
}
|
|
4309
|
-
async function runWatchMode(
|
|
4310
|
-
console.log(
|
|
4499
|
+
async function runWatchMode(_config, options) {
|
|
4500
|
+
console.log(chalk6.cyan(" \u76D1\u542C\u6A21\u5F0F\u5DF2\u542F\u52A8 (Ctrl+C \u9000\u51FA)\n"));
|
|
4311
4501
|
const { spawn } = await import("child_process");
|
|
4312
4502
|
const args = ["vitest", "--watch"];
|
|
4313
4503
|
if (options.file) {
|
|
@@ -4322,7 +4512,7 @@ async function runWatchMode(config, options) {
|
|
|
4322
4512
|
};
|
|
4323
4513
|
const dirs = types.map((t) => dirMap[t]).filter(Boolean);
|
|
4324
4514
|
if (dirs.length > 0) {
|
|
4325
|
-
args.push(
|
|
4515
|
+
args.push(...dirs.map((d) => `${d}/**/*.test.ts`));
|
|
4326
4516
|
}
|
|
4327
4517
|
}
|
|
4328
4518
|
const child = spawn("npx", args, {
|
|
@@ -4331,7 +4521,7 @@ async function runWatchMode(config, options) {
|
|
|
4331
4521
|
shell: true
|
|
4332
4522
|
});
|
|
4333
4523
|
child.on("error", (err) => {
|
|
4334
|
-
console.error(
|
|
4524
|
+
console.error(chalk6.red(`
|
|
4335
4525
|
Vitest \u542F\u52A8\u5931\u8D25: ${err.message}
|
|
4336
4526
|
`));
|
|
4337
4527
|
});
|
|
@@ -4386,17 +4576,17 @@ function displayJestStyleResults(results) {
|
|
|
4386
4576
|
for (const result of results) {
|
|
4387
4577
|
const typeLabel = TYPE_LABELS2[result.type] || result.type;
|
|
4388
4578
|
if (result.suites.length === 0) {
|
|
4389
|
-
console.log(
|
|
4579
|
+
console.log(chalk6.gray(` ${typeLabel}: \u65E0\u6D4B\u8BD5\u7ED3\u679C`));
|
|
4390
4580
|
continue;
|
|
4391
4581
|
}
|
|
4392
4582
|
for (const suite of result.suites) {
|
|
4393
4583
|
if (suite.tests.length === 0) continue;
|
|
4394
|
-
const suiteIcon = suite.status === "passed" ?
|
|
4395
|
-
console.log(` ${suiteIcon} ${
|
|
4584
|
+
const suiteIcon = suite.status === "passed" ? chalk6.green("PASS") : chalk6.red("FAIL");
|
|
4585
|
+
console.log(` ${suiteIcon} ${chalk6.white(suite.name)} ${chalk6.gray(`(${formatDuration2(suite.duration)})`)}`);
|
|
4396
4586
|
for (const test of suite.tests) {
|
|
4397
|
-
const icon = test.status === "passed" ?
|
|
4398
|
-
const name = test.status === "failed" ?
|
|
4399
|
-
console.log(` ${icon} ${name} ${
|
|
4587
|
+
const icon = test.status === "passed" ? chalk6.green(" \u2713") : test.status === "failed" ? chalk6.red(" \u2715") : chalk6.yellow(" \u25CB");
|
|
4588
|
+
const name = test.status === "failed" ? chalk6.red(test.name) : test.name;
|
|
4589
|
+
console.log(` ${icon} ${name} ${chalk6.gray(formatDuration2(test.duration))}`);
|
|
4400
4590
|
}
|
|
4401
4591
|
}
|
|
4402
4592
|
console.log();
|
|
@@ -4428,28 +4618,28 @@ function displayJestStyleResults(results) {
|
|
|
4428
4618
|
}
|
|
4429
4619
|
}
|
|
4430
4620
|
const total = totalPassed + totalFailed + totalSkipped;
|
|
4431
|
-
console.log(
|
|
4432
|
-
console.log(
|
|
4433
|
-
console.log(
|
|
4621
|
+
console.log(chalk6.cyan(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
4622
|
+
console.log(chalk6.white(" \u6D4B\u8BD5\u7ED3\u679C\u6C47\u603B"));
|
|
4623
|
+
console.log(chalk6.cyan(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
4434
4624
|
console.log();
|
|
4435
|
-
console.log(` ${
|
|
4625
|
+
console.log(` ${chalk6.white("\u7C7B\u578B".padEnd(14))} ${chalk6.white("\u901A\u8FC7".padStart(6))} ${chalk6.white("\u5931\u8D25".padStart(6))} ${chalk6.white("\u8DF3\u8FC7".padStart(6))} ${chalk6.white("\u603B\u8BA1".padStart(6))} ${chalk6.white("\u901A\u8FC7\u7387".padStart(8))} ${chalk6.white("\u8017\u65F6".padStart(8))}`);
|
|
4436
4626
|
console.log(` ${"\u2500".repeat(14)} ${"\u2500".repeat(6)} ${"\u2500".repeat(6)} ${"\u2500".repeat(6)} ${"\u2500".repeat(6)} ${"\u2500".repeat(8)} ${"\u2500".repeat(8)}`);
|
|
4437
4627
|
for (const [type, stats] of Object.entries(typeStats)) {
|
|
4438
4628
|
const label = (TYPE_LABELS2[type] || type).padEnd(14);
|
|
4439
4629
|
const typeTotal = stats.passed + stats.failed + stats.skipped;
|
|
4440
4630
|
const rate = typeTotal > 0 ? (stats.passed / typeTotal * 100).toFixed(0) + "%" : "-";
|
|
4441
|
-
const rateColored = typeTotal > 0 && stats.passed === typeTotal ?
|
|
4631
|
+
const rateColored = typeTotal > 0 && stats.passed === typeTotal ? chalk6.green(rate.padStart(8)) : stats.failed > 0 ? chalk6.red(rate.padStart(8)) : rate.padStart(8);
|
|
4442
4632
|
console.log(` ${label} ${String(stats.passed).padStart(6)} ${String(stats.failed).padStart(6)} ${String(stats.skipped).padStart(6)} ${String(typeTotal).padStart(6)} ${rateColored} ${formatDuration2(stats.duration).padStart(8)}`);
|
|
4443
4633
|
}
|
|
4444
4634
|
console.log(` ${"\u2500".repeat(14)} ${"\u2500".repeat(6)} ${"\u2500".repeat(6)} ${"\u2500".repeat(6)} ${"\u2500".repeat(6)} ${"\u2500".repeat(8)} ${"\u2500".repeat(8)}`);
|
|
4445
4635
|
const totalRate = total > 0 ? (totalPassed / total * 100).toFixed(0) + "%" : "-";
|
|
4446
|
-
const totalRateColored = total > 0 && totalFailed === 0 ?
|
|
4636
|
+
const totalRateColored = total > 0 && totalFailed === 0 ? chalk6.green(totalRate.padStart(8)) : totalFailed > 0 ? chalk6.red(totalRate.padStart(8)) : totalRate.padStart(8);
|
|
4447
4637
|
console.log(` ${"\u603B\u8BA1".padEnd(14)} ${String(totalPassed).padStart(6)} ${String(totalFailed).padStart(6)} ${String(totalSkipped).padStart(6)} ${String(total).padStart(6)} ${totalRateColored} ${formatDuration2(totalDuration).padStart(8)}`);
|
|
4448
4638
|
console.log();
|
|
4449
4639
|
for (const result of results) {
|
|
4450
4640
|
if (result.coverage) {
|
|
4451
4641
|
const c = result.coverage;
|
|
4452
|
-
console.log(
|
|
4642
|
+
console.log(chalk6.cyan(" \u8986\u76D6\u7387:"));
|
|
4453
4643
|
console.log(` \u8BED\u53E5: ${coverageColor(c.statements)} \u5206\u652F: ${coverageColor(c.branches)} \u51FD\u6570: ${coverageColor(c.functions)} \u884C: ${coverageColor(c.lines)}`);
|
|
4454
4644
|
console.log();
|
|
4455
4645
|
}
|
|
@@ -4457,7 +4647,7 @@ function displayJestStyleResults(results) {
|
|
|
4457
4647
|
for (const result of results) {
|
|
4458
4648
|
if (result.type === "performance" && result.performance) {
|
|
4459
4649
|
const p = result.performance;
|
|
4460
|
-
console.log(
|
|
4650
|
+
console.log(chalk6.cyan(" \u6027\u80FD\u6307\u6807:"));
|
|
4461
4651
|
console.log(` Performance: ${scoreColor(p.performance)} ${p.performance}/100`);
|
|
4462
4652
|
console.log(` Accessibility: ${scoreColor(p.accessibility)} ${p.accessibility}/100`);
|
|
4463
4653
|
console.log(` Best Practices: ${scoreColor(p.bestPractices)} ${p.bestPractices}/100`);
|
|
@@ -4474,31 +4664,31 @@ function displayJestStyleResults(results) {
|
|
|
4474
4664
|
)
|
|
4475
4665
|
);
|
|
4476
4666
|
if (failedTests.length > 0) {
|
|
4477
|
-
console.log(
|
|
4667
|
+
console.log(chalk6.red(" \u5931\u8D25\u8BE6\u60C5:"));
|
|
4478
4668
|
for (const { suite, test } of failedTests) {
|
|
4479
|
-
console.log(
|
|
4669
|
+
console.log(chalk6.red(` \u2715 ${suite.name} > ${test.name}`));
|
|
4480
4670
|
if (test.error?.message) {
|
|
4481
|
-
console.log(
|
|
4671
|
+
console.log(chalk6.gray(` ${test.error.message.split("\n")[0]}`));
|
|
4482
4672
|
}
|
|
4483
4673
|
}
|
|
4484
4674
|
console.log();
|
|
4485
4675
|
}
|
|
4486
4676
|
if (totalFailed > 0) {
|
|
4487
|
-
console.log(
|
|
4677
|
+
console.log(chalk6.red(` Tests: ${totalFailed} failed, ${totalPassed} passed, ${total} total`));
|
|
4488
4678
|
process.exitCode = 1;
|
|
4489
4679
|
} else if (total === 0) {
|
|
4490
|
-
console.log(
|
|
4680
|
+
console.log(chalk6.yellow(" \u6CA1\u6709\u53D1\u73B0\u6D4B\u8BD5\u7528\u4F8B"));
|
|
4491
4681
|
} else {
|
|
4492
|
-
console.log(
|
|
4682
|
+
console.log(chalk6.green(` Tests: ${totalPassed} passed, ${total} total`));
|
|
4493
4683
|
}
|
|
4494
|
-
console.log(
|
|
4684
|
+
console.log(chalk6.gray(` Time: ${formatDuration2(totalDuration)}`));
|
|
4495
4685
|
console.log();
|
|
4496
4686
|
}
|
|
4497
4687
|
function coverageColor(value) {
|
|
4498
4688
|
const pct3 = `${(value * 100).toFixed(1)}%`;
|
|
4499
|
-
if (value >= 0.8) return
|
|
4500
|
-
if (value >= 0.5) return
|
|
4501
|
-
return
|
|
4689
|
+
if (value >= 0.8) return chalk6.green(pct3);
|
|
4690
|
+
if (value >= 0.5) return chalk6.yellow(pct3);
|
|
4691
|
+
return chalk6.red(pct3);
|
|
4502
4692
|
}
|
|
4503
4693
|
async function aiAnalyzeFailures(results, aiConfig) {
|
|
4504
4694
|
const failedTests = results.flatMap(
|
|
@@ -4507,23 +4697,23 @@ async function aiAnalyzeFailures(results, aiConfig) {
|
|
|
4507
4697
|
)
|
|
4508
4698
|
);
|
|
4509
4699
|
if (failedTests.length === 0) return;
|
|
4510
|
-
console.log(
|
|
4700
|
+
console.log(chalk6.magenta(" \u{1F916} AI \u5206\u6790\u5931\u8D25\u539F\u56E0..."));
|
|
4511
4701
|
console.log();
|
|
4512
4702
|
const provider = getAIProvider(aiConfig);
|
|
4513
4703
|
if (!provider.capabilities.suggestFix) return;
|
|
4514
4704
|
for (const { suite, test } of failedTests.slice(0, 5)) {
|
|
4515
4705
|
try {
|
|
4516
4706
|
const suggestions = await provider.suggestFix(test.error);
|
|
4517
|
-
console.log(
|
|
4707
|
+
console.log(chalk6.white(` ${suite.name} > ${test.name}`));
|
|
4518
4708
|
if (test.error?.message) {
|
|
4519
|
-
console.log(
|
|
4709
|
+
console.log(chalk6.gray(` \u9519\u8BEF: ${test.error.message.split("\n")[0]}`));
|
|
4520
4710
|
}
|
|
4521
4711
|
for (const suggestion of suggestions.slice(0, 3)) {
|
|
4522
|
-
console.log(
|
|
4712
|
+
console.log(chalk6.cyan(` \u2192 ${suggestion}`));
|
|
4523
4713
|
}
|
|
4524
4714
|
console.log();
|
|
4525
4715
|
} catch (error) {
|
|
4526
|
-
console.log(
|
|
4716
|
+
console.log(chalk6.gray(` AI \u5206\u6790\u5931\u8D25: ${error instanceof Error ? error.message : String(error)}`));
|
|
4527
4717
|
}
|
|
4528
4718
|
}
|
|
4529
4719
|
}
|
|
@@ -4535,9 +4725,9 @@ function formatDuration2(ms) {
|
|
|
4535
4725
|
return `${min}m ${sec}s`;
|
|
4536
4726
|
}
|
|
4537
4727
|
function scoreColor(score) {
|
|
4538
|
-
if (score >= 90) return
|
|
4539
|
-
if (score >= 50) return
|
|
4540
|
-
return
|
|
4728
|
+
if (score >= 90) return chalk6.green("\u25CF");
|
|
4729
|
+
if (score >= 50) return chalk6.yellow("\u25CF");
|
|
4730
|
+
return chalk6.red("\u25CF");
|
|
4541
4731
|
}
|
|
4542
4732
|
async function checkDevServer(config) {
|
|
4543
4733
|
const baseUrl = config.playwright.baseURL || "http://localhost:5173";
|
|
@@ -4556,7 +4746,7 @@ async function startDevServer(config) {
|
|
|
4556
4746
|
const hasPnpm = fs11.existsSync(path12.join(process.cwd(), "pnpm-lock.yaml"));
|
|
4557
4747
|
const hasYarn = fs11.existsSync(path12.join(process.cwd(), "yarn.lock"));
|
|
4558
4748
|
const pkgCmd = hasPnpm ? "pnpm" : hasYarn ? "yarn" : "npm";
|
|
4559
|
-
console.log(
|
|
4749
|
+
console.log(chalk6.cyan(` \u6B63\u5728\u542F\u52A8 dev server (${pkgCmd} run dev) ...`));
|
|
4560
4750
|
const child = spawn(pkgCmd, ["run", "dev"], {
|
|
4561
4751
|
cwd: process.cwd(),
|
|
4562
4752
|
stdio: "pipe",
|
|
@@ -4572,13 +4762,13 @@ async function startDevServer(config) {
|
|
|
4572
4762
|
waited += interval;
|
|
4573
4763
|
const isUp = await checkDevServer(config);
|
|
4574
4764
|
if (isUp) {
|
|
4575
|
-
console.log(
|
|
4765
|
+
console.log(chalk6.green(` \u2713 dev server \u5DF2\u542F\u52A8 (${baseUrl})`));
|
|
4576
4766
|
child.unref();
|
|
4577
4767
|
return true;
|
|
4578
4768
|
}
|
|
4579
4769
|
}
|
|
4580
4770
|
child.kill();
|
|
4581
|
-
console.log(
|
|
4771
|
+
console.log(chalk6.red(" \u2717 dev server \u542F\u52A8\u8D85\u65F6"));
|
|
4582
4772
|
return false;
|
|
4583
4773
|
}
|
|
4584
4774
|
function saveRunResults(results) {
|
|
@@ -4670,7 +4860,7 @@ async function installTestDependencies(missingDeps) {
|
|
|
4670
4860
|
spinner.succeed(`${dep.pkg} \u5B89\u88C5\u6210\u529F`);
|
|
4671
4861
|
} catch (error) {
|
|
4672
4862
|
spinner.fail(`${dep.pkg} \u5B89\u88C5\u5931\u8D25`);
|
|
4673
|
-
console.log(
|
|
4863
|
+
console.log(chalk6.gray(` \u53EF\u624B\u52A8\u5B89\u88C5: ${chalk6.cyan(dep.installCmd)}`));
|
|
4674
4864
|
allSuccess = false;
|
|
4675
4865
|
}
|
|
4676
4866
|
}
|
|
@@ -4678,7 +4868,7 @@ async function installTestDependencies(missingDeps) {
|
|
|
4678
4868
|
}
|
|
4679
4869
|
|
|
4680
4870
|
// src/commands/mock.ts
|
|
4681
|
-
import
|
|
4871
|
+
import chalk7 from "chalk";
|
|
4682
4872
|
import ora5 from "ora";
|
|
4683
4873
|
function registerMockCommand(program2) {
|
|
4684
4874
|
program2.command("mock").description("Mock\u670D\u52A1\u7BA1\u7406 - \u542F\u52A8/\u505C\u6B62Mock API\u670D\u52A1\u5668").argument("<action>", "\u64CD\u4F5C\u7C7B\u578B (start|stop|status)").option("-p, --port <port>", "\u6307\u5B9A\u7AEF\u53E3\u53F7", "3456").action(async (action, options) => {
|
|
@@ -4689,7 +4879,7 @@ function registerMockCommand(program2) {
|
|
|
4689
4879
|
config: options.config
|
|
4690
4880
|
});
|
|
4691
4881
|
} catch (error) {
|
|
4692
|
-
console.error(
|
|
4882
|
+
console.error(chalk7.red(`
|
|
4693
4883
|
\u2717 ${error instanceof Error ? error.message : String(error)}
|
|
4694
4884
|
`));
|
|
4695
4885
|
process.exit(1);
|
|
@@ -4713,9 +4903,9 @@ async function executeMock(options) {
|
|
|
4713
4903
|
break;
|
|
4714
4904
|
}
|
|
4715
4905
|
default: {
|
|
4716
|
-
console.error(
|
|
4906
|
+
console.error(chalk7.red(`
|
|
4717
4907
|
\u672A\u77E5\u64CD\u4F5C: ${options.action}`));
|
|
4718
|
-
console.log(
|
|
4908
|
+
console.log(chalk7.gray(" \u53EF\u7528\u64CD\u4F5C: start, stop, status\n"));
|
|
4719
4909
|
process.exit(1);
|
|
4720
4910
|
}
|
|
4721
4911
|
}
|
|
@@ -4723,7 +4913,7 @@ async function executeMock(options) {
|
|
|
4723
4913
|
async function startMock(port, routesDir) {
|
|
4724
4914
|
const state = getMockServerState();
|
|
4725
4915
|
if (state.running) {
|
|
4726
|
-
console.log(
|
|
4916
|
+
console.log(chalk7.yellow(`
|
|
4727
4917
|
Mock\u670D\u52A1\u5668\u5DF2\u5728\u8FD0\u884C (\u7AEF\u53E3: ${state.port})
|
|
4728
4918
|
`));
|
|
4729
4919
|
return;
|
|
@@ -4737,22 +4927,22 @@ async function startMock(port, routesDir) {
|
|
|
4737
4927
|
await startMockServer(port, routes);
|
|
4738
4928
|
spinner.succeed(`Mock\u670D\u52A1\u5668\u5DF2\u542F\u52A8`);
|
|
4739
4929
|
console.log();
|
|
4740
|
-
console.log(
|
|
4741
|
-
console.log(
|
|
4742
|
-
console.log(
|
|
4930
|
+
console.log(chalk7.white(" \u5730\u5740:"), chalk7.cyan(`http://localhost:${port}`));
|
|
4931
|
+
console.log(chalk7.white(" \u8DEF\u7531:"), routes.length > 0 ? `${routes.length} \u4E2A` : chalk7.gray("\u4F7F\u7528\u9ED8\u8BA4\u8DEF\u7531"));
|
|
4932
|
+
console.log(chalk7.white(" \u5065\u5EB7\u68C0\u67E5:"), chalk7.cyan(`http://localhost:${port}/api/health`));
|
|
4743
4933
|
if (routes.length > 0) {
|
|
4744
4934
|
console.log();
|
|
4745
|
-
console.log(
|
|
4935
|
+
console.log(chalk7.white(" \u5DF2\u6CE8\u518C\u8DEF\u7531:"));
|
|
4746
4936
|
for (const route of routes.slice(0, 10)) {
|
|
4747
|
-
const method =
|
|
4937
|
+
const method = chalk7.gray(route.method.padEnd(6));
|
|
4748
4938
|
console.log(` ${method} ${route.path}`);
|
|
4749
4939
|
}
|
|
4750
4940
|
if (routes.length > 10) {
|
|
4751
|
-
console.log(
|
|
4941
|
+
console.log(chalk7.gray(` ... \u8FD8\u6709 ${routes.length - 10} \u4E2A\u8DEF\u7531`));
|
|
4752
4942
|
}
|
|
4753
4943
|
}
|
|
4754
4944
|
console.log();
|
|
4755
|
-
console.log(
|
|
4945
|
+
console.log(chalk7.gray(" \u6309 Ctrl+C \u505C\u6B62\u670D\u52A1\u5668"));
|
|
4756
4946
|
process.on("SIGINT", async () => {
|
|
4757
4947
|
const stopSpinner = ora5("\u6B63\u5728\u505C\u6B62Mock\u670D\u52A1\u5668...").start();
|
|
4758
4948
|
await stopMockServer();
|
|
@@ -4769,7 +4959,7 @@ async function startMock(port, routesDir) {
|
|
|
4769
4959
|
async function stopMock() {
|
|
4770
4960
|
const state = getMockServerState();
|
|
4771
4961
|
if (!state.running) {
|
|
4772
|
-
console.log(
|
|
4962
|
+
console.log(chalk7.yellow("\n Mock\u670D\u52A1\u5668\u672A\u5728\u8FD0\u884C\n"));
|
|
4773
4963
|
return;
|
|
4774
4964
|
}
|
|
4775
4965
|
const spinner = ora5("\u6B63\u5728\u505C\u6B62Mock\u670D\u52A1\u5668...").start();
|
|
@@ -4786,18 +4976,18 @@ function showStatus() {
|
|
|
4786
4976
|
const state = getMockServerState();
|
|
4787
4977
|
console.log();
|
|
4788
4978
|
if (state.running) {
|
|
4789
|
-
console.log(
|
|
4790
|
-
console.log(
|
|
4791
|
-
console.log(
|
|
4979
|
+
console.log(chalk7.green(" \u25CF Mock\u670D\u52A1\u5668\u8FD0\u884C\u4E2D"));
|
|
4980
|
+
console.log(chalk7.white(" \u7AEF\u53E3:"), state.port);
|
|
4981
|
+
console.log(chalk7.white(" \u8DEF\u7531:"), state.routes.length);
|
|
4792
4982
|
} else {
|
|
4793
|
-
console.log(
|
|
4794
|
-
console.log(
|
|
4983
|
+
console.log(chalk7.gray(" \u25CB Mock\u670D\u52A1\u5668\u672A\u8FD0\u884C"));
|
|
4984
|
+
console.log(chalk7.gray(" \u4F7F\u7528 qat mock start \u542F\u52A8"));
|
|
4795
4985
|
}
|
|
4796
4986
|
console.log();
|
|
4797
4987
|
}
|
|
4798
4988
|
|
|
4799
4989
|
// src/commands/report.ts
|
|
4800
|
-
import
|
|
4990
|
+
import chalk8 from "chalk";
|
|
4801
4991
|
import ora6 from "ora";
|
|
4802
4992
|
import fs12 from "fs";
|
|
4803
4993
|
import path13 from "path";
|
|
@@ -4807,7 +4997,7 @@ function registerReportCommand(program2) {
|
|
|
4807
4997
|
try {
|
|
4808
4998
|
await executeReport(options);
|
|
4809
4999
|
} catch (error) {
|
|
4810
|
-
console.error(
|
|
5000
|
+
console.error(chalk8.red(`
|
|
4811
5001
|
\u2717 ${error instanceof Error ? error.message : String(error)}
|
|
4812
5002
|
`));
|
|
4813
5003
|
process.exit(1);
|
|
@@ -4821,7 +5011,7 @@ async function executeReport(options) {
|
|
|
4821
5011
|
const results = collectResults();
|
|
4822
5012
|
if (results.length === 0) {
|
|
4823
5013
|
spinner.info("\u6CA1\u6709\u627E\u5230\u6D4B\u8BD5\u7ED3\u679C");
|
|
4824
|
-
console.log(
|
|
5014
|
+
console.log(chalk8.gray("\n \u63D0\u793A: \u5148\u8FD0\u884C qat run \u751F\u6210\u6D4B\u8BD5\u7ED3\u679C\n"));
|
|
4825
5015
|
return;
|
|
4826
5016
|
}
|
|
4827
5017
|
spinner.text = "\u6B63\u5728\u751F\u6210\u6D4B\u8BD5\u62A5\u544A...";
|
|
@@ -4874,31 +5064,31 @@ function displayReportResult(reportPath, data) {
|
|
|
4874
5064
|
const relativePath = path13.relative(process.cwd(), reportPath);
|
|
4875
5065
|
const passRate = data.summary.total > 0 ? (data.summary.passed / data.summary.total * 100).toFixed(1) : "0";
|
|
4876
5066
|
console.log();
|
|
4877
|
-
console.log(
|
|
5067
|
+
console.log(chalk8.green(" \u2713 \u6D4B\u8BD5\u62A5\u544A\u5DF2\u751F\u6210"));
|
|
4878
5068
|
console.log();
|
|
4879
|
-
console.log(
|
|
4880
|
-
console.log(
|
|
4881
|
-
console.log(
|
|
4882
|
-
console.log(
|
|
5069
|
+
console.log(chalk8.white(" \u62A5\u544A\u8DEF\u5F84:"), chalk8.cyan(relativePath));
|
|
5070
|
+
console.log(chalk8.white(" \u901A\u8FC7\u7387: "), parseFloat(passRate) >= 80 ? chalk8.green(`${passRate}%`) : parseFloat(passRate) >= 50 ? chalk8.yellow(`${passRate}%`) : chalk8.red(`${passRate}%`));
|
|
5071
|
+
console.log(chalk8.white(" \u6D4B\u8BD5\u7528\u4F8B:"), `${data.summary.total} total`);
|
|
5072
|
+
console.log(chalk8.white(" \u2705 \u901A\u8FC7: "), chalk8.green(String(data.summary.passed)));
|
|
4883
5073
|
if (data.summary.failed > 0) {
|
|
4884
|
-
console.log(
|
|
5074
|
+
console.log(chalk8.white(" \u274C \u5931\u8D25: "), chalk8.red(String(data.summary.failed)));
|
|
4885
5075
|
}
|
|
4886
5076
|
if (data.summary.skipped > 0) {
|
|
4887
|
-
console.log(
|
|
5077
|
+
console.log(chalk8.white(" \u23ED\uFE0F \u8DF3\u8FC7: "), chalk8.yellow(String(data.summary.skipped)));
|
|
4888
5078
|
}
|
|
4889
5079
|
if (Object.keys(data.byType).length > 0) {
|
|
4890
5080
|
console.log();
|
|
4891
|
-
console.log(
|
|
5081
|
+
console.log(chalk8.white(" \u6309\u7C7B\u578B:"));
|
|
4892
5082
|
for (const [type, stats] of Object.entries(data.byType)) {
|
|
4893
5083
|
const rate = stats.total > 0 ? (stats.passed / stats.total * 100).toFixed(0) : "0";
|
|
4894
|
-
const icon = stats.failed > 0 ?
|
|
5084
|
+
const icon = stats.failed > 0 ? chalk8.red("\u274C") : chalk8.green("\u2705");
|
|
4895
5085
|
console.log(` ${icon} ${type}: ${stats.passed}/${stats.total} (${rate}%)`);
|
|
4896
5086
|
}
|
|
4897
5087
|
}
|
|
4898
5088
|
if (data.coverage) {
|
|
4899
5089
|
console.log();
|
|
4900
|
-
console.log(
|
|
4901
|
-
console.log(` \u8BED\u53E5: ${
|
|
5090
|
+
console.log(chalk8.white(" \u8986\u76D6\u7387:"));
|
|
5091
|
+
console.log(` \u8BED\u53E5: ${chalk8.cyan(pct2(data.coverage.statements))} \u5206\u652F: ${chalk8.cyan(pct2(data.coverage.branches))} \u51FD\u6570: ${chalk8.cyan(pct2(data.coverage.functions))} \u884C: ${chalk8.cyan(pct2(data.coverage.lines))}`);
|
|
4902
5092
|
}
|
|
4903
5093
|
console.log();
|
|
4904
5094
|
}
|
|
@@ -4918,13 +5108,13 @@ async function openReport(reportPath) {
|
|
|
4918
5108
|
}
|
|
4919
5109
|
exec(command, { shell: true }, (error) => {
|
|
4920
5110
|
if (error) {
|
|
4921
|
-
console.log(
|
|
5111
|
+
console.log(chalk8.gray(" \u63D0\u793A: \u624B\u52A8\u6253\u5F00\u62A5\u544A\u67E5\u770B"));
|
|
4922
5112
|
}
|
|
4923
5113
|
});
|
|
4924
5114
|
}
|
|
4925
5115
|
|
|
4926
5116
|
// src/commands/visual.ts
|
|
4927
|
-
import
|
|
5117
|
+
import chalk9 from "chalk";
|
|
4928
5118
|
import ora7 from "ora";
|
|
4929
5119
|
|
|
4930
5120
|
// src/services/visual.ts
|
|
@@ -5091,7 +5281,7 @@ function registerVisualCommand(program2) {
|
|
|
5091
5281
|
config: options.config
|
|
5092
5282
|
});
|
|
5093
5283
|
} catch (error) {
|
|
5094
|
-
console.error(
|
|
5284
|
+
console.error(chalk9.red(`
|
|
5095
5285
|
\u2717 ${error instanceof Error ? error.message : String(error)}
|
|
5096
5286
|
`));
|
|
5097
5287
|
process.exit(1);
|
|
@@ -5117,9 +5307,9 @@ async function executeVisual(options) {
|
|
|
5117
5307
|
break;
|
|
5118
5308
|
}
|
|
5119
5309
|
default: {
|
|
5120
|
-
console.error(
|
|
5310
|
+
console.error(chalk9.red(`
|
|
5121
5311
|
\u672A\u77E5\u64CD\u4F5C: ${options.action}`));
|
|
5122
|
-
console.log(
|
|
5312
|
+
console.log(chalk9.gray(" \u53EF\u7528\u64CD\u4F5C: test, approve, clean\n"));
|
|
5123
5313
|
process.exit(1);
|
|
5124
5314
|
}
|
|
5125
5315
|
}
|
|
@@ -5143,7 +5333,7 @@ async function runVisualTest(threshold, baselineDir, diffDir, config) {
|
|
|
5143
5333
|
const currentDir = findCurrentScreenshotsDir(baselineDir);
|
|
5144
5334
|
if (!currentDir) {
|
|
5145
5335
|
compareSpinner.info("\u6CA1\u6709\u627E\u5230\u622A\u56FE\u6587\u4EF6");
|
|
5146
|
-
console.log(
|
|
5336
|
+
console.log(chalk9.gray("\n \u63D0\u793A: \u5148\u8FD0\u884C qat run -t visual \u751F\u6210\u622A\u56FE\n"));
|
|
5147
5337
|
return;
|
|
5148
5338
|
}
|
|
5149
5339
|
const results = compareDirectories(baselineDir, currentDir, diffDir, threshold);
|
|
@@ -5189,48 +5379,48 @@ function displayVisualResults(results, threshold) {
|
|
|
5189
5379
|
const passed = results.filter((r) => r.passed);
|
|
5190
5380
|
const failed = results.filter((r) => !r.passed);
|
|
5191
5381
|
console.log();
|
|
5192
|
-
console.log(
|
|
5382
|
+
console.log(chalk9.cyan(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
5193
5383
|
if (failed.length === 0) {
|
|
5194
|
-
console.log(
|
|
5384
|
+
console.log(chalk9.green(` \u2713 \u5168\u90E8\u901A\u8FC7 (${results.length} \u4E2A\u622A\u56FE\u6BD4\u5BF9)`));
|
|
5195
5385
|
} else {
|
|
5196
|
-
console.log(
|
|
5386
|
+
console.log(chalk9.red(` \u2717 ${failed.length} \u4E2A\u622A\u56FE\u5B58\u5728\u5DEE\u5F02`));
|
|
5197
5387
|
}
|
|
5198
5388
|
console.log();
|
|
5199
|
-
console.log(` ${
|
|
5200
|
-
console.log(` ${
|
|
5201
|
-
console.log(` ${
|
|
5389
|
+
console.log(` ${chalk9.white("\u9608\u503C:")} ${(threshold * 100).toFixed(0)}%`);
|
|
5390
|
+
console.log(` ${chalk9.green("\u901A\u8FC7:")} ${passed.length}`);
|
|
5391
|
+
console.log(` ${chalk9.red("\u5931\u8D25:")} ${failed.length}`);
|
|
5202
5392
|
if (passed.length > 0) {
|
|
5203
5393
|
console.log();
|
|
5204
|
-
console.log(
|
|
5394
|
+
console.log(chalk9.green(" \u901A\u8FC7\u7684\u622A\u56FE:"));
|
|
5205
5395
|
for (const result of passed) {
|
|
5206
5396
|
const name = path15.basename(result.baselinePath);
|
|
5207
5397
|
if (result.totalPixels > 0) {
|
|
5208
5398
|
const diffPct = (result.diffRatio * 100).toFixed(2);
|
|
5209
|
-
console.log(
|
|
5399
|
+
console.log(chalk9.green(` \u2713 ${name} (\u5DEE\u5F02: ${diffPct}%)`));
|
|
5210
5400
|
} else {
|
|
5211
|
-
console.log(
|
|
5401
|
+
console.log(chalk9.green(` \u2713 ${name} (\u65B0\u5EFA\u57FA\u7EBF)`));
|
|
5212
5402
|
}
|
|
5213
5403
|
}
|
|
5214
5404
|
}
|
|
5215
5405
|
if (failed.length > 0) {
|
|
5216
5406
|
console.log();
|
|
5217
|
-
console.log(
|
|
5407
|
+
console.log(chalk9.red(" \u5931\u8D25\u7684\u622A\u56FE:"));
|
|
5218
5408
|
for (const result of failed) {
|
|
5219
5409
|
const name = path15.basename(result.baselinePath);
|
|
5220
5410
|
if (result.diffPixels === -1) {
|
|
5221
|
-
console.log(
|
|
5411
|
+
console.log(chalk9.red(` \u2717 ${name} (\u5C3A\u5BF8\u4E0D\u5339\u914D)`));
|
|
5222
5412
|
} else {
|
|
5223
5413
|
const diffPct = (result.diffRatio * 100).toFixed(2);
|
|
5224
|
-
console.log(
|
|
5414
|
+
console.log(chalk9.red(` \u2717 ${name} (\u5DEE\u5F02: ${diffPct}%)`));
|
|
5225
5415
|
}
|
|
5226
5416
|
if (result.diffPath) {
|
|
5227
|
-
console.log(
|
|
5417
|
+
console.log(chalk9.gray(` \u5DEE\u5F02\u56FE: ${path15.relative(process.cwd(), result.diffPath)}`));
|
|
5228
5418
|
}
|
|
5229
5419
|
}
|
|
5230
5420
|
console.log();
|
|
5231
|
-
console.log(
|
|
5421
|
+
console.log(chalk9.yellow(" \u63D0\u793A: \u8FD0\u884C qat visual approve \u66F4\u65B0\u57FA\u7EBF"));
|
|
5232
5422
|
}
|
|
5233
|
-
console.log(
|
|
5423
|
+
console.log(chalk9.cyan(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
5234
5424
|
console.log();
|
|
5235
5425
|
}
|
|
5236
5426
|
async function approveBaselines(baselineDir, diffDir) {
|
|
@@ -5249,7 +5439,7 @@ async function approveBaselines(baselineDir, diffDir) {
|
|
|
5249
5439
|
spinner.succeed(`\u5DF2\u66F4\u65B0 ${updated.length} \u4E2A\u57FA\u7EBF\u5FEB\u7167`);
|
|
5250
5440
|
console.log();
|
|
5251
5441
|
for (const file of updated) {
|
|
5252
|
-
console.log(
|
|
5442
|
+
console.log(chalk9.green(` \u2713 ${file}`));
|
|
5253
5443
|
}
|
|
5254
5444
|
console.log();
|
|
5255
5445
|
}
|
|
@@ -5259,14 +5449,14 @@ async function cleanAll(baselineDir, diffDir) {
|
|
|
5259
5449
|
const diffs = cleanDiffs(diffDir);
|
|
5260
5450
|
spinner.succeed("\u6E05\u7406\u5B8C\u6210");
|
|
5261
5451
|
console.log();
|
|
5262
|
-
console.log(
|
|
5452
|
+
console.log(chalk9.white(" \u5DF2\u5220\u9664:"));
|
|
5263
5453
|
console.log(` \u57FA\u7EBF: ${baselines} \u4E2A`);
|
|
5264
5454
|
console.log(` \u5DEE\u5F02: ${diffs} \u4E2A`);
|
|
5265
5455
|
console.log();
|
|
5266
5456
|
}
|
|
5267
5457
|
|
|
5268
5458
|
// src/commands/setup.ts
|
|
5269
|
-
import
|
|
5459
|
+
import chalk10 from "chalk";
|
|
5270
5460
|
import inquirer4 from "inquirer";
|
|
5271
5461
|
import ora8 from "ora";
|
|
5272
5462
|
import { execFile as execFile4 } from "child_process";
|
|
@@ -5295,7 +5485,7 @@ function registerSetupCommand(program2) {
|
|
|
5295
5485
|
try {
|
|
5296
5486
|
await executeSetup(options);
|
|
5297
5487
|
} catch (error) {
|
|
5298
|
-
console.error(
|
|
5488
|
+
console.error(chalk10.red(`
|
|
5299
5489
|
\u2717 ${error instanceof Error ? error.message : String(error)}
|
|
5300
5490
|
`));
|
|
5301
5491
|
process.exit(1);
|
|
@@ -5303,20 +5493,20 @@ function registerSetupCommand(program2) {
|
|
|
5303
5493
|
});
|
|
5304
5494
|
}
|
|
5305
5495
|
async function executeSetup(options) {
|
|
5306
|
-
console.log(
|
|
5496
|
+
console.log(chalk10.cyan("\n QAT \u4F9D\u8D56\u5B89\u88C5\u5668\n"));
|
|
5307
5497
|
const projectInfo = detectProject();
|
|
5308
5498
|
if (!fs15.existsSync(path16.join(process.cwd(), "package.json"))) {
|
|
5309
5499
|
throw new Error("\u672A\u627E\u5230 package.json\uFF0C\u8BF7\u5728\u9879\u76EE\u6839\u76EE\u5F55\u6267\u884C\u6B64\u547D\u4EE4");
|
|
5310
5500
|
}
|
|
5311
5501
|
if (projectInfo.frameworkConfidence > 0) {
|
|
5312
|
-
console.log(
|
|
5502
|
+
console.log(chalk10.white(` \u68C0\u6D4B\u5230\u6846\u67B6: ${chalk10.cyan(projectInfo.frameworkDisplayName)}`));
|
|
5313
5503
|
if (projectInfo.uiLibrary !== "none") {
|
|
5314
|
-
console.log(
|
|
5504
|
+
console.log(chalk10.white(` UI \u7EC4\u4EF6\u5E93: ${chalk10.cyan(projectInfo.uiLibrary)}`));
|
|
5315
5505
|
}
|
|
5316
5506
|
if (projectInfo.monorepo !== "none") {
|
|
5317
|
-
console.log(
|
|
5507
|
+
console.log(chalk10.white(` Monorepo: ${chalk10.cyan(projectInfo.monorepo)}`));
|
|
5318
5508
|
if (projectInfo.appDirs.length > 0) {
|
|
5319
|
-
console.log(
|
|
5509
|
+
console.log(chalk10.white(` \u5B50\u9879\u76EE: ${chalk10.gray(projectInfo.appDirs.join(", "))}`));
|
|
5320
5510
|
}
|
|
5321
5511
|
}
|
|
5322
5512
|
console.log();
|
|
@@ -5352,12 +5542,12 @@ async function executeSetup(options) {
|
|
|
5352
5542
|
}
|
|
5353
5543
|
const groupsToInstall = determineGroups(config, projectInfo, options.force);
|
|
5354
5544
|
if (groupsToInstall.length === 0) {
|
|
5355
|
-
console.log(
|
|
5545
|
+
console.log(chalk10.green(" \u2713 \u6240\u6709\u4F9D\u8D56\u5DF2\u5B89\u88C5\uFF0C\u65E0\u9700\u989D\u5916\u64CD\u4F5C\n"));
|
|
5356
5546
|
return;
|
|
5357
5547
|
}
|
|
5358
5548
|
const selectedGroups = await selectGroups(groupsToInstall, options.dryRun);
|
|
5359
5549
|
if (selectedGroups.length === 0) {
|
|
5360
|
-
console.log(
|
|
5550
|
+
console.log(chalk10.gray("\n \u5DF2\u53D6\u6D88\u5B89\u88C5\n"));
|
|
5361
5551
|
return;
|
|
5362
5552
|
}
|
|
5363
5553
|
const allPackages = selectedGroups.flatMap((g) => g.packages);
|
|
@@ -5371,19 +5561,19 @@ ${allPackages.map((p) => ` - ${p}`).join("\n")}`,
|
|
|
5371
5561
|
}
|
|
5372
5562
|
]);
|
|
5373
5563
|
if (!confirmed) {
|
|
5374
|
-
console.log(
|
|
5564
|
+
console.log(chalk10.gray("\n \u5DF2\u53D6\u6D88\u5B89\u88C5\n"));
|
|
5375
5565
|
return;
|
|
5376
5566
|
}
|
|
5377
5567
|
if (options.dryRun) {
|
|
5378
|
-
console.log(
|
|
5568
|
+
console.log(chalk10.yellow("\n [Dry Run] \u4EE5\u4E0B\u547D\u4EE4\u5C06\u88AB\u6267\u884C:\n"));
|
|
5379
5569
|
const pm = getPackageManager(projectInfo.packageManager);
|
|
5380
5570
|
const installCmd = pm === "npm" ? "npm install -D" : pm === "yarn" ? "yarn add -D" : pm === "pnpm" ? "pnpm add -D" : "bun add -D";
|
|
5381
5571
|
for (const group of selectedGroups) {
|
|
5382
5572
|
const dirHint = installDir !== process.cwd() ? ` (\u5728 ${path16.relative(process.cwd(), installDir) || installDir})` : "";
|
|
5383
|
-
console.log(
|
|
5573
|
+
console.log(chalk10.white(` ${installCmd} ${group.packages.join(" ")}${dirHint}`));
|
|
5384
5574
|
if (group.postInstall) {
|
|
5385
5575
|
for (const cmd of group.postInstall) {
|
|
5386
|
-
console.log(
|
|
5576
|
+
console.log(chalk10.white(` ${cmd}`));
|
|
5387
5577
|
}
|
|
5388
5578
|
}
|
|
5389
5579
|
}
|
|
@@ -5496,36 +5686,36 @@ ${stderr}` : "")));
|
|
|
5496
5686
|
});
|
|
5497
5687
|
}
|
|
5498
5688
|
function displaySetupResult(groups) {
|
|
5499
|
-
console.log(
|
|
5500
|
-
console.log(
|
|
5689
|
+
console.log(chalk10.green("\n \u2713 \u4F9D\u8D56\u5B89\u88C5\u5B8C\u6210!\n"));
|
|
5690
|
+
console.log(chalk10.white(" \u5DF2\u5B89\u88C5:"));
|
|
5501
5691
|
for (const group of groups) {
|
|
5502
|
-
console.log(
|
|
5692
|
+
console.log(chalk10.cyan(`
|
|
5503
5693
|
${group.name}`));
|
|
5504
5694
|
for (const pkg of group.packages) {
|
|
5505
|
-
console.log(
|
|
5695
|
+
console.log(chalk10.gray(` \u2713 ${pkg}`));
|
|
5506
5696
|
}
|
|
5507
5697
|
if (group.postInstall) {
|
|
5508
5698
|
for (const cmd of group.postInstall) {
|
|
5509
|
-
console.log(
|
|
5699
|
+
console.log(chalk10.gray(` \u2713 ${cmd}`));
|
|
5510
5700
|
}
|
|
5511
5701
|
}
|
|
5512
5702
|
}
|
|
5513
5703
|
console.log();
|
|
5514
|
-
console.log(
|
|
5515
|
-
console.log(
|
|
5516
|
-
console.log(
|
|
5704
|
+
console.log(chalk10.cyan(" \u4E0B\u4E00\u6B65:"));
|
|
5705
|
+
console.log(chalk10.gray(" 1. \u8FD0\u884C qat create \u521B\u5EFA\u6D4B\u8BD5\u7528\u4F8B"));
|
|
5706
|
+
console.log(chalk10.gray(" 2. \u8FD0\u884C qat run \u6267\u884C\u6D4B\u8BD5"));
|
|
5517
5707
|
console.log();
|
|
5518
5708
|
}
|
|
5519
5709
|
|
|
5520
5710
|
// src/commands/status.ts
|
|
5521
|
-
import
|
|
5711
|
+
import chalk11 from "chalk";
|
|
5522
5712
|
import ora9 from "ora";
|
|
5523
5713
|
function registerStatusCommand(program2) {
|
|
5524
5714
|
program2.command("status").description("\u67E5\u770B QAT \u72B6\u6001 - AI \u6A21\u578B\u4FE1\u606F\u3001\u9879\u76EE\u914D\u7F6E").action(async (options) => {
|
|
5525
5715
|
try {
|
|
5526
5716
|
await executeStatus(options);
|
|
5527
5717
|
} catch (error) {
|
|
5528
|
-
console.error(
|
|
5718
|
+
console.error(chalk11.red(`
|
|
5529
5719
|
\u2717 ${error instanceof Error ? error.message : String(error)}
|
|
5530
5720
|
`));
|
|
5531
5721
|
process.exit(1);
|
|
@@ -5533,24 +5723,24 @@ function registerStatusCommand(program2) {
|
|
|
5533
5723
|
});
|
|
5534
5724
|
}
|
|
5535
5725
|
async function executeStatus(_options) {
|
|
5536
|
-
console.log(
|
|
5537
|
-
console.log(
|
|
5726
|
+
console.log(chalk11.cyan("\n AI \u6A21\u578B\u72B6\u6001"));
|
|
5727
|
+
console.log(chalk11.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
5538
5728
|
const globalAI = loadGlobalAIConfig();
|
|
5539
5729
|
if (!globalAI) {
|
|
5540
|
-
console.log(
|
|
5541
|
-
console.log(
|
|
5730
|
+
console.log(chalk11.yellow(" \u2717 \u672A\u914D\u7F6E AI \u6A21\u578B"));
|
|
5731
|
+
console.log(chalk11.gray(" \u8FD0\u884C qat change \u914D\u7F6E AI \u6A21\u578B"));
|
|
5542
5732
|
} else {
|
|
5543
|
-
console.log(` ${
|
|
5544
|
-
console.log(` ${
|
|
5545
|
-
console.log(` ${
|
|
5546
|
-
console.log(` ${
|
|
5547
|
-
console.log(` ${
|
|
5733
|
+
console.log(` ${chalk11.white("\u6A21\u578B:")} ${chalk11.green(globalAI.model)}`);
|
|
5734
|
+
console.log(` ${chalk11.white("API URL:")} ${chalk11.gray(globalAI.baseUrl)}`);
|
|
5735
|
+
console.log(` ${chalk11.white("API Key:")} ${chalk11.gray(maskApiKey(globalAI.apiKey))}`);
|
|
5736
|
+
console.log(` ${chalk11.white("Provider:")} ${chalk11.gray(globalAI.provider)}`);
|
|
5737
|
+
console.log(` ${chalk11.white("\u914D\u7F6E\u6587\u4EF6:")} ${chalk11.gray(getAIConfigPath())}`);
|
|
5548
5738
|
const testSpinner = ora9(" \u6B63\u5728\u6D4B\u8BD5\u8FDE\u901A\u6027...").start();
|
|
5549
5739
|
try {
|
|
5550
5740
|
const aiConfig = toAIConfig(globalAI);
|
|
5551
5741
|
const result = await testAIConnection(aiConfig);
|
|
5552
5742
|
if (result.ok) {
|
|
5553
|
-
testSpinner.succeed(` \u8FDE\u901A\u6B63\u5E38 ${
|
|
5743
|
+
testSpinner.succeed(` \u8FDE\u901A\u6B63\u5E38 ${chalk11.gray(`(${result.latencyMs}ms)`)}`);
|
|
5554
5744
|
} else {
|
|
5555
5745
|
testSpinner.fail(` \u8FDE\u901A\u5F02\u5E38: ${result.message}`);
|
|
5556
5746
|
}
|
|
@@ -5559,26 +5749,26 @@ async function executeStatus(_options) {
|
|
|
5559
5749
|
}
|
|
5560
5750
|
}
|
|
5561
5751
|
console.log();
|
|
5562
|
-
console.log(
|
|
5563
|
-
console.log(
|
|
5752
|
+
console.log(chalk11.cyan(" \u9879\u76EE\u914D\u7F6E"));
|
|
5753
|
+
console.log(chalk11.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
5564
5754
|
try {
|
|
5565
5755
|
const config = await loadConfig();
|
|
5566
5756
|
const projectInfo = detectProject();
|
|
5567
|
-
console.log(` ${
|
|
5568
|
-
console.log(` ${
|
|
5569
|
-
console.log(` ${
|
|
5570
|
-
console.log(` ${
|
|
5571
|
-
console.log(` ${
|
|
5572
|
-
console.log(` ${
|
|
5573
|
-
console.log(` ${
|
|
5757
|
+
console.log(` ${chalk11.white("\u6846\u67B6:")} ${projectInfo.frameworkDisplayName}`);
|
|
5758
|
+
console.log(` ${chalk11.white("\u6E90\u7801\u76EE\u5F55:")} ${config.project.srcDir}`);
|
|
5759
|
+
console.log(` ${chalk11.white("Vitest:")} ${config.vitest.enabled ? chalk11.green("\u2713") : chalk11.red("\u2717")} (${config.vitest.environment})`);
|
|
5760
|
+
console.log(` ${chalk11.white("Playwright:")} ${config.playwright.enabled ? chalk11.green("\u2713") : chalk11.red("\u2717")} (${config.playwright.browsers.join(", ")})`);
|
|
5761
|
+
console.log(` ${chalk11.white("Mock:")} ${config.mock.enabled ? chalk11.green("\u2713") : chalk11.red("\u2717")} (port ${config.mock.port})`);
|
|
5762
|
+
console.log(` ${chalk11.white("Visual:")} ${config.visual.enabled ? chalk11.green("\u2713") : chalk11.red("\u2717")}`);
|
|
5763
|
+
console.log(` ${chalk11.white("Lighthouse:")} ${config.lighthouse.enabled ? chalk11.green("\u2713") : chalk11.red("\u2717")}`);
|
|
5574
5764
|
} catch {
|
|
5575
|
-
console.log(
|
|
5765
|
+
console.log(chalk11.yellow(" \u2717 \u672A\u627E\u5230\u9879\u76EE\u914D\u7F6E (\u8FD0\u884C qat init \u521D\u59CB\u5316)"));
|
|
5576
5766
|
}
|
|
5577
5767
|
console.log();
|
|
5578
5768
|
}
|
|
5579
5769
|
|
|
5580
5770
|
// src/commands/change.ts
|
|
5581
|
-
import
|
|
5771
|
+
import chalk12 from "chalk";
|
|
5582
5772
|
import inquirer5 from "inquirer";
|
|
5583
5773
|
import ora10 from "ora";
|
|
5584
5774
|
function registerChangeCommand(program2) {
|
|
@@ -5586,7 +5776,7 @@ function registerChangeCommand(program2) {
|
|
|
5586
5776
|
try {
|
|
5587
5777
|
await executeChange(options);
|
|
5588
5778
|
} catch (error) {
|
|
5589
|
-
console.error(
|
|
5779
|
+
console.error(chalk12.red(`
|
|
5590
5780
|
\u2717 ${error instanceof Error ? error.message : String(error)}
|
|
5591
5781
|
`));
|
|
5592
5782
|
process.exit(1);
|
|
@@ -5596,13 +5786,13 @@ function registerChangeCommand(program2) {
|
|
|
5596
5786
|
async function executeChange(_options) {
|
|
5597
5787
|
const current = loadGlobalAIConfig();
|
|
5598
5788
|
if (current) {
|
|
5599
|
-
console.log(
|
|
5600
|
-
console.log(` ${
|
|
5601
|
-
console.log(` ${
|
|
5602
|
-
console.log(` ${
|
|
5789
|
+
console.log(chalk12.cyan("\n \u5F53\u524D AI \u914D\u7F6E:"));
|
|
5790
|
+
console.log(` ${chalk12.white("\u6A21\u578B:")} ${chalk12.green(current.model)}`);
|
|
5791
|
+
console.log(` ${chalk12.white("API URL:")} ${chalk12.gray(current.baseUrl)}`);
|
|
5792
|
+
console.log(` ${chalk12.white("API Key:")} ${chalk12.gray(maskApiKey(current.apiKey))}`);
|
|
5603
5793
|
console.log();
|
|
5604
5794
|
} else {
|
|
5605
|
-
console.log(
|
|
5795
|
+
console.log(chalk12.yellow("\n \u5F53\u524D\u672A\u914D\u7F6E AI \u6A21\u578B\uFF0C\u8BF7\u914D\u7F6E:\n"));
|
|
5606
5796
|
}
|
|
5607
5797
|
const answers = await inquirer5.prompt([
|
|
5608
5798
|
{
|
|
@@ -5640,16 +5830,16 @@ async function executeChange(_options) {
|
|
|
5640
5830
|
model: answers.model?.trim() || "deepseek-chat"
|
|
5641
5831
|
};
|
|
5642
5832
|
saveGlobalAIConfig(newConfig);
|
|
5643
|
-
console.log(
|
|
5833
|
+
console.log(chalk12.green(`
|
|
5644
5834
|
\u2713 AI \u914D\u7F6E\u5DF2\u4FDD\u5B58`));
|
|
5645
|
-
console.log(
|
|
5646
|
-
console.log(` ${
|
|
5835
|
+
console.log(chalk12.gray(` ${getAIConfigPath()}`));
|
|
5836
|
+
console.log(` ${chalk12.white("\u6A21\u578B:")} ${chalk12.green(newConfig.model)} @ ${chalk12.gray(newConfig.baseUrl)}`);
|
|
5647
5837
|
const testSpinner = ora10(" \u6B63\u5728\u6D4B\u8BD5\u8FDE\u901A\u6027...").start();
|
|
5648
5838
|
try {
|
|
5649
5839
|
const aiConfig = toAIConfig(newConfig);
|
|
5650
5840
|
const result = await testAIConnection(aiConfig);
|
|
5651
5841
|
if (result.ok) {
|
|
5652
|
-
testSpinner.succeed(` AI \u8FDE\u901A\u6B63\u5E38 ${
|
|
5842
|
+
testSpinner.succeed(` AI \u8FDE\u901A\u6B63\u5E38 ${chalk12.gray(`(${newConfig.model}, ${result.latencyMs}ms)`)}`);
|
|
5653
5843
|
} else {
|
|
5654
5844
|
testSpinner.fail(` AI \u8FDE\u901A\u5F02\u5E38: ${result.message}`);
|
|
5655
5845
|
}
|
|
@@ -5660,37 +5850,40 @@ async function executeChange(_options) {
|
|
|
5660
5850
|
}
|
|
5661
5851
|
|
|
5662
5852
|
// src/cli.ts
|
|
5663
|
-
var VERSION = "0.3.
|
|
5853
|
+
var VERSION = "0.3.04";
|
|
5664
5854
|
function printLogo() {
|
|
5665
5855
|
const logo = `
|
|
5666
|
-
${
|
|
5667
|
-
${
|
|
5668
|
-
${
|
|
5669
|
-
${
|
|
5670
|
-
${
|
|
5671
|
-
${
|
|
5672
|
-
${
|
|
5856
|
+
${chalk13.bold.cyan(" ___ _ _ _ _ _____ _ _ ")}
|
|
5857
|
+
${chalk13.bold.cyan(" / _ \\ _ _ (_) ___ | | __ / \\ _ _ | |_ ___ |_ _| ___ ___ | |_ (_) _ __ __ _ ")}
|
|
5858
|
+
${chalk13.bold.cyan(" | | | | | | | | | | / __| | |/ / / _ \\ | | | | | __| / _ \\ | | / _ \\ / __| | __| | | | '_ \\ / _` |")}
|
|
5859
|
+
${chalk13.bold.cyan(" | |_| | | |_| | | | | (__ | < / ___ \\ | |_| | | |_ | (_) | | | | __/ \\__ \\ | |_ | | | | | | | (_| |")}
|
|
5860
|
+
${chalk13.bold.cyan(" \\__\\_\\ \\__,_| |_| \\___| |_|\\_\\ /_/ \\_\\ \\__,_| \\__| \\___/ |_| \\___| |___/ \\__| |_| |_| |_| \\__, |")}
|
|
5861
|
+
${chalk13.bold.cyan(" |___/ ")}
|
|
5862
|
+
${chalk13.gray(" CLI\u81EA\u52A8\u5316\u6D4B\u8BD5\u5DE5\u5177 v")}${chalk13.green(VERSION)}
|
|
5673
5863
|
`;
|
|
5674
5864
|
console.log(logo);
|
|
5675
5865
|
}
|
|
5676
5866
|
var program = new Command();
|
|
5677
|
-
program.name("qat").description("CLI\u81EA\u52A8\u5316\u6D4B\u8BD5\u5DE5\u5177 - \u9762\u5411Vue\u9879\u76EE\uFF0C\u96C6\u6210Vitest\u3001Playwright\uFF0C\u8986\u76D6\u6D4B\u8BD5\u5168\u6D41\u7A0B").version(VERSION).option("-c, --config <path>", "\u6307\u5B9A\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").option("-v, --verbose", "\u663E\u793A\u8BE6\u7EC6\u8F93\u51FA").hook("preAction", async (thisCommand) => {
|
|
5867
|
+
program.name("qat").description("CLI\u81EA\u52A8\u5316\u6D4B\u8BD5\u5DE5\u5177 - \u9762\u5411Vue\u9879\u76EE\uFF0C\u96C6\u6210Vitest\u3001Playwright\uFF0C\u8986\u76D6\u6D4B\u8BD5\u5168\u6D41\u7A0B").version(VERSION).option("-c, --config <path>", "\u6307\u5B9A\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").option("-v, --verbose", "\u663E\u793A\u8BE6\u7EC6\u8F93\u51FA").option("-d, --debug", "\u8C03\u8BD5\u6A21\u5F0F\uFF1A\u663E\u793AAI\u8BF7\u6C42/\u54CD\u5E94\u8BE6\u60C5").hook("preAction", async (thisCommand) => {
|
|
5678
5868
|
printLogo();
|
|
5679
5869
|
const opts = thisCommand.opts();
|
|
5680
5870
|
if (opts.verbose) {
|
|
5681
5871
|
process.env.QAT_VERBOSE = "true";
|
|
5682
5872
|
}
|
|
5873
|
+
if (opts.debug) {
|
|
5874
|
+
process.env.QAT_DEBUG = "true";
|
|
5875
|
+
}
|
|
5683
5876
|
if (opts.config) {
|
|
5684
5877
|
process.env.QAT_CONFIG_PATH = opts.config;
|
|
5685
5878
|
}
|
|
5686
5879
|
const { loadExternalFrameworks } = await import("./framework-registry-YGZ63RDX.js");
|
|
5687
5880
|
const loadResult = await loadExternalFrameworks(process.cwd());
|
|
5688
5881
|
if (loadResult.loaded > 0 && opts.verbose) {
|
|
5689
|
-
console.log(
|
|
5882
|
+
console.log(chalk13.gray(` [ext] \u5DF2\u52A0\u8F7D ${loadResult.loaded} \u4E2A\u5916\u90E8\u6269\u5C55\u6587\u4EF6: ${loadResult.files.join(", ")}`));
|
|
5690
5883
|
}
|
|
5691
5884
|
if (loadResult.errors.length > 0) {
|
|
5692
5885
|
for (const err of loadResult.errors) {
|
|
5693
|
-
console.log(
|
|
5886
|
+
console.log(chalk13.yellow(` [ext] \u8B66\u544A: ${err.file} - ${err.error}`));
|
|
5694
5887
|
}
|
|
5695
5888
|
}
|
|
5696
5889
|
});
|
|
@@ -5704,9 +5897,9 @@ registerSetupCommand(program);
|
|
|
5704
5897
|
registerStatusCommand(program);
|
|
5705
5898
|
registerChangeCommand(program);
|
|
5706
5899
|
program.on("command:*", (operands) => {
|
|
5707
|
-
console.error(
|
|
5900
|
+
console.error(chalk13.red(`
|
|
5708
5901
|
\u672A\u77E5\u547D\u4EE4: ${operands[0]}`));
|
|
5709
|
-
console.log(
|
|
5902
|
+
console.log(chalk13.gray(` \u4F7F\u7528 qat --help \u67E5\u770B\u53EF\u7528\u547D\u4EE4
|
|
5710
5903
|
`));
|
|
5711
5904
|
process.exit(1);
|
|
5712
5905
|
});
|