qat-cli 0.2.4 → 0.2.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 +101 -5
- package/dist/cli.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
|
@@ -2470,11 +2470,22 @@ async function autoGenerateTests(config, projectInfo, aiConfig, useAI) {
|
|
|
2470
2470
|
for (const utilPath of utilities.slice(0, 30)) {
|
|
2471
2471
|
allTargets.push({ filePath: utilPath, testType: inferTestType(utilPath) });
|
|
2472
2472
|
}
|
|
2473
|
-
const
|
|
2473
|
+
const selectedTargets = await selectTargetFiles(allTargets);
|
|
2474
|
+
if (selectedTargets.length === 0) {
|
|
2475
|
+
console.log(chalk3.gray("\n \u672A\u9009\u62E9\u4EFB\u4F55\u6587\u4EF6\uFF0C\u8DF3\u8FC7\u6D4B\u8BD5\u7528\u4F8B\u751F\u6210\u3002"));
|
|
2476
|
+
return [];
|
|
2477
|
+
}
|
|
2478
|
+
const total = selectedTargets.length;
|
|
2479
|
+
const genSpinner = ora2(`\u6B63\u5728\u751F\u6210\u6D4B\u8BD5\u7528\u4F8B [0/${total}] ...`).start();
|
|
2474
2480
|
const generatedFiles = [];
|
|
2475
2481
|
const typeCount = {};
|
|
2476
2482
|
const reviewReport = [];
|
|
2477
|
-
|
|
2483
|
+
let current = 0;
|
|
2484
|
+
let failed = 0;
|
|
2485
|
+
for (const { filePath, testType } of selectedTargets) {
|
|
2486
|
+
current++;
|
|
2487
|
+
const fileLabel = path7.basename(filePath);
|
|
2488
|
+
genSpinner.text = `\u6B63\u5728\u751F\u6210\u6D4B\u8BD5\u7528\u4F8B [${current}/${total}] ${chalk3.cyan(fileLabel)} ...`;
|
|
2478
2489
|
try {
|
|
2479
2490
|
const result = await generateTestForTarget(
|
|
2480
2491
|
testType,
|
|
@@ -2492,20 +2503,105 @@ async function autoGenerateTests(config, projectInfo, aiConfig, useAI) {
|
|
|
2492
2503
|
}
|
|
2493
2504
|
}
|
|
2494
2505
|
} catch {
|
|
2506
|
+
failed++;
|
|
2495
2507
|
}
|
|
2496
2508
|
}
|
|
2497
2509
|
if (generatedFiles.length > 0) {
|
|
2498
2510
|
const summary = Object.entries(typeCount).map(([type, count]) => `${count} ${type}`).join(", ");
|
|
2499
2511
|
const approvedCount = reviewReport.filter((r) => r.approved).length;
|
|
2500
|
-
|
|
2512
|
+
let msg = `\u5DF2\u751F\u6210 ${generatedFiles.length}/${total} \u4E2A\u6D4B\u8BD5\u7528\u4F8B (${summary}) \u2014 AI \u5BA1\u8BA1 ${approvedCount}/${reviewReport.length} \u901A\u8FC7`;
|
|
2513
|
+
if (failed > 0) msg += chalk3.yellow(` ${failed} \u4E2A\u5931\u8D25`);
|
|
2514
|
+
genSpinner.succeed(msg);
|
|
2501
2515
|
} else {
|
|
2502
|
-
genSpinner.warn(
|
|
2516
|
+
genSpinner.warn(`\u672A\u751F\u6210\u6D4B\u8BD5\u7528\u4F8B (${failed} \u4E2A\u5931\u8D25)`);
|
|
2503
2517
|
}
|
|
2504
2518
|
if (reviewReport.length > 0) {
|
|
2505
2519
|
printReviewReport(reviewReport);
|
|
2506
2520
|
}
|
|
2507
2521
|
return generatedFiles;
|
|
2508
2522
|
}
|
|
2523
|
+
async function selectTargetFiles(allTargets) {
|
|
2524
|
+
const testTypeLabels = {
|
|
2525
|
+
unit: chalk3.blue("[unit]"),
|
|
2526
|
+
component: chalk3.magenta("[comp]"),
|
|
2527
|
+
e2e: chalk3.green("[e2e]"),
|
|
2528
|
+
api: chalk3.yellow("[api]"),
|
|
2529
|
+
visual: chalk3.cyan("[visual]"),
|
|
2530
|
+
performance: chalk3.gray("[perf]")
|
|
2531
|
+
};
|
|
2532
|
+
const choices = allTargets.map(({ filePath, testType }) => ({
|
|
2533
|
+
name: `${testTypeLabels[testType]} ${filePath}`,
|
|
2534
|
+
value: filePath,
|
|
2535
|
+
short: filePath
|
|
2536
|
+
}));
|
|
2537
|
+
choices.push({
|
|
2538
|
+
name: chalk3.gray("\u270E \u624B\u52A8\u8F93\u5165\u6587\u4EF6/\u76EE\u5F55\u8DEF\u5F84"),
|
|
2539
|
+
value: "__manual__",
|
|
2540
|
+
short: "\u624B\u52A8\u8F93\u5165"
|
|
2541
|
+
});
|
|
2542
|
+
const { selected } = await inquirer.prompt([
|
|
2543
|
+
{
|
|
2544
|
+
type: "checkbox",
|
|
2545
|
+
name: "selected",
|
|
2546
|
+
message: "\u9009\u62E9\u8981\u751F\u6210\u6D4B\u8BD5\u7528\u4F8B\u7684\u6587\u4EF6 (\u7A7A\u683C\u9009\u62E9/\u53D6\u6D88\uFF0C\u56DE\u8F66\u786E\u8BA4):",
|
|
2547
|
+
choices,
|
|
2548
|
+
pageSize: 15
|
|
2549
|
+
}
|
|
2550
|
+
]);
|
|
2551
|
+
const manualPaths = [];
|
|
2552
|
+
if (selected.includes("__manual__")) {
|
|
2553
|
+
const { manualInput } = await inquirer.prompt([
|
|
2554
|
+
{
|
|
2555
|
+
type: "input",
|
|
2556
|
+
name: "manualInput",
|
|
2557
|
+
message: "\u8F93\u5165\u6587\u4EF6\u6216\u76EE\u5F55\u8DEF\u5F84\uFF08\u591A\u4E2A\u7528\u9017\u53F7\u5206\u9694\uFF09:",
|
|
2558
|
+
default: "",
|
|
2559
|
+
filter: (input) => input.split(",").map((s) => s.trim()).filter(Boolean)
|
|
2560
|
+
}
|
|
2561
|
+
]);
|
|
2562
|
+
for (const p of manualInput) {
|
|
2563
|
+
const resolved = path7.resolve(process.cwd(), p);
|
|
2564
|
+
if (fs7.existsSync(resolved)) {
|
|
2565
|
+
const stat = fs7.statSync(resolved);
|
|
2566
|
+
if (stat.isDirectory()) {
|
|
2567
|
+
const dirFiles = walkDirForTestableFiles(resolved);
|
|
2568
|
+
manualPaths.push(...dirFiles);
|
|
2569
|
+
} else if (stat.isFile()) {
|
|
2570
|
+
manualPaths.push(p.replace(/\\/g, "/"));
|
|
2571
|
+
}
|
|
2572
|
+
} else {
|
|
2573
|
+
console.log(chalk3.yellow(` \u8DEF\u5F84\u4E0D\u5B58\u5728\uFF0C\u5DF2\u8DF3\u8FC7: ${p}`));
|
|
2574
|
+
}
|
|
2575
|
+
}
|
|
2576
|
+
}
|
|
2577
|
+
const selectedPaths = selected.filter((s) => s !== "__manual__");
|
|
2578
|
+
const selectedSet = new Set(selectedPaths);
|
|
2579
|
+
const result = allTargets.filter((t) => selectedSet.has(t.filePath));
|
|
2580
|
+
const existingPaths = new Set(result.map((t) => t.filePath));
|
|
2581
|
+
for (const mp of manualPaths) {
|
|
2582
|
+
if (!existingPaths.has(mp)) {
|
|
2583
|
+
result.push({ filePath: mp, testType: inferTestType(mp) });
|
|
2584
|
+
}
|
|
2585
|
+
}
|
|
2586
|
+
return result;
|
|
2587
|
+
}
|
|
2588
|
+
function walkDirForTestableFiles(dir) {
|
|
2589
|
+
const files = [];
|
|
2590
|
+
try {
|
|
2591
|
+
const entries = fs7.readdirSync(dir, { withFileTypes: true });
|
|
2592
|
+
for (const entry of entries) {
|
|
2593
|
+
if (entry.name === "node_modules" || entry.name === "dist" || entry.name.startsWith(".")) continue;
|
|
2594
|
+
const fullPath = path7.join(dir, entry.name);
|
|
2595
|
+
if (entry.isDirectory()) {
|
|
2596
|
+
files.push(...walkDirForTestableFiles(fullPath));
|
|
2597
|
+
} else if (entry.isFile() && /\.(vue|ts|js)$/.test(entry.name)) {
|
|
2598
|
+
files.push(path7.relative(process.cwd(), fullPath).replace(/\\/g, "/"));
|
|
2599
|
+
}
|
|
2600
|
+
}
|
|
2601
|
+
} catch {
|
|
2602
|
+
}
|
|
2603
|
+
return files;
|
|
2604
|
+
}
|
|
2509
2605
|
async function generateTestForTarget(testType, targetPath, config, projectInfo, aiConfig, useAI) {
|
|
2510
2606
|
const basename = path7.basename(targetPath, path7.extname(targetPath));
|
|
2511
2607
|
const name = basename.replace(/[^a-zA-Z0-9]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "") || `${testType}-test`;
|
|
@@ -5367,7 +5463,7 @@ async function executeChange(_options) {
|
|
|
5367
5463
|
}
|
|
5368
5464
|
|
|
5369
5465
|
// src/cli.ts
|
|
5370
|
-
var VERSION = "0.2.
|
|
5466
|
+
var VERSION = "0.2.5";
|
|
5371
5467
|
function printLogo() {
|
|
5372
5468
|
const logo = `
|
|
5373
5469
|
${chalk12.bold.cyan(" ___ _ _ _ _ _____ _ _ ")}
|