qat-cli 0.2.4 → 0.2.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 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.6-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
@@ -2342,14 +2342,41 @@ async function executeInit(options) {
2342
2342
  }
2343
2343
  }
2344
2344
  const config = buildProjectConfig(projectInfo);
2345
- const fileSpinner = ora2("\u6B63\u5728\u751F\u6210\u914D\u7F6E\u6587\u4EF6...").start();
2346
2345
  let configPath;
2347
- try {
2348
- configPath = await writeConfigFile(process.cwd(), config, options.force);
2349
- fileSpinner.succeed("\u914D\u7F6E\u6587\u4EF6\u5DF2\u751F\u6210");
2350
- } catch (error) {
2351
- fileSpinner.fail("\u914D\u7F6E\u6587\u4EF6\u751F\u6210\u5931\u8D25");
2352
- throw error;
2346
+ const existingConfigPath = path7.join(process.cwd(), "qat.config.js");
2347
+ const existingTsPath = path7.join(process.cwd(), "qat.config.ts");
2348
+ const configExists = fs7.existsSync(existingConfigPath) || fs7.existsSync(existingTsPath);
2349
+ if (configExists && !options.force) {
2350
+ const { overwrite } = await inquirer.prompt([
2351
+ {
2352
+ type: "confirm",
2353
+ name: "overwrite",
2354
+ message: "\u914D\u7F6E\u6587\u4EF6 qat.config.js \u5DF2\u5B58\u5728\uFF0C\u662F\u5426\u8986\u76D6\uFF1F",
2355
+ default: true
2356
+ }
2357
+ ]);
2358
+ if (!overwrite) {
2359
+ console.log(chalk3.gray(" \u4FDD\u7559\u73B0\u6709\u914D\u7F6E\u6587\u4EF6\uFF0C\u7EE7\u7EED\u540E\u7EED\u6B65\u9AA4..."));
2360
+ configPath = existingConfigPath;
2361
+ } else {
2362
+ const fileSpinner = ora2("\u6B63\u5728\u8986\u76D6\u914D\u7F6E\u6587\u4EF6...").start();
2363
+ try {
2364
+ configPath = await writeConfigFile(process.cwd(), config, true);
2365
+ fileSpinner.succeed("\u914D\u7F6E\u6587\u4EF6\u5DF2\u8986\u76D6");
2366
+ } catch (error) {
2367
+ fileSpinner.fail("\u914D\u7F6E\u6587\u4EF6\u8986\u76D6\u5931\u8D25");
2368
+ throw error;
2369
+ }
2370
+ }
2371
+ } else {
2372
+ const fileSpinner = ora2("\u6B63\u5728\u751F\u6210\u914D\u7F6E\u6587\u4EF6...").start();
2373
+ try {
2374
+ configPath = await writeConfigFile(process.cwd(), config, options.force);
2375
+ fileSpinner.succeed("\u914D\u7F6E\u6587\u4EF6\u5DF2\u751F\u6210");
2376
+ } catch (error) {
2377
+ fileSpinner.fail("\u914D\u7F6E\u6587\u4EF6\u751F\u6210\u5931\u8D25");
2378
+ throw error;
2379
+ }
2353
2380
  }
2354
2381
  const dirSpinner = ora2("\u6B63\u5728\u521B\u5EFA\u6D4B\u8BD5\u76EE\u5F55...").start();
2355
2382
  const createdDirs = createTestDirectories(config);
@@ -2470,11 +2497,22 @@ async function autoGenerateTests(config, projectInfo, aiConfig, useAI) {
2470
2497
  for (const utilPath of utilities.slice(0, 30)) {
2471
2498
  allTargets.push({ filePath: utilPath, testType: inferTestType(utilPath) });
2472
2499
  }
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();
2500
+ const selectedTargets = await selectTargetFiles(allTargets);
2501
+ if (selectedTargets.length === 0) {
2502
+ console.log(chalk3.gray("\n \u672A\u9009\u62E9\u4EFB\u4F55\u6587\u4EF6\uFF0C\u8DF3\u8FC7\u6D4B\u8BD5\u7528\u4F8B\u751F\u6210\u3002"));
2503
+ return [];
2504
+ }
2505
+ const total = selectedTargets.length;
2506
+ const genSpinner = ora2(`\u6B63\u5728\u751F\u6210\u6D4B\u8BD5\u7528\u4F8B [0/${total}] ...`).start();
2474
2507
  const generatedFiles = [];
2475
2508
  const typeCount = {};
2476
2509
  const reviewReport = [];
2477
- for (const { filePath, testType } of allTargets) {
2510
+ let current = 0;
2511
+ let failed = 0;
2512
+ for (const { filePath, testType } of selectedTargets) {
2513
+ current++;
2514
+ const fileLabel = path7.basename(filePath);
2515
+ genSpinner.text = `\u6B63\u5728\u751F\u6210\u6D4B\u8BD5\u7528\u4F8B [${current}/${total}] ${chalk3.cyan(fileLabel)} ...`;
2478
2516
  try {
2479
2517
  const result = await generateTestForTarget(
2480
2518
  testType,
@@ -2492,20 +2530,105 @@ async function autoGenerateTests(config, projectInfo, aiConfig, useAI) {
2492
2530
  }
2493
2531
  }
2494
2532
  } catch {
2533
+ failed++;
2495
2534
  }
2496
2535
  }
2497
2536
  if (generatedFiles.length > 0) {
2498
2537
  const summary = Object.entries(typeCount).map(([type, count]) => `${count} ${type}`).join(", ");
2499
2538
  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`);
2539
+ let msg = `\u5DF2\u751F\u6210 ${generatedFiles.length}/${total} \u4E2A\u6D4B\u8BD5\u7528\u4F8B (${summary}) \u2014 AI \u5BA1\u8BA1 ${approvedCount}/${reviewReport.length} \u901A\u8FC7`;
2540
+ if (failed > 0) msg += chalk3.yellow(` ${failed} \u4E2A\u5931\u8D25`);
2541
+ genSpinner.succeed(msg);
2501
2542
  } else {
2502
- genSpinner.warn("\u672A\u751F\u6210\u6D4B\u8BD5\u7528\u4F8B");
2543
+ genSpinner.warn(`\u672A\u751F\u6210\u6D4B\u8BD5\u7528\u4F8B (${failed} \u4E2A\u5931\u8D25)`);
2503
2544
  }
2504
2545
  if (reviewReport.length > 0) {
2505
2546
  printReviewReport(reviewReport);
2506
2547
  }
2507
2548
  return generatedFiles;
2508
2549
  }
2550
+ async function selectTargetFiles(allTargets) {
2551
+ const testTypeLabels = {
2552
+ unit: chalk3.blue("[unit]"),
2553
+ component: chalk3.magenta("[comp]"),
2554
+ e2e: chalk3.green("[e2e]"),
2555
+ api: chalk3.yellow("[api]"),
2556
+ visual: chalk3.cyan("[visual]"),
2557
+ performance: chalk3.gray("[perf]")
2558
+ };
2559
+ const choices = allTargets.map(({ filePath, testType }) => ({
2560
+ name: `${testTypeLabels[testType]} ${filePath}`,
2561
+ value: filePath,
2562
+ short: filePath
2563
+ }));
2564
+ choices.push({
2565
+ name: chalk3.gray("\u270E \u624B\u52A8\u8F93\u5165\u6587\u4EF6/\u76EE\u5F55\u8DEF\u5F84"),
2566
+ value: "__manual__",
2567
+ short: "\u624B\u52A8\u8F93\u5165"
2568
+ });
2569
+ const { selected } = await inquirer.prompt([
2570
+ {
2571
+ type: "checkbox",
2572
+ name: "selected",
2573
+ message: "\u9009\u62E9\u8981\u751F\u6210\u6D4B\u8BD5\u7528\u4F8B\u7684\u6587\u4EF6 (\u7A7A\u683C\u9009\u62E9/\u53D6\u6D88\uFF0C\u56DE\u8F66\u786E\u8BA4):",
2574
+ choices,
2575
+ pageSize: 15
2576
+ }
2577
+ ]);
2578
+ const manualPaths = [];
2579
+ if (selected.includes("__manual__")) {
2580
+ const { manualInput } = await inquirer.prompt([
2581
+ {
2582
+ type: "input",
2583
+ name: "manualInput",
2584
+ message: "\u8F93\u5165\u6587\u4EF6\u6216\u76EE\u5F55\u8DEF\u5F84\uFF08\u591A\u4E2A\u7528\u9017\u53F7\u5206\u9694\uFF09:",
2585
+ default: "",
2586
+ filter: (input) => input.split(",").map((s) => s.trim()).filter(Boolean)
2587
+ }
2588
+ ]);
2589
+ for (const p of manualInput) {
2590
+ const resolved = path7.resolve(process.cwd(), p);
2591
+ if (fs7.existsSync(resolved)) {
2592
+ const stat = fs7.statSync(resolved);
2593
+ if (stat.isDirectory()) {
2594
+ const dirFiles = walkDirForTestableFiles(resolved);
2595
+ manualPaths.push(...dirFiles);
2596
+ } else if (stat.isFile()) {
2597
+ manualPaths.push(p.replace(/\\/g, "/"));
2598
+ }
2599
+ } else {
2600
+ console.log(chalk3.yellow(` \u8DEF\u5F84\u4E0D\u5B58\u5728\uFF0C\u5DF2\u8DF3\u8FC7: ${p}`));
2601
+ }
2602
+ }
2603
+ }
2604
+ const selectedPaths = selected.filter((s) => s !== "__manual__");
2605
+ const selectedSet = new Set(selectedPaths);
2606
+ const result = allTargets.filter((t) => selectedSet.has(t.filePath));
2607
+ const existingPaths = new Set(result.map((t) => t.filePath));
2608
+ for (const mp of manualPaths) {
2609
+ if (!existingPaths.has(mp)) {
2610
+ result.push({ filePath: mp, testType: inferTestType(mp) });
2611
+ }
2612
+ }
2613
+ return result;
2614
+ }
2615
+ function walkDirForTestableFiles(dir) {
2616
+ const files = [];
2617
+ try {
2618
+ const entries = fs7.readdirSync(dir, { withFileTypes: true });
2619
+ for (const entry of entries) {
2620
+ if (entry.name === "node_modules" || entry.name === "dist" || entry.name.startsWith(".")) continue;
2621
+ const fullPath = path7.join(dir, entry.name);
2622
+ if (entry.isDirectory()) {
2623
+ files.push(...walkDirForTestableFiles(fullPath));
2624
+ } else if (entry.isFile() && /\.(vue|ts|js)$/.test(entry.name)) {
2625
+ files.push(path7.relative(process.cwd(), fullPath).replace(/\\/g, "/"));
2626
+ }
2627
+ }
2628
+ } catch {
2629
+ }
2630
+ return files;
2631
+ }
2509
2632
  async function generateTestForTarget(testType, targetPath, config, projectInfo, aiConfig, useAI) {
2510
2633
  const basename = path7.basename(targetPath, path7.extname(targetPath));
2511
2634
  const name = basename.replace(/[^a-zA-Z0-9]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "") || `${testType}-test`;
@@ -5367,7 +5490,7 @@ async function executeChange(_options) {
5367
5490
  }
5368
5491
 
5369
5492
  // src/cli.ts
5370
- var VERSION = "0.2.4";
5493
+ var VERSION = "0.2.6";
5371
5494
  function printLogo() {
5372
5495
  const logo = `
5373
5496
  ${chalk12.bold.cyan(" ___ _ _ _ _ _____ _ _ ")}