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 CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  **Quick Auto Testing — 面向 Vue 项目,集成 Vitest & Playwright,AI 驱动覆盖测试全流程**
6
6
 
7
- [![npm version](https://img.shields.io/badge/version-0.2.4-blue.svg)](https://www.npmjs.com/package/qat-cli)
7
+ [![npm version](https://img.shields.io/badge/version-0.2.5-blue.svg)](https://www.npmjs.com/package/qat-cli)
8
8
  [![Node.js](https://img.shields.io/badge/node-%3E%3D18.0.0-green.svg)](https://nodejs.org/)
9
9
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
10
10
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.6-blue.svg)](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 genSpinner = ora2(`\u6B63\u5728\u751F\u6210\u6D4B\u8BD5\u7528\u4F8B (AI \u8F85\u52A9 + \u5BA1\u8BA1) \u2014 \u53D1\u73B0 ${allTargets.length} \u4E2A\u6587\u4EF6...`).start();
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
- for (const { filePath, testType } of allTargets) {
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
- genSpinner.succeed(`\u5DF2\u751F\u6210 ${generatedFiles.length} \u4E2A\u6D4B\u8BD5\u7528\u4F8B (${summary}) \u2014 AI \u751F\u6210+\u5BA1\u8BA1 ${approvedCount}/${reviewReport.length} \u901A\u8FC7`);
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("\u672A\u751F\u6210\u6D4B\u8BD5\u7528\u4F8B");
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.4";
5466
+ var VERSION = "0.2.5";
5371
5467
  function printLogo() {
5372
5468
  const logo = `
5373
5469
  ${chalk12.bold.cyan(" ___ _ _ _ _ _____ _ _ ")}