qat-cli 0.3.3 → 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cli.js +744 -552
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +414 -144
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +48 -3
- package/dist/index.d.ts +48 -3
- package/dist/index.js +414 -144
- 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";
|
|
@@ -524,71 +524,6 @@ function validateConfig(config) {
|
|
|
524
524
|
}
|
|
525
525
|
return merged;
|
|
526
526
|
}
|
|
527
|
-
function generateConfigFile(overrides = {}) {
|
|
528
|
-
const config = validateConfig(overrides);
|
|
529
|
-
return `// @ts-check
|
|
530
|
-
/**
|
|
531
|
-
* QAT \u914D\u7F6E\u6587\u4EF6 - Quick Auto Testing
|
|
532
|
-
* \u4FEE\u6539\u540E\u65E0\u9700\u91CD\u542F\uFF0C\u4E0B\u6B21\u8FD0\u884C qat \u547D\u4EE4\u65F6\u81EA\u52A8\u751F\u6548
|
|
533
|
-
*
|
|
534
|
-
* AI \u6A21\u578B\u914D\u7F6E\u8BF7\u8FD0\u884C: qat change
|
|
535
|
-
* AI \u72B6\u6001\u67E5\u770B\u8BF7\u8FD0\u884C: qat status
|
|
536
|
-
*/
|
|
537
|
-
import { defineConfig } from 'qat-cli';
|
|
538
|
-
|
|
539
|
-
export default defineConfig({
|
|
540
|
-
project: {
|
|
541
|
-
framework: '${config.project.framework}',
|
|
542
|
-
vite: ${config.project.vite ?? true},
|
|
543
|
-
srcDir: '${config.project.srcDir}',
|
|
544
|
-
},
|
|
545
|
-
vitest: {
|
|
546
|
-
enabled: ${config.vitest.enabled},
|
|
547
|
-
coverage: ${config.vitest.coverage},
|
|
548
|
-
globals: ${config.vitest.globals},
|
|
549
|
-
environment: '${config.vitest.environment}',
|
|
550
|
-
},
|
|
551
|
-
playwright: {
|
|
552
|
-
enabled: ${config.playwright.enabled},
|
|
553
|
-
browsers: [${config.playwright.browsers.map((b) => `'${b}'`).join(", ")}],
|
|
554
|
-
baseURL: '${config.playwright.baseURL}',
|
|
555
|
-
screenshot: '${config.playwright.screenshot}',
|
|
556
|
-
},
|
|
557
|
-
visual: {
|
|
558
|
-
enabled: ${config.visual.enabled},
|
|
559
|
-
threshold: ${config.visual.threshold},
|
|
560
|
-
baselineDir: '${config.visual.baselineDir}',
|
|
561
|
-
diffDir: '${config.visual.diffDir}',
|
|
562
|
-
},
|
|
563
|
-
lighthouse: {
|
|
564
|
-
enabled: ${config.lighthouse.enabled},
|
|
565
|
-
urls: [${config.lighthouse.urls.map((u) => `'${u}'`).join(", ")}],
|
|
566
|
-
runs: ${config.lighthouse.runs},
|
|
567
|
-
thresholds: {${Object.entries(config.lighthouse.thresholds).map(([k, v]) => `
|
|
568
|
-
${k}: ${v},`).join("")}
|
|
569
|
-
},
|
|
570
|
-
},
|
|
571
|
-
mock: {
|
|
572
|
-
enabled: ${config.mock.enabled},
|
|
573
|
-
port: ${config.mock.port},
|
|
574
|
-
routesDir: '${config.mock.routesDir}',
|
|
575
|
-
},
|
|
576
|
-
report: {
|
|
577
|
-
outputDir: '${config.report.outputDir}',
|
|
578
|
-
open: ${config.report.open},
|
|
579
|
-
},
|
|
580
|
-
});
|
|
581
|
-
`;
|
|
582
|
-
}
|
|
583
|
-
async function writeConfigFile(cwd, overrides = {}, force = false) {
|
|
584
|
-
const configPath = path2.join(cwd, "qat.config.js");
|
|
585
|
-
if (fs2.existsSync(configPath) && !force) {
|
|
586
|
-
throw new Error(`\u914D\u7F6E\u6587\u4EF6\u5DF2\u5B58\u5728: ${configPath}\uFF0C\u4F7F\u7528 --force \u8986\u76D6`);
|
|
587
|
-
}
|
|
588
|
-
const content = generateConfigFile(overrides);
|
|
589
|
-
fs2.writeFileSync(configPath, content, "utf-8");
|
|
590
|
-
return configPath;
|
|
591
|
-
}
|
|
592
527
|
function isFileNotFoundError(error) {
|
|
593
528
|
if (error instanceof Error) {
|
|
594
529
|
return error.message.includes("Cannot find") || error.message.includes("ENOENT") || error.message.includes("\u65E0\u6CD5\u52A0\u8F7D\u914D\u7F6E\u6587\u4EF6");
|
|
@@ -843,6 +778,15 @@ var NoopAIProvider = class {
|
|
|
843
778
|
};
|
|
844
779
|
|
|
845
780
|
// src/ai/openai-provider.ts
|
|
781
|
+
import chalk2 from "chalk";
|
|
782
|
+
function isDebug() {
|
|
783
|
+
return process.env.QAT_DEBUG === "true";
|
|
784
|
+
}
|
|
785
|
+
function debugLog(tag, ...args) {
|
|
786
|
+
if (!isDebug()) return;
|
|
787
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(11, 19);
|
|
788
|
+
console.error(chalk2.gray(`[DEBUG ${timestamp}] [${tag}]`), ...args);
|
|
789
|
+
}
|
|
846
790
|
var OpenAICompatibleProvider = class {
|
|
847
791
|
constructor(config) {
|
|
848
792
|
this.capabilities = {
|
|
@@ -856,25 +800,20 @@ var OpenAICompatibleProvider = class {
|
|
|
856
800
|
this.baseUrl = config.baseUrl || this.getDefaultBaseUrl(config.provider);
|
|
857
801
|
}
|
|
858
802
|
async generateTest(req) {
|
|
803
|
+
debugLog("GENERATE", `type=${req.type} target=${req.target}`);
|
|
859
804
|
const systemPrompt = this.buildGenerateTestSystemPrompt(req);
|
|
860
805
|
const userPrompt = this.buildGenerateTestUserPrompt(req);
|
|
861
806
|
const content = await this.chat(systemPrompt, userPrompt);
|
|
862
|
-
|
|
807
|
+
const result = this.parseGenerateTestResponse(content);
|
|
808
|
+
debugLog("GENERATE", `done code=${result.code.length}chars confidence=${result.confidence}`);
|
|
809
|
+
return result;
|
|
863
810
|
}
|
|
864
811
|
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:
|
|
812
|
+
const systemPrompt = `\u6D4B\u8BD5\u5206\u6790\u4E13\u5BB6\u3002\u627E\u95EE\u9898\u6839\u56E0\uFF0C\u7ED9\u53EF\u64CD\u4F5C\u5EFA\u8BAE\u3002
|
|
813
|
+
\u8F93\u51FA:1.\u6458\u8981(1-3\u53E5) 2.\u5EFA\u8BAE\u5217\u8868`;
|
|
814
|
+
const failed = req.testResults.flatMap((r) => r.suites.flatMap((s) => s.tests.filter((t) => t.status === "failed")));
|
|
815
|
+
const errorDetails = req.errorLogs?.join("\n") || failed.map((t) => `[${t.name}] ${t.error?.message}`).join("\n") || "\u65E0";
|
|
816
|
+
const userPrompt = `\u5931\u8D25:${failed.length}
|
|
878
817
|
${errorDetails}`;
|
|
879
818
|
const content = await this.chat(systemPrompt, userPrompt);
|
|
880
819
|
return {
|
|
@@ -884,23 +823,22 @@ ${errorDetails}`;
|
|
|
884
823
|
};
|
|
885
824
|
}
|
|
886
825
|
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}` : ""}`;
|
|
826
|
+
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`;
|
|
827
|
+
const userPrompt = `\u9519\u8BEF:${error.message}${error.stack ? `
|
|
828
|
+
\u5806\u6808:${error.stack}` : ""}${error.expected ? `
|
|
829
|
+
\u671F\u671B:${error.expected}` : ""}${error.actual ? `
|
|
830
|
+
\u5B9E\u9645:${error.actual}` : ""}`;
|
|
896
831
|
const content = await this.chat(systemPrompt, userPrompt);
|
|
897
832
|
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
833
|
}
|
|
899
834
|
async reviewTest(req) {
|
|
835
|
+
debugLog("REVIEW", `target=${req.target} type=${req.testType}`);
|
|
900
836
|
const systemPrompt = this.buildReviewTestSystemPrompt(req);
|
|
901
837
|
const userPrompt = this.buildReviewTestUserPrompt(req);
|
|
902
838
|
const content = await this.chat(systemPrompt, userPrompt);
|
|
903
|
-
|
|
839
|
+
const result = this.parseReviewTestResponse(content);
|
|
840
|
+
debugLog("REVIEW", `approved=${result.approved} score=${result.score} feedback=${result.feedback}`);
|
|
841
|
+
return result;
|
|
904
842
|
}
|
|
905
843
|
// ─── 内部方法 ──────────────────────────────────────────────
|
|
906
844
|
/**
|
|
@@ -964,8 +902,9 @@ ${error.actual ? `\u5B9E\u9645\u503C: ${error.actual}` : ""}`;
|
|
|
964
902
|
return { ok: false, message: `\u8FDE\u63A5\u5931\u8D25: ${error instanceof Error ? error.message : String(error)}`, latencyMs };
|
|
965
903
|
}
|
|
966
904
|
}
|
|
967
|
-
async chat(systemPrompt, userPrompt) {
|
|
905
|
+
async chat(systemPrompt, userPrompt, retries = 2) {
|
|
968
906
|
const url = `${this.baseUrl}/chat/completions`;
|
|
907
|
+
const useStream = isDebug();
|
|
969
908
|
const body = {
|
|
970
909
|
model: this.model,
|
|
971
910
|
messages: [
|
|
@@ -975,101 +914,202 @@ ${error.actual ? `\u5B9E\u9645\u503C: ${error.actual}` : ""}`;
|
|
|
975
914
|
temperature: 0.3,
|
|
976
915
|
max_tokens: 4096
|
|
977
916
|
};
|
|
917
|
+
if (useStream) {
|
|
918
|
+
body.stream = true;
|
|
919
|
+
}
|
|
978
920
|
const headers = {
|
|
979
921
|
"Content-Type": "application/json"
|
|
980
922
|
};
|
|
981
923
|
if (this.apiKey) {
|
|
982
924
|
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
983
925
|
}
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
926
|
+
debugLog("REQUEST", `POST ${url}`);
|
|
927
|
+
debugLog("REQUEST", `model=${this.model} stream=${useStream}`);
|
|
928
|
+
debugLog("SYSTEM", systemPrompt.length > 500 ? `${systemPrompt.slice(0, 500)}...` : systemPrompt);
|
|
929
|
+
debugLog("USER", userPrompt.length > 1e3 ? `${userPrompt.slice(0, 1e3)}...` : userPrompt);
|
|
930
|
+
let lastError = null;
|
|
931
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
932
|
+
try {
|
|
933
|
+
if (attempt > 0) {
|
|
934
|
+
debugLog("RETRY", `\u7B2C${attempt}\u6B21\u91CD\u8BD5...`);
|
|
935
|
+
}
|
|
936
|
+
const response = await fetch(url, {
|
|
937
|
+
method: "POST",
|
|
938
|
+
headers,
|
|
939
|
+
body: JSON.stringify(body),
|
|
940
|
+
signal: AbortSignal.timeout(12e4)
|
|
941
|
+
// 120s timeout
|
|
942
|
+
});
|
|
943
|
+
if (!response.ok) {
|
|
944
|
+
const text = await response.text().catch(() => "");
|
|
945
|
+
if ((response.status === 429 || response.status >= 500) && attempt < retries) {
|
|
946
|
+
const delay = Math.min(1e3 * Math.pow(2, attempt), 8e3);
|
|
947
|
+
debugLog("RETRY", `HTTP ${response.status}, ${delay}ms\u540E\u91CD\u8BD5`);
|
|
948
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
949
|
+
lastError = new Error(`AI API \u8BF7\u6C42\u5931\u8D25 (${response.status}): ${text.slice(0, 200)}`);
|
|
950
|
+
continue;
|
|
951
|
+
}
|
|
952
|
+
throw new Error(`AI API \u8BF7\u6C42\u5931\u8D25 (${response.status}): ${text.slice(0, 500)}`);
|
|
953
|
+
}
|
|
954
|
+
if (useStream && response.body) {
|
|
955
|
+
const content = await this.readStream(response.body);
|
|
956
|
+
debugLog("RESPONSE", content.length > 500 ? `${content.slice(0, 500)}...` : content);
|
|
957
|
+
return content;
|
|
958
|
+
}
|
|
959
|
+
const data = await response.json();
|
|
960
|
+
if (!data.choices?.[0]?.message?.content) {
|
|
961
|
+
throw new Error("AI API \u8FD4\u56DE\u7A7A\u54CD\u5E94");
|
|
962
|
+
}
|
|
963
|
+
debugLog("RESPONSE", `tokens: prompt=${data.usage?.prompt_tokens} completion=${data.usage?.completion_tokens} total=${data.usage?.total_tokens}`);
|
|
964
|
+
debugLog("RESPONSE", data.choices[0].message.content.length > 500 ? `${data.choices[0].message.content.slice(0, 500)}...` : data.choices[0].message.content);
|
|
965
|
+
return data.choices[0].message.content;
|
|
966
|
+
} catch (error) {
|
|
967
|
+
if (error instanceof Error && error.name === "TimeoutError" && attempt < retries) {
|
|
968
|
+
debugLog("TIMEOUT", `\u7B2C${attempt}\u6B21\u8D85\u65F6\uFF0C\u91CD\u8BD5\u4E2D...`);
|
|
969
|
+
lastError = error;
|
|
970
|
+
continue;
|
|
971
|
+
}
|
|
972
|
+
throw error;
|
|
973
|
+
}
|
|
994
974
|
}
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
975
|
+
throw lastError || new Error("AI API \u8BF7\u6C42\u5931\u8D25");
|
|
976
|
+
}
|
|
977
|
+
/**
|
|
978
|
+
* 读取 SSE 流式响应,实时输出内容
|
|
979
|
+
*/
|
|
980
|
+
async readStream(body) {
|
|
981
|
+
const chunks = [];
|
|
982
|
+
const decoder = new TextDecoder();
|
|
983
|
+
const reader = body.getReader();
|
|
984
|
+
let buffer = "";
|
|
985
|
+
let lineCount = 0;
|
|
986
|
+
try {
|
|
987
|
+
while (true) {
|
|
988
|
+
const { done, value } = await reader.read();
|
|
989
|
+
if (done) break;
|
|
990
|
+
buffer += decoder.decode(value, { stream: true });
|
|
991
|
+
const lines = buffer.split("\n");
|
|
992
|
+
buffer = lines.pop() || "";
|
|
993
|
+
for (const line of lines) {
|
|
994
|
+
const trimmed = line.trim();
|
|
995
|
+
if (!trimmed || trimmed === "data: [DONE]") continue;
|
|
996
|
+
if (!trimmed.startsWith("data: ")) continue;
|
|
997
|
+
try {
|
|
998
|
+
const json = JSON.parse(trimmed.slice(6));
|
|
999
|
+
const delta = json.choices?.[0]?.delta?.content;
|
|
1000
|
+
if (delta) {
|
|
1001
|
+
chunks.push(delta);
|
|
1002
|
+
lineCount++;
|
|
1003
|
+
process.stderr.write(chalk2.gray(delta));
|
|
1004
|
+
if (lineCount % 20 === 0) {
|
|
1005
|
+
process.stderr.write("\n");
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
if (json.choices?.[0]?.finish_reason === "stop") {
|
|
1009
|
+
debugLog("STREAM", "\u5B8C\u6210");
|
|
1010
|
+
}
|
|
1011
|
+
} catch {
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
} finally {
|
|
1016
|
+
reader.releaseLock();
|
|
1017
|
+
}
|
|
1018
|
+
if (chunks.length > 0) {
|
|
1019
|
+
process.stderr.write("\n");
|
|
1020
|
+
}
|
|
1021
|
+
return chunks.join("");
|
|
1022
|
+
}
|
|
1023
|
+
/**
|
|
1024
|
+
* 压缩源码:保留签名和关键逻辑,剔除注释、空行、样式块
|
|
1025
|
+
* 目标:在保留准确性的前提下减少 token 消耗
|
|
1026
|
+
* @param code 源码内容
|
|
1027
|
+
* @param maxLength 最大长度
|
|
1028
|
+
* @param importPathRewrites import 路径重写映射(原路径→正确路径)
|
|
1029
|
+
*/
|
|
1030
|
+
compressSourceCode(code, maxLength = 3e3, importPathRewrites) {
|
|
1031
|
+
let compressed = code;
|
|
1032
|
+
if (importPathRewrites && importPathRewrites.size > 0) {
|
|
1033
|
+
compressed = this.rewriteImportPaths(compressed, importPathRewrites);
|
|
1034
|
+
}
|
|
1035
|
+
compressed = compressed.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "");
|
|
1036
|
+
compressed = compressed.replace(/<template[^>]*>([\s\S]*?)<\/template>/gi, (_match, content) => {
|
|
1037
|
+
return `<template>${content.replace(/\s*(?:class|style)\s*=\s*["'][^"']*["']/gi, "")}</template>`;
|
|
1038
|
+
});
|
|
1039
|
+
compressed = compressed.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
1040
|
+
compressed = compressed.replace(/(^|[^:])(\/\/.*$)/gm, "$1");
|
|
1041
|
+
compressed = compressed.replace(/\n\s*\n\s*\n/g, "\n\n");
|
|
1042
|
+
compressed = compressed.split("\n").map((line) => line.trimEnd()).join("\n").trim();
|
|
1043
|
+
if (compressed.length > maxLength) {
|
|
1044
|
+
const scriptMatch = compressed.match(/<script[^>]*>([\s\S]*?)<\/script>/i);
|
|
1045
|
+
const templateMatch = compressed.match(/<template[^>]*>([\s\S]*?)<\/template>/i);
|
|
1046
|
+
if (scriptMatch) {
|
|
1047
|
+
let scriptPart = scriptMatch[1].trim();
|
|
1048
|
+
scriptPart = compressFunctionBodies(scriptPart, maxLength * 0.7);
|
|
1049
|
+
const templatePart = templateMatch ? `<template>${templateMatch[1].replace(/\s+/g, " ").trim().slice(0, 300)}...</template>` : "";
|
|
1050
|
+
compressed = `${templatePart}
|
|
1051
|
+
<script${scriptMatch[0].match(/<script[^>]*>/)?.[0]?.slice(7) || ">"}>${scriptPart}</script>`;
|
|
1052
|
+
} else {
|
|
1053
|
+
compressed = compressFunctionBodies(compressed, maxLength);
|
|
1054
|
+
}
|
|
998
1055
|
}
|
|
999
|
-
return
|
|
1056
|
+
return compressed;
|
|
1000
1057
|
}
|
|
1001
1058
|
buildGenerateTestSystemPrompt(req) {
|
|
1002
1059
|
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
|
|
1060
|
+
unit: "\u5355\u5143\u6D4B\u8BD5(Vitest)",
|
|
1061
|
+
component: "\u7EC4\u4EF6\u6D4B\u8BD5(Vitest+@vue/test-utils)",
|
|
1062
|
+
e2e: "E2E\u6D4B\u8BD5(Playwright)",
|
|
1063
|
+
api: "API\u6D4B\u8BD5(Vitest+fetch)",
|
|
1064
|
+
visual: "\u89C6\u89C9\u56DE\u5F52\u6D4B\u8BD5(Playwright)",
|
|
1065
|
+
performance: "\u6027\u80FD\u6D4B\u8BD5(Playwright)"
|
|
1009
1066
|
};
|
|
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`;
|
|
1067
|
+
return `\u4F60\u662F\u524D\u7AEF\u6D4B\u8BD5\u5DE5\u7A0B\u5E08\uFF0C\u7F16\u5199${typeMap[req.type] || req.type}\u3002
|
|
1068
|
+
\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
1069
|
}
|
|
1019
1070
|
buildGenerateTestUserPrompt(req) {
|
|
1020
1071
|
const importPath = this.computeTestImportPath(req.type, req.target);
|
|
1021
|
-
let prompt = `\
|
|
1022
|
-
\
|
|
1023
|
-
\u6D4B\u8BD5\u6587\u4EF6\u5C06\u653E\u5728: ${this.getTestOutputDir(req.type)}/
|
|
1024
|
-
\u6B63\u786E\u7684 import \u8DEF\u5F84: ${importPath}
|
|
1025
|
-
|
|
1026
|
-
\u91CD\u8981\uFF1Aimport \u8BED\u53E5\u4E2D\u5FC5\u987B\u4F7F\u7528\u4E0A\u8FF0\u6B63\u786E\u7684\u76F8\u5BF9\u8DEF\u5F84 ${importPath}\uFF0C\u4E0D\u8981\u4F7F\u7528 ${req.target} \u6216\u5176\u4ED6\u8DEF\u5F84\uFF01
|
|
1072
|
+
let prompt = `\u4E3A${req.target}\u751F\u6210${req.type}\u6D4B\u8BD5\u3002
|
|
1073
|
+
\u3010\u5F3A\u5236\u3011import\u88AB\u6D4B\u6A21\u5757\u5FC5\u987B\u7528: ${importPath}
|
|
1027
1074
|
`;
|
|
1028
1075
|
if (req.analysis) {
|
|
1029
|
-
|
|
1076
|
+
const parts = [];
|
|
1030
1077
|
if (req.analysis.exports?.length > 0) {
|
|
1031
|
-
|
|
1032
|
-
${
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
return ` - ${asyncFlag}${e.name}${params} [${e.kind}]`;
|
|
1036
|
-
}).join("\n")}
|
|
1037
|
-
`;
|
|
1078
|
+
parts.push(`\u5BFC\u51FA:${req.analysis.exports.map((e) => {
|
|
1079
|
+
const p = e.params?.length ? `(${e.params.join(",")})` : "";
|
|
1080
|
+
return `${e.isAsync ? "async " : ""}${e.name}${p}[${e.kind}]`;
|
|
1081
|
+
}).join(",")}`);
|
|
1038
1082
|
}
|
|
1039
1083
|
if (req.analysis.props?.length) {
|
|
1040
|
-
|
|
1041
|
-
${req.analysis.props.map(
|
|
1042
|
-
(p) => ` - ${p.name}: ${p.type}${p.required ? " (\u5FC5\u586B)" : " (\u53EF\u9009)"}`
|
|
1043
|
-
).join("\n")}
|
|
1044
|
-
`;
|
|
1084
|
+
parts.push(`Props:${req.analysis.props.map((p) => `${p.name}:${p.type}${p.required ? "!" : "?"}`).join(",")}`);
|
|
1045
1085
|
}
|
|
1046
1086
|
if (req.analysis.emits?.length) {
|
|
1047
|
-
|
|
1048
|
-
${req.analysis.emits.map(
|
|
1049
|
-
(e) => ` - ${e.name}${e.params?.length ? `(${e.params.join(", ")})` : ""}`
|
|
1050
|
-
).join("\n")}
|
|
1051
|
-
`;
|
|
1087
|
+
parts.push(`Emits:${req.analysis.emits.map((e) => `${e.name}(${e.params?.join(",") || ""})`).join(",")}`);
|
|
1052
1088
|
}
|
|
1053
1089
|
if (req.analysis.methods?.length) {
|
|
1054
|
-
|
|
1055
|
-
`;
|
|
1090
|
+
parts.push(`Methods:${req.analysis.methods.join(",")}`);
|
|
1056
1091
|
}
|
|
1057
1092
|
if (req.analysis.computed?.length) {
|
|
1058
|
-
|
|
1059
|
-
|
|
1093
|
+
parts.push(`Computed:${req.analysis.computed.join(",")}`);
|
|
1094
|
+
}
|
|
1095
|
+
if (req.analysis.importSignatures?.length) {
|
|
1096
|
+
parts.push(`\u4F9D\u8D56\u7B7E\u540D:${req.analysis.importSignatures.map(
|
|
1097
|
+
(imp) => `${imp.source}{${Object.entries(imp.signatures).map(([k, v]) => `${k}:${v}`).join(",")}}`
|
|
1098
|
+
).join(";")}`);
|
|
1060
1099
|
}
|
|
1100
|
+
prompt += parts.join("\n") + "\n";
|
|
1061
1101
|
}
|
|
1062
1102
|
if (req.context) {
|
|
1103
|
+
const importPathRewrites = this.buildImportPathRewrites(req.type, req.target);
|
|
1063
1104
|
prompt += `
|
|
1064
|
-
\u6E90\u7801
|
|
1065
|
-
\`\`\`
|
|
1066
|
-
${req.context}
|
|
1105
|
+
\u6E90\u7801:
|
|
1106
|
+
\`\`\`
|
|
1107
|
+
${this.compressSourceCode(req.context, 3e3, importPathRewrites)}
|
|
1067
1108
|
\`\`\`
|
|
1068
1109
|
`;
|
|
1069
1110
|
}
|
|
1070
1111
|
if (req.framework) {
|
|
1071
|
-
prompt +=
|
|
1072
|
-
\u6846\u67B6: ${req.framework}`;
|
|
1112
|
+
prompt += `\u6846\u67B6:${req.framework}`;
|
|
1073
1113
|
}
|
|
1074
1114
|
return prompt;
|
|
1075
1115
|
}
|
|
@@ -1100,18 +1140,54 @@ ${req.context}
|
|
|
1100
1140
|
return `${prefix}${cleanPath}`;
|
|
1101
1141
|
}
|
|
1102
1142
|
/**
|
|
1103
|
-
*
|
|
1143
|
+
* 构建 import 路径重写映射
|
|
1144
|
+
* 将源码中可能引用自身的各种写法,映射到测试文件中正确的导入路径
|
|
1104
1145
|
*/
|
|
1105
|
-
|
|
1106
|
-
const
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1146
|
+
buildImportPathRewrites(testType, targetPath) {
|
|
1147
|
+
const rewrites = /* @__PURE__ */ new Map();
|
|
1148
|
+
if (!targetPath) return rewrites;
|
|
1149
|
+
const correctImportPath = this.computeTestImportPath(testType, targetPath);
|
|
1150
|
+
const variations = /* @__PURE__ */ new Set();
|
|
1151
|
+
variations.add(targetPath);
|
|
1152
|
+
variations.add(targetPath.replace(/^\.\//, ""));
|
|
1153
|
+
const withoutExt = targetPath.replace(/\.(ts|js|tsx|jsx)$/, "");
|
|
1154
|
+
variations.add(withoutExt);
|
|
1155
|
+
const srcDirMatch = targetPath.match(/^(?:\.\/)?(src\/.+)$/);
|
|
1156
|
+
if (srcDirMatch) {
|
|
1157
|
+
variations.add(`@/${srcDirMatch[1]}`);
|
|
1158
|
+
variations.add(`@/${srcDirMatch[1].replace(/\.(ts|js|tsx|jsx)$/, "")}`);
|
|
1159
|
+
}
|
|
1160
|
+
const pathParts = targetPath.replace(/^\.\//, "").split("/");
|
|
1161
|
+
const fileName = pathParts[pathParts.length - 1];
|
|
1162
|
+
const fileNameNoExt = fileName.replace(/\.(ts|js|tsx|jsx|vue)$/, "");
|
|
1163
|
+
variations.add(`./${fileName}`);
|
|
1164
|
+
variations.add(`./${fileNameNoExt}`);
|
|
1165
|
+
for (let i = 1; i < pathParts.length; i++) {
|
|
1166
|
+
const relPath = "../".repeat(i) + pathParts.slice(i).join("/");
|
|
1167
|
+
variations.add(relPath);
|
|
1168
|
+
variations.add(relPath.replace(/\.(ts|js|tsx|jsx)$/, ""));
|
|
1169
|
+
}
|
|
1170
|
+
for (const variant of variations) {
|
|
1171
|
+
if (variant && variant !== correctImportPath) {
|
|
1172
|
+
rewrites.set(variant, correctImportPath);
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
return rewrites;
|
|
1176
|
+
}
|
|
1177
|
+
/**
|
|
1178
|
+
* 重写源码中的 import 路径
|
|
1179
|
+
* 将 from 'oldPath' / from "oldPath" 替换为正确的路径
|
|
1180
|
+
*/
|
|
1181
|
+
rewriteImportPaths(code, rewrites) {
|
|
1182
|
+
let result = code;
|
|
1183
|
+
for (const [oldPath, newPath] of rewrites) {
|
|
1184
|
+
const escaped = oldPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1185
|
+
const singleQuoteRegex = new RegExp(`(from\\s+')${escaped}(')`, "g");
|
|
1186
|
+
const doubleQuoteRegex = new RegExp(`(from\\s+")${escaped}(")`, "g");
|
|
1187
|
+
result = result.replace(singleQuoteRegex, `$1${newPath}$2`);
|
|
1188
|
+
result = result.replace(doubleQuoteRegex, `$1${newPath}$2`);
|
|
1189
|
+
}
|
|
1190
|
+
return result;
|
|
1115
1191
|
}
|
|
1116
1192
|
parseGenerateTestResponse(content) {
|
|
1117
1193
|
const codeBlockMatch = content.match(/```(?:typescript|ts|javascript|js)?\s*\n([\s\S]*?)```/);
|
|
@@ -1124,69 +1200,56 @@ ${req.context}
|
|
|
1124
1200
|
};
|
|
1125
1201
|
}
|
|
1126
1202
|
buildReviewTestSystemPrompt(_req) {
|
|
1127
|
-
return `\u4F60\u662F\
|
|
1128
|
-
|
|
1129
|
-
\
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
\u8F93\u51FA\u683C\u5F0F\uFF08\u4E25\u683C\u9075\u5B88\uFF09\uFF1A
|
|
1137
|
-
APPROVED: true \u6216 false
|
|
1138
|
-
SCORE: 0.0 \u5230 1.0 \u4E4B\u95F4\u7684\u8BC4\u5206
|
|
1139
|
-
FEEDBACK: \u4E00\u53E5\u8BDD\u5BA1\u8BA1\u610F\u89C1
|
|
1140
|
-
ISSUES: \u95EE\u9898\u5217\u8868\uFF08\u6BCF\u884C\u4E00\u4E2A\uFF0C\u683C\u5F0F "- \u95EE\u9898\u63CF\u8FF0"\uFF09
|
|
1141
|
-
SUGGESTIONS: \u6539\u8FDB\u5EFA\u8BAE\u5217\u8868\uFF08\u6BCF\u884C\u4E00\u4E2A\uFF0C\u683C\u5F0F "- \u5EFA\u8BAE\u63CF\u8FF0"\uFF09`;
|
|
1203
|
+
return `\u4F60\u662F\u6D4B\u8BD5\u5BA1\u8BA1\u4E13\u5BB6\u3002\u5BA1\u67E5\u6D4B\u8BD5\u4EE3\u7801\u662F\u5426\u51C6\u786E\u3002
|
|
1204
|
+
\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
|
|
1205
|
+
\u8F93\u51FA:
|
|
1206
|
+
APPROVED:true\u6216false
|
|
1207
|
+
SCORE:0.0-1.0
|
|
1208
|
+
FEEDBACK:\u4E00\u53E5\u8BDD
|
|
1209
|
+
ISSUES:- \u95EE\u9898(\u6BCF\u884C\u4E00\u4E2A)
|
|
1210
|
+
SUGGESTIONS:- \u5EFA\u8BAE(\u6BCF\u884C\u4E00\u4E2A)`;
|
|
1142
1211
|
}
|
|
1143
1212
|
buildReviewTestUserPrompt(req) {
|
|
1144
1213
|
const importPath = this.computeTestImportPath(req.testType, req.target);
|
|
1145
|
-
let prompt = `\
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1214
|
+
let prompt = `\u5BA1\u67E5\u6D4B\u8BD5\u4EE3\u7801\u3002\u88AB\u6D4B:${req.target} \u7C7B\u578B:${req.testType}
|
|
1215
|
+
\u3010\u5F3A\u5236\u3011import\u88AB\u6D4B\u6A21\u5757\u5FC5\u987B\u7528: ${importPath}
|
|
1216
|
+
`;
|
|
1217
|
+
const importPathRewrites = this.buildImportPathRewrites(req.testType, req.target);
|
|
1218
|
+
prompt += `
|
|
1219
|
+
\u6E90\u7801:
|
|
1220
|
+
\`\`\`
|
|
1221
|
+
${this.compressSourceCode(req.sourceCode, 2e3, importPathRewrites)}
|
|
1222
|
+
\`\`\`
|
|
1223
|
+
`;
|
|
1224
|
+
prompt += `
|
|
1225
|
+
\u6D4B\u8BD5\u4EE3\u7801:
|
|
1154
1226
|
\`\`\`
|
|
1155
|
-
|
|
1156
|
-
--- \u751F\u6210\u7684\u6D4B\u8BD5\u4EE3\u7801 ---
|
|
1157
|
-
\`\`\`typescript
|
|
1158
1227
|
${req.testCode}
|
|
1159
1228
|
\`\`\``;
|
|
1160
1229
|
if (req.analysis) {
|
|
1161
|
-
|
|
1230
|
+
const parts = [];
|
|
1162
1231
|
if (req.analysis.exports?.length > 0) {
|
|
1163
|
-
|
|
1164
|
-
\u5BFC\u51FA\u9879:
|
|
1165
|
-
${req.analysis.exports.map((e) => {
|
|
1166
|
-
const params = e.params?.length ? `(${e.params.join(", ")})` : "";
|
|
1167
|
-
const asyncFlag = e.isAsync ? "async " : "";
|
|
1168
|
-
return ` - ${asyncFlag}${e.name}${params} [${e.kind}]`;
|
|
1169
|
-
}).join("\n")}`;
|
|
1232
|
+
parts.push(`\u5BFC\u51FA:${req.analysis.exports.map((e) => `${e.isAsync ? "async " : ""}${e.name}(${e.params?.join(",") || ""})[${e.kind}]`).join(",")}`);
|
|
1170
1233
|
}
|
|
1171
1234
|
if (req.analysis.props?.length) {
|
|
1172
|
-
|
|
1173
|
-
Props:
|
|
1174
|
-
${req.analysis.props.map(
|
|
1175
|
-
(p) => ` - ${p.name}: ${p.type}${p.required ? " (\u5FC5\u586B)" : " (\u53EF\u9009)"}`
|
|
1176
|
-
).join("\n")}`;
|
|
1235
|
+
parts.push(`Props:${req.analysis.props.map((p) => `${p.name}:${p.type}${p.required ? "!" : "?"}`).join(",")}`);
|
|
1177
1236
|
}
|
|
1178
1237
|
if (req.analysis.emits?.length) {
|
|
1238
|
+
parts.push(`Emits:${req.analysis.emits.map((e) => `${e.name}(${e.params?.join(",") || ""})`).join(",")}`);
|
|
1239
|
+
}
|
|
1240
|
+
if (req.analysis.importSignatures?.length) {
|
|
1241
|
+
parts.push(`\u4F9D\u8D56:${req.analysis.importSignatures.map(
|
|
1242
|
+
(imp) => `${imp.source}{${Object.entries(imp.signatures).map(([k, v]) => `${k}:${v}`).join(",")}}`
|
|
1243
|
+
).join(";")}`);
|
|
1244
|
+
}
|
|
1245
|
+
if (parts.length > 0) {
|
|
1179
1246
|
prompt += `
|
|
1180
|
-
|
|
1181
|
-
${req.analysis.emits.map(
|
|
1182
|
-
(e) => ` - ${e.name}${e.params?.length ? `(${e.params.join(", ")})` : ""}`
|
|
1183
|
-
).join("\n")}`;
|
|
1247
|
+
\u5206\u6790:${parts.join("|")}`;
|
|
1184
1248
|
}
|
|
1185
1249
|
}
|
|
1186
1250
|
if (req.generationDescription) {
|
|
1187
1251
|
prompt += `
|
|
1188
|
-
|
|
1189
|
-
\u751F\u6210\u8005\u8BF4\u660E: ${req.generationDescription}`;
|
|
1252
|
+
\u8BF4\u660E:${req.generationDescription}`;
|
|
1190
1253
|
}
|
|
1191
1254
|
return prompt;
|
|
1192
1255
|
}
|
|
@@ -1246,6 +1309,61 @@ ${req.analysis.emits.map(
|
|
|
1246
1309
|
return urlMap[provider] || "https://api.openai.com/v1";
|
|
1247
1310
|
}
|
|
1248
1311
|
};
|
|
1312
|
+
function compressFunctionBodies(code, maxLength) {
|
|
1313
|
+
const lines = code.split("\n");
|
|
1314
|
+
const result = [];
|
|
1315
|
+
let depth = 0;
|
|
1316
|
+
let fnDepth = 0;
|
|
1317
|
+
let inFunction = false;
|
|
1318
|
+
let braceBalance = 0;
|
|
1319
|
+
let capturedLines = 0;
|
|
1320
|
+
for (const line of lines) {
|
|
1321
|
+
const trimmed = line.trim();
|
|
1322
|
+
for (const ch of trimmed) {
|
|
1323
|
+
if (ch === "{") {
|
|
1324
|
+
braceBalance++;
|
|
1325
|
+
depth++;
|
|
1326
|
+
}
|
|
1327
|
+
if (ch === "}") {
|
|
1328
|
+
braceBalance--;
|
|
1329
|
+
depth = Math.max(0, depth - 1);
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
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);
|
|
1333
|
+
if (isFnSignature && !inFunction) {
|
|
1334
|
+
inFunction = true;
|
|
1335
|
+
fnDepth = depth;
|
|
1336
|
+
result.push(line);
|
|
1337
|
+
capturedLines++;
|
|
1338
|
+
continue;
|
|
1339
|
+
}
|
|
1340
|
+
if (inFunction) {
|
|
1341
|
+
const isReturn = trimmed.startsWith("return ");
|
|
1342
|
+
const isBranch = /^(if|else|switch|case|try|catch|finally|for|while)/.test(trimmed);
|
|
1343
|
+
const isClosing = trimmed === "}" && depth <= fnDepth - 1;
|
|
1344
|
+
if (isClosing) {
|
|
1345
|
+
result.push(line);
|
|
1346
|
+
inFunction = false;
|
|
1347
|
+
capturedLines++;
|
|
1348
|
+
} else if (isReturn || isBranch) {
|
|
1349
|
+
result.push(trimmed.length > 120 ? `${trimmed.slice(0, 120)}...` : line);
|
|
1350
|
+
capturedLines++;
|
|
1351
|
+
} else if (trimmed.startsWith("//") || trimmed === "") {
|
|
1352
|
+
} else if (capturedLines < maxLength / 30) {
|
|
1353
|
+
result.push(trimmed.length > 100 ? `${trimmed.slice(0, 100)}...` : line);
|
|
1354
|
+
capturedLines++;
|
|
1355
|
+
}
|
|
1356
|
+
} else {
|
|
1357
|
+
result.push(line);
|
|
1358
|
+
capturedLines++;
|
|
1359
|
+
}
|
|
1360
|
+
if (result.join("\n").length > maxLength) {
|
|
1361
|
+
result.push("// ... (truncated)");
|
|
1362
|
+
break;
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
return result.join("\n");
|
|
1366
|
+
}
|
|
1249
1367
|
|
|
1250
1368
|
// src/ai/provider.ts
|
|
1251
1369
|
var providerRegistry = /* @__PURE__ */ new Map();
|
|
@@ -1315,15 +1433,18 @@ import path4 from "path";
|
|
|
1315
1433
|
function analyzeFile(filePath) {
|
|
1316
1434
|
const absolutePath = path4.resolve(process.cwd(), filePath);
|
|
1317
1435
|
if (!fs4.existsSync(absolutePath)) {
|
|
1318
|
-
return { filePath, exports: [], apiCalls: [] };
|
|
1436
|
+
return { filePath, exports: [], imports: [], importSignatures: [], apiCalls: [] };
|
|
1319
1437
|
}
|
|
1320
1438
|
const content = fs4.readFileSync(absolutePath, "utf-8");
|
|
1321
1439
|
const ext = path4.extname(filePath);
|
|
1322
1440
|
const result = {
|
|
1323
1441
|
filePath,
|
|
1324
1442
|
exports: extractExports(content, ext),
|
|
1443
|
+
imports: extractImports(content),
|
|
1444
|
+
importSignatures: [],
|
|
1325
1445
|
apiCalls: extractAPICalls(content, filePath)
|
|
1326
1446
|
};
|
|
1447
|
+
result.importSignatures = analyzeImportSignatures(result.imports, path4.dirname(absolutePath));
|
|
1327
1448
|
if (ext === ".vue") {
|
|
1328
1449
|
result.vueAnalysis = analyzeVueComponent(content, path4.basename(filePath, ".vue"));
|
|
1329
1450
|
const scriptMatch = content.match(/<script[^>]*>([\s\S]*?)<\/script>/);
|
|
@@ -1743,6 +1864,90 @@ function generateMockRoutesFromAPICalls(apiCalls) {
|
|
|
1743
1864
|
}
|
|
1744
1865
|
return routes;
|
|
1745
1866
|
}
|
|
1867
|
+
function extractImports(content) {
|
|
1868
|
+
const imports = [];
|
|
1869
|
+
const namedImportRegex = /import\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/g;
|
|
1870
|
+
let match;
|
|
1871
|
+
while ((match = namedImportRegex.exec(content)) !== null) {
|
|
1872
|
+
const names = match[1].split(",").map((n) => {
|
|
1873
|
+
const parts = n.trim().split(/\s+as\s+/);
|
|
1874
|
+
return parts[0].trim();
|
|
1875
|
+
}).filter(Boolean);
|
|
1876
|
+
imports.push({ names, source: match[2], isDefault: false, isNamespace: false });
|
|
1877
|
+
}
|
|
1878
|
+
const defaultImportRegex = /import\s+(\w+)\s+from\s*['"]([^'"]+)['"]/g;
|
|
1879
|
+
while ((match = defaultImportRegex.exec(content)) !== null) {
|
|
1880
|
+
const fullLine = content.slice(Math.max(0, match.index - 5), match.index + match[0].length);
|
|
1881
|
+
if (fullLine.includes("{")) continue;
|
|
1882
|
+
imports.push({ names: [match[1]], source: match[2], isDefault: true, isNamespace: false });
|
|
1883
|
+
}
|
|
1884
|
+
const namespaceImportRegex = /import\s*\*\s*as\s+(\w+)\s*from\s*['"]([^'"]+)['"]/g;
|
|
1885
|
+
while ((match = namespaceImportRegex.exec(content)) !== null) {
|
|
1886
|
+
imports.push({ names: [match[1]], source: match[2], isDefault: false, isNamespace: true });
|
|
1887
|
+
}
|
|
1888
|
+
return imports;
|
|
1889
|
+
}
|
|
1890
|
+
function analyzeImportSignatures(imports, baseDir) {
|
|
1891
|
+
const signatures = [];
|
|
1892
|
+
for (const imp of imports) {
|
|
1893
|
+
if (!imp.source.startsWith(".") && !imp.source.startsWith("@/") && !imp.source.startsWith("~")) {
|
|
1894
|
+
continue;
|
|
1895
|
+
}
|
|
1896
|
+
const resolvedPath = resolveImportPath(imp.source, baseDir);
|
|
1897
|
+
if (!resolvedPath || !fs4.existsSync(resolvedPath)) continue;
|
|
1898
|
+
try {
|
|
1899
|
+
const content = fs4.readFileSync(resolvedPath, "utf-8");
|
|
1900
|
+
const ext = path4.extname(resolvedPath);
|
|
1901
|
+
const exports = extractExports(content, ext);
|
|
1902
|
+
const sigMap = {};
|
|
1903
|
+
for (const name of imp.names) {
|
|
1904
|
+
const exp = exports.find((e) => e.name === name);
|
|
1905
|
+
if (exp) {
|
|
1906
|
+
const params = exp.params.length > 0 ? `(${exp.params.join(", ")})` : "";
|
|
1907
|
+
const ret = exp.returnType ? `: ${exp.returnType}` : "";
|
|
1908
|
+
const async = exp.isAsync ? "async " : "";
|
|
1909
|
+
sigMap[name] = `${async}${exp.kind}${params}${ret}`;
|
|
1910
|
+
} else if (imp.isDefault) {
|
|
1911
|
+
const defaultExp = exports.find((e) => e.kind === "default");
|
|
1912
|
+
if (defaultExp) {
|
|
1913
|
+
sigMap[name] = `default[${defaultExp.name || "anonymous"}]`;
|
|
1914
|
+
}
|
|
1915
|
+
} else if (imp.isNamespace) {
|
|
1916
|
+
sigMap[name] = `{${exports.map((e) => e.name).join(", ")}}`;
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
if (ext === ".vue") {
|
|
1920
|
+
const vueAnalysis = analyzeVueComponent(content, path4.basename(resolvedPath, ".vue"));
|
|
1921
|
+
if (vueAnalysis.props.length > 0) {
|
|
1922
|
+
sigMap["__props__"] = vueAnalysis.props.map((p) => `${p.name}:${p.type}${p.required ? "!" : "?"}`).join(",");
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
if (Object.keys(sigMap).length > 0) {
|
|
1926
|
+
signatures.push({ source: imp.source, signatures: sigMap });
|
|
1927
|
+
}
|
|
1928
|
+
} catch {
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
return signatures;
|
|
1932
|
+
}
|
|
1933
|
+
function resolveImportPath(importSource, baseDir) {
|
|
1934
|
+
let resolved;
|
|
1935
|
+
if (importSource.startsWith("@/") || importSource.startsWith("~")) {
|
|
1936
|
+
const relativePath = importSource.replace(/^[@~]\//, "src/");
|
|
1937
|
+
resolved = path4.resolve(process.cwd(), relativePath);
|
|
1938
|
+
} else if (importSource.startsWith(".")) {
|
|
1939
|
+
resolved = path4.resolve(baseDir, importSource);
|
|
1940
|
+
} else {
|
|
1941
|
+
return null;
|
|
1942
|
+
}
|
|
1943
|
+
const extensions = [".ts", ".tsx", ".js", ".jsx", ".vue", "/index.ts", "/index.js"];
|
|
1944
|
+
if (fs4.existsSync(resolved)) return resolved;
|
|
1945
|
+
for (const ext of extensions) {
|
|
1946
|
+
const withExt = resolved + ext;
|
|
1947
|
+
if (fs4.existsSync(withExt)) return withExt;
|
|
1948
|
+
}
|
|
1949
|
+
return null;
|
|
1950
|
+
}
|
|
1746
1951
|
|
|
1747
1952
|
// src/services/global-config.ts
|
|
1748
1953
|
import fs5 from "fs";
|
|
@@ -1794,7 +1999,7 @@ function registerInitCommand(program2) {
|
|
|
1794
1999
|
try {
|
|
1795
2000
|
await executeInit(options);
|
|
1796
2001
|
} catch (error) {
|
|
1797
|
-
console.error(
|
|
2002
|
+
console.error(chalk3.red(`
|
|
1798
2003
|
\u2717 ${error instanceof Error ? error.message : String(error)}
|
|
1799
2004
|
`));
|
|
1800
2005
|
process.exit(1);
|
|
@@ -1816,19 +2021,19 @@ async function executeInit(options) {
|
|
|
1816
2021
|
}
|
|
1817
2022
|
]);
|
|
1818
2023
|
if (!proceed) {
|
|
1819
|
-
console.log(
|
|
2024
|
+
console.log(chalk3.gray("\n \u5DF2\u53D6\u6D88\u521D\u59CB\u5316\n"));
|
|
1820
2025
|
return;
|
|
1821
2026
|
}
|
|
1822
2027
|
}
|
|
1823
2028
|
let globalAI = loadGlobalAIConfig();
|
|
1824
2029
|
if (!globalAI) {
|
|
1825
|
-
console.log(
|
|
2030
|
+
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"));
|
|
1826
2031
|
globalAI = await promptAIConfig();
|
|
1827
2032
|
saveGlobalAIConfig(globalAI);
|
|
1828
|
-
console.log(
|
|
2033
|
+
console.log(chalk3.gray(` \u914D\u7F6E\u5DF2\u4FDD\u5B58\u81F3 ${getAIConfigPath()}
|
|
1829
2034
|
`));
|
|
1830
2035
|
} else {
|
|
1831
|
-
console.log(
|
|
2036
|
+
console.log(chalk3.green(` \u2713 \u5F53\u524D AI \u6A21\u578B: ${chalk3.white(globalAI.model)} @ ${chalk3.gray(globalAI.baseUrl)} (${maskApiKey(globalAI.apiKey)})
|
|
1832
2037
|
`));
|
|
1833
2038
|
}
|
|
1834
2039
|
const aiConfig = toAIConfig(globalAI);
|
|
@@ -1837,51 +2042,23 @@ async function executeInit(options) {
|
|
|
1837
2042
|
try {
|
|
1838
2043
|
const result = await testAIConnection(aiConfig);
|
|
1839
2044
|
if (result.ok) {
|
|
1840
|
-
testSpinner.succeed(`AI \u8FDE\u901A\u6B63\u5E38 ${
|
|
2045
|
+
testSpinner.succeed(`AI \u8FDE\u901A\u6B63\u5E38 ${chalk3.gray(`${globalAI.model} (${result.latencyMs}ms)`)}`);
|
|
1841
2046
|
} else {
|
|
1842
2047
|
testSpinner.fail(`AI \u8FDE\u901A\u5F02\u5E38: ${result.message}`);
|
|
1843
|
-
console.log(
|
|
2048
|
+
console.log(chalk3.yellow(" \u53EF\u8FD0\u884C qat change \u4FEE\u6539 AI \u914D\u7F6E\u3002"));
|
|
1844
2049
|
}
|
|
1845
2050
|
} catch (error) {
|
|
1846
2051
|
testSpinner.fail(`AI \u8FDE\u901A\u6D4B\u8BD5\u5931\u8D25: ${error instanceof Error ? error.message : String(error)}`);
|
|
1847
2052
|
}
|
|
1848
2053
|
}
|
|
1849
2054
|
const config = buildProjectConfig(projectInfo);
|
|
1850
|
-
let configPath;
|
|
1851
2055
|
const existingConfigPath = path6.join(process.cwd(), "qat.config.js");
|
|
1852
2056
|
const existingTsPath = path6.join(process.cwd(), "qat.config.ts");
|
|
1853
2057
|
const configExists = fs6.existsSync(existingConfigPath) || fs6.existsSync(existingTsPath);
|
|
1854
|
-
if (configExists
|
|
1855
|
-
|
|
1856
|
-
{
|
|
1857
|
-
type: "confirm",
|
|
1858
|
-
name: "overwrite",
|
|
1859
|
-
message: "\u914D\u7F6E\u6587\u4EF6 qat.config.js \u5DF2\u5B58\u5728\uFF0C\u662F\u5426\u8986\u76D6\uFF1F",
|
|
1860
|
-
default: true
|
|
1861
|
-
}
|
|
1862
|
-
]);
|
|
1863
|
-
if (!overwrite) {
|
|
1864
|
-
console.log(chalk2.gray(" \u4FDD\u7559\u73B0\u6709\u914D\u7F6E\u6587\u4EF6\uFF0C\u7EE7\u7EED\u540E\u7EED\u6B65\u9AA4..."));
|
|
1865
|
-
configPath = existingConfigPath;
|
|
1866
|
-
} else {
|
|
1867
|
-
const fileSpinner = ora("\u6B63\u5728\u8986\u76D6\u914D\u7F6E\u6587\u4EF6...").start();
|
|
1868
|
-
try {
|
|
1869
|
-
configPath = await writeConfigFile(process.cwd(), config, true);
|
|
1870
|
-
fileSpinner.succeed("\u914D\u7F6E\u6587\u4EF6\u5DF2\u8986\u76D6");
|
|
1871
|
-
} catch (error) {
|
|
1872
|
-
fileSpinner.fail("\u914D\u7F6E\u6587\u4EF6\u8986\u76D6\u5931\u8D25");
|
|
1873
|
-
throw error;
|
|
1874
|
-
}
|
|
1875
|
-
}
|
|
2058
|
+
if (!configExists) {
|
|
2059
|
+
console.log(chalk3.gray(" \u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6\uFF0C\u4F7F\u7528\u9ED8\u8BA4\u914D\u7F6E\uFF08\u9700\u8981\u81EA\u5B9A\u4E49\u65F6\u53EF\u521B\u5EFA qat.config.js\uFF09"));
|
|
1876
2060
|
} else {
|
|
1877
|
-
|
|
1878
|
-
try {
|
|
1879
|
-
configPath = await writeConfigFile(process.cwd(), config, options.force);
|
|
1880
|
-
fileSpinner.succeed("\u914D\u7F6E\u6587\u4EF6\u5DF2\u751F\u6210");
|
|
1881
|
-
} catch (error) {
|
|
1882
|
-
fileSpinner.fail("\u914D\u7F6E\u6587\u4EF6\u751F\u6210\u5931\u8D25");
|
|
1883
|
-
throw error;
|
|
1884
|
-
}
|
|
2061
|
+
console.log(chalk3.green(` \u2713 \u5DF2\u6709\u914D\u7F6E\u6587\u4EF6: ${path6.basename(fs6.existsSync(existingConfigPath) ? existingConfigPath : existingTsPath)}`));
|
|
1885
2062
|
}
|
|
1886
2063
|
const dirSpinner = ora("\u6B63\u5728\u521B\u5EFA\u6D4B\u8BD5\u76EE\u5F55...").start();
|
|
1887
2064
|
const createdDirs = createTestDirectories(config);
|
|
@@ -1896,10 +2073,10 @@ async function executeInit(options) {
|
|
|
1896
2073
|
const mockFilePath = path6.join(process.cwd(), mockDir, "auto-generated.json");
|
|
1897
2074
|
if (!fs6.existsSync(mockFilePath)) {
|
|
1898
2075
|
fs6.writeFileSync(mockFilePath, JSON.stringify(mockRoutes, null, 2), "utf-8");
|
|
1899
|
-
console.log(
|
|
2076
|
+
console.log(chalk3.green(` \u81EA\u52A8\u53D1\u73B0 ${apiCalls.length} \u4E2A API \u63A5\u53E3\uFF0C\u5DF2\u751F\u6210 Mock \u8DEF\u7531`));
|
|
1900
2077
|
}
|
|
1901
2078
|
} else {
|
|
1902
|
-
console.log(
|
|
2079
|
+
console.log(chalk3.gray(" \u672A\u53D1\u73B0 API \u8C03\u7528\uFF0C\u5DF2\u751F\u6210\u793A\u4F8B Mock \u8DEF\u7531"));
|
|
1903
2080
|
}
|
|
1904
2081
|
}
|
|
1905
2082
|
const srcDir = config.project?.srcDir || "src";
|
|
@@ -1907,10 +2084,10 @@ async function executeInit(options) {
|
|
|
1907
2084
|
const utilities = discoverUtilityFiles(process.cwd(), srcDir);
|
|
1908
2085
|
const totalFiles = components.length + utilities.length;
|
|
1909
2086
|
if (totalFiles > 0) {
|
|
1910
|
-
console.log(
|
|
2087
|
+
console.log(chalk3.cyan(`
|
|
1911
2088
|
\u53D1\u73B0 ${totalFiles} \u4E2A\u53EF\u6D4B\u8BD5\u6587\u4EF6 (${components.length} \u7EC4\u4EF6, ${utilities.length} \u5DE5\u5177/\u670D\u52A1)`));
|
|
1912
2089
|
}
|
|
1913
|
-
displayResult(
|
|
2090
|
+
displayResult(createdDirs, totalFiles, projectInfo);
|
|
1914
2091
|
}
|
|
1915
2092
|
async function promptAIConfig() {
|
|
1916
2093
|
const answers = await inquirer.prompt([
|
|
@@ -1994,8 +2171,8 @@ function buildProjectConfig(projectInfo) {
|
|
|
1994
2171
|
};
|
|
1995
2172
|
}
|
|
1996
2173
|
function displayProjectInfo(info) {
|
|
1997
|
-
console.log(
|
|
1998
|
-
console.log(
|
|
2174
|
+
console.log(chalk3.cyan("\n \u9879\u76EE\u68C0\u6D4B\u7ED3\u679C:"));
|
|
2175
|
+
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"));
|
|
1999
2176
|
const items = [
|
|
2000
2177
|
["\u9879\u76EE\u540D\u79F0", info.name],
|
|
2001
2178
|
["\u6846\u67B6", info.frameworkConfidence > 0.5 ? `${info.frameworkDisplayName} (\u7F6E\u4FE1\u5EA6 ${Math.round(info.frameworkConfidence * 100)}%)` : info.frameworkDisplayName],
|
|
@@ -2017,8 +2194,8 @@ function displayProjectInfo(info) {
|
|
|
2017
2194
|
items.push(["\u7EC4\u4EF6\u76EE\u5F55", info.componentDirs.join(", ")]);
|
|
2018
2195
|
}
|
|
2019
2196
|
for (const [label, value] of items) {
|
|
2020
|
-
const displayValue = value === true ?
|
|
2021
|
-
console.log(` ${
|
|
2197
|
+
const displayValue = value === true ? chalk3.green("\u2713") : value === false ? chalk3.red("\u2717") : String(value);
|
|
2198
|
+
console.log(` ${chalk3.white(label.padEnd(12))} ${displayValue}`);
|
|
2022
2199
|
}
|
|
2023
2200
|
console.log();
|
|
2024
2201
|
}
|
|
@@ -2047,40 +2224,39 @@ function createTestDirectories(config) {
|
|
|
2047
2224
|
}
|
|
2048
2225
|
return dirs;
|
|
2049
2226
|
}
|
|
2050
|
-
function displayResult(
|
|
2051
|
-
|
|
2052
|
-
console.log(chalk2.green("\n \u2713 \u9879\u76EE\u521D\u59CB\u5316\u5B8C\u6210!\n"));
|
|
2053
|
-
console.log(chalk2.white(" \u5DF2\u751F\u6210\u914D\u7F6E:"));
|
|
2054
|
-
console.log(chalk2.gray(` ${relativeConfig}`));
|
|
2055
|
-
console.log();
|
|
2227
|
+
function displayResult(createdDirs, totalTestableFiles, projectInfo) {
|
|
2228
|
+
console.log(chalk3.green("\n \u2713 \u9879\u76EE\u521D\u59CB\u5316\u5B8C\u6210!\n"));
|
|
2056
2229
|
if (createdDirs.length > 0) {
|
|
2057
|
-
console.log(
|
|
2230
|
+
console.log(chalk3.white(" \u5DF2\u521B\u5EFA\u76EE\u5F55:"));
|
|
2058
2231
|
for (const dir of createdDirs) {
|
|
2059
|
-
console.log(
|
|
2232
|
+
console.log(chalk3.gray(` ${dir}/`));
|
|
2060
2233
|
}
|
|
2061
2234
|
console.log();
|
|
2062
2235
|
}
|
|
2063
|
-
console.log(
|
|
2236
|
+
console.log(chalk3.white(" \u914D\u7F6E:"));
|
|
2237
|
+
console.log(chalk3.gray(" \u9ED8\u8BA4\u914D\u7F6E\u5DF2\u5185\u7F6E\uFF0C\u9700\u8981\u81EA\u5B9A\u4E49\u65F6\u521B\u5EFA qat.config.js"));
|
|
2238
|
+
console.log();
|
|
2239
|
+
console.log(chalk3.cyan(" \u4E0B\u4E00\u6B65:"));
|
|
2064
2240
|
if (totalTestableFiles > 0) {
|
|
2065
|
-
console.log(
|
|
2241
|
+
console.log(chalk3.gray(` 1. qat create \u9009\u62E9\u6587\u4EF6\u5E76\u751F\u6210\u6D4B\u8BD5\u7528\u4F8B (\u53D1\u73B0 ${totalTestableFiles} \u4E2A\u53EF\u6D4B\u8BD5\u6587\u4EF6)`));
|
|
2066
2242
|
} else {
|
|
2067
|
-
console.log(
|
|
2243
|
+
console.log(chalk3.gray(" 1. qat create \u521B\u5EFA\u6D4B\u8BD5\u7528\u4F8B"));
|
|
2068
2244
|
}
|
|
2069
|
-
console.log(
|
|
2070
|
-
console.log(
|
|
2245
|
+
console.log(chalk3.gray(" 2. qat run \u6267\u884C\u6D4B\u8BD5"));
|
|
2246
|
+
console.log(chalk3.gray(" 3. qat status \u67E5\u770B AI \u6A21\u578B\u72B6\u6001"));
|
|
2071
2247
|
if (projectInfo.testFrameworks.length === 0) {
|
|
2072
2248
|
console.log();
|
|
2073
|
-
console.log(
|
|
2249
|
+
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"));
|
|
2074
2250
|
}
|
|
2075
2251
|
console.log();
|
|
2076
2252
|
}
|
|
2077
2253
|
|
|
2078
2254
|
// src/commands/create.ts
|
|
2079
|
-
import
|
|
2255
|
+
import chalk5 from "chalk";
|
|
2080
2256
|
import inquirer2 from "inquirer";
|
|
2081
2257
|
import ora3 from "ora";
|
|
2082
2258
|
import fs8 from "fs";
|
|
2083
|
-
import
|
|
2259
|
+
import path9 from "path";
|
|
2084
2260
|
|
|
2085
2261
|
// src/services/template.ts
|
|
2086
2262
|
import fs7 from "fs";
|
|
@@ -2129,7 +2305,7 @@ Handlebars.registerHelper("propTestValue", (prop) => {
|
|
|
2129
2305
|
};
|
|
2130
2306
|
return map[prop.type] || "'test-value'";
|
|
2131
2307
|
});
|
|
2132
|
-
function
|
|
2308
|
+
function resolveImportPath2(testType, targetPath) {
|
|
2133
2309
|
if (!targetPath) return targetPath;
|
|
2134
2310
|
const testDirMap = {
|
|
2135
2311
|
unit: "tests/unit",
|
|
@@ -2155,7 +2331,7 @@ function resolveImportPath(testType, targetPath) {
|
|
|
2155
2331
|
function renderTemplate(type, context) {
|
|
2156
2332
|
const templateContent = loadTemplate(type);
|
|
2157
2333
|
const template = Handlebars.compile(templateContent);
|
|
2158
|
-
const resolvedTarget =
|
|
2334
|
+
const resolvedTarget = resolveImportPath2(type, context.target);
|
|
2159
2335
|
const fullContext = {
|
|
2160
2336
|
vueVersion: 3,
|
|
2161
2337
|
typescript: true,
|
|
@@ -2537,12 +2713,13 @@ function toPascalCase(str) {
|
|
|
2537
2713
|
}
|
|
2538
2714
|
|
|
2539
2715
|
// src/services/test-reviewer.ts
|
|
2540
|
-
import
|
|
2716
|
+
import chalk4 from "chalk";
|
|
2541
2717
|
import ora2 from "ora";
|
|
2718
|
+
import path8 from "path";
|
|
2542
2719
|
var MAX_RETRIES = 3;
|
|
2543
2720
|
var REVIEW_THRESHOLD = 0.6;
|
|
2544
2721
|
async function generateWithReview(params) {
|
|
2545
|
-
const { testType, targetPath, sourceCode, analysis, aiConfig, framework, onAttempt } = params;
|
|
2722
|
+
const { testType, targetPath, sourceCode, analysis, aiConfig, framework, onAttempt, onProgress } = params;
|
|
2546
2723
|
const generatorProvider = createAIProvider(aiConfig);
|
|
2547
2724
|
const reviewerProvider = createAIProvider(aiConfig);
|
|
2548
2725
|
if (!generatorProvider.capabilities.generateTest) {
|
|
@@ -2554,9 +2731,15 @@ async function generateWithReview(params) {
|
|
|
2554
2731
|
let lastReview = null;
|
|
2555
2732
|
let approved = false;
|
|
2556
2733
|
let attempts = 0;
|
|
2734
|
+
const targetName = path8.basename(targetPath);
|
|
2557
2735
|
for (let i = 0; i < MAX_RETRIES; i++) {
|
|
2558
2736
|
attempts = i + 1;
|
|
2559
2737
|
onAttempt?.(attempts, MAX_RETRIES);
|
|
2738
|
+
const genLabel = attempts > 1 ? `\u27F3 \u91CD\u65B0\u751F\u6210 (${attempts}/${MAX_RETRIES})` : "\u751F\u6210\u6D4B\u8BD5\u4EE3\u7801";
|
|
2739
|
+
onProgress?.(`${genLabel} \u2190 ${targetName}`);
|
|
2740
|
+
if (process.env.QAT_DEBUG === "true") {
|
|
2741
|
+
console.error(chalk4.gray(`[DEBUG] \u2500\u2500\u2500 \u751F\u6210\u8005 AI \u7B2C${attempts}\u6B21\u5C1D\u8BD5 \u2500\u2500\u2500`));
|
|
2742
|
+
}
|
|
2560
2743
|
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
|
|
2561
2744
|
\u5BA1\u8BA1\u8BC4\u5206: ${(lastReview.score * 100).toFixed(0)}%
|
|
2562
2745
|
\u5BA1\u8BA1\u610F\u89C1: ${lastReview.feedback}
|
|
@@ -2569,16 +2752,18 @@ ${lastReview.suggestions.map((s) => `- ${s}`).join("\n")}
|
|
|
2569
2752
|
const generateResponse = await generatorProvider.generateTest({
|
|
2570
2753
|
type: testType,
|
|
2571
2754
|
target: targetPath,
|
|
2572
|
-
context
|
|
2573
|
-
|
|
2574
|
-
--- \u5BA1\u8BA1\u53CD\u9988 ---
|
|
2575
|
-
${generationPrompt}`,
|
|
2755
|
+
// 重试时:源码已压缩在分析摘要中,context只传审计反馈以减少token
|
|
2756
|
+
context: i === 0 ? sourceCode : generationPrompt,
|
|
2576
2757
|
framework: framework || void 0,
|
|
2577
2758
|
analysis
|
|
2578
2759
|
});
|
|
2579
2760
|
currentCode = generateResponse.code;
|
|
2580
2761
|
currentDescription = generateResponse.description;
|
|
2581
2762
|
currentConfidence = generateResponse.confidence;
|
|
2763
|
+
onProgress?.(`\u5BA1\u8BA1\u5BA1\u67E5 \u2190 ${targetName}`);
|
|
2764
|
+
if (process.env.QAT_DEBUG === "true") {
|
|
2765
|
+
console.error(chalk4.gray(`[DEBUG] \u2500\u2500\u2500 \u5BA1\u8BA1\u5458 AI \u5BA1\u67E5 \u2500\u2500\u2500`));
|
|
2766
|
+
}
|
|
2582
2767
|
const reviewRequest = {
|
|
2583
2768
|
target: targetPath,
|
|
2584
2769
|
sourceCode,
|
|
@@ -2610,20 +2795,20 @@ function printReviewReport(report) {
|
|
|
2610
2795
|
const approved = report.filter((r) => r.approved);
|
|
2611
2796
|
const failed = report.filter((r) => !r.approved);
|
|
2612
2797
|
console.log();
|
|
2613
|
-
console.log(
|
|
2614
|
-
console.log(
|
|
2798
|
+
console.log(chalk4.cyan(" \u5BA1\u8BA1\u62A5\u544A:"));
|
|
2799
|
+
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"));
|
|
2615
2800
|
for (const entry of approved) {
|
|
2616
|
-
console.log(` ${
|
|
2801
|
+
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`) : ""})`);
|
|
2617
2802
|
}
|
|
2618
2803
|
for (const entry of failed) {
|
|
2619
|
-
console.log(` ${
|
|
2804
|
+
console.log(` ${chalk4.red("\u2717")} ${chalk4.gray(entry.target)} ${chalk4.red(`(${(entry.score * 100).toFixed(0)}%)`)}`);
|
|
2620
2805
|
if (entry.issues.length > 0) {
|
|
2621
2806
|
for (const issue of entry.issues.slice(0, 3)) {
|
|
2622
|
-
console.log(
|
|
2807
|
+
console.log(chalk4.gray(` - ${issue}`));
|
|
2623
2808
|
}
|
|
2624
2809
|
}
|
|
2625
2810
|
if (entry.feedback) {
|
|
2626
|
-
console.log(
|
|
2811
|
+
console.log(chalk4.gray(` \u53CD\u9988: ${entry.feedback}`));
|
|
2627
2812
|
}
|
|
2628
2813
|
}
|
|
2629
2814
|
console.log();
|
|
@@ -2651,7 +2836,7 @@ function registerCreateCommand(program2) {
|
|
|
2651
2836
|
try {
|
|
2652
2837
|
await executeCreate(options);
|
|
2653
2838
|
} catch (error) {
|
|
2654
|
-
console.error(
|
|
2839
|
+
console.error(chalk5.red(`
|
|
2655
2840
|
\u2717 ${error instanceof Error ? error.message : String(error)}
|
|
2656
2841
|
`));
|
|
2657
2842
|
process.exit(1);
|
|
@@ -2676,7 +2861,7 @@ async function executeCreate(options) {
|
|
|
2676
2861
|
name: "types",
|
|
2677
2862
|
message: "\u9009\u62E9\u6D4B\u8BD5\u7C7B\u578B (\u7A7A\u683C\u9009\u62E9/\u53D6\u6D88\uFF0C\u56DE\u8F66\u786E\u8BA4):",
|
|
2678
2863
|
choices: Object.entries(TEST_TYPE_LABELS).map(([value, label]) => ({
|
|
2679
|
-
name: `${label} - ${
|
|
2864
|
+
name: `${label} - ${chalk5.gray(TEST_TYPE_DESCRIPTIONS[value])}`,
|
|
2680
2865
|
value,
|
|
2681
2866
|
short: label,
|
|
2682
2867
|
checked: value === "unit" || value === "component"
|
|
@@ -2734,11 +2919,13 @@ async function executeCreate(options) {
|
|
|
2734
2919
|
const reviewReportEntries = [];
|
|
2735
2920
|
for (const testType of types) {
|
|
2736
2921
|
for (const testTarget of targets) {
|
|
2737
|
-
const spinner = ora3(`\u6B63\u5728\u751F\u6210 ${TEST_TYPE_LABELS[testType]} - ${
|
|
2922
|
+
const spinner = ora3(`\u6B63\u5728\u751F\u6210 ${TEST_TYPE_LABELS[testType]} - ${path9.basename(testTarget)}...`).start();
|
|
2738
2923
|
try {
|
|
2739
2924
|
let content;
|
|
2740
2925
|
if (useAI && testTarget) {
|
|
2741
|
-
const aiResult = await generateWithAI(testType, name, testTarget, config)
|
|
2926
|
+
const aiResult = await generateWithAI(testType, name, testTarget, config, (text) => {
|
|
2927
|
+
spinner.text = `${text}`;
|
|
2928
|
+
});
|
|
2742
2929
|
content = aiResult.code;
|
|
2743
2930
|
if (aiResult.reviewEntry) {
|
|
2744
2931
|
reviewReportEntries.push(aiResult.reviewEntry);
|
|
@@ -2779,9 +2966,9 @@ async function executeCreate(options) {
|
|
|
2779
2966
|
});
|
|
2780
2967
|
}
|
|
2781
2968
|
const outputDir = getTestOutputDir(testType);
|
|
2782
|
-
const filePath =
|
|
2969
|
+
const filePath = path9.join(outputDir, `${name}.${testType === "e2e" ? "spec" : "test"}.ts`);
|
|
2783
2970
|
if (fs8.existsSync(filePath)) {
|
|
2784
|
-
spinner.warn(`\u6D4B\u8BD5\u6587\u4EF6\u5DF2\u5B58\u5728: ${
|
|
2971
|
+
spinner.warn(`\u6D4B\u8BD5\u6587\u4EF6\u5DF2\u5B58\u5728: ${path9.relative(process.cwd(), filePath)}`);
|
|
2785
2972
|
skippedCount++;
|
|
2786
2973
|
continue;
|
|
2787
2974
|
}
|
|
@@ -2789,11 +2976,11 @@ async function executeCreate(options) {
|
|
|
2789
2976
|
fs8.mkdirSync(outputDir, { recursive: true });
|
|
2790
2977
|
}
|
|
2791
2978
|
fs8.writeFileSync(filePath, content, "utf-8");
|
|
2792
|
-
spinner.succeed(`${TEST_TYPE_LABELS[testType]} - ${
|
|
2979
|
+
spinner.succeed(`${TEST_TYPE_LABELS[testType]} - ${path9.basename(testTarget)}`);
|
|
2793
2980
|
createdFiles.push({ type: testType, filePath });
|
|
2794
2981
|
} catch (error) {
|
|
2795
|
-
spinner.fail(`${TEST_TYPE_LABELS[testType]} - ${
|
|
2796
|
-
console.log(
|
|
2982
|
+
spinner.fail(`${TEST_TYPE_LABELS[testType]} - ${path9.basename(testTarget)} \u521B\u5EFA\u5931\u8D25`);
|
|
2983
|
+
console.log(chalk5.gray(` ${error instanceof Error ? error.message : String(error)}`));
|
|
2797
2984
|
}
|
|
2798
2985
|
}
|
|
2799
2986
|
}
|
|
@@ -2807,13 +2994,13 @@ async function selectTargets(types, projectInfo, srcDir) {
|
|
|
2807
2994
|
if (types.includes("unit")) {
|
|
2808
2995
|
const utils = discoverUtilityFiles(process.cwd(), srcDir);
|
|
2809
2996
|
for (const f of utils.slice(0, 30)) {
|
|
2810
|
-
allTargets.push({ name: `${
|
|
2997
|
+
allTargets.push({ name: `${chalk5.gray("[unit]")} ${f}`, value: f });
|
|
2811
2998
|
}
|
|
2812
2999
|
}
|
|
2813
3000
|
if (types.includes("component")) {
|
|
2814
3001
|
const components = discoverVueComponents(process.cwd(), srcDir);
|
|
2815
3002
|
for (const f of components.slice(0, 30)) {
|
|
2816
|
-
allTargets.push({ name: `${
|
|
3003
|
+
allTargets.push({ name: `${chalk5.gray("[comp]")} ${f}`, value: f });
|
|
2817
3004
|
}
|
|
2818
3005
|
}
|
|
2819
3006
|
if (allTargets.length === 0) {
|
|
@@ -2834,7 +3021,7 @@ async function selectTargets(types, projectInfo, srcDir) {
|
|
|
2834
3021
|
message: "\u9009\u62E9\u88AB\u6D4B\u76EE\u6807 (\u7A7A\u683C\u9009\u62E9/\u53D6\u6D88\uFF0C\u56DE\u8F66\u786E\u8BA4):",
|
|
2835
3022
|
choices: [
|
|
2836
3023
|
...allTargets,
|
|
2837
|
-
{ name:
|
|
3024
|
+
{ name: chalk5.gray("\u624B\u52A8\u8F93\u5165\u8DEF\u5F84"), value: "__manual__" }
|
|
2838
3025
|
],
|
|
2839
3026
|
validate: (input) => {
|
|
2840
3027
|
if (input.length === 0) return "\u8BF7\u81F3\u5C11\u9009\u62E9\u4E00\u4E2A\u76EE\u6807";
|
|
@@ -2859,22 +3046,22 @@ async function selectTargets(types, projectInfo, srcDir) {
|
|
|
2859
3046
|
}
|
|
2860
3047
|
function generateDefaultName(type, target) {
|
|
2861
3048
|
if (!target) return `${type}-test`;
|
|
2862
|
-
const basename =
|
|
3049
|
+
const basename = path9.basename(target, path9.extname(target));
|
|
2863
3050
|
const cleaned = basename.replace(/[^a-zA-Z0-9]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
2864
3051
|
return cleaned || `${type}-test`;
|
|
2865
3052
|
}
|
|
2866
|
-
async function generateWithAI(type, name, target, config) {
|
|
3053
|
+
async function generateWithAI(type, name, target, config, onProgress) {
|
|
2867
3054
|
const provider = getAIProvider(config.ai);
|
|
2868
3055
|
if (!provider.capabilities.generateTest) {
|
|
2869
3056
|
throw new Error("\u5F53\u524D AI Provider \u4E0D\u652F\u6301\u6D4B\u8BD5\u751F\u6210");
|
|
2870
3057
|
}
|
|
2871
3058
|
let sourceCode = "";
|
|
2872
|
-
const fullPath =
|
|
3059
|
+
const fullPath = path9.resolve(process.cwd(), target);
|
|
2873
3060
|
if (fs8.existsSync(fullPath)) {
|
|
2874
3061
|
sourceCode = fs8.readFileSync(fullPath, "utf-8");
|
|
2875
3062
|
}
|
|
2876
3063
|
const analysis = analyzeFile(target);
|
|
2877
|
-
const analysisSummary = analysis.exports.length > 0 || analysis.vueAnalysis ? {
|
|
3064
|
+
const analysisSummary = analysis.exports.length > 0 || analysis.vueAnalysis || analysis.importSignatures.length > 0 ? {
|
|
2878
3065
|
exports: analysis.exports.map((e) => ({
|
|
2879
3066
|
name: e.name,
|
|
2880
3067
|
kind: e.kind,
|
|
@@ -2891,7 +3078,8 @@ async function generateWithAI(type, name, target, config) {
|
|
|
2891
3078
|
params: e.params
|
|
2892
3079
|
})),
|
|
2893
3080
|
methods: analysis.vueAnalysis?.methods,
|
|
2894
|
-
computed: analysis.vueAnalysis?.computed
|
|
3081
|
+
computed: analysis.vueAnalysis?.computed,
|
|
3082
|
+
importSignatures: analysis.importSignatures.length > 0 ? analysis.importSignatures : void 0
|
|
2895
3083
|
} : void 0;
|
|
2896
3084
|
const reviewResult = await generateWithReview({
|
|
2897
3085
|
testType: type,
|
|
@@ -2899,7 +3087,8 @@ async function generateWithAI(type, name, target, config) {
|
|
|
2899
3087
|
sourceCode,
|
|
2900
3088
|
analysis: analysisSummary,
|
|
2901
3089
|
aiConfig: config.ai,
|
|
2902
|
-
framework: "vue"
|
|
3090
|
+
framework: "vue",
|
|
3091
|
+
onProgress
|
|
2903
3092
|
});
|
|
2904
3093
|
const headerComment = [
|
|
2905
3094
|
`// AI Generated Test - ${name}`,
|
|
@@ -2933,46 +3122,46 @@ function getTestOutputDir(type) {
|
|
|
2933
3122
|
}
|
|
2934
3123
|
function displayCreateResult(createdFiles, skippedCount, usedAI) {
|
|
2935
3124
|
if (createdFiles.length === 0 && skippedCount === 0) {
|
|
2936
|
-
console.log(
|
|
3125
|
+
console.log(chalk5.yellow("\n \u6CA1\u6709\u521B\u5EFA\u4EFB\u4F55\u6D4B\u8BD5\u6587\u4EF6\n"));
|
|
2937
3126
|
return;
|
|
2938
3127
|
}
|
|
2939
|
-
console.log(
|
|
3128
|
+
console.log(chalk5.green(`
|
|
2940
3129
|
\u2713 \u5DF2\u521B\u5EFA ${createdFiles.length} \u4E2A\u6D4B\u8BD5\u6587\u4EF6`));
|
|
2941
3130
|
if (skippedCount > 0) {
|
|
2942
|
-
console.log(
|
|
3131
|
+
console.log(chalk5.yellow(` \u26A0 \u8DF3\u8FC7 ${skippedCount} \u4E2A\u5DF2\u5B58\u5728\u7684\u6587\u4EF6`));
|
|
2943
3132
|
}
|
|
2944
3133
|
console.log();
|
|
2945
3134
|
for (const { type, filePath } of createdFiles) {
|
|
2946
|
-
const relativePath =
|
|
2947
|
-
console.log(
|
|
3135
|
+
const relativePath = path9.relative(process.cwd(), filePath);
|
|
3136
|
+
console.log(chalk5.white(` ${chalk5.cyan(TEST_TYPE_LABELS[type])} ${chalk5.gray(relativePath)}`));
|
|
2948
3137
|
}
|
|
2949
3138
|
if (usedAI) {
|
|
2950
3139
|
console.log();
|
|
2951
|
-
console.log(
|
|
3140
|
+
console.log(chalk5.magenta(" \u751F\u6210\u65B9\u5F0F: AI \u8F85\u52A9"));
|
|
2952
3141
|
}
|
|
2953
3142
|
console.log();
|
|
2954
|
-
console.log(
|
|
3143
|
+
console.log(chalk5.gray(" \u8FD0\u884C\u6D4B\u8BD5:"));
|
|
2955
3144
|
const uniqueTypes = [...new Set(createdFiles.map((f) => f.type))];
|
|
2956
3145
|
if (uniqueTypes.length === 1) {
|
|
2957
|
-
console.log(
|
|
3146
|
+
console.log(chalk5.cyan(` qat run -t ${uniqueTypes[0]}`));
|
|
2958
3147
|
} else {
|
|
2959
|
-
console.log(
|
|
2960
|
-
console.log(
|
|
2961
|
-
console.log(
|
|
3148
|
+
console.log(chalk5.cyan(` qat run -t ${uniqueTypes.join(" -t ")}`));
|
|
3149
|
+
console.log(chalk5.gray(" # \u6216\u8FD0\u884C\u5168\u90E8"));
|
|
3150
|
+
console.log(chalk5.cyan(" qat run"));
|
|
2962
3151
|
}
|
|
2963
3152
|
console.log();
|
|
2964
3153
|
}
|
|
2965
3154
|
|
|
2966
3155
|
// src/commands/run.ts
|
|
2967
|
-
import
|
|
3156
|
+
import chalk6 from "chalk";
|
|
2968
3157
|
import inquirer3 from "inquirer";
|
|
2969
3158
|
import ora4 from "ora";
|
|
2970
3159
|
import fs11 from "fs";
|
|
2971
|
-
import
|
|
3160
|
+
import path13 from "path";
|
|
2972
3161
|
|
|
2973
3162
|
// src/runners/vitest-runner.ts
|
|
2974
3163
|
import { execFile } from "child_process";
|
|
2975
|
-
import
|
|
3164
|
+
import path10 from "path";
|
|
2976
3165
|
import fs9 from "fs";
|
|
2977
3166
|
import os2 from "os";
|
|
2978
3167
|
var isVerbose = () => process.env.QAT_VERBOSE === "true";
|
|
@@ -3054,7 +3243,7 @@ function buildVitestArgs(options) {
|
|
|
3054
3243
|
return args;
|
|
3055
3244
|
}
|
|
3056
3245
|
async function execVitest(args) {
|
|
3057
|
-
const tmpFile =
|
|
3246
|
+
const tmpFile = path10.join(os2.tmpdir(), `qat-vitest-result-${Date.now()}.json`);
|
|
3058
3247
|
const argsWithOutput = [...args, "--outputFile", tmpFile];
|
|
3059
3248
|
return new Promise((resolve, reject) => {
|
|
3060
3249
|
const npx = process.platform === "win32" ? "npx.cmd" : "npx";
|
|
@@ -3133,7 +3322,7 @@ function parseVitestJSON(jsonStr) {
|
|
|
3133
3322
|
for (const fileResult of data.testResults) {
|
|
3134
3323
|
const suiteTests = parseTestResults(fileResult);
|
|
3135
3324
|
suites.push({
|
|
3136
|
-
name:
|
|
3325
|
+
name: path10.basename(fileResult.name || "unknown"),
|
|
3137
3326
|
file: fileResult.name || "unknown",
|
|
3138
3327
|
type: "unit",
|
|
3139
3328
|
status: mapVitestStatus(fileResult.status),
|
|
@@ -3244,7 +3433,7 @@ function parseFromStdout(output) {
|
|
|
3244
3433
|
if (data.testResults && Array.isArray(data.testResults)) {
|
|
3245
3434
|
for (const fileResult of data.testResults) {
|
|
3246
3435
|
suites.push({
|
|
3247
|
-
name:
|
|
3436
|
+
name: path10.basename(fileResult.name || "unknown"),
|
|
3248
3437
|
file: fileResult.name || "unknown",
|
|
3249
3438
|
type: "unit",
|
|
3250
3439
|
status: mapVitestStatus(fileResult.status),
|
|
@@ -3280,7 +3469,7 @@ function parseVitestTextOutput(output, hasError) {
|
|
|
3280
3469
|
if (isPassed) totalPassed += testCount;
|
|
3281
3470
|
else totalFailed += testCount;
|
|
3282
3471
|
suites.push({
|
|
3283
|
-
name:
|
|
3472
|
+
name: path10.basename(file),
|
|
3284
3473
|
file,
|
|
3285
3474
|
type: "unit",
|
|
3286
3475
|
status: isPassed ? "passed" : "failed",
|
|
@@ -3381,7 +3570,7 @@ function extractCoverage(coverageMap) {
|
|
|
3381
3570
|
|
|
3382
3571
|
// src/runners/playwright-runner.ts
|
|
3383
3572
|
import { execFile as execFile2 } from "child_process";
|
|
3384
|
-
import
|
|
3573
|
+
import path11 from "path";
|
|
3385
3574
|
async function runPlaywright(options) {
|
|
3386
3575
|
const startTime = Date.now();
|
|
3387
3576
|
const args = buildPlaywrightArgs(options);
|
|
@@ -3581,7 +3770,7 @@ function parsePlaywrightTextOutput(output, hasError) {
|
|
|
3581
3770
|
let existingSuite = suites.find((s) => s.file === file);
|
|
3582
3771
|
if (!existingSuite) {
|
|
3583
3772
|
existingSuite = {
|
|
3584
|
-
name:
|
|
3773
|
+
name: path11.basename(file),
|
|
3585
3774
|
file,
|
|
3586
3775
|
type: "e2e",
|
|
3587
3776
|
status: "passed",
|
|
@@ -3816,7 +4005,7 @@ function calculateAverageMetrics(results) {
|
|
|
3816
4005
|
|
|
3817
4006
|
// src/services/reporter.ts
|
|
3818
4007
|
import fs10 from "fs";
|
|
3819
|
-
import
|
|
4008
|
+
import path12 from "path";
|
|
3820
4009
|
function aggregateResults(results) {
|
|
3821
4010
|
const summary = {
|
|
3822
4011
|
total: 0,
|
|
@@ -4012,13 +4201,13 @@ function generateMDReport(data) {
|
|
|
4012
4201
|
}
|
|
4013
4202
|
function writeReportToDisk(data, outputDir) {
|
|
4014
4203
|
const md = generateMDReport(data);
|
|
4015
|
-
const dir =
|
|
4204
|
+
const dir = path12.resolve(outputDir);
|
|
4016
4205
|
if (!fs10.existsSync(dir)) {
|
|
4017
4206
|
fs10.mkdirSync(dir, { recursive: true });
|
|
4018
4207
|
}
|
|
4019
|
-
const mdPath =
|
|
4208
|
+
const mdPath = path12.join(dir, "report.md");
|
|
4020
4209
|
fs10.writeFileSync(mdPath, md, "utf-8");
|
|
4021
|
-
const jsonPath =
|
|
4210
|
+
const jsonPath = path12.join(dir, "report.json");
|
|
4022
4211
|
fs10.writeFileSync(jsonPath, JSON.stringify(data, null, 2), "utf-8");
|
|
4023
4212
|
return mdPath;
|
|
4024
4213
|
}
|
|
@@ -4055,7 +4244,7 @@ function registerRunCommand(program2) {
|
|
|
4055
4244
|
try {
|
|
4056
4245
|
await executeRun(options);
|
|
4057
4246
|
} catch (error) {
|
|
4058
|
-
console.error(
|
|
4247
|
+
console.error(chalk6.red(`
|
|
4059
4248
|
\u2717 ${error instanceof Error ? error.message : String(error)}
|
|
4060
4249
|
`));
|
|
4061
4250
|
process.exit(1);
|
|
@@ -4075,15 +4264,15 @@ async function executeRun(options) {
|
|
|
4075
4264
|
}
|
|
4076
4265
|
let typesToRun = await determineTypesToRun(type, file, config);
|
|
4077
4266
|
if (typesToRun.length === 0) {
|
|
4078
|
-
console.log(
|
|
4267
|
+
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"));
|
|
4079
4268
|
return;
|
|
4080
4269
|
}
|
|
4081
4270
|
const missingDeps = checkTestDependencies(typesToRun);
|
|
4082
4271
|
if (missingDeps.length > 0) {
|
|
4083
|
-
console.log(
|
|
4272
|
+
console.log(chalk6.yellow("\n \u26A0 \u4EE5\u4E0B\u6D4B\u8BD5\u6846\u67B6\u4F9D\u8D56\u672A\u5B89\u88C5:\n"));
|
|
4084
4273
|
for (const dep of missingDeps) {
|
|
4085
|
-
console.log(
|
|
4086
|
-
console.log(
|
|
4274
|
+
console.log(chalk6.white(` ${dep.runner} (${dep.pkg})`));
|
|
4275
|
+
console.log(chalk6.gray(` \u5B89\u88C5\u547D\u4EE4: ${chalk6.cyan(dep.installCmd)}`));
|
|
4087
4276
|
}
|
|
4088
4277
|
console.log();
|
|
4089
4278
|
const { action } = await inquirer3.prompt([
|
|
@@ -4100,13 +4289,13 @@ async function executeRun(options) {
|
|
|
4100
4289
|
}
|
|
4101
4290
|
]);
|
|
4102
4291
|
if (action === "cancel") {
|
|
4103
|
-
console.log(
|
|
4292
|
+
console.log(chalk6.gray("\n \u5DF2\u53D6\u6D88\u8FD0\u884C\n"));
|
|
4104
4293
|
return;
|
|
4105
4294
|
}
|
|
4106
4295
|
if (action === "install") {
|
|
4107
4296
|
const installed = await installTestDependencies(missingDeps);
|
|
4108
4297
|
if (!installed) {
|
|
4109
|
-
console.log(
|
|
4298
|
+
console.log(chalk6.yellow(" \u90E8\u5206\u4F9D\u8D56\u5B89\u88C5\u5931\u8D25\uFF0C\u5C06\u8DF3\u8FC7\u5BF9\u5E94\u7684\u6D4B\u8BD5\u7C7B\u578B"));
|
|
4110
4299
|
}
|
|
4111
4300
|
const stillMissing = checkTestDependencies(typesToRun);
|
|
4112
4301
|
if (stillMissing.length > 0) {
|
|
@@ -4116,7 +4305,7 @@ async function executeRun(options) {
|
|
|
4116
4305
|
return !dep || !missingRunners.has(dep.pkg);
|
|
4117
4306
|
});
|
|
4118
4307
|
if (typesToRun.length === 0) {
|
|
4119
|
-
console.log(
|
|
4308
|
+
console.log(chalk6.yellow("\n \u6CA1\u6709\u53EF\u8FD0\u884C\u7684\u6D4B\u8BD5\u7C7B\u578B\uFF08\u4F9D\u8D56\u672A\u5B89\u88C5\uFF09\n"));
|
|
4120
4309
|
return;
|
|
4121
4310
|
}
|
|
4122
4311
|
}
|
|
@@ -4128,7 +4317,7 @@ async function executeRun(options) {
|
|
|
4128
4317
|
return !dep || !missingRunners.has(dep.pkg);
|
|
4129
4318
|
});
|
|
4130
4319
|
if (typesToRun.length === 0) {
|
|
4131
|
-
console.log(
|
|
4320
|
+
console.log(chalk6.yellow("\n \u6CA1\u6709\u53EF\u8FD0\u884C\u7684\u6D4B\u8BD5\u7C7B\u578B\n"));
|
|
4132
4321
|
return;
|
|
4133
4322
|
}
|
|
4134
4323
|
}
|
|
@@ -4151,13 +4340,13 @@ async function executeRun(options) {
|
|
|
4151
4340
|
}
|
|
4152
4341
|
]);
|
|
4153
4342
|
if (action === "cancel") {
|
|
4154
|
-
console.log(
|
|
4343
|
+
console.log(chalk6.gray("\n \u5DF2\u53D6\u6D88\u8FD0\u884C\n"));
|
|
4155
4344
|
return;
|
|
4156
4345
|
}
|
|
4157
4346
|
if (action === "skip") {
|
|
4158
4347
|
typesToRun = typesToRun.filter((t) => !SERVER_REQUIRED_TYPES.includes(t));
|
|
4159
4348
|
if (typesToRun.length === 0) {
|
|
4160
|
-
console.log(
|
|
4349
|
+
console.log(chalk6.yellow("\n \u6CA1\u6709\u53EF\u8FD0\u884C\u7684\u6D4B\u8BD5\u7C7B\u578B\n"));
|
|
4161
4350
|
return;
|
|
4162
4351
|
}
|
|
4163
4352
|
}
|
|
@@ -4177,12 +4366,12 @@ async function executeRun(options) {
|
|
|
4177
4366
|
}
|
|
4178
4367
|
]);
|
|
4179
4368
|
if (fallback === "cancel") {
|
|
4180
|
-
console.log(
|
|
4369
|
+
console.log(chalk6.gray("\n \u5DF2\u53D6\u6D88\u8FD0\u884C\n"));
|
|
4181
4370
|
return;
|
|
4182
4371
|
}
|
|
4183
4372
|
typesToRun = typesToRun.filter((t) => !SERVER_REQUIRED_TYPES.includes(t));
|
|
4184
4373
|
if (typesToRun.length === 0) {
|
|
4185
|
-
console.log(
|
|
4374
|
+
console.log(chalk6.yellow("\n \u6CA1\u6709\u53EF\u8FD0\u884C\u7684\u6D4B\u8BD5\u7C7B\u578B\n"));
|
|
4186
4375
|
return;
|
|
4187
4376
|
}
|
|
4188
4377
|
}
|
|
@@ -4239,9 +4428,9 @@ async function executeRun(options) {
|
|
|
4239
4428
|
const reportData = aggregateResults(results);
|
|
4240
4429
|
const outputDir = config.report.outputDir || "qat-report";
|
|
4241
4430
|
const reportPath = writeReportToDisk(reportData, outputDir);
|
|
4242
|
-
const relativePath =
|
|
4243
|
-
console.log(
|
|
4244
|
-
\u62A5\u544A\u5DF2\u751F\u6210: ${
|
|
4431
|
+
const relativePath = path13.relative(process.cwd(), reportPath);
|
|
4432
|
+
console.log(chalk6.gray(`
|
|
4433
|
+
\u62A5\u544A\u5DF2\u751F\u6210: ${chalk6.cyan(relativePath)}`));
|
|
4245
4434
|
console.log();
|
|
4246
4435
|
const hasFailures = results.some((r) => r.status === "failed");
|
|
4247
4436
|
if (hasFailures && isAIAvailable(config.ai)) {
|
|
@@ -4250,7 +4439,7 @@ async function executeRun(options) {
|
|
|
4250
4439
|
}
|
|
4251
4440
|
function printDryRunCommands(types, options, config) {
|
|
4252
4441
|
console.log();
|
|
4253
|
-
console.log(
|
|
4442
|
+
console.log(chalk6.cyan(" \u53EF\u6267\u884C\u7684\u6D4B\u8BD5\u547D\u4EE4:\n"));
|
|
4254
4443
|
for (const testType of types) {
|
|
4255
4444
|
const label = TYPE_LABELS2[testType];
|
|
4256
4445
|
const runner = TYPE_RUNNERS[testType];
|
|
@@ -4287,17 +4476,17 @@ function printDryRunCommands(types, options, config) {
|
|
|
4287
4476
|
default:
|
|
4288
4477
|
cmd = `# ${label}: \u672A\u77E5\u8FD0\u884C\u5668`;
|
|
4289
4478
|
}
|
|
4290
|
-
console.log(
|
|
4291
|
-
console.log(
|
|
4479
|
+
console.log(chalk6.white(` ${label}:`));
|
|
4480
|
+
console.log(chalk6.cyan(` ${cmd}`));
|
|
4292
4481
|
console.log();
|
|
4293
4482
|
}
|
|
4294
|
-
console.log(
|
|
4483
|
+
console.log(chalk6.gray(" \u6216\u4F7F\u7528 QAT \u7EDF\u4E00\u8FD0\u884C:"));
|
|
4295
4484
|
if (types.length === 1) {
|
|
4296
|
-
console.log(
|
|
4485
|
+
console.log(chalk6.cyan(` qat run -t ${types[0]}`));
|
|
4297
4486
|
} else {
|
|
4298
|
-
console.log(
|
|
4299
|
-
console.log(
|
|
4300
|
-
console.log(
|
|
4487
|
+
console.log(chalk6.cyan(` qat run -t ${types.join(" -t ")}`));
|
|
4488
|
+
console.log(chalk6.gray(" # \u5E76\u884C\u8FD0\u884C"));
|
|
4489
|
+
console.log(chalk6.cyan(" qat run -p"));
|
|
4301
4490
|
}
|
|
4302
4491
|
console.log();
|
|
4303
4492
|
}
|
|
@@ -4320,7 +4509,7 @@ async function determineTypesToRun(type, file, config) {
|
|
|
4320
4509
|
name: "selectedTypes",
|
|
4321
4510
|
message: "\u9009\u62E9\u8981\u8FD0\u884C\u7684\u6D4B\u8BD5\u7C7B\u578B (\u7A7A\u683C\u9009\u62E9/\u53D6\u6D88\uFF0C\u56DE\u8F66\u786E\u8BA4):",
|
|
4322
4511
|
choices: enabledTypes.map((t) => ({
|
|
4323
|
-
name: `${TYPE_LABELS2[t]} (${
|
|
4512
|
+
name: `${TYPE_LABELS2[t]} (${chalk6.gray(TYPE_RUNNERS[t])})`,
|
|
4324
4513
|
value: t,
|
|
4325
4514
|
checked: true
|
|
4326
4515
|
})),
|
|
@@ -4380,7 +4569,7 @@ async function runTestType(testType, config, options) {
|
|
|
4380
4569
|
}
|
|
4381
4570
|
}
|
|
4382
4571
|
async function runWatchMode(_config, options) {
|
|
4383
|
-
console.log(
|
|
4572
|
+
console.log(chalk6.cyan(" \u76D1\u542C\u6A21\u5F0F\u5DF2\u542F\u52A8 (Ctrl+C \u9000\u51FA)\n"));
|
|
4384
4573
|
const { spawn } = await import("child_process");
|
|
4385
4574
|
const args = ["vitest", "--watch"];
|
|
4386
4575
|
if (options.file) {
|
|
@@ -4404,7 +4593,7 @@ async function runWatchMode(_config, options) {
|
|
|
4404
4593
|
shell: true
|
|
4405
4594
|
});
|
|
4406
4595
|
child.on("error", (err) => {
|
|
4407
|
-
console.error(
|
|
4596
|
+
console.error(chalk6.red(`
|
|
4408
4597
|
Vitest \u542F\u52A8\u5931\u8D25: ${err.message}
|
|
4409
4598
|
`));
|
|
4410
4599
|
});
|
|
@@ -4459,17 +4648,17 @@ function displayJestStyleResults(results) {
|
|
|
4459
4648
|
for (const result of results) {
|
|
4460
4649
|
const typeLabel = TYPE_LABELS2[result.type] || result.type;
|
|
4461
4650
|
if (result.suites.length === 0) {
|
|
4462
|
-
console.log(
|
|
4651
|
+
console.log(chalk6.gray(` ${typeLabel}: \u65E0\u6D4B\u8BD5\u7ED3\u679C`));
|
|
4463
4652
|
continue;
|
|
4464
4653
|
}
|
|
4465
4654
|
for (const suite of result.suites) {
|
|
4466
4655
|
if (suite.tests.length === 0) continue;
|
|
4467
|
-
const suiteIcon = suite.status === "passed" ?
|
|
4468
|
-
console.log(` ${suiteIcon} ${
|
|
4656
|
+
const suiteIcon = suite.status === "passed" ? chalk6.green("PASS") : chalk6.red("FAIL");
|
|
4657
|
+
console.log(` ${suiteIcon} ${chalk6.white(suite.name)} ${chalk6.gray(`(${formatDuration2(suite.duration)})`)}`);
|
|
4469
4658
|
for (const test of suite.tests) {
|
|
4470
|
-
const icon = test.status === "passed" ?
|
|
4471
|
-
const name = test.status === "failed" ?
|
|
4472
|
-
console.log(` ${icon} ${name} ${
|
|
4659
|
+
const icon = test.status === "passed" ? chalk6.green(" \u2713") : test.status === "failed" ? chalk6.red(" \u2715") : chalk6.yellow(" \u25CB");
|
|
4660
|
+
const name = test.status === "failed" ? chalk6.red(test.name) : test.name;
|
|
4661
|
+
console.log(` ${icon} ${name} ${chalk6.gray(formatDuration2(test.duration))}`);
|
|
4473
4662
|
}
|
|
4474
4663
|
}
|
|
4475
4664
|
console.log();
|
|
@@ -4501,28 +4690,28 @@ function displayJestStyleResults(results) {
|
|
|
4501
4690
|
}
|
|
4502
4691
|
}
|
|
4503
4692
|
const total = totalPassed + totalFailed + totalSkipped;
|
|
4504
|
-
console.log(
|
|
4505
|
-
console.log(
|
|
4506
|
-
console.log(
|
|
4693
|
+
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"));
|
|
4694
|
+
console.log(chalk6.white(" \u6D4B\u8BD5\u7ED3\u679C\u6C47\u603B"));
|
|
4695
|
+
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"));
|
|
4507
4696
|
console.log();
|
|
4508
|
-
console.log(` ${
|
|
4697
|
+
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))}`);
|
|
4509
4698
|
console.log(` ${"\u2500".repeat(14)} ${"\u2500".repeat(6)} ${"\u2500".repeat(6)} ${"\u2500".repeat(6)} ${"\u2500".repeat(6)} ${"\u2500".repeat(8)} ${"\u2500".repeat(8)}`);
|
|
4510
4699
|
for (const [type, stats] of Object.entries(typeStats)) {
|
|
4511
4700
|
const label = (TYPE_LABELS2[type] || type).padEnd(14);
|
|
4512
4701
|
const typeTotal = stats.passed + stats.failed + stats.skipped;
|
|
4513
4702
|
const rate = typeTotal > 0 ? (stats.passed / typeTotal * 100).toFixed(0) + "%" : "-";
|
|
4514
|
-
const rateColored = typeTotal > 0 && stats.passed === typeTotal ?
|
|
4703
|
+
const rateColored = typeTotal > 0 && stats.passed === typeTotal ? chalk6.green(rate.padStart(8)) : stats.failed > 0 ? chalk6.red(rate.padStart(8)) : rate.padStart(8);
|
|
4515
4704
|
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)}`);
|
|
4516
4705
|
}
|
|
4517
4706
|
console.log(` ${"\u2500".repeat(14)} ${"\u2500".repeat(6)} ${"\u2500".repeat(6)} ${"\u2500".repeat(6)} ${"\u2500".repeat(6)} ${"\u2500".repeat(8)} ${"\u2500".repeat(8)}`);
|
|
4518
4707
|
const totalRate = total > 0 ? (totalPassed / total * 100).toFixed(0) + "%" : "-";
|
|
4519
|
-
const totalRateColored = total > 0 && totalFailed === 0 ?
|
|
4708
|
+
const totalRateColored = total > 0 && totalFailed === 0 ? chalk6.green(totalRate.padStart(8)) : totalFailed > 0 ? chalk6.red(totalRate.padStart(8)) : totalRate.padStart(8);
|
|
4520
4709
|
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)}`);
|
|
4521
4710
|
console.log();
|
|
4522
4711
|
for (const result of results) {
|
|
4523
4712
|
if (result.coverage) {
|
|
4524
4713
|
const c = result.coverage;
|
|
4525
|
-
console.log(
|
|
4714
|
+
console.log(chalk6.cyan(" \u8986\u76D6\u7387:"));
|
|
4526
4715
|
console.log(` \u8BED\u53E5: ${coverageColor(c.statements)} \u5206\u652F: ${coverageColor(c.branches)} \u51FD\u6570: ${coverageColor(c.functions)} \u884C: ${coverageColor(c.lines)}`);
|
|
4527
4716
|
console.log();
|
|
4528
4717
|
}
|
|
@@ -4530,7 +4719,7 @@ function displayJestStyleResults(results) {
|
|
|
4530
4719
|
for (const result of results) {
|
|
4531
4720
|
if (result.type === "performance" && result.performance) {
|
|
4532
4721
|
const p = result.performance;
|
|
4533
|
-
console.log(
|
|
4722
|
+
console.log(chalk6.cyan(" \u6027\u80FD\u6307\u6807:"));
|
|
4534
4723
|
console.log(` Performance: ${scoreColor(p.performance)} ${p.performance}/100`);
|
|
4535
4724
|
console.log(` Accessibility: ${scoreColor(p.accessibility)} ${p.accessibility}/100`);
|
|
4536
4725
|
console.log(` Best Practices: ${scoreColor(p.bestPractices)} ${p.bestPractices}/100`);
|
|
@@ -4547,31 +4736,31 @@ function displayJestStyleResults(results) {
|
|
|
4547
4736
|
)
|
|
4548
4737
|
);
|
|
4549
4738
|
if (failedTests.length > 0) {
|
|
4550
|
-
console.log(
|
|
4739
|
+
console.log(chalk6.red(" \u5931\u8D25\u8BE6\u60C5:"));
|
|
4551
4740
|
for (const { suite, test } of failedTests) {
|
|
4552
|
-
console.log(
|
|
4741
|
+
console.log(chalk6.red(` \u2715 ${suite.name} > ${test.name}`));
|
|
4553
4742
|
if (test.error?.message) {
|
|
4554
|
-
console.log(
|
|
4743
|
+
console.log(chalk6.gray(` ${test.error.message.split("\n")[0]}`));
|
|
4555
4744
|
}
|
|
4556
4745
|
}
|
|
4557
4746
|
console.log();
|
|
4558
4747
|
}
|
|
4559
4748
|
if (totalFailed > 0) {
|
|
4560
|
-
console.log(
|
|
4749
|
+
console.log(chalk6.red(` Tests: ${totalFailed} failed, ${totalPassed} passed, ${total} total`));
|
|
4561
4750
|
process.exitCode = 1;
|
|
4562
4751
|
} else if (total === 0) {
|
|
4563
|
-
console.log(
|
|
4752
|
+
console.log(chalk6.yellow(" \u6CA1\u6709\u53D1\u73B0\u6D4B\u8BD5\u7528\u4F8B"));
|
|
4564
4753
|
} else {
|
|
4565
|
-
console.log(
|
|
4754
|
+
console.log(chalk6.green(` Tests: ${totalPassed} passed, ${total} total`));
|
|
4566
4755
|
}
|
|
4567
|
-
console.log(
|
|
4756
|
+
console.log(chalk6.gray(` Time: ${formatDuration2(totalDuration)}`));
|
|
4568
4757
|
console.log();
|
|
4569
4758
|
}
|
|
4570
4759
|
function coverageColor(value) {
|
|
4571
4760
|
const pct3 = `${(value * 100).toFixed(1)}%`;
|
|
4572
|
-
if (value >= 0.8) return
|
|
4573
|
-
if (value >= 0.5) return
|
|
4574
|
-
return
|
|
4761
|
+
if (value >= 0.8) return chalk6.green(pct3);
|
|
4762
|
+
if (value >= 0.5) return chalk6.yellow(pct3);
|
|
4763
|
+
return chalk6.red(pct3);
|
|
4575
4764
|
}
|
|
4576
4765
|
async function aiAnalyzeFailures(results, aiConfig) {
|
|
4577
4766
|
const failedTests = results.flatMap(
|
|
@@ -4580,23 +4769,23 @@ async function aiAnalyzeFailures(results, aiConfig) {
|
|
|
4580
4769
|
)
|
|
4581
4770
|
);
|
|
4582
4771
|
if (failedTests.length === 0) return;
|
|
4583
|
-
console.log(
|
|
4772
|
+
console.log(chalk6.magenta(" \u{1F916} AI \u5206\u6790\u5931\u8D25\u539F\u56E0..."));
|
|
4584
4773
|
console.log();
|
|
4585
4774
|
const provider = getAIProvider(aiConfig);
|
|
4586
4775
|
if (!provider.capabilities.suggestFix) return;
|
|
4587
4776
|
for (const { suite, test } of failedTests.slice(0, 5)) {
|
|
4588
4777
|
try {
|
|
4589
4778
|
const suggestions = await provider.suggestFix(test.error);
|
|
4590
|
-
console.log(
|
|
4779
|
+
console.log(chalk6.white(` ${suite.name} > ${test.name}`));
|
|
4591
4780
|
if (test.error?.message) {
|
|
4592
|
-
console.log(
|
|
4781
|
+
console.log(chalk6.gray(` \u9519\u8BEF: ${test.error.message.split("\n")[0]}`));
|
|
4593
4782
|
}
|
|
4594
4783
|
for (const suggestion of suggestions.slice(0, 3)) {
|
|
4595
|
-
console.log(
|
|
4784
|
+
console.log(chalk6.cyan(` \u2192 ${suggestion}`));
|
|
4596
4785
|
}
|
|
4597
4786
|
console.log();
|
|
4598
4787
|
} catch (error) {
|
|
4599
|
-
console.log(
|
|
4788
|
+
console.log(chalk6.gray(` AI \u5206\u6790\u5931\u8D25: ${error instanceof Error ? error.message : String(error)}`));
|
|
4600
4789
|
}
|
|
4601
4790
|
}
|
|
4602
4791
|
}
|
|
@@ -4608,9 +4797,9 @@ function formatDuration2(ms) {
|
|
|
4608
4797
|
return `${min}m ${sec}s`;
|
|
4609
4798
|
}
|
|
4610
4799
|
function scoreColor(score) {
|
|
4611
|
-
if (score >= 90) return
|
|
4612
|
-
if (score >= 50) return
|
|
4613
|
-
return
|
|
4800
|
+
if (score >= 90) return chalk6.green("\u25CF");
|
|
4801
|
+
if (score >= 50) return chalk6.yellow("\u25CF");
|
|
4802
|
+
return chalk6.red("\u25CF");
|
|
4614
4803
|
}
|
|
4615
4804
|
async function checkDevServer(config) {
|
|
4616
4805
|
const baseUrl = config.playwright.baseURL || "http://localhost:5173";
|
|
@@ -4626,10 +4815,10 @@ async function checkDevServer(config) {
|
|
|
4626
4815
|
}
|
|
4627
4816
|
async function startDevServer(config) {
|
|
4628
4817
|
const { spawn } = await import("child_process");
|
|
4629
|
-
const hasPnpm = fs11.existsSync(
|
|
4630
|
-
const hasYarn = fs11.existsSync(
|
|
4818
|
+
const hasPnpm = fs11.existsSync(path13.join(process.cwd(), "pnpm-lock.yaml"));
|
|
4819
|
+
const hasYarn = fs11.existsSync(path13.join(process.cwd(), "yarn.lock"));
|
|
4631
4820
|
const pkgCmd = hasPnpm ? "pnpm" : hasYarn ? "yarn" : "npm";
|
|
4632
|
-
console.log(
|
|
4821
|
+
console.log(chalk6.cyan(` \u6B63\u5728\u542F\u52A8 dev server (${pkgCmd} run dev) ...`));
|
|
4633
4822
|
const child = spawn(pkgCmd, ["run", "dev"], {
|
|
4634
4823
|
cwd: process.cwd(),
|
|
4635
4824
|
stdio: "pipe",
|
|
@@ -4645,24 +4834,24 @@ async function startDevServer(config) {
|
|
|
4645
4834
|
waited += interval;
|
|
4646
4835
|
const isUp = await checkDevServer(config);
|
|
4647
4836
|
if (isUp) {
|
|
4648
|
-
console.log(
|
|
4837
|
+
console.log(chalk6.green(` \u2713 dev server \u5DF2\u542F\u52A8 (${baseUrl})`));
|
|
4649
4838
|
child.unref();
|
|
4650
4839
|
return true;
|
|
4651
4840
|
}
|
|
4652
4841
|
}
|
|
4653
4842
|
child.kill();
|
|
4654
|
-
console.log(
|
|
4843
|
+
console.log(chalk6.red(" \u2717 dev server \u542F\u52A8\u8D85\u65F6"));
|
|
4655
4844
|
return false;
|
|
4656
4845
|
}
|
|
4657
4846
|
function saveRunResults(results) {
|
|
4658
4847
|
if (results.length === 0) return;
|
|
4659
|
-
const resultsPath =
|
|
4848
|
+
const resultsPath = path13.join(process.cwd(), RESULTS_DIR);
|
|
4660
4849
|
if (!fs11.existsSync(resultsPath)) {
|
|
4661
4850
|
fs11.mkdirSync(resultsPath, { recursive: true });
|
|
4662
4851
|
}
|
|
4663
4852
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
4664
4853
|
const fileName = `result-${timestamp}.json`;
|
|
4665
|
-
const filePath =
|
|
4854
|
+
const filePath = path13.join(resultsPath, fileName);
|
|
4666
4855
|
const data = {
|
|
4667
4856
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4668
4857
|
results
|
|
@@ -4672,13 +4861,13 @@ function saveRunResults(results) {
|
|
|
4672
4861
|
const files = fs11.readdirSync(resultsPath).filter((f) => f.startsWith("result-") && f.endsWith(".json")).sort();
|
|
4673
4862
|
while (files.length > 20) {
|
|
4674
4863
|
const oldest = files.shift();
|
|
4675
|
-
fs11.unlinkSync(
|
|
4864
|
+
fs11.unlinkSync(path13.join(resultsPath, oldest));
|
|
4676
4865
|
}
|
|
4677
4866
|
} catch {
|
|
4678
4867
|
}
|
|
4679
4868
|
}
|
|
4680
4869
|
function checkTestDependencies(types) {
|
|
4681
|
-
const pkgPath =
|
|
4870
|
+
const pkgPath = path13.join(process.cwd(), "package.json");
|
|
4682
4871
|
let allDeps = {};
|
|
4683
4872
|
if (fs11.existsSync(pkgPath)) {
|
|
4684
4873
|
try {
|
|
@@ -4704,8 +4893,8 @@ function checkTestDependencies(types) {
|
|
|
4704
4893
|
return missingDeps;
|
|
4705
4894
|
}
|
|
4706
4895
|
async function installTestDependencies(missingDeps) {
|
|
4707
|
-
const hasPnpm = fs11.existsSync(
|
|
4708
|
-
const hasYarn = fs11.existsSync(
|
|
4896
|
+
const hasPnpm = fs11.existsSync(path13.join(process.cwd(), "pnpm-lock.yaml"));
|
|
4897
|
+
const hasYarn = fs11.existsSync(path13.join(process.cwd(), "yarn.lock"));
|
|
4709
4898
|
const pkgManager = hasPnpm ? "pnpm" : hasYarn ? "yarn" : "npm";
|
|
4710
4899
|
const { execFile: execFile5 } = await import("child_process");
|
|
4711
4900
|
let allSuccess = true;
|
|
@@ -4743,7 +4932,7 @@ async function installTestDependencies(missingDeps) {
|
|
|
4743
4932
|
spinner.succeed(`${dep.pkg} \u5B89\u88C5\u6210\u529F`);
|
|
4744
4933
|
} catch (error) {
|
|
4745
4934
|
spinner.fail(`${dep.pkg} \u5B89\u88C5\u5931\u8D25`);
|
|
4746
|
-
console.log(
|
|
4935
|
+
console.log(chalk6.gray(` \u53EF\u624B\u52A8\u5B89\u88C5: ${chalk6.cyan(dep.installCmd)}`));
|
|
4747
4936
|
allSuccess = false;
|
|
4748
4937
|
}
|
|
4749
4938
|
}
|
|
@@ -4751,7 +4940,7 @@ async function installTestDependencies(missingDeps) {
|
|
|
4751
4940
|
}
|
|
4752
4941
|
|
|
4753
4942
|
// src/commands/mock.ts
|
|
4754
|
-
import
|
|
4943
|
+
import chalk7 from "chalk";
|
|
4755
4944
|
import ora5 from "ora";
|
|
4756
4945
|
function registerMockCommand(program2) {
|
|
4757
4946
|
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) => {
|
|
@@ -4762,7 +4951,7 @@ function registerMockCommand(program2) {
|
|
|
4762
4951
|
config: options.config
|
|
4763
4952
|
});
|
|
4764
4953
|
} catch (error) {
|
|
4765
|
-
console.error(
|
|
4954
|
+
console.error(chalk7.red(`
|
|
4766
4955
|
\u2717 ${error instanceof Error ? error.message : String(error)}
|
|
4767
4956
|
`));
|
|
4768
4957
|
process.exit(1);
|
|
@@ -4786,9 +4975,9 @@ async function executeMock(options) {
|
|
|
4786
4975
|
break;
|
|
4787
4976
|
}
|
|
4788
4977
|
default: {
|
|
4789
|
-
console.error(
|
|
4978
|
+
console.error(chalk7.red(`
|
|
4790
4979
|
\u672A\u77E5\u64CD\u4F5C: ${options.action}`));
|
|
4791
|
-
console.log(
|
|
4980
|
+
console.log(chalk7.gray(" \u53EF\u7528\u64CD\u4F5C: start, stop, status\n"));
|
|
4792
4981
|
process.exit(1);
|
|
4793
4982
|
}
|
|
4794
4983
|
}
|
|
@@ -4796,7 +4985,7 @@ async function executeMock(options) {
|
|
|
4796
4985
|
async function startMock(port, routesDir) {
|
|
4797
4986
|
const state = getMockServerState();
|
|
4798
4987
|
if (state.running) {
|
|
4799
|
-
console.log(
|
|
4988
|
+
console.log(chalk7.yellow(`
|
|
4800
4989
|
Mock\u670D\u52A1\u5668\u5DF2\u5728\u8FD0\u884C (\u7AEF\u53E3: ${state.port})
|
|
4801
4990
|
`));
|
|
4802
4991
|
return;
|
|
@@ -4810,22 +4999,22 @@ async function startMock(port, routesDir) {
|
|
|
4810
4999
|
await startMockServer(port, routes);
|
|
4811
5000
|
spinner.succeed(`Mock\u670D\u52A1\u5668\u5DF2\u542F\u52A8`);
|
|
4812
5001
|
console.log();
|
|
4813
|
-
console.log(
|
|
4814
|
-
console.log(
|
|
4815
|
-
console.log(
|
|
5002
|
+
console.log(chalk7.white(" \u5730\u5740:"), chalk7.cyan(`http://localhost:${port}`));
|
|
5003
|
+
console.log(chalk7.white(" \u8DEF\u7531:"), routes.length > 0 ? `${routes.length} \u4E2A` : chalk7.gray("\u4F7F\u7528\u9ED8\u8BA4\u8DEF\u7531"));
|
|
5004
|
+
console.log(chalk7.white(" \u5065\u5EB7\u68C0\u67E5:"), chalk7.cyan(`http://localhost:${port}/api/health`));
|
|
4816
5005
|
if (routes.length > 0) {
|
|
4817
5006
|
console.log();
|
|
4818
|
-
console.log(
|
|
5007
|
+
console.log(chalk7.white(" \u5DF2\u6CE8\u518C\u8DEF\u7531:"));
|
|
4819
5008
|
for (const route of routes.slice(0, 10)) {
|
|
4820
|
-
const method =
|
|
5009
|
+
const method = chalk7.gray(route.method.padEnd(6));
|
|
4821
5010
|
console.log(` ${method} ${route.path}`);
|
|
4822
5011
|
}
|
|
4823
5012
|
if (routes.length > 10) {
|
|
4824
|
-
console.log(
|
|
5013
|
+
console.log(chalk7.gray(` ... \u8FD8\u6709 ${routes.length - 10} \u4E2A\u8DEF\u7531`));
|
|
4825
5014
|
}
|
|
4826
5015
|
}
|
|
4827
5016
|
console.log();
|
|
4828
|
-
console.log(
|
|
5017
|
+
console.log(chalk7.gray(" \u6309 Ctrl+C \u505C\u6B62\u670D\u52A1\u5668"));
|
|
4829
5018
|
process.on("SIGINT", async () => {
|
|
4830
5019
|
const stopSpinner = ora5("\u6B63\u5728\u505C\u6B62Mock\u670D\u52A1\u5668...").start();
|
|
4831
5020
|
await stopMockServer();
|
|
@@ -4842,7 +5031,7 @@ async function startMock(port, routesDir) {
|
|
|
4842
5031
|
async function stopMock() {
|
|
4843
5032
|
const state = getMockServerState();
|
|
4844
5033
|
if (!state.running) {
|
|
4845
|
-
console.log(
|
|
5034
|
+
console.log(chalk7.yellow("\n Mock\u670D\u52A1\u5668\u672A\u5728\u8FD0\u884C\n"));
|
|
4846
5035
|
return;
|
|
4847
5036
|
}
|
|
4848
5037
|
const spinner = ora5("\u6B63\u5728\u505C\u6B62Mock\u670D\u52A1\u5668...").start();
|
|
@@ -4859,28 +5048,28 @@ function showStatus() {
|
|
|
4859
5048
|
const state = getMockServerState();
|
|
4860
5049
|
console.log();
|
|
4861
5050
|
if (state.running) {
|
|
4862
|
-
console.log(
|
|
4863
|
-
console.log(
|
|
4864
|
-
console.log(
|
|
5051
|
+
console.log(chalk7.green(" \u25CF Mock\u670D\u52A1\u5668\u8FD0\u884C\u4E2D"));
|
|
5052
|
+
console.log(chalk7.white(" \u7AEF\u53E3:"), state.port);
|
|
5053
|
+
console.log(chalk7.white(" \u8DEF\u7531:"), state.routes.length);
|
|
4865
5054
|
} else {
|
|
4866
|
-
console.log(
|
|
4867
|
-
console.log(
|
|
5055
|
+
console.log(chalk7.gray(" \u25CB Mock\u670D\u52A1\u5668\u672A\u8FD0\u884C"));
|
|
5056
|
+
console.log(chalk7.gray(" \u4F7F\u7528 qat mock start \u542F\u52A8"));
|
|
4868
5057
|
}
|
|
4869
5058
|
console.log();
|
|
4870
5059
|
}
|
|
4871
5060
|
|
|
4872
5061
|
// src/commands/report.ts
|
|
4873
|
-
import
|
|
5062
|
+
import chalk8 from "chalk";
|
|
4874
5063
|
import ora6 from "ora";
|
|
4875
5064
|
import fs12 from "fs";
|
|
4876
|
-
import
|
|
5065
|
+
import path14 from "path";
|
|
4877
5066
|
var RESULTS_DIR2 = ".qat-results";
|
|
4878
5067
|
function registerReportCommand(program2) {
|
|
4879
5068
|
program2.command("report").description("\u751F\u6210\u6D4B\u8BD5\u62A5\u544A - \u805A\u5408\u6240\u6709\u6D4B\u8BD5\u7ED3\u679C\u5E76\u8F93\u51FAHTML").option("-o, --output <dir>", "\u62A5\u544A\u8F93\u51FA\u76EE\u5F55").option("--open", "\u751F\u6210\u540E\u81EA\u52A8\u6253\u5F00\u62A5\u544A", false).action(async (options) => {
|
|
4880
5069
|
try {
|
|
4881
5070
|
await executeReport(options);
|
|
4882
5071
|
} catch (error) {
|
|
4883
|
-
console.error(
|
|
5072
|
+
console.error(chalk8.red(`
|
|
4884
5073
|
\u2717 ${error instanceof Error ? error.message : String(error)}
|
|
4885
5074
|
`));
|
|
4886
5075
|
process.exit(1);
|
|
@@ -4894,7 +5083,7 @@ async function executeReport(options) {
|
|
|
4894
5083
|
const results = collectResults();
|
|
4895
5084
|
if (results.length === 0) {
|
|
4896
5085
|
spinner.info("\u6CA1\u6709\u627E\u5230\u6D4B\u8BD5\u7ED3\u679C");
|
|
4897
|
-
console.log(
|
|
5086
|
+
console.log(chalk8.gray("\n \u63D0\u793A: \u5148\u8FD0\u884C qat run \u751F\u6210\u6D4B\u8BD5\u7ED3\u679C\n"));
|
|
4898
5087
|
return;
|
|
4899
5088
|
}
|
|
4900
5089
|
spinner.text = "\u6B63\u5728\u751F\u6210\u6D4B\u8BD5\u62A5\u544A...";
|
|
@@ -4909,13 +5098,13 @@ async function executeReport(options) {
|
|
|
4909
5098
|
}
|
|
4910
5099
|
function collectResults() {
|
|
4911
5100
|
const results = [];
|
|
4912
|
-
const resultsPath =
|
|
5101
|
+
const resultsPath = path14.join(process.cwd(), RESULTS_DIR2);
|
|
4913
5102
|
if (!fs12.existsSync(resultsPath)) {
|
|
4914
5103
|
return results;
|
|
4915
5104
|
}
|
|
4916
5105
|
const files = fs12.readdirSync(resultsPath).filter((f) => f.endsWith(".json")).sort().reverse();
|
|
4917
5106
|
if (files.length > 0) {
|
|
4918
|
-
const latestFile =
|
|
5107
|
+
const latestFile = path14.join(resultsPath, files[0]);
|
|
4919
5108
|
try {
|
|
4920
5109
|
const data = JSON.parse(fs12.readFileSync(latestFile, "utf-8"));
|
|
4921
5110
|
if (Array.isArray(data)) {
|
|
@@ -4929,49 +5118,49 @@ function collectResults() {
|
|
|
4929
5118
|
return results;
|
|
4930
5119
|
}
|
|
4931
5120
|
function saveResultToHistory(reportData) {
|
|
4932
|
-
const resultsPath =
|
|
5121
|
+
const resultsPath = path14.join(process.cwd(), RESULTS_DIR2);
|
|
4933
5122
|
if (!fs12.existsSync(resultsPath)) {
|
|
4934
5123
|
fs12.mkdirSync(resultsPath, { recursive: true });
|
|
4935
5124
|
}
|
|
4936
5125
|
const timestamp = new Date(reportData.timestamp).toISOString().replace(/[:.]/g, "-");
|
|
4937
5126
|
const fileName = `result-${timestamp}.json`;
|
|
4938
|
-
const filePath =
|
|
5127
|
+
const filePath = path14.join(resultsPath, fileName);
|
|
4939
5128
|
fs12.writeFileSync(filePath, JSON.stringify(reportData, null, 2), "utf-8");
|
|
4940
5129
|
const files = fs12.readdirSync(resultsPath).filter((f) => f.startsWith("result-") && f.endsWith(".json")).sort();
|
|
4941
5130
|
while (files.length > 20) {
|
|
4942
5131
|
const oldest = files.shift();
|
|
4943
|
-
fs12.unlinkSync(
|
|
5132
|
+
fs12.unlinkSync(path14.join(resultsPath, oldest));
|
|
4944
5133
|
}
|
|
4945
5134
|
}
|
|
4946
5135
|
function displayReportResult(reportPath, data) {
|
|
4947
|
-
const relativePath =
|
|
5136
|
+
const relativePath = path14.relative(process.cwd(), reportPath);
|
|
4948
5137
|
const passRate = data.summary.total > 0 ? (data.summary.passed / data.summary.total * 100).toFixed(1) : "0";
|
|
4949
5138
|
console.log();
|
|
4950
|
-
console.log(
|
|
5139
|
+
console.log(chalk8.green(" \u2713 \u6D4B\u8BD5\u62A5\u544A\u5DF2\u751F\u6210"));
|
|
4951
5140
|
console.log();
|
|
4952
|
-
console.log(
|
|
4953
|
-
console.log(
|
|
4954
|
-
console.log(
|
|
4955
|
-
console.log(
|
|
5141
|
+
console.log(chalk8.white(" \u62A5\u544A\u8DEF\u5F84:"), chalk8.cyan(relativePath));
|
|
5142
|
+
console.log(chalk8.white(" \u901A\u8FC7\u7387: "), parseFloat(passRate) >= 80 ? chalk8.green(`${passRate}%`) : parseFloat(passRate) >= 50 ? chalk8.yellow(`${passRate}%`) : chalk8.red(`${passRate}%`));
|
|
5143
|
+
console.log(chalk8.white(" \u6D4B\u8BD5\u7528\u4F8B:"), `${data.summary.total} total`);
|
|
5144
|
+
console.log(chalk8.white(" \u2705 \u901A\u8FC7: "), chalk8.green(String(data.summary.passed)));
|
|
4956
5145
|
if (data.summary.failed > 0) {
|
|
4957
|
-
console.log(
|
|
5146
|
+
console.log(chalk8.white(" \u274C \u5931\u8D25: "), chalk8.red(String(data.summary.failed)));
|
|
4958
5147
|
}
|
|
4959
5148
|
if (data.summary.skipped > 0) {
|
|
4960
|
-
console.log(
|
|
5149
|
+
console.log(chalk8.white(" \u23ED\uFE0F \u8DF3\u8FC7: "), chalk8.yellow(String(data.summary.skipped)));
|
|
4961
5150
|
}
|
|
4962
5151
|
if (Object.keys(data.byType).length > 0) {
|
|
4963
5152
|
console.log();
|
|
4964
|
-
console.log(
|
|
5153
|
+
console.log(chalk8.white(" \u6309\u7C7B\u578B:"));
|
|
4965
5154
|
for (const [type, stats] of Object.entries(data.byType)) {
|
|
4966
5155
|
const rate = stats.total > 0 ? (stats.passed / stats.total * 100).toFixed(0) : "0";
|
|
4967
|
-
const icon = stats.failed > 0 ?
|
|
5156
|
+
const icon = stats.failed > 0 ? chalk8.red("\u274C") : chalk8.green("\u2705");
|
|
4968
5157
|
console.log(` ${icon} ${type}: ${stats.passed}/${stats.total} (${rate}%)`);
|
|
4969
5158
|
}
|
|
4970
5159
|
}
|
|
4971
5160
|
if (data.coverage) {
|
|
4972
5161
|
console.log();
|
|
4973
|
-
console.log(
|
|
4974
|
-
console.log(` \u8BED\u53E5: ${
|
|
5162
|
+
console.log(chalk8.white(" \u8986\u76D6\u7387:"));
|
|
5163
|
+
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))}`);
|
|
4975
5164
|
}
|
|
4976
5165
|
console.log();
|
|
4977
5166
|
}
|
|
@@ -4991,18 +5180,18 @@ async function openReport(reportPath) {
|
|
|
4991
5180
|
}
|
|
4992
5181
|
exec(command, { shell: true }, (error) => {
|
|
4993
5182
|
if (error) {
|
|
4994
|
-
console.log(
|
|
5183
|
+
console.log(chalk8.gray(" \u63D0\u793A: \u624B\u52A8\u6253\u5F00\u62A5\u544A\u67E5\u770B"));
|
|
4995
5184
|
}
|
|
4996
5185
|
});
|
|
4997
5186
|
}
|
|
4998
5187
|
|
|
4999
5188
|
// src/commands/visual.ts
|
|
5000
|
-
import
|
|
5189
|
+
import chalk9 from "chalk";
|
|
5001
5190
|
import ora7 from "ora";
|
|
5002
5191
|
|
|
5003
5192
|
// src/services/visual.ts
|
|
5004
5193
|
import fs13 from "fs";
|
|
5005
|
-
import
|
|
5194
|
+
import path15 from "path";
|
|
5006
5195
|
import pixelmatch from "pixelmatch";
|
|
5007
5196
|
import { PNG } from "pngjs";
|
|
5008
5197
|
function compareImages(baselinePath, currentPath, diffOutputPath, threshold = 0.1) {
|
|
@@ -5041,7 +5230,7 @@ function compareImages(baselinePath, currentPath, diffOutputPath, threshold = 0.
|
|
|
5041
5230
|
const passed = diffRatio <= threshold;
|
|
5042
5231
|
let diffPath;
|
|
5043
5232
|
if (diffPixels > 0) {
|
|
5044
|
-
const diffDir =
|
|
5233
|
+
const diffDir = path15.dirname(diffOutputPath);
|
|
5045
5234
|
if (!fs13.existsSync(diffDir)) {
|
|
5046
5235
|
fs13.mkdirSync(diffDir, { recursive: true });
|
|
5047
5236
|
}
|
|
@@ -5062,7 +5251,7 @@ function createBaseline(currentPath, baselinePath) {
|
|
|
5062
5251
|
if (!fs13.existsSync(currentPath)) {
|
|
5063
5252
|
throw new Error(`\u5F53\u524D\u622A\u56FE\u4E0D\u5B58\u5728: ${currentPath}`);
|
|
5064
5253
|
}
|
|
5065
|
-
const baselineDir =
|
|
5254
|
+
const baselineDir = path15.dirname(baselinePath);
|
|
5066
5255
|
if (!fs13.existsSync(baselineDir)) {
|
|
5067
5256
|
fs13.mkdirSync(baselineDir, { recursive: true });
|
|
5068
5257
|
}
|
|
@@ -5076,9 +5265,9 @@ function updateAllBaselines(currentDir, baselineDir) {
|
|
|
5076
5265
|
}
|
|
5077
5266
|
const files = fs13.readdirSync(currentDir).filter((f) => f.endsWith(".png"));
|
|
5078
5267
|
for (const file of files) {
|
|
5079
|
-
const currentPath =
|
|
5080
|
-
const baselinePath =
|
|
5081
|
-
const baselineDirAbs =
|
|
5268
|
+
const currentPath = path15.join(currentDir, file);
|
|
5269
|
+
const baselinePath = path15.join(baselineDir, file);
|
|
5270
|
+
const baselineDirAbs = path15.dirname(baselinePath);
|
|
5082
5271
|
if (!fs13.existsSync(baselineDirAbs)) {
|
|
5083
5272
|
fs13.mkdirSync(baselineDirAbs, { recursive: true });
|
|
5084
5273
|
}
|
|
@@ -5094,7 +5283,7 @@ function cleanBaselines(baselineDir) {
|
|
|
5094
5283
|
const files = fs13.readdirSync(baselineDir).filter((f) => f.endsWith(".png"));
|
|
5095
5284
|
let count = 0;
|
|
5096
5285
|
for (const file of files) {
|
|
5097
|
-
fs13.unlinkSync(
|
|
5286
|
+
fs13.unlinkSync(path15.join(baselineDir, file));
|
|
5098
5287
|
count++;
|
|
5099
5288
|
}
|
|
5100
5289
|
return count;
|
|
@@ -5106,7 +5295,7 @@ function cleanDiffs(diffDir) {
|
|
|
5106
5295
|
const files = fs13.readdirSync(diffDir).filter((f) => f.endsWith(".png"));
|
|
5107
5296
|
let count = 0;
|
|
5108
5297
|
for (const file of files) {
|
|
5109
|
-
fs13.unlinkSync(
|
|
5298
|
+
fs13.unlinkSync(path15.join(diffDir, file));
|
|
5110
5299
|
count++;
|
|
5111
5300
|
}
|
|
5112
5301
|
return count;
|
|
@@ -5118,9 +5307,9 @@ function compareDirectories(baselineDir, currentDir, diffDir, threshold = 0.1) {
|
|
|
5118
5307
|
}
|
|
5119
5308
|
const currentFiles = fs13.readdirSync(currentDir).filter((f) => f.endsWith(".png"));
|
|
5120
5309
|
for (const file of currentFiles) {
|
|
5121
|
-
const currentPath =
|
|
5122
|
-
const baselinePath =
|
|
5123
|
-
const diffPath =
|
|
5310
|
+
const currentPath = path15.join(currentDir, file);
|
|
5311
|
+
const baselinePath = path15.join(baselineDir, file);
|
|
5312
|
+
const diffPath = path15.join(diffDir, file);
|
|
5124
5313
|
if (!fs13.existsSync(baselinePath)) {
|
|
5125
5314
|
createBaseline(currentPath, baselinePath);
|
|
5126
5315
|
results.push({
|
|
@@ -5154,7 +5343,7 @@ function compareDirectories(baselineDir, currentDir, diffDir, threshold = 0.1) {
|
|
|
5154
5343
|
|
|
5155
5344
|
// src/commands/visual.ts
|
|
5156
5345
|
import fs14 from "fs";
|
|
5157
|
-
import
|
|
5346
|
+
import path16 from "path";
|
|
5158
5347
|
function registerVisualCommand(program2) {
|
|
5159
5348
|
program2.command("visual").description("\u89C6\u89C9\u56DE\u5F52\u6D4B\u8BD5 - \u622A\u56FE\u6BD4\u5BF9\u4E0E\u57FA\u7EBF\u7BA1\u7406").argument("<action>", "\u64CD\u4F5C\u7C7B\u578B (test|approve|clean)").option("--threshold <number>", "\u50CF\u7D20\u5DEE\u5F02\u9608\u503C (0-1)", "0.1").action(async (action, options) => {
|
|
5160
5349
|
try {
|
|
@@ -5164,7 +5353,7 @@ function registerVisualCommand(program2) {
|
|
|
5164
5353
|
config: options.config
|
|
5165
5354
|
});
|
|
5166
5355
|
} catch (error) {
|
|
5167
|
-
console.error(
|
|
5356
|
+
console.error(chalk9.red(`
|
|
5168
5357
|
\u2717 ${error instanceof Error ? error.message : String(error)}
|
|
5169
5358
|
`));
|
|
5170
5359
|
process.exit(1);
|
|
@@ -5190,9 +5379,9 @@ async function executeVisual(options) {
|
|
|
5190
5379
|
break;
|
|
5191
5380
|
}
|
|
5192
5381
|
default: {
|
|
5193
|
-
console.error(
|
|
5382
|
+
console.error(chalk9.red(`
|
|
5194
5383
|
\u672A\u77E5\u64CD\u4F5C: ${options.action}`));
|
|
5195
|
-
console.log(
|
|
5384
|
+
console.log(chalk9.gray(" \u53EF\u7528\u64CD\u4F5C: test, approve, clean\n"));
|
|
5196
5385
|
process.exit(1);
|
|
5197
5386
|
}
|
|
5198
5387
|
}
|
|
@@ -5216,7 +5405,7 @@ async function runVisualTest(threshold, baselineDir, diffDir, config) {
|
|
|
5216
5405
|
const currentDir = findCurrentScreenshotsDir(baselineDir);
|
|
5217
5406
|
if (!currentDir) {
|
|
5218
5407
|
compareSpinner.info("\u6CA1\u6709\u627E\u5230\u622A\u56FE\u6587\u4EF6");
|
|
5219
|
-
console.log(
|
|
5408
|
+
console.log(chalk9.gray("\n \u63D0\u793A: \u5148\u8FD0\u884C qat run -t visual \u751F\u6210\u622A\u56FE\n"));
|
|
5220
5409
|
return;
|
|
5221
5410
|
}
|
|
5222
5411
|
const results = compareDirectories(baselineDir, currentDir, diffDir, threshold);
|
|
@@ -5229,9 +5418,9 @@ async function runVisualTest(threshold, baselineDir, diffDir, config) {
|
|
|
5229
5418
|
}
|
|
5230
5419
|
function findCurrentScreenshotsDir(baselineDir) {
|
|
5231
5420
|
const possibleDirs = [
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
|
|
5421
|
+
path16.join(process.cwd(), "test-results"),
|
|
5422
|
+
path16.join(process.cwd(), "tests", "visual", "current"),
|
|
5423
|
+
path16.join(process.cwd(), baselineDir, "..", "current")
|
|
5235
5424
|
];
|
|
5236
5425
|
for (const dir of possibleDirs) {
|
|
5237
5426
|
if (fs14.existsSync(dir)) {
|
|
@@ -5247,7 +5436,7 @@ function findPngFiles(dir) {
|
|
|
5247
5436
|
if (!fs14.existsSync(d)) return;
|
|
5248
5437
|
const entries = fs14.readdirSync(d, { withFileTypes: true });
|
|
5249
5438
|
for (const entry of entries) {
|
|
5250
|
-
const fullPath =
|
|
5439
|
+
const fullPath = path16.join(d, entry.name);
|
|
5251
5440
|
if (entry.isDirectory() && entry.name !== "node_modules") {
|
|
5252
5441
|
walk(fullPath);
|
|
5253
5442
|
} else if (entry.name.endsWith(".png")) {
|
|
@@ -5262,48 +5451,48 @@ function displayVisualResults(results, threshold) {
|
|
|
5262
5451
|
const passed = results.filter((r) => r.passed);
|
|
5263
5452
|
const failed = results.filter((r) => !r.passed);
|
|
5264
5453
|
console.log();
|
|
5265
|
-
console.log(
|
|
5454
|
+
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"));
|
|
5266
5455
|
if (failed.length === 0) {
|
|
5267
|
-
console.log(
|
|
5456
|
+
console.log(chalk9.green(` \u2713 \u5168\u90E8\u901A\u8FC7 (${results.length} \u4E2A\u622A\u56FE\u6BD4\u5BF9)`));
|
|
5268
5457
|
} else {
|
|
5269
|
-
console.log(
|
|
5458
|
+
console.log(chalk9.red(` \u2717 ${failed.length} \u4E2A\u622A\u56FE\u5B58\u5728\u5DEE\u5F02`));
|
|
5270
5459
|
}
|
|
5271
5460
|
console.log();
|
|
5272
|
-
console.log(` ${
|
|
5273
|
-
console.log(` ${
|
|
5274
|
-
console.log(` ${
|
|
5461
|
+
console.log(` ${chalk9.white("\u9608\u503C:")} ${(threshold * 100).toFixed(0)}%`);
|
|
5462
|
+
console.log(` ${chalk9.green("\u901A\u8FC7:")} ${passed.length}`);
|
|
5463
|
+
console.log(` ${chalk9.red("\u5931\u8D25:")} ${failed.length}`);
|
|
5275
5464
|
if (passed.length > 0) {
|
|
5276
5465
|
console.log();
|
|
5277
|
-
console.log(
|
|
5466
|
+
console.log(chalk9.green(" \u901A\u8FC7\u7684\u622A\u56FE:"));
|
|
5278
5467
|
for (const result of passed) {
|
|
5279
|
-
const name =
|
|
5468
|
+
const name = path16.basename(result.baselinePath);
|
|
5280
5469
|
if (result.totalPixels > 0) {
|
|
5281
5470
|
const diffPct = (result.diffRatio * 100).toFixed(2);
|
|
5282
|
-
console.log(
|
|
5471
|
+
console.log(chalk9.green(` \u2713 ${name} (\u5DEE\u5F02: ${diffPct}%)`));
|
|
5283
5472
|
} else {
|
|
5284
|
-
console.log(
|
|
5473
|
+
console.log(chalk9.green(` \u2713 ${name} (\u65B0\u5EFA\u57FA\u7EBF)`));
|
|
5285
5474
|
}
|
|
5286
5475
|
}
|
|
5287
5476
|
}
|
|
5288
5477
|
if (failed.length > 0) {
|
|
5289
5478
|
console.log();
|
|
5290
|
-
console.log(
|
|
5479
|
+
console.log(chalk9.red(" \u5931\u8D25\u7684\u622A\u56FE:"));
|
|
5291
5480
|
for (const result of failed) {
|
|
5292
|
-
const name =
|
|
5481
|
+
const name = path16.basename(result.baselinePath);
|
|
5293
5482
|
if (result.diffPixels === -1) {
|
|
5294
|
-
console.log(
|
|
5483
|
+
console.log(chalk9.red(` \u2717 ${name} (\u5C3A\u5BF8\u4E0D\u5339\u914D)`));
|
|
5295
5484
|
} else {
|
|
5296
5485
|
const diffPct = (result.diffRatio * 100).toFixed(2);
|
|
5297
|
-
console.log(
|
|
5486
|
+
console.log(chalk9.red(` \u2717 ${name} (\u5DEE\u5F02: ${diffPct}%)`));
|
|
5298
5487
|
}
|
|
5299
5488
|
if (result.diffPath) {
|
|
5300
|
-
console.log(
|
|
5489
|
+
console.log(chalk9.gray(` \u5DEE\u5F02\u56FE: ${path16.relative(process.cwd(), result.diffPath)}`));
|
|
5301
5490
|
}
|
|
5302
5491
|
}
|
|
5303
5492
|
console.log();
|
|
5304
|
-
console.log(
|
|
5493
|
+
console.log(chalk9.yellow(" \u63D0\u793A: \u8FD0\u884C qat visual approve \u66F4\u65B0\u57FA\u7EBF"));
|
|
5305
5494
|
}
|
|
5306
|
-
console.log(
|
|
5495
|
+
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"));
|
|
5307
5496
|
console.log();
|
|
5308
5497
|
}
|
|
5309
5498
|
async function approveBaselines(baselineDir, diffDir) {
|
|
@@ -5322,7 +5511,7 @@ async function approveBaselines(baselineDir, diffDir) {
|
|
|
5322
5511
|
spinner.succeed(`\u5DF2\u66F4\u65B0 ${updated.length} \u4E2A\u57FA\u7EBF\u5FEB\u7167`);
|
|
5323
5512
|
console.log();
|
|
5324
5513
|
for (const file of updated) {
|
|
5325
|
-
console.log(
|
|
5514
|
+
console.log(chalk9.green(` \u2713 ${file}`));
|
|
5326
5515
|
}
|
|
5327
5516
|
console.log();
|
|
5328
5517
|
}
|
|
@@ -5332,19 +5521,19 @@ async function cleanAll(baselineDir, diffDir) {
|
|
|
5332
5521
|
const diffs = cleanDiffs(diffDir);
|
|
5333
5522
|
spinner.succeed("\u6E05\u7406\u5B8C\u6210");
|
|
5334
5523
|
console.log();
|
|
5335
|
-
console.log(
|
|
5524
|
+
console.log(chalk9.white(" \u5DF2\u5220\u9664:"));
|
|
5336
5525
|
console.log(` \u57FA\u7EBF: ${baselines} \u4E2A`);
|
|
5337
5526
|
console.log(` \u5DEE\u5F02: ${diffs} \u4E2A`);
|
|
5338
5527
|
console.log();
|
|
5339
5528
|
}
|
|
5340
5529
|
|
|
5341
5530
|
// src/commands/setup.ts
|
|
5342
|
-
import
|
|
5531
|
+
import chalk10 from "chalk";
|
|
5343
5532
|
import inquirer4 from "inquirer";
|
|
5344
5533
|
import ora8 from "ora";
|
|
5345
5534
|
import { execFile as execFile4 } from "child_process";
|
|
5346
5535
|
import fs15 from "fs";
|
|
5347
|
-
import
|
|
5536
|
+
import path17 from "path";
|
|
5348
5537
|
var DEPENDENCY_GROUPS = [
|
|
5349
5538
|
{
|
|
5350
5539
|
name: "Vitest (\u5355\u5143/\u7EC4\u4EF6/API\u6D4B\u8BD5)",
|
|
@@ -5368,7 +5557,7 @@ function registerSetupCommand(program2) {
|
|
|
5368
5557
|
try {
|
|
5369
5558
|
await executeSetup(options);
|
|
5370
5559
|
} catch (error) {
|
|
5371
|
-
console.error(
|
|
5560
|
+
console.error(chalk10.red(`
|
|
5372
5561
|
\u2717 ${error instanceof Error ? error.message : String(error)}
|
|
5373
5562
|
`));
|
|
5374
5563
|
process.exit(1);
|
|
@@ -5376,20 +5565,20 @@ function registerSetupCommand(program2) {
|
|
|
5376
5565
|
});
|
|
5377
5566
|
}
|
|
5378
5567
|
async function executeSetup(options) {
|
|
5379
|
-
console.log(
|
|
5568
|
+
console.log(chalk10.cyan("\n QAT \u4F9D\u8D56\u5B89\u88C5\u5668\n"));
|
|
5380
5569
|
const projectInfo = detectProject();
|
|
5381
|
-
if (!fs15.existsSync(
|
|
5570
|
+
if (!fs15.existsSync(path17.join(process.cwd(), "package.json"))) {
|
|
5382
5571
|
throw new Error("\u672A\u627E\u5230 package.json\uFF0C\u8BF7\u5728\u9879\u76EE\u6839\u76EE\u5F55\u6267\u884C\u6B64\u547D\u4EE4");
|
|
5383
5572
|
}
|
|
5384
5573
|
if (projectInfo.frameworkConfidence > 0) {
|
|
5385
|
-
console.log(
|
|
5574
|
+
console.log(chalk10.white(` \u68C0\u6D4B\u5230\u6846\u67B6: ${chalk10.cyan(projectInfo.frameworkDisplayName)}`));
|
|
5386
5575
|
if (projectInfo.uiLibrary !== "none") {
|
|
5387
|
-
console.log(
|
|
5576
|
+
console.log(chalk10.white(` UI \u7EC4\u4EF6\u5E93: ${chalk10.cyan(projectInfo.uiLibrary)}`));
|
|
5388
5577
|
}
|
|
5389
5578
|
if (projectInfo.monorepo !== "none") {
|
|
5390
|
-
console.log(
|
|
5579
|
+
console.log(chalk10.white(` Monorepo: ${chalk10.cyan(projectInfo.monorepo)}`));
|
|
5391
5580
|
if (projectInfo.appDirs.length > 0) {
|
|
5392
|
-
console.log(
|
|
5581
|
+
console.log(chalk10.white(` \u5B50\u9879\u76EE: ${chalk10.gray(projectInfo.appDirs.join(", "))}`));
|
|
5393
5582
|
}
|
|
5394
5583
|
}
|
|
5395
5584
|
console.log();
|
|
@@ -5412,8 +5601,8 @@ async function executeSetup(options) {
|
|
|
5412
5601
|
}
|
|
5413
5602
|
]);
|
|
5414
5603
|
if (chooseDir !== "root") {
|
|
5415
|
-
installDir =
|
|
5416
|
-
if (!fs15.existsSync(
|
|
5604
|
+
installDir = path17.join(process.cwd(), chooseDir);
|
|
5605
|
+
if (!fs15.existsSync(path17.join(installDir, "package.json"))) {
|
|
5417
5606
|
throw new Error(`${chooseDir} \u4E0B\u6CA1\u6709 package.json`);
|
|
5418
5607
|
}
|
|
5419
5608
|
}
|
|
@@ -5425,12 +5614,12 @@ async function executeSetup(options) {
|
|
|
5425
5614
|
}
|
|
5426
5615
|
const groupsToInstall = determineGroups(config, projectInfo, options.force);
|
|
5427
5616
|
if (groupsToInstall.length === 0) {
|
|
5428
|
-
console.log(
|
|
5617
|
+
console.log(chalk10.green(" \u2713 \u6240\u6709\u4F9D\u8D56\u5DF2\u5B89\u88C5\uFF0C\u65E0\u9700\u989D\u5916\u64CD\u4F5C\n"));
|
|
5429
5618
|
return;
|
|
5430
5619
|
}
|
|
5431
5620
|
const selectedGroups = await selectGroups(groupsToInstall, options.dryRun);
|
|
5432
5621
|
if (selectedGroups.length === 0) {
|
|
5433
|
-
console.log(
|
|
5622
|
+
console.log(chalk10.gray("\n \u5DF2\u53D6\u6D88\u5B89\u88C5\n"));
|
|
5434
5623
|
return;
|
|
5435
5624
|
}
|
|
5436
5625
|
const allPackages = selectedGroups.flatMap((g) => g.packages);
|
|
@@ -5444,19 +5633,19 @@ ${allPackages.map((p) => ` - ${p}`).join("\n")}`,
|
|
|
5444
5633
|
}
|
|
5445
5634
|
]);
|
|
5446
5635
|
if (!confirmed) {
|
|
5447
|
-
console.log(
|
|
5636
|
+
console.log(chalk10.gray("\n \u5DF2\u53D6\u6D88\u5B89\u88C5\n"));
|
|
5448
5637
|
return;
|
|
5449
5638
|
}
|
|
5450
5639
|
if (options.dryRun) {
|
|
5451
|
-
console.log(
|
|
5640
|
+
console.log(chalk10.yellow("\n [Dry Run] \u4EE5\u4E0B\u547D\u4EE4\u5C06\u88AB\u6267\u884C:\n"));
|
|
5452
5641
|
const pm = getPackageManager(projectInfo.packageManager);
|
|
5453
5642
|
const installCmd = pm === "npm" ? "npm install -D" : pm === "yarn" ? "yarn add -D" : pm === "pnpm" ? "pnpm add -D" : "bun add -D";
|
|
5454
5643
|
for (const group of selectedGroups) {
|
|
5455
|
-
const dirHint = installDir !== process.cwd() ? ` (\u5728 ${
|
|
5456
|
-
console.log(
|
|
5644
|
+
const dirHint = installDir !== process.cwd() ? ` (\u5728 ${path17.relative(process.cwd(), installDir) || installDir})` : "";
|
|
5645
|
+
console.log(chalk10.white(` ${installCmd} ${group.packages.join(" ")}${dirHint}`));
|
|
5457
5646
|
if (group.postInstall) {
|
|
5458
5647
|
for (const cmd of group.postInstall) {
|
|
5459
|
-
console.log(
|
|
5648
|
+
console.log(chalk10.white(` ${cmd}`));
|
|
5460
5649
|
}
|
|
5461
5650
|
}
|
|
5462
5651
|
}
|
|
@@ -5569,36 +5758,36 @@ ${stderr}` : "")));
|
|
|
5569
5758
|
});
|
|
5570
5759
|
}
|
|
5571
5760
|
function displaySetupResult(groups) {
|
|
5572
|
-
console.log(
|
|
5573
|
-
console.log(
|
|
5761
|
+
console.log(chalk10.green("\n \u2713 \u4F9D\u8D56\u5B89\u88C5\u5B8C\u6210!\n"));
|
|
5762
|
+
console.log(chalk10.white(" \u5DF2\u5B89\u88C5:"));
|
|
5574
5763
|
for (const group of groups) {
|
|
5575
|
-
console.log(
|
|
5764
|
+
console.log(chalk10.cyan(`
|
|
5576
5765
|
${group.name}`));
|
|
5577
5766
|
for (const pkg of group.packages) {
|
|
5578
|
-
console.log(
|
|
5767
|
+
console.log(chalk10.gray(` \u2713 ${pkg}`));
|
|
5579
5768
|
}
|
|
5580
5769
|
if (group.postInstall) {
|
|
5581
5770
|
for (const cmd of group.postInstall) {
|
|
5582
|
-
console.log(
|
|
5771
|
+
console.log(chalk10.gray(` \u2713 ${cmd}`));
|
|
5583
5772
|
}
|
|
5584
5773
|
}
|
|
5585
5774
|
}
|
|
5586
5775
|
console.log();
|
|
5587
|
-
console.log(
|
|
5588
|
-
console.log(
|
|
5589
|
-
console.log(
|
|
5776
|
+
console.log(chalk10.cyan(" \u4E0B\u4E00\u6B65:"));
|
|
5777
|
+
console.log(chalk10.gray(" 1. \u8FD0\u884C qat create \u521B\u5EFA\u6D4B\u8BD5\u7528\u4F8B"));
|
|
5778
|
+
console.log(chalk10.gray(" 2. \u8FD0\u884C qat run \u6267\u884C\u6D4B\u8BD5"));
|
|
5590
5779
|
console.log();
|
|
5591
5780
|
}
|
|
5592
5781
|
|
|
5593
5782
|
// src/commands/status.ts
|
|
5594
|
-
import
|
|
5783
|
+
import chalk11 from "chalk";
|
|
5595
5784
|
import ora9 from "ora";
|
|
5596
5785
|
function registerStatusCommand(program2) {
|
|
5597
5786
|
program2.command("status").description("\u67E5\u770B QAT \u72B6\u6001 - AI \u6A21\u578B\u4FE1\u606F\u3001\u9879\u76EE\u914D\u7F6E").action(async (options) => {
|
|
5598
5787
|
try {
|
|
5599
5788
|
await executeStatus(options);
|
|
5600
5789
|
} catch (error) {
|
|
5601
|
-
console.error(
|
|
5790
|
+
console.error(chalk11.red(`
|
|
5602
5791
|
\u2717 ${error instanceof Error ? error.message : String(error)}
|
|
5603
5792
|
`));
|
|
5604
5793
|
process.exit(1);
|
|
@@ -5606,24 +5795,24 @@ function registerStatusCommand(program2) {
|
|
|
5606
5795
|
});
|
|
5607
5796
|
}
|
|
5608
5797
|
async function executeStatus(_options) {
|
|
5609
|
-
console.log(
|
|
5610
|
-
console.log(
|
|
5798
|
+
console.log(chalk11.cyan("\n AI \u6A21\u578B\u72B6\u6001"));
|
|
5799
|
+
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"));
|
|
5611
5800
|
const globalAI = loadGlobalAIConfig();
|
|
5612
5801
|
if (!globalAI) {
|
|
5613
|
-
console.log(
|
|
5614
|
-
console.log(
|
|
5802
|
+
console.log(chalk11.yellow(" \u2717 \u672A\u914D\u7F6E AI \u6A21\u578B"));
|
|
5803
|
+
console.log(chalk11.gray(" \u8FD0\u884C qat change \u914D\u7F6E AI \u6A21\u578B"));
|
|
5615
5804
|
} else {
|
|
5616
|
-
console.log(` ${
|
|
5617
|
-
console.log(` ${
|
|
5618
|
-
console.log(` ${
|
|
5619
|
-
console.log(` ${
|
|
5620
|
-
console.log(` ${
|
|
5805
|
+
console.log(` ${chalk11.white("\u6A21\u578B:")} ${chalk11.green(globalAI.model)}`);
|
|
5806
|
+
console.log(` ${chalk11.white("API URL:")} ${chalk11.gray(globalAI.baseUrl)}`);
|
|
5807
|
+
console.log(` ${chalk11.white("API Key:")} ${chalk11.gray(maskApiKey(globalAI.apiKey))}`);
|
|
5808
|
+
console.log(` ${chalk11.white("Provider:")} ${chalk11.gray(globalAI.provider)}`);
|
|
5809
|
+
console.log(` ${chalk11.white("\u914D\u7F6E\u6587\u4EF6:")} ${chalk11.gray(getAIConfigPath())}`);
|
|
5621
5810
|
const testSpinner = ora9(" \u6B63\u5728\u6D4B\u8BD5\u8FDE\u901A\u6027...").start();
|
|
5622
5811
|
try {
|
|
5623
5812
|
const aiConfig = toAIConfig(globalAI);
|
|
5624
5813
|
const result = await testAIConnection(aiConfig);
|
|
5625
5814
|
if (result.ok) {
|
|
5626
|
-
testSpinner.succeed(` \u8FDE\u901A\u6B63\u5E38 ${
|
|
5815
|
+
testSpinner.succeed(` \u8FDE\u901A\u6B63\u5E38 ${chalk11.gray(`(${result.latencyMs}ms)`)}`);
|
|
5627
5816
|
} else {
|
|
5628
5817
|
testSpinner.fail(` \u8FDE\u901A\u5F02\u5E38: ${result.message}`);
|
|
5629
5818
|
}
|
|
@@ -5632,26 +5821,26 @@ async function executeStatus(_options) {
|
|
|
5632
5821
|
}
|
|
5633
5822
|
}
|
|
5634
5823
|
console.log();
|
|
5635
|
-
console.log(
|
|
5636
|
-
console.log(
|
|
5824
|
+
console.log(chalk11.cyan(" \u9879\u76EE\u914D\u7F6E"));
|
|
5825
|
+
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"));
|
|
5637
5826
|
try {
|
|
5638
5827
|
const config = await loadConfig();
|
|
5639
5828
|
const projectInfo = detectProject();
|
|
5640
|
-
console.log(` ${
|
|
5641
|
-
console.log(` ${
|
|
5642
|
-
console.log(` ${
|
|
5643
|
-
console.log(` ${
|
|
5644
|
-
console.log(` ${
|
|
5645
|
-
console.log(` ${
|
|
5646
|
-
console.log(` ${
|
|
5829
|
+
console.log(` ${chalk11.white("\u6846\u67B6:")} ${projectInfo.frameworkDisplayName}`);
|
|
5830
|
+
console.log(` ${chalk11.white("\u6E90\u7801\u76EE\u5F55:")} ${config.project.srcDir}`);
|
|
5831
|
+
console.log(` ${chalk11.white("Vitest:")} ${config.vitest.enabled ? chalk11.green("\u2713") : chalk11.red("\u2717")} (${config.vitest.environment})`);
|
|
5832
|
+
console.log(` ${chalk11.white("Playwright:")} ${config.playwright.enabled ? chalk11.green("\u2713") : chalk11.red("\u2717")} (${config.playwright.browsers.join(", ")})`);
|
|
5833
|
+
console.log(` ${chalk11.white("Mock:")} ${config.mock.enabled ? chalk11.green("\u2713") : chalk11.red("\u2717")} (port ${config.mock.port})`);
|
|
5834
|
+
console.log(` ${chalk11.white("Visual:")} ${config.visual.enabled ? chalk11.green("\u2713") : chalk11.red("\u2717")}`);
|
|
5835
|
+
console.log(` ${chalk11.white("Lighthouse:")} ${config.lighthouse.enabled ? chalk11.green("\u2713") : chalk11.red("\u2717")}`);
|
|
5647
5836
|
} catch {
|
|
5648
|
-
console.log(
|
|
5837
|
+
console.log(chalk11.yellow(" \u2717 \u672A\u627E\u5230\u9879\u76EE\u914D\u7F6E (\u8FD0\u884C qat init \u521D\u59CB\u5316)"));
|
|
5649
5838
|
}
|
|
5650
5839
|
console.log();
|
|
5651
5840
|
}
|
|
5652
5841
|
|
|
5653
5842
|
// src/commands/change.ts
|
|
5654
|
-
import
|
|
5843
|
+
import chalk12 from "chalk";
|
|
5655
5844
|
import inquirer5 from "inquirer";
|
|
5656
5845
|
import ora10 from "ora";
|
|
5657
5846
|
function registerChangeCommand(program2) {
|
|
@@ -5659,7 +5848,7 @@ function registerChangeCommand(program2) {
|
|
|
5659
5848
|
try {
|
|
5660
5849
|
await executeChange(options);
|
|
5661
5850
|
} catch (error) {
|
|
5662
|
-
console.error(
|
|
5851
|
+
console.error(chalk12.red(`
|
|
5663
5852
|
\u2717 ${error instanceof Error ? error.message : String(error)}
|
|
5664
5853
|
`));
|
|
5665
5854
|
process.exit(1);
|
|
@@ -5669,13 +5858,13 @@ function registerChangeCommand(program2) {
|
|
|
5669
5858
|
async function executeChange(_options) {
|
|
5670
5859
|
const current = loadGlobalAIConfig();
|
|
5671
5860
|
if (current) {
|
|
5672
|
-
console.log(
|
|
5673
|
-
console.log(` ${
|
|
5674
|
-
console.log(` ${
|
|
5675
|
-
console.log(` ${
|
|
5861
|
+
console.log(chalk12.cyan("\n \u5F53\u524D AI \u914D\u7F6E:"));
|
|
5862
|
+
console.log(` ${chalk12.white("\u6A21\u578B:")} ${chalk12.green(current.model)}`);
|
|
5863
|
+
console.log(` ${chalk12.white("API URL:")} ${chalk12.gray(current.baseUrl)}`);
|
|
5864
|
+
console.log(` ${chalk12.white("API Key:")} ${chalk12.gray(maskApiKey(current.apiKey))}`);
|
|
5676
5865
|
console.log();
|
|
5677
5866
|
} else {
|
|
5678
|
-
console.log(
|
|
5867
|
+
console.log(chalk12.yellow("\n \u5F53\u524D\u672A\u914D\u7F6E AI \u6A21\u578B\uFF0C\u8BF7\u914D\u7F6E:\n"));
|
|
5679
5868
|
}
|
|
5680
5869
|
const answers = await inquirer5.prompt([
|
|
5681
5870
|
{
|
|
@@ -5713,16 +5902,16 @@ async function executeChange(_options) {
|
|
|
5713
5902
|
model: answers.model?.trim() || "deepseek-chat"
|
|
5714
5903
|
};
|
|
5715
5904
|
saveGlobalAIConfig(newConfig);
|
|
5716
|
-
console.log(
|
|
5905
|
+
console.log(chalk12.green(`
|
|
5717
5906
|
\u2713 AI \u914D\u7F6E\u5DF2\u4FDD\u5B58`));
|
|
5718
|
-
console.log(
|
|
5719
|
-
console.log(` ${
|
|
5907
|
+
console.log(chalk12.gray(` ${getAIConfigPath()}`));
|
|
5908
|
+
console.log(` ${chalk12.white("\u6A21\u578B:")} ${chalk12.green(newConfig.model)} @ ${chalk12.gray(newConfig.baseUrl)}`);
|
|
5720
5909
|
const testSpinner = ora10(" \u6B63\u5728\u6D4B\u8BD5\u8FDE\u901A\u6027...").start();
|
|
5721
5910
|
try {
|
|
5722
5911
|
const aiConfig = toAIConfig(newConfig);
|
|
5723
5912
|
const result = await testAIConnection(aiConfig);
|
|
5724
5913
|
if (result.ok) {
|
|
5725
|
-
testSpinner.succeed(` AI \u8FDE\u901A\u6B63\u5E38 ${
|
|
5914
|
+
testSpinner.succeed(` AI \u8FDE\u901A\u6B63\u5E38 ${chalk12.gray(`(${newConfig.model}, ${result.latencyMs}ms)`)}`);
|
|
5726
5915
|
} else {
|
|
5727
5916
|
testSpinner.fail(` AI \u8FDE\u901A\u5F02\u5E38: ${result.message}`);
|
|
5728
5917
|
}
|
|
@@ -5733,37 +5922,40 @@ async function executeChange(_options) {
|
|
|
5733
5922
|
}
|
|
5734
5923
|
|
|
5735
5924
|
// src/cli.ts
|
|
5736
|
-
var VERSION = "0.3.
|
|
5925
|
+
var VERSION = "0.3.05";
|
|
5737
5926
|
function printLogo() {
|
|
5738
5927
|
const logo = `
|
|
5739
|
-
${
|
|
5740
|
-
${
|
|
5741
|
-
${
|
|
5742
|
-
${
|
|
5743
|
-
${
|
|
5744
|
-
${
|
|
5745
|
-
${
|
|
5928
|
+
${chalk13.bold.cyan(" ___ _ _ _ _ _____ _ _ ")}
|
|
5929
|
+
${chalk13.bold.cyan(" / _ \\ _ _ (_) ___ | | __ / \\ _ _ | |_ ___ |_ _| ___ ___ | |_ (_) _ __ __ _ ")}
|
|
5930
|
+
${chalk13.bold.cyan(" | | | | | | | | | | / __| | |/ / / _ \\ | | | | | __| / _ \\ | | / _ \\ / __| | __| | | | '_ \\ / _` |")}
|
|
5931
|
+
${chalk13.bold.cyan(" | |_| | | |_| | | | | (__ | < / ___ \\ | |_| | | |_ | (_) | | | | __/ \\__ \\ | |_ | | | | | | | (_| |")}
|
|
5932
|
+
${chalk13.bold.cyan(" \\__\\_\\ \\__,_| |_| \\___| |_|\\_\\ /_/ \\_\\ \\__,_| \\__| \\___/ |_| \\___| |___/ \\__| |_| |_| |_| \\__, |")}
|
|
5933
|
+
${chalk13.bold.cyan(" |___/ ")}
|
|
5934
|
+
${chalk13.gray(" CLI\u81EA\u52A8\u5316\u6D4B\u8BD5\u5DE5\u5177 v")}${chalk13.green(VERSION)}
|
|
5746
5935
|
`;
|
|
5747
5936
|
console.log(logo);
|
|
5748
5937
|
}
|
|
5749
5938
|
var program = new Command();
|
|
5750
|
-
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) => {
|
|
5939
|
+
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) => {
|
|
5751
5940
|
printLogo();
|
|
5752
5941
|
const opts = thisCommand.opts();
|
|
5753
5942
|
if (opts.verbose) {
|
|
5754
5943
|
process.env.QAT_VERBOSE = "true";
|
|
5755
5944
|
}
|
|
5945
|
+
if (opts.debug) {
|
|
5946
|
+
process.env.QAT_DEBUG = "true";
|
|
5947
|
+
}
|
|
5756
5948
|
if (opts.config) {
|
|
5757
5949
|
process.env.QAT_CONFIG_PATH = opts.config;
|
|
5758
5950
|
}
|
|
5759
5951
|
const { loadExternalFrameworks } = await import("./framework-registry-YGZ63RDX.js");
|
|
5760
5952
|
const loadResult = await loadExternalFrameworks(process.cwd());
|
|
5761
5953
|
if (loadResult.loaded > 0 && opts.verbose) {
|
|
5762
|
-
console.log(
|
|
5954
|
+
console.log(chalk13.gray(` [ext] \u5DF2\u52A0\u8F7D ${loadResult.loaded} \u4E2A\u5916\u90E8\u6269\u5C55\u6587\u4EF6: ${loadResult.files.join(", ")}`));
|
|
5763
5955
|
}
|
|
5764
5956
|
if (loadResult.errors.length > 0) {
|
|
5765
5957
|
for (const err of loadResult.errors) {
|
|
5766
|
-
console.log(
|
|
5958
|
+
console.log(chalk13.yellow(` [ext] \u8B66\u544A: ${err.file} - ${err.error}`));
|
|
5767
5959
|
}
|
|
5768
5960
|
}
|
|
5769
5961
|
});
|
|
@@ -5777,9 +5969,9 @@ registerSetupCommand(program);
|
|
|
5777
5969
|
registerStatusCommand(program);
|
|
5778
5970
|
registerChangeCommand(program);
|
|
5779
5971
|
program.on("command:*", (operands) => {
|
|
5780
|
-
console.error(
|
|
5972
|
+
console.error(chalk13.red(`
|
|
5781
5973
|
\u672A\u77E5\u547D\u4EE4: ${operands[0]}`));
|
|
5782
|
-
console.log(
|
|
5974
|
+
console.log(chalk13.gray(` \u4F7F\u7528 qat --help \u67E5\u770B\u53EF\u7528\u547D\u4EE4
|
|
5783
5975
|
`));
|
|
5784
5976
|
process.exit(1);
|
|
5785
5977
|
});
|