qat-cli 0.3.5 → 0.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cli.js +127 -55
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +5 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
**Quick Auto Testing — 面向 Vue 项目,集成 Vitest & Playwright,AI 驱动覆盖测试全流程**
|
|
6
6
|
|
|
7
|
-
[](https://www.npmjs.com/package/qat-cli)
|
|
8
8
|
[](https://nodejs.org/)
|
|
9
9
|
[](https://opensource.org/licenses/MIT)
|
|
10
10
|
[](https://www.typescriptlang.org/)
|
package/dist/cli.js
CHANGED
|
@@ -1156,6 +1156,8 @@ ${this.compressSourceCode(req.context, 3e3, importPathRewrites)}
|
|
|
1156
1156
|
if (srcDirMatch) {
|
|
1157
1157
|
variations.add(`@/${srcDirMatch[1]}`);
|
|
1158
1158
|
variations.add(`@/${srcDirMatch[1].replace(/\.(ts|js|tsx|jsx)$/, "")}`);
|
|
1159
|
+
variations.add(`#/${srcDirMatch[1]}`);
|
|
1160
|
+
variations.add(`#/${srcDirMatch[1].replace(/\.(ts|js|tsx|jsx)$/, "")}`);
|
|
1159
1161
|
}
|
|
1160
1162
|
const pathParts = targetPath.replace(/^\.\//, "").split("/");
|
|
1161
1163
|
const fileName = pathParts[pathParts.length - 1];
|
|
@@ -1890,7 +1892,7 @@ function extractImports(content) {
|
|
|
1890
1892
|
function analyzeImportSignatures(imports, baseDir) {
|
|
1891
1893
|
const signatures = [];
|
|
1892
1894
|
for (const imp of imports) {
|
|
1893
|
-
if (!imp.source.startsWith(".") && !imp.source.startsWith("@/") && !imp.source.startsWith("~")) {
|
|
1895
|
+
if (!imp.source.startsWith(".") && !imp.source.startsWith("@/") && !imp.source.startsWith("#/") && !imp.source.startsWith("~")) {
|
|
1894
1896
|
continue;
|
|
1895
1897
|
}
|
|
1896
1898
|
const resolvedPath = resolveImportPath(imp.source, baseDir);
|
|
@@ -1932,8 +1934,8 @@ function analyzeImportSignatures(imports, baseDir) {
|
|
|
1932
1934
|
}
|
|
1933
1935
|
function resolveImportPath(importSource, baseDir) {
|
|
1934
1936
|
let resolved;
|
|
1935
|
-
if (importSource.startsWith("@/") || importSource.startsWith("~")) {
|
|
1936
|
-
const relativePath = importSource.replace(/^[
|
|
1937
|
+
if (importSource.startsWith("@/") || importSource.startsWith("#/") || importSource.startsWith("~")) {
|
|
1938
|
+
const relativePath = importSource.replace(/^[@#~]\//, "src/");
|
|
1937
1939
|
resolved = path4.resolve(process.cwd(), relativePath);
|
|
1938
1940
|
} else if (importSource.startsWith(".")) {
|
|
1939
1941
|
resolved = path4.resolve(baseDir, importSource);
|
|
@@ -2914,59 +2916,101 @@ async function executeCreate(options) {
|
|
|
2914
2916
|
]);
|
|
2915
2917
|
useAI = ai;
|
|
2916
2918
|
}
|
|
2919
|
+
const tasks = [];
|
|
2920
|
+
for (const testType of types) {
|
|
2921
|
+
for (const testTarget of targets) {
|
|
2922
|
+
tasks.push({ testType, target: testTarget });
|
|
2923
|
+
}
|
|
2924
|
+
}
|
|
2917
2925
|
const createdFiles = [];
|
|
2918
2926
|
let skippedCount = 0;
|
|
2919
2927
|
const reviewReportEntries = [];
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2928
|
+
if (useAI && tasks.length > 0) {
|
|
2929
|
+
const spinner = ora3(`\u5E76\u884C\u751F\u6210 ${tasks.length} \u4E2A\u6D4B\u8BD5\u7528\u4F8B...`).start();
|
|
2930
|
+
let completed = 0;
|
|
2931
|
+
let approvedCount = 0;
|
|
2932
|
+
let failedCount = 0;
|
|
2933
|
+
const results = await Promise.allSettled(
|
|
2934
|
+
tasks.map(async (task) => {
|
|
2935
|
+
const result = await generateWithAI(task.testType, name, task.target, config, (text) => {
|
|
2936
|
+
completed++;
|
|
2937
|
+
spinner.text = `\u5E76\u884C\u751F\u6210\u4E2D (${completed}/${tasks.length}) ${text}`;
|
|
2938
|
+
});
|
|
2939
|
+
return { ...task, ...result };
|
|
2940
|
+
})
|
|
2941
|
+
);
|
|
2942
|
+
spinner.stop();
|
|
2943
|
+
for (let i = 0; i < results.length; i++) {
|
|
2944
|
+
const r = results[i];
|
|
2945
|
+
const task = tasks[i];
|
|
2946
|
+
if (r.status === "rejected") {
|
|
2947
|
+
console.log(chalk5.red(` \u2717 ${TEST_TYPE_LABELS[task.testType]} - ${path9.basename(task.target)}: ${r.reason instanceof Error ? r.reason.message : String(r.reason)}`));
|
|
2948
|
+
failedCount++;
|
|
2949
|
+
continue;
|
|
2950
|
+
}
|
|
2951
|
+
const { code, reviewEntry } = r.value;
|
|
2952
|
+
const outputDir = getTestOutputDir(task.testType);
|
|
2953
|
+
const filePath = path9.join(outputDir, `${name}.${task.testType === "e2e" ? "spec" : "test"}.ts`);
|
|
2954
|
+
if (fs8.existsSync(filePath)) {
|
|
2955
|
+
console.log(chalk5.yellow(` \u26A0 \u6D4B\u8BD5\u6587\u4EF6\u5DF2\u5B58\u5728: ${path9.relative(process.cwd(), filePath)}`));
|
|
2956
|
+
skippedCount++;
|
|
2957
|
+
continue;
|
|
2958
|
+
}
|
|
2959
|
+
if (!fs8.existsSync(outputDir)) {
|
|
2960
|
+
fs8.mkdirSync(outputDir, { recursive: true });
|
|
2961
|
+
}
|
|
2962
|
+
fs8.writeFileSync(filePath, code, "utf-8");
|
|
2963
|
+
createdFiles.push({ type: task.testType, filePath });
|
|
2964
|
+
if (reviewEntry) {
|
|
2965
|
+
reviewReportEntries.push(reviewEntry);
|
|
2966
|
+
if (reviewEntry.approved) {
|
|
2967
|
+
approvedCount++;
|
|
2933
2968
|
} else {
|
|
2934
|
-
|
|
2935
|
-
content = renderTemplate(testType, {
|
|
2936
|
-
name,
|
|
2937
|
-
target: testTarget || `./${config.project.srcDir}`,
|
|
2938
|
-
framework: projectInfo.framework,
|
|
2939
|
-
vueVersion: projectInfo.vueVersion,
|
|
2940
|
-
typescript: projectInfo.typescript,
|
|
2941
|
-
uiLibrary: projectInfo.uiLibrary,
|
|
2942
|
-
extraImports: projectInfo.componentTestSetup?.extraImports,
|
|
2943
|
-
globalPlugins: projectInfo.componentTestSetup?.globalPlugins,
|
|
2944
|
-
globalStubs: projectInfo.componentTestSetup?.globalStubs,
|
|
2945
|
-
mountOptions: projectInfo.componentTestSetup?.mountOptions,
|
|
2946
|
-
// 注入源码分析结果
|
|
2947
|
-
exports: analysis?.exports.map((e) => ({
|
|
2948
|
-
name: e.name,
|
|
2949
|
-
kind: e.kind,
|
|
2950
|
-
params: e.params,
|
|
2951
|
-
isAsync: e.isAsync,
|
|
2952
|
-
returnType: e.returnType
|
|
2953
|
-
})),
|
|
2954
|
-
props: analysis?.vueAnalysis?.props.map((p) => ({
|
|
2955
|
-
name: p.name,
|
|
2956
|
-
type: p.type,
|
|
2957
|
-
required: p.required,
|
|
2958
|
-
defaultValue: p.defaultValue
|
|
2959
|
-
})),
|
|
2960
|
-
emits: analysis?.vueAnalysis?.emits.map((e) => ({
|
|
2961
|
-
name: e.name,
|
|
2962
|
-
params: e.params
|
|
2963
|
-
})),
|
|
2964
|
-
methods: analysis?.vueAnalysis?.methods,
|
|
2965
|
-
computed: analysis?.vueAnalysis?.computed
|
|
2966
|
-
});
|
|
2969
|
+
failedCount++;
|
|
2967
2970
|
}
|
|
2968
|
-
|
|
2969
|
-
|
|
2971
|
+
}
|
|
2972
|
+
}
|
|
2973
|
+
console.log(chalk5.cyan(`
|
|
2974
|
+
\u5E76\u884C\u751F\u6210\u5B8C\u6210: ${createdFiles.length} \u6210\u529F, ${skippedCount} \u8DF3\u8FC7, ${failedCount} \u5BA1\u8BA1\u672A\u901A\u8FC7`));
|
|
2975
|
+
} else {
|
|
2976
|
+
for (const task of tasks) {
|
|
2977
|
+
const spinner = ora3(`\u6B63\u5728\u751F\u6210 ${TEST_TYPE_LABELS[task.testType]} - ${path9.basename(task.target)}...`).start();
|
|
2978
|
+
try {
|
|
2979
|
+
const analysis = task.target ? analyzeFile(task.target) : void 0;
|
|
2980
|
+
const content = renderTemplate(task.testType, {
|
|
2981
|
+
name,
|
|
2982
|
+
target: task.target || `./${config.project.srcDir}`,
|
|
2983
|
+
framework: projectInfo.framework,
|
|
2984
|
+
vueVersion: projectInfo.vueVersion,
|
|
2985
|
+
typescript: projectInfo.typescript,
|
|
2986
|
+
uiLibrary: projectInfo.uiLibrary,
|
|
2987
|
+
extraImports: projectInfo.componentTestSetup?.extraImports,
|
|
2988
|
+
globalPlugins: projectInfo.componentTestSetup?.globalPlugins,
|
|
2989
|
+
globalStubs: projectInfo.componentTestSetup?.globalStubs,
|
|
2990
|
+
mountOptions: projectInfo.componentTestSetup?.mountOptions,
|
|
2991
|
+
// 注入源码分析结果
|
|
2992
|
+
exports: analysis?.exports.map((e) => ({
|
|
2993
|
+
name: e.name,
|
|
2994
|
+
kind: e.kind,
|
|
2995
|
+
params: e.params,
|
|
2996
|
+
isAsync: e.isAsync,
|
|
2997
|
+
returnType: e.returnType
|
|
2998
|
+
})),
|
|
2999
|
+
props: analysis?.vueAnalysis?.props.map((p) => ({
|
|
3000
|
+
name: p.name,
|
|
3001
|
+
type: p.type,
|
|
3002
|
+
required: p.required,
|
|
3003
|
+
defaultValue: p.defaultValue
|
|
3004
|
+
})),
|
|
3005
|
+
emits: analysis?.vueAnalysis?.emits.map((e) => ({
|
|
3006
|
+
name: e.name,
|
|
3007
|
+
params: e.params
|
|
3008
|
+
})),
|
|
3009
|
+
methods: analysis?.vueAnalysis?.methods,
|
|
3010
|
+
computed: analysis?.vueAnalysis?.computed
|
|
3011
|
+
});
|
|
3012
|
+
const outputDir = getTestOutputDir(task.testType);
|
|
3013
|
+
const filePath = path9.join(outputDir, `${name}.${task.testType === "e2e" ? "spec" : "test"}.ts`);
|
|
2970
3014
|
if (fs8.existsSync(filePath)) {
|
|
2971
3015
|
spinner.warn(`\u6D4B\u8BD5\u6587\u4EF6\u5DF2\u5B58\u5728: ${path9.relative(process.cwd(), filePath)}`);
|
|
2972
3016
|
skippedCount++;
|
|
@@ -2976,10 +3020,10 @@ async function executeCreate(options) {
|
|
|
2976
3020
|
fs8.mkdirSync(outputDir, { recursive: true });
|
|
2977
3021
|
}
|
|
2978
3022
|
fs8.writeFileSync(filePath, content, "utf-8");
|
|
2979
|
-
spinner.succeed(`${TEST_TYPE_LABELS[testType]} - ${path9.basename(
|
|
2980
|
-
createdFiles.push({ type: testType, filePath });
|
|
3023
|
+
spinner.succeed(`${TEST_TYPE_LABELS[task.testType]} - ${path9.basename(task.target)}`);
|
|
3024
|
+
createdFiles.push({ type: task.testType, filePath });
|
|
2981
3025
|
} catch (error) {
|
|
2982
|
-
spinner.fail(`${TEST_TYPE_LABELS[testType]} - ${path9.basename(
|
|
3026
|
+
spinner.fail(`${TEST_TYPE_LABELS[task.testType]} - ${path9.basename(task.target)} \u521B\u5EFA\u5931\u8D25`);
|
|
2983
3027
|
console.log(chalk5.gray(` ${error instanceof Error ? error.message : String(error)}`));
|
|
2984
3028
|
}
|
|
2985
3029
|
}
|
|
@@ -2988,6 +3032,34 @@ async function executeCreate(options) {
|
|
|
2988
3032
|
if (reviewReportEntries.length > 0) {
|
|
2989
3033
|
printReviewReport(reviewReportEntries);
|
|
2990
3034
|
}
|
|
3035
|
+
const failedEntries = reviewReportEntries.filter((e) => !e.approved);
|
|
3036
|
+
if (failedEntries.length > 0 && useAI) {
|
|
3037
|
+
const { retry } = await inquirer2.prompt([
|
|
3038
|
+
{
|
|
3039
|
+
type: "confirm",
|
|
3040
|
+
name: "retry",
|
|
3041
|
+
message: `${failedEntries.length} \u4E2A\u6D4B\u8BD5\u7528\u4F8B\u5BA1\u8BA1\u672A\u901A\u8FC7\uFF0C\u662F\u5426\u91CD\u65B0\u751F\u6210\uFF1F`,
|
|
3042
|
+
default: false
|
|
3043
|
+
}
|
|
3044
|
+
]);
|
|
3045
|
+
if (retry) {
|
|
3046
|
+
console.log(chalk5.cyan("\n \u91CD\u65B0\u751F\u6210\u672A\u901A\u8FC7\u7684\u6D4B\u8BD5\u7528\u4F8B...\n"));
|
|
3047
|
+
for (const entry of failedEntries) {
|
|
3048
|
+
const spinner = ora3(`\u91CD\u65B0\u751F\u6210 ${path9.basename(entry.target)}...`).start();
|
|
3049
|
+
try {
|
|
3050
|
+
const aiResult = await generateWithAI(entry.testType, name, entry.target, config, (text) => {
|
|
3051
|
+
spinner.text = text;
|
|
3052
|
+
});
|
|
3053
|
+
const outputDir = getTestOutputDir(entry.testType);
|
|
3054
|
+
const filePath = path9.join(outputDir, `${name}.${entry.testType === "e2e" ? "spec" : "test"}.ts`);
|
|
3055
|
+
fs8.writeFileSync(filePath, aiResult.code, "utf-8");
|
|
3056
|
+
spinner.succeed(`${path9.basename(entry.target)} \u5DF2\u91CD\u65B0\u751F\u6210${aiResult.reviewEntry?.approved ? chalk5.green(" (\u5BA1\u8BA1\u901A\u8FC7)") : chalk5.yellow(" (\u4ECD\u9700\u5BA1\u67E5)")}`);
|
|
3057
|
+
} catch (error) {
|
|
3058
|
+
spinner.fail(`${path9.basename(entry.target)} \u91CD\u65B0\u751F\u6210\u5931\u8D25`);
|
|
3059
|
+
}
|
|
3060
|
+
}
|
|
3061
|
+
}
|
|
3062
|
+
}
|
|
2991
3063
|
}
|
|
2992
3064
|
async function selectTargets(types, projectInfo, srcDir) {
|
|
2993
3065
|
const allTargets = [];
|
|
@@ -5922,7 +5994,7 @@ async function executeChange(_options) {
|
|
|
5922
5994
|
}
|
|
5923
5995
|
|
|
5924
5996
|
// src/cli.ts
|
|
5925
|
-
var VERSION = "0.3.
|
|
5997
|
+
var VERSION = "0.3.06";
|
|
5926
5998
|
function printLogo() {
|
|
5927
5999
|
const logo = `
|
|
5928
6000
|
${chalk13.bold.cyan(" ___ _ _ _ _ _____ _ _ ")}
|