qat-cli 0.2.98 → 0.3.2

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/dist/index.cjs CHANGED
@@ -1466,6 +1466,7 @@ function aggregateResults(results) {
1466
1466
  pending: 0
1467
1467
  };
1468
1468
  const byType = {};
1469
+ let coverage;
1469
1470
  for (const result of results) {
1470
1471
  const typeKey = result.type;
1471
1472
  if (!byType[typeKey]) {
@@ -1489,6 +1490,16 @@ function aggregateResults(results) {
1489
1490
  }
1490
1491
  }
1491
1492
  }
1493
+ if (result.coverage) {
1494
+ if (!coverage) {
1495
+ coverage = { ...result.coverage };
1496
+ } else {
1497
+ coverage.lines = Math.max(coverage.lines, result.coverage.lines);
1498
+ coverage.statements = Math.max(coverage.statements, result.coverage.statements);
1499
+ coverage.functions = Math.max(coverage.functions, result.coverage.functions);
1500
+ coverage.branches = Math.max(coverage.branches, result.coverage.branches);
1501
+ }
1502
+ }
1492
1503
  }
1493
1504
  const totalDuration = results.reduce((sum, r) => sum + r.duration, 0);
1494
1505
  return {
@@ -1496,7 +1507,8 @@ function aggregateResults(results) {
1496
1507
  duration: totalDuration,
1497
1508
  results,
1498
1509
  summary,
1499
- byType
1510
+ byType,
1511
+ coverage: coverage?.lines ? coverage : void 0
1500
1512
  };
1501
1513
  }
1502
1514
  function formatDuration(ms) {
@@ -1516,56 +1528,149 @@ function formatTimestamp(ts) {
1516
1528
  second: "2-digit"
1517
1529
  });
1518
1530
  }
1519
- function renderSuiteHTML(suite) {
1520
- const statusClass = suite.status === "passed" ? "passed" : suite.status === "failed" ? "failed" : "skipped";
1521
- const testsHTML = suite.tests.map((test) => {
1522
- const testStatusClass = test.status;
1523
- const errorHTML = test.error ? `<div class="error-message"><strong>${escapeHTML(test.error.message)}</strong>${test.error.stack ? `
1524
- ${escapeHTML(test.error.stack)}` : ""}${test.error.expected && test.error.actual ? `
1531
+ function pct(value) {
1532
+ return `${(value * 100).toFixed(1)}%`;
1533
+ }
1534
+ function renderCoverageMD(coverage) {
1535
+ return `
1536
+ ### \u8986\u76D6\u7387
1525
1537
 
1526
- Expected: ${escapeHTML(test.error.expected)}
1527
- Actual: ${escapeHTML(test.error.actual)}` : ""}</div>` : "";
1528
- return `<div class="test-item">
1529
- <span class="status-dot ${testStatusClass}"></span>
1530
- <span class="test-name">${escapeHTML(test.name)}</span>
1531
- <span class="duration">${formatDuration(test.duration)}</span>
1532
- ${test.retries > 0 ? `<span class="retries">\u91CD\u8BD5 ${test.retries} \u6B21</span>` : ""}
1533
- </div>
1534
- ${errorHTML}`;
1535
- }).join("");
1536
- return `<div class="suite">
1537
- <div class="suite-header" onclick="this.parentElement.classList.toggle('collapsed')">
1538
- <div>
1539
- <span class="status-dot ${statusClass}"></span>
1540
- <strong>${escapeHTML(suite.name)}</strong>
1541
- <span class="suite-file">${escapeHTML(suite.file)}</span>
1542
- </div>
1543
- <div>
1544
- <span class="duration">${formatDuration(suite.duration)}</span>
1545
- <span class="toggle-icon">\u25BC</span>
1546
- </div>
1547
- </div>
1548
- <div class="suite-body">${testsHTML}</div>
1549
- </div>`;
1550
- }
1551
- function escapeHTML(str) {
1552
- return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
1538
+ | \u6307\u6807 | \u8986\u76D6\u7387 | \u8FDB\u5EA6 |
1539
+ |------|--------|------|
1540
+ | \u8BED\u53E5 (Statements) | ${pct(coverage.statements)} | ${renderProgressBar(coverage.statements)} |
1541
+ | \u5206\u652F (Branches) | ${pct(coverage.branches)} | ${renderProgressBar(coverage.branches)} |
1542
+ | \u51FD\u6570 (Functions) | ${pct(coverage.functions)} | ${renderProgressBar(coverage.functions)} |
1543
+ | \u884C (Lines) | ${pct(coverage.lines)} | ${renderProgressBar(coverage.lines)} |
1544
+ `;
1545
+ }
1546
+ function renderProgressBar(value) {
1547
+ const filled = Math.round(value * 10);
1548
+ const empty = 10 - filled;
1549
+ return `${"\u2588".repeat(filled)}${"\u2591".repeat(empty)}`;
1550
+ }
1551
+ var TYPE_LABELS = {
1552
+ unit: "\u5355\u5143\u6D4B\u8BD5",
1553
+ component: "\u7EC4\u4EF6\u6D4B\u8BD5",
1554
+ e2e: "E2E \u6D4B\u8BD5",
1555
+ api: "API \u6D4B\u8BD5",
1556
+ visual: "\u89C6\u89C9\u56DE\u5F52\u6D4B\u8BD5",
1557
+ performance: "\u6027\u80FD\u6D4B\u8BD5"
1558
+ };
1559
+ function generateMDReport(data) {
1560
+ const passRate = data.summary.total > 0 ? (data.summary.passed / data.summary.total * 100).toFixed(1) : "0";
1561
+ const rateIcon = parseFloat(passRate) >= 80 ? "\u2705" : parseFloat(passRate) >= 50 ? "\u26A0\uFE0F" : "\u274C";
1562
+ const lines = [];
1563
+ lines.push(`# QAT \u6D4B\u8BD5\u62A5\u544A`);
1564
+ lines.push("");
1565
+ lines.push(`> \u751F\u6210\u65F6\u95F4: ${formatTimestamp(data.timestamp)} | \u603B\u8017\u65F6: ${formatDuration(data.duration)}`);
1566
+ lines.push("");
1567
+ lines.push(`## \u603B\u89C8`);
1568
+ lines.push("");
1569
+ lines.push(`| \u6307\u6807 | \u6570\u503C |`);
1570
+ lines.push(`|------|------|`);
1571
+ lines.push(`| \u901A\u8FC7\u7387 | ${rateIcon} **${passRate}%** |`);
1572
+ lines.push(`| \u603B\u7528\u4F8B | ${data.summary.total} |`);
1573
+ lines.push(`| \u2705 \u901A\u8FC7 | ${data.summary.passed} |`);
1574
+ if (data.summary.failed > 0) lines.push(`| \u274C \u5931\u8D25 | ${data.summary.failed} |`);
1575
+ if (data.summary.skipped > 0) lines.push(`| \u23ED\uFE0F \u8DF3\u8FC7 | ${data.summary.skipped} |`);
1576
+ if (data.summary.pending > 0) lines.push(`| \u23F3 \u5F85\u5B9A | ${data.summary.pending} |`);
1577
+ lines.push(`| \u23F1\uFE0F \u8017\u65F6 | ${formatDuration(data.duration)} |`);
1578
+ lines.push("");
1579
+ if (Object.keys(data.byType).length > 0) {
1580
+ lines.push(`## \u6309\u7C7B\u578B\u7EDF\u8BA1`);
1581
+ lines.push("");
1582
+ lines.push(`| \u7C7B\u578B | \u901A\u8FC7 | \u5931\u8D25 | \u8DF3\u8FC7 | \u603B\u8BA1 | \u901A\u8FC7\u7387 |`);
1583
+ lines.push(`|------|------|------|------|------|--------|`);
1584
+ for (const [type, stats] of Object.entries(data.byType)) {
1585
+ const label = TYPE_LABELS[type] || type;
1586
+ const rate = stats.total > 0 ? (stats.passed / stats.total * 100).toFixed(0) + "%" : "-";
1587
+ lines.push(`| ${label} | ${stats.passed} | ${stats.failed} | ${stats.skipped} | ${stats.total} | ${rate} |`);
1588
+ }
1589
+ lines.push("");
1590
+ }
1591
+ if (data.coverage) {
1592
+ lines.push(renderCoverageMD(data.coverage));
1593
+ lines.push("");
1594
+ }
1595
+ lines.push(`## \u6D4B\u8BD5\u8BE6\u60C5`);
1596
+ lines.push("");
1597
+ for (const result of data.results) {
1598
+ const typeLabel = TYPE_LABELS[result.type] || result.type;
1599
+ const statusIcon = result.status === "passed" ? "\u2705" : result.status === "failed" ? "\u274C" : "\u26A0\uFE0F";
1600
+ lines.push(`### ${statusIcon} ${typeLabel}`);
1601
+ lines.push("");
1602
+ if (result.suites.length === 0) {
1603
+ lines.push(`*\u65E0\u6D4B\u8BD5\u7ED3\u679C*`);
1604
+ lines.push("");
1605
+ continue;
1606
+ }
1607
+ for (const suite of result.suites) {
1608
+ const suiteIcon = suite.status === "passed" ? "\u2705" : suite.status === "failed" ? "\u274C" : "\u26A0\uFE0F";
1609
+ lines.push(`#### ${suiteIcon} ${suite.name}`);
1610
+ lines.push("");
1611
+ lines.push(`- \u6587\u4EF6: \`${suite.file}\``);
1612
+ lines.push(`- \u8017\u65F6: ${formatDuration(suite.duration)}`);
1613
+ lines.push("");
1614
+ if (suite.tests.length > 0) {
1615
+ lines.push(`| \u72B6\u6001 | \u6D4B\u8BD5\u540D\u79F0 | \u8017\u65F6 |`);
1616
+ lines.push(`|------|----------|------|`);
1617
+ for (const test of suite.tests) {
1618
+ const testIcon = test.status === "passed" ? "\u2705" : test.status === "failed" ? "\u274C" : test.status === "skipped" ? "\u23ED\uFE0F" : "\u23F3";
1619
+ const name = test.error ? `**${test.name}**` : test.name;
1620
+ lines.push(`| ${testIcon} | ${name} | ${formatDuration(test.duration)} |`);
1621
+ }
1622
+ lines.push("");
1623
+ }
1624
+ const failedTests = suite.tests.filter((t) => t.status === "failed" && t.error);
1625
+ if (failedTests.length > 0) {
1626
+ lines.push(`<details>`);
1627
+ lines.push(`<summary>\u274C \u5931\u8D25\u8BE6\u60C5 (${failedTests.length})</summary>`);
1628
+ lines.push("");
1629
+ for (const test of failedTests) {
1630
+ lines.push(`**${test.name}**`);
1631
+ lines.push("```");
1632
+ lines.push(test.error.message);
1633
+ if (test.error.stack) {
1634
+ lines.push(test.error.stack);
1635
+ }
1636
+ if (test.error.expected && test.error.actual) {
1637
+ lines.push(`Expected: ${test.error.expected}`);
1638
+ lines.push(`Actual: ${test.error.actual}`);
1639
+ }
1640
+ lines.push("```");
1641
+ lines.push("");
1642
+ }
1643
+ lines.push(`</details>`);
1644
+ lines.push("");
1645
+ }
1646
+ }
1647
+ }
1648
+ lines.push("---");
1649
+ lines.push("");
1650
+ lines.push(`*\u7531 QAT \u81EA\u52A8\u5316\u6D4B\u8BD5\u5DE5\u5177\u751F\u6210 | ${formatTimestamp(data.timestamp)}*`);
1651
+ return lines.join("\n");
1652
+ }
1653
+ function writeReportToDisk(data, outputDir) {
1654
+ const md = generateMDReport(data);
1655
+ const dir = import_node_path5.default.resolve(outputDir);
1656
+ if (!import_node_fs5.default.existsSync(dir)) {
1657
+ import_node_fs5.default.mkdirSync(dir, { recursive: true });
1658
+ }
1659
+ const mdPath = import_node_path5.default.join(dir, "report.md");
1660
+ import_node_fs5.default.writeFileSync(mdPath, md, "utf-8");
1661
+ const jsonPath = import_node_path5.default.join(dir, "report.json");
1662
+ import_node_fs5.default.writeFileSync(jsonPath, JSON.stringify(data, null, 2), "utf-8");
1663
+ return mdPath;
1553
1664
  }
1554
1665
  function generateHTMLReport(data) {
1555
1666
  const passRate = data.summary.total > 0 ? (data.summary.passed / data.summary.total * 100).toFixed(1) : "0";
1556
- const suitesHTML = data.results.flatMap((r) => r.suites).map(renderSuiteHTML).join("\n");
1557
- const byTypeHTML = Object.entries(data.byType).map(([type, stats]) => {
1558
- const rate = stats.total > 0 ? (stats.passed / stats.total * 100).toFixed(0) : "0";
1559
- return `<div class="type-card">
1560
- <div class="type-name">${type}</div>
1561
- <div class="type-stats">
1562
- <span class="passed">${stats.passed} \u901A\u8FC7</span>
1563
- <span class="failed">${stats.failed} \u5931\u8D25</span>
1564
- <span class="skipped">${stats.skipped} \u8DF3\u8FC7</span>
1565
- </div>
1566
- <div class="type-rate">${rate}%</div>
1567
- </div>`;
1568
- }).join("\n");
1667
+ const md = generateMDReport(data);
1668
+ const html = md.replace(/^### (.+)$/gm, "<h3>$1</h3>").replace(/^## (.+)$/gm, "<h2>$1</h2>").replace(/^# (.+)$/gm, "<h1>$1</h1>").replace(/^> (.+)$/gm, "<blockquote>$1</blockquote>").replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>").replace(/`([^`]+)`/g, "<code>$1</code>").replace(/^---$/gm, "<hr>").replace(/\|(.+)\|/g, (match) => {
1669
+ const cells = match.split("|").filter(Boolean).map((c) => c.trim());
1670
+ if (cells.every((c) => c.startsWith("-") || c === "")) return "";
1671
+ const tds = cells.map((c) => `<td>${c}</td>`).join("");
1672
+ return `<tr>${tds}</tr>`;
1673
+ }).replace(/<tr>/g, "<table><tr>").replace(/<\/tr>(?!\s*<table>)/g, "</tr></table>").replace(/<\/table>\s*<table>/g, "").replace(/^✅/gm, '<span style="color:#22c55e">\u2705</span>').replace(/^❌/gm, '<span style="color:#ef4444">\u274C</span>').replace(/^⚠️/gm, '<span style="color:#f59e0b">\u26A0\uFE0F</span>');
1569
1674
  return `<!DOCTYPE html>
1570
1675
  <html lang="zh-CN">
1571
1676
  <head>
@@ -1573,162 +1678,60 @@ function generateHTMLReport(data) {
1573
1678
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
1574
1679
  <title>QAT \u6D4B\u8BD5\u62A5\u544A - ${formatTimestamp(data.timestamp)}</title>
1575
1680
  <style>
1576
- :root {
1577
- --color-passed: #22c55e;
1578
- --color-failed: #ef4444;
1579
- --color-skipped: #f59e0b;
1580
- --color-info: #3b82f6;
1581
- --bg-primary: #ffffff;
1582
- --bg-secondary: #f8fafc;
1583
- --text-primary: #1e293b;
1584
- --text-secondary: #64748b;
1585
- --border-color: #e2e8f0;
1586
- }
1587
- * { margin: 0; padding: 0; box-sizing: border-box; }
1588
1681
  body {
1589
1682
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
1590
- background: var(--bg-secondary);
1591
- color: var(--text-primary);
1592
- line-height: 1.6;
1593
- }
1594
- .container { max-width: 1200px; margin: 0 auto; padding: 20px; }
1595
- header {
1596
- background: white;
1597
- border-bottom: 1px solid var(--border-color);
1598
- padding: 20px;
1599
- margin-bottom: 20px;
1600
- border-radius: 8px;
1601
- }
1602
- header h1 { font-size: 24px; font-weight: 600; }
1603
- header .meta { color: var(--text-secondary); font-size: 14px; margin-top: 4px; }
1604
- .summary { display: flex; gap: 16px; margin: 20px 0; flex-wrap: wrap; }
1605
- .card {
1606
- padding: 16px 24px; border-radius: 8px; color: white;
1607
- font-weight: 600; min-width: 120px; text-align: center;
1608
- }
1609
- .card.passed { background: var(--color-passed); }
1610
- .card.failed { background: var(--color-failed); }
1611
- .card.skipped { background: var(--color-skipped); }
1612
- .card.total { background: var(--color-info); }
1613
- .card .card-value { font-size: 28px; }
1614
- .card .card-label { font-size: 13px; opacity: 0.9; }
1615
- .pass-rate {
1616
- font-size: 48px; font-weight: 700; text-align: center; margin: 20px 0;
1617
- color: ${parseFloat(passRate) >= 80 ? "var(--color-passed)" : parseFloat(passRate) >= 50 ? "var(--color-skipped)" : "var(--color-failed)"};
1618
- }
1619
- .pass-rate-label { text-align: center; color: var(--text-secondary); margin-bottom: 20px; }
1620
- .by-type { display: flex; gap: 12px; flex-wrap: wrap; margin: 20px 0; }
1621
- .type-card {
1622
- background: white; border: 1px solid var(--border-color); border-radius: 8px;
1623
- padding: 12px 16px; min-width: 180px;
1624
- }
1625
- .type-name { font-weight: 600; text-transform: capitalize; margin-bottom: 4px; }
1626
- .type-stats { font-size: 13px; }
1627
- .type-stats span { margin-right: 8px; }
1628
- .type-stats .passed { color: var(--color-passed); }
1629
- .type-stats .failed { color: var(--color-failed); }
1630
- .type-stats .skipped { color: var(--color-skipped); }
1631
- .type-rate { font-size: 20px; font-weight: 700; margin-top: 4px; }
1632
- .suite {
1633
- background: white; border: 1px solid var(--border-color);
1634
- border-radius: 8px; margin-bottom: 12px; overflow: hidden;
1635
- }
1636
- .suite-header {
1637
- padding: 12px 16px; display: flex; justify-content: space-between;
1638
- align-items: center; cursor: pointer; user-select: none;
1639
- }
1640
- .suite-header:hover { background: var(--bg-secondary); }
1641
- .suite-header div { display: flex; align-items: center; gap: 8px; }
1642
- .suite.collapsed .suite-body { display: none; }
1643
- .suite-file { color: var(--text-secondary); font-size: 12px; }
1644
- .toggle-icon { font-size: 12px; color: var(--text-secondary); transition: transform 0.2s; }
1645
- .suite.collapsed .toggle-icon { transform: rotate(-90deg); }
1646
- .suite-body { border-top: 1px solid var(--border-color); padding: 8px 16px; }
1647
- .test-item {
1648
- padding: 8px 0; display: flex; align-items: center; gap: 8px;
1649
- }
1650
- .test-item + .test-item { border-top: 1px solid var(--border-color); }
1651
- .status-dot {
1652
- width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0;
1653
- }
1654
- .status-dot.passed { background: var(--color-passed); }
1655
- .status-dot.failed { background: var(--color-failed); }
1656
- .status-dot.skipped { background: var(--color-skipped); }
1657
- .status-dot.pending { background: var(--text-secondary); }
1658
- .test-name { flex: 1; }
1659
- .duration { color: var(--text-secondary); font-size: 13px; }
1660
- .retries { color: var(--color-skipped); font-size: 12px; }
1661
- .error-message {
1662
- background: #fef2f2; color: #991b1b; padding: 12px;
1663
- border-radius: 4px; font-family: 'Fira Code', monospace;
1664
- font-size: 13px; margin: 8px 0 8px 16px; white-space: pre-wrap;
1665
- word-break: break-all;
1666
- }
1667
- footer {
1668
- text-align: center; padding: 20px; color: var(--text-secondary);
1669
- font-size: 13px; margin-top: 40px;
1670
- }
1683
+ max-width: 960px; margin: 0 auto; padding: 20px;
1684
+ background: #f8fafc; color: #1e293b; line-height: 1.6;
1685
+ }
1686
+ h1 { border-bottom: 2px solid #3b82f6; padding-bottom: 8px; }
1687
+ h2 { margin-top: 24px; color: #1e40af; }
1688
+ h3 { margin-top: 16px; color: #334155; }
1689
+ h4 { margin-top: 12px; color: #475569; }
1690
+ blockquote { color: #64748b; border-left: 3px solid #cbd5e1; padding-left: 12px; margin: 8px 0; }
1691
+ table { border-collapse: collapse; width: 100%; margin: 12px 0; }
1692
+ td, th { border: 1px solid #e2e8f0; padding: 8px 12px; text-align: left; }
1693
+ tr:nth-child(even) { background: #f1f5f9; }
1694
+ code { background: #e2e8f0; padding: 2px 6px; border-radius: 3px; font-size: 0.9em; }
1695
+ pre { background: #1e293b; color: #e2e8f0; padding: 16px; border-radius: 8px; overflow-x: auto; }
1696
+ pre code { background: none; padding: 0; }
1697
+ hr { border: none; border-top: 1px solid #e2e8f0; margin: 24px 0; }
1698
+ details { margin: 8px 0; }
1699
+ summary { cursor: pointer; color: #ef4444; font-weight: 600; }
1700
+ strong { color: #1e293b; }
1701
+ .pass-rate { font-size: 48px; font-weight: 700; text-align: center; margin: 20px 0;
1702
+ color: ${parseFloat(passRate) >= 80 ? "#22c55e" : parseFloat(passRate) >= 50 ? "#f59e0b" : "#ef4444"};
1703
+ }
1704
+ .pass-rate-label { text-align: center; color: #64748b; margin-bottom: 20px; }
1671
1705
  </style>
1672
1706
  </head>
1673
1707
  <body>
1674
- <div class="container">
1675
- <header>
1676
- <h1>QAT \u6D4B\u8BD5\u62A5\u544A</h1>
1677
- <div class="meta">\u751F\u6210\u65F6\u95F4: ${formatTimestamp(data.timestamp)} | \u603B\u8017\u65F6: ${formatDuration(data.duration)}</div>
1678
- </header>
1679
-
1680
- <div class="pass-rate">${passRate}%</div>
1681
- <div class="pass-rate-label">\u6D4B\u8BD5\u901A\u8FC7\u7387</div>
1682
-
1683
- <div class="summary">
1684
- <div class="card total">
1685
- <div class="card-value">${data.summary.total}</div>
1686
- <div class="card-label">\u603B\u8BA1</div>
1687
- </div>
1688
- <div class="card passed">
1689
- <div class="card-value">${data.summary.passed}</div>
1690
- <div class="card-label">\u901A\u8FC7</div>
1691
- </div>
1692
- <div class="card failed">
1693
- <div class="card-value">${data.summary.failed}</div>
1694
- <div class="card-label">\u5931\u8D25</div>
1695
- </div>
1696
- <div class="card skipped">
1697
- <div class="card-value">${data.summary.skipped}</div>
1698
- <div class="card-label">\u8DF3\u8FC7</div>
1699
- </div>
1700
- </div>
1701
-
1702
- ${Object.keys(data.byType).length > 0 ? `<h2 style="margin-top:30px;margin-bottom:10px;">\u6309\u7C7B\u578B\u7EDF\u8BA1</h2><div class="by-type">${byTypeHTML}</div>` : ""}
1703
-
1704
- <h2 style="margin-top:30px;margin-bottom:10px;">\u6D4B\u8BD5\u8BE6\u60C5</h2>
1705
- ${suitesHTML || '<p style="color:var(--text-secondary)">\u6682\u65E0\u6D4B\u8BD5\u7ED3\u679C</p>'}
1706
-
1707
- <footer>\u7531 QAT \u81EA\u52A8\u5316\u6D4B\u8BD5\u5DE5\u5177\u751F\u6210</footer>
1708
- </div>
1708
+ <div class="pass-rate">${passRate}%</div>
1709
+ <div class="pass-rate-label">\u6D4B\u8BD5\u901A\u8FC7\u7387</div>
1710
+ ${html}
1709
1711
  </body>
1710
1712
  </html>`;
1711
1713
  }
1712
- function writeReportToDisk(data, outputDir) {
1713
- const html = generateHTMLReport(data);
1714
- const dir = import_node_path5.default.resolve(outputDir);
1715
- if (!import_node_fs5.default.existsSync(dir)) {
1716
- import_node_fs5.default.mkdirSync(dir, { recursive: true });
1717
- }
1718
- const indexPath = import_node_path5.default.join(dir, "index.html");
1719
- import_node_fs5.default.writeFileSync(indexPath, html, "utf-8");
1720
- return indexPath;
1721
- }
1722
1714
 
1723
1715
  // src/runners/vitest-runner.ts
1724
1716
  var import_node_child_process = require("child_process");
1725
1717
  var import_node_path6 = __toESM(require("path"), 1);
1718
+ var import_node_fs6 = __toESM(require("fs"), 1);
1719
+ var import_node_os = __toESM(require("os"), 1);
1720
+ var isVerbose = () => process.env.QAT_VERBOSE === "true";
1721
+ function debug(label, ...args) {
1722
+ if (isVerbose()) {
1723
+ console.log(`\x1B[90m [debug:${label}]\x1B[0m`, ...args);
1724
+ }
1725
+ }
1726
1726
  async function runVitest(options) {
1727
1727
  const startTime = Date.now();
1728
1728
  const args = buildVitestArgs(options);
1729
+ debug("vitest", "\u547D\u4EE4\u53C2\u6570:", args.join(" "));
1729
1730
  try {
1730
1731
  const result = await execVitest(args);
1731
1732
  const endTime = Date.now();
1733
+ debug("vitest", `\u89E3\u6790\u7ED3\u679C: ${result.suites.length} \u4E2A\u5957\u4EF6, ${result.suites.reduce((s, su) => s + su.tests.length, 0)} \u4E2A\u7528\u4F8B`);
1734
+ debug("vitest", "\u89E3\u6790\u65B9\u5F0F:", result.parseMethod);
1732
1735
  return {
1733
1736
  type: options.type,
1734
1737
  status: result.success ? "passed" : "failed",
@@ -1793,83 +1796,228 @@ function buildVitestArgs(options) {
1793
1796
  return args;
1794
1797
  }
1795
1798
  async function execVitest(args) {
1799
+ const tmpFile = import_node_path6.default.join(import_node_os.default.tmpdir(), `qat-vitest-result-${Date.now()}.json`);
1800
+ const argsWithOutput = [...args, "--outputFile", tmpFile];
1796
1801
  return new Promise((resolve, reject) => {
1797
1802
  const npx = process.platform === "win32" ? "npx.cmd" : "npx";
1798
- const child = (0, import_node_child_process.execFile)(npx, args, {
1803
+ debug("vitest", "\u6267\u884C\u547D\u4EE4:", npx, argsWithOutput.join(" "));
1804
+ const child = (0, import_node_child_process.execFile)(npx, argsWithOutput, {
1799
1805
  cwd: process.cwd(),
1800
- env: { ...process.env, FORCE_COLOR: "0" },
1806
+ env: { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" },
1801
1807
  maxBuffer: 50 * 1024 * 1024,
1802
- // 50MB
1803
1808
  shell: true
1804
1809
  }, (error, stdout, stderr) => {
1805
- const output = stdout || stderr || "";
1810
+ const rawOutput = stdout || stderr || "";
1811
+ const exitCode = error && "code" in error ? error.code : 0;
1812
+ debug("vitest", `\u9000\u51FA\u7801: ${exitCode}`);
1813
+ debug("vitest", `stdout \u957F\u5EA6: ${stdout?.length || 0}, stderr \u957F\u5EA6: ${stderr?.length || 0}`);
1814
+ let jsonResult = null;
1806
1815
  try {
1807
- const parsed = parseVitestJSONOutput(output);
1808
- resolve(parsed);
1809
- } catch {
1810
- if (output) {
1811
- resolve(parseVitestTextOutput(output, !!error));
1812
- } else if (error && error.message.includes("ENOENT")) {
1813
- reject(new Error("\u672A\u627E\u5230 vitest\uFF0C\u8BF7\u786E\u4FDD\u5DF2\u5B89\u88C5: npm install -D vitest"));
1816
+ if (import_node_fs6.default.existsSync(tmpFile)) {
1817
+ jsonResult = import_node_fs6.default.readFileSync(tmpFile, "utf-8");
1818
+ debug("vitest", `\u4ECE\u4E34\u65F6\u6587\u4EF6\u8BFB\u53D6\u5230 JSON (${jsonResult.length} \u5B57\u7B26)`);
1819
+ debug("vitest", "JSON \u524D 500 \u5B57\u7B26:", jsonResult.substring(0, 500));
1820
+ import_node_fs6.default.unlinkSync(tmpFile);
1814
1821
  } else {
1815
- resolve({ success: !error, suites: [] });
1822
+ debug("vitest", "\u4E34\u65F6\u6587\u4EF6\u4E0D\u5B58\u5728:", tmpFile);
1816
1823
  }
1824
+ } catch (e) {
1825
+ debug("vitest", "\u4E34\u65F6\u6587\u4EF6\u8BFB\u53D6\u5931\u8D25:", e instanceof Error ? e.message : String(e));
1817
1826
  }
1827
+ if (jsonResult) {
1828
+ try {
1829
+ const parsed = parseVitestJSON(jsonResult);
1830
+ debug("vitest", "\u4ECE\u4E34\u65F6\u6587\u4EF6\u89E3\u6790\u6210\u529F:", parsed.suites.length, "\u4E2A\u5957\u4EF6");
1831
+ resolve({ ...parsed, rawOutput, parseMethod: "outputFile-JSON" });
1832
+ return;
1833
+ } catch (e) {
1834
+ debug("vitest", "\u4E34\u65F6\u6587\u4EF6 JSON \u89E3\u6790\u5931\u8D25:", e instanceof Error ? e.message : String(e));
1835
+ }
1836
+ }
1837
+ debug("vitest", "\u5C1D\u8BD5\u4ECE stdout \u63D0\u53D6 JSON...");
1838
+ try {
1839
+ const parsed = parseFromStdout(rawOutput);
1840
+ debug("vitest", "\u4ECE stdout \u89E3\u6790\u6210\u529F:", parsed.suites.length, "\u4E2A\u5957\u4EF6");
1841
+ resolve({ ...parsed, rawOutput, parseMethod: "stdout-JSON" });
1842
+ return;
1843
+ } catch (e) {
1844
+ debug("vitest", "stdout JSON \u89E3\u6790\u5931\u8D25:", e instanceof Error ? e.message : String(e));
1845
+ }
1846
+ debug("vitest", "\u5C1D\u8BD5\u4ECE\u6587\u672C\u8F93\u51FA\u89E3\u6790...");
1847
+ if (rawOutput) {
1848
+ const parsed = parseVitestTextOutput(rawOutput, !!error);
1849
+ debug("vitest", "\u6587\u672C\u89E3\u6790\u7ED3\u679C:", parsed.suites.length, "\u4E2A\u5957\u4EF6");
1850
+ resolve({ ...parsed, rawOutput, parseMethod: "text-fallback" });
1851
+ return;
1852
+ }
1853
+ if (error && error.message.includes("ENOENT")) {
1854
+ reject(new Error("\u672A\u627E\u5230 vitest\uFF0C\u8BF7\u786E\u4FDD\u5DF2\u5B89\u88C5: npm install -D vitest"));
1855
+ return;
1856
+ }
1857
+ debug("vitest", "\u6240\u6709\u89E3\u6790\u65B9\u5F0F\u5747\u5931\u8D25\uFF0C\u8FD4\u56DE\u7A7A\u7ED3\u679C");
1858
+ resolve({ success: !error, suites: [], rawOutput, parseMethod: "none" });
1818
1859
  });
1819
1860
  child.on("error", (err) => {
1861
+ try {
1862
+ import_node_fs6.default.unlinkSync(tmpFile);
1863
+ } catch {
1864
+ }
1820
1865
  reject(new Error(`Vitest \u6267\u884C\u5931\u8D25: ${err.message}`));
1821
1866
  });
1822
1867
  });
1823
1868
  }
1824
- function parseVitestJSONOutput(output) {
1825
- const jsonMatch = output.match(/\{[\s\S]*"testResults"[\s\S]*\}/);
1826
- if (!jsonMatch) {
1827
- return parseVitestTextOutput(output, false);
1869
+ function parseVitestJSON(jsonStr) {
1870
+ const data = JSON.parse(jsonStr);
1871
+ const suites = [];
1872
+ debug("vitest-json", "JSON \u9876\u5C42\u5B57\u6BB5:", Object.keys(data).join(", "));
1873
+ if (data.testResults && Array.isArray(data.testResults)) {
1874
+ debug("vitest-json", `testResults \u6570\u91CF: ${data.testResults.length}`);
1875
+ for (const fileResult of data.testResults) {
1876
+ const suiteTests = parseTestResults(fileResult);
1877
+ suites.push({
1878
+ name: import_node_path6.default.basename(fileResult.name || "unknown"),
1879
+ file: fileResult.name || "unknown",
1880
+ type: "unit",
1881
+ status: mapVitestStatus(fileResult.status),
1882
+ duration: fileResult.duration || 0,
1883
+ tests: suiteTests
1884
+ });
1885
+ }
1828
1886
  }
1829
- try {
1887
+ if (suites.length === 0 && data.numTotalTests !== void 0) {
1888
+ debug("vitest-json", `\u4F7F\u7528\u6C47\u603B\u683C\u5F0F: total=${data.numTotalTests}, passed=${data.numPassedTests}`);
1889
+ suites.push({
1890
+ name: "Vitest Results",
1891
+ file: "unknown",
1892
+ type: "unit",
1893
+ status: data.numFailedTests > 0 ? "failed" : "passed",
1894
+ duration: 0,
1895
+ tests: buildTestsFromSummary(data)
1896
+ });
1897
+ }
1898
+ if (suites.length === 0 && data.suites && Array.isArray(data.suites)) {
1899
+ debug("vitest-json", `suites \u6570\u91CF: ${data.suites.length}`);
1900
+ for (const suiteData of data.suites) {
1901
+ const suiteTests = [];
1902
+ for (const test of suiteData.tests || []) {
1903
+ suiteTests.push({
1904
+ name: test.name || test.title || "unknown",
1905
+ file: test.file || suiteData.file || "unknown",
1906
+ status: mapVitestStatus(test.status || test.result?.status),
1907
+ duration: test.duration || test.result?.duration || 0,
1908
+ error: test.result?.errors?.[0] ? { message: test.result.errors[0].message || String(test.result.errors[0]) } : void 0,
1909
+ retries: 0
1910
+ });
1911
+ }
1912
+ suites.push({
1913
+ name: suiteData.name || "unknown",
1914
+ file: suiteData.file || "unknown",
1915
+ type: "unit",
1916
+ status: suiteTests.some((t) => t.status === "failed") ? "failed" : "passed",
1917
+ duration: 0,
1918
+ tests: suiteTests
1919
+ });
1920
+ }
1921
+ }
1922
+ let coverage;
1923
+ if (data.coverageMap) {
1924
+ coverage = extractCoverage(data.coverageMap);
1925
+ }
1926
+ if (!coverage && data.coverage && typeof data.coverage === "object") {
1927
+ const cov = data.coverage;
1928
+ const totals = cov.totals || cov;
1929
+ const getVal = (key) => {
1930
+ const v = totals[key];
1931
+ return typeof v === "number" ? v : typeof v === "object" && v !== null && "pct" in v ? v.pct / 100 : 0;
1932
+ };
1933
+ coverage = {
1934
+ lines: getVal("lines"),
1935
+ statements: getVal("statements"),
1936
+ functions: getVal("functions"),
1937
+ branches: getVal("branches")
1938
+ };
1939
+ }
1940
+ const success = data.success !== false ? data.numFailedTests !== void 0 ? data.numFailedTests === 0 : suites.every((s) => s.status !== "failed") : false;
1941
+ return { success, suites, coverage };
1942
+ }
1943
+ function parseTestResults(fileResult) {
1944
+ const tests = [];
1945
+ const assertions = fileResult.assertionResults || fileResult.tests || [];
1946
+ for (const assertion of assertions) {
1947
+ tests.push({
1948
+ name: assertion.title || assertion.fullName || assertion.name || "unknown",
1949
+ file: fileResult.name || "unknown",
1950
+ status: mapVitestStatus(assertion.status),
1951
+ duration: assertion.duration || 0,
1952
+ error: assertion.failureMessages?.length ? { message: assertion.failureMessages[0] } : assertion.failureMessage ? { message: assertion.failureMessage } : void 0,
1953
+ retries: 0
1954
+ });
1955
+ }
1956
+ if (tests.length === 0 && fileResult.numPassingTests !== void 0) {
1957
+ tests.push(...buildTestsFromSummary(fileResult));
1958
+ }
1959
+ return tests;
1960
+ }
1961
+ function buildTestsFromSummary(data) {
1962
+ const tests = [];
1963
+ const counts = [
1964
+ [data.numPassedTests || 0, "passed"],
1965
+ [data.numFailedTests || 0, "failed"],
1966
+ [data.numPendingTests || 0, "skipped"]
1967
+ ];
1968
+ for (const [n, s] of counts) {
1969
+ for (let i = 0; i < n; i++) {
1970
+ tests.push({
1971
+ name: `${s} test ${i + 1}`,
1972
+ file: data.name || "unknown",
1973
+ status: s,
1974
+ duration: 0,
1975
+ retries: 0
1976
+ });
1977
+ }
1978
+ }
1979
+ return tests;
1980
+ }
1981
+ function parseFromStdout(output) {
1982
+ const jsonMatch = output.match(/\{[\s\S]*"testResults"[\s\S]*\}/);
1983
+ if (jsonMatch) {
1830
1984
  const data = JSON.parse(jsonMatch[0]);
1831
1985
  const suites = [];
1832
1986
  if (data.testResults && Array.isArray(data.testResults)) {
1833
1987
  for (const fileResult of data.testResults) {
1834
- const suite = {
1835
- name: import_node_path6.default.basename(fileResult.name || fileResult.assertionResults?.[0]?.ancestorTitles?.[0] || "unknown"),
1988
+ suites.push({
1989
+ name: import_node_path6.default.basename(fileResult.name || "unknown"),
1836
1990
  file: fileResult.name || "unknown",
1837
1991
  type: "unit",
1838
1992
  status: mapVitestStatus(fileResult.status),
1839
1993
  duration: fileResult.duration || 0,
1840
- tests: (fileResult.assertionResults || []).map((assertion) => ({
1841
- name: assertion.title || assertion.fullName || "unknown",
1842
- file: fileResult.name || "unknown",
1843
- status: mapVitestStatus(assertion.status),
1844
- duration: assertion.duration || 0,
1845
- error: assertion.failureMessages?.length ? { message: assertion.failureMessages[0] } : void 0,
1846
- retries: 0
1847
- }))
1848
- };
1849
- suites.push(suite);
1994
+ tests: parseTestResults(fileResult)
1995
+ });
1850
1996
  }
1851
1997
  }
1852
- let coverage;
1853
- if (data.coverageMap) {
1854
- coverage = extractCoverage(data.coverageMap);
1855
- }
1856
1998
  const success = data.success !== false && suites.every((s) => s.status !== "failed");
1999
+ let coverage;
2000
+ if (data.coverageMap) coverage = extractCoverage(data.coverageMap);
1857
2001
  return { success, suites, coverage };
1858
- } catch {
1859
- return parseVitestTextOutput(output, false);
1860
2002
  }
2003
+ const anyJsonMatch = output.match(/\{[\s\S]*"numTotalTests"[\s\S]*\}/);
2004
+ if (anyJsonMatch) {
2005
+ return parseVitestJSON(anyJsonMatch[0]);
2006
+ }
2007
+ throw new Error("stdout \u4E2D\u672A\u627E\u5230\u6709\u6548 JSON");
1861
2008
  }
1862
2009
  function parseVitestTextOutput(output, hasError) {
1863
2010
  const suites = [];
2011
+ debug("vitest-text", "\u6587\u672C\u8F93\u51FA\u524D 1000 \u5B57\u7B26:", output.substring(0, 1e3));
2012
+ const suiteRegex = /[✓✗×✕]\s+(.+\.test\.(ts|js)|.+\.spec\.(ts|js))\s*\((\d+)[^)]*\)/i;
2013
+ const lines = output.split("\n");
1864
2014
  let totalPassed = 0;
1865
2015
  let totalFailed = 0;
1866
- const suiteRegex = /[✓✗×]\s+(.+\.test\.ts|.+\.spec\.ts)\s*\((\d+)\s+test/i;
1867
- const lines = output.split("\n");
1868
2016
  for (const line of lines) {
1869
2017
  const match = line.match(suiteRegex);
1870
2018
  if (match) {
1871
2019
  const file = match[1];
1872
- const testCount = parseInt(match[2], 10);
2020
+ const testCount = parseInt(match[4], 10);
1873
2021
  const isPassed = line.includes("\u2713");
1874
2022
  if (isPassed) totalPassed += testCount;
1875
2023
  else totalFailed += testCount;
@@ -1889,15 +2037,39 @@ function parseVitestTextOutput(output, hasError) {
1889
2037
  });
1890
2038
  }
1891
2039
  }
2040
+ if (suites.length === 0) {
2041
+ const summaryMatch = output.match(/Tests\s+(\d+)\s+(passed|failed)/i);
2042
+ if (summaryMatch) {
2043
+ const count = parseInt(summaryMatch[1], 10);
2044
+ const status = summaryMatch[2].toLowerCase() === "passed" ? "passed" : "failed";
2045
+ suites.push({
2046
+ name: "Vitest Summary",
2047
+ file: "unknown",
2048
+ type: "unit",
2049
+ status,
2050
+ duration: 0,
2051
+ tests: Array.from({ length: count }, (_, i) => ({
2052
+ name: `test ${i + 1}`,
2053
+ file: "unknown",
2054
+ status,
2055
+ duration: 0,
2056
+ retries: 0
2057
+ }))
2058
+ });
2059
+ }
2060
+ }
2061
+ debug("vitest-text", `\u89E3\u6790\u5230 ${suites.length} \u4E2A\u5957\u4EF6, ${totalPassed} \u901A\u8FC7, ${totalFailed} \u5931\u8D25`);
1892
2062
  return {
1893
2063
  success: !hasError || totalFailed === 0,
1894
2064
  suites
1895
2065
  };
1896
2066
  }
1897
2067
  function mapVitestStatus(status) {
2068
+ if (!status) return "pending";
1898
2069
  switch (status) {
1899
2070
  case "passed":
1900
2071
  case "pass":
2072
+ case "done":
1901
2073
  return "passed";
1902
2074
  case "failed":
1903
2075
  case "fail":
@@ -1905,6 +2077,7 @@ function mapVitestStatus(status) {
1905
2077
  case "skipped":
1906
2078
  case "skip":
1907
2079
  case "pending":
2080
+ case "todo":
1908
2081
  return "skipped";
1909
2082
  default:
1910
2083
  return "pending";
@@ -2841,7 +3014,7 @@ async function testAIConnection(config) {
2841
3014
  }
2842
3015
 
2843
3016
  // src/services/mock-server.ts
2844
- var import_node_fs6 = __toESM(require("fs"), 1);
3017
+ var import_node_fs7 = __toESM(require("fs"), 1);
2845
3018
  var import_node_path8 = __toESM(require("path"), 1);
2846
3019
  var serverState = {
2847
3020
  running: false,
@@ -2854,18 +3027,18 @@ function getMockServerState() {
2854
3027
  }
2855
3028
  async function loadMockRoutes(routesDir) {
2856
3029
  const absDir = import_node_path8.default.resolve(process.cwd(), routesDir);
2857
- if (!import_node_fs6.default.existsSync(absDir)) {
3030
+ if (!import_node_fs7.default.existsSync(absDir)) {
2858
3031
  return [];
2859
3032
  }
2860
3033
  const routes = [];
2861
- const entries = import_node_fs6.default.readdirSync(absDir, { withFileTypes: true });
3034
+ const entries = import_node_fs7.default.readdirSync(absDir, { withFileTypes: true });
2862
3035
  for (const entry of entries) {
2863
3036
  if (!entry.isFile()) continue;
2864
3037
  const filePath = import_node_path8.default.join(absDir, entry.name);
2865
3038
  const ext = import_node_path8.default.extname(entry.name);
2866
3039
  try {
2867
3040
  if (ext === ".json") {
2868
- const content = import_node_fs6.default.readFileSync(filePath, "utf-8");
3041
+ const content = import_node_fs7.default.readFileSync(filePath, "utf-8");
2869
3042
  const parsed = JSON.parse(content);
2870
3043
  if (Array.isArray(parsed)) {
2871
3044
  routes.push(...parsed);
@@ -3057,11 +3230,11 @@ function generateMockRouteTemplate(name) {
3057
3230
  }
3058
3231
  function initMockRoutesDir(routesDir) {
3059
3232
  const absDir = import_node_path8.default.resolve(process.cwd(), routesDir);
3060
- if (!import_node_fs6.default.existsSync(absDir)) {
3061
- import_node_fs6.default.mkdirSync(absDir, { recursive: true });
3233
+ if (!import_node_fs7.default.existsSync(absDir)) {
3234
+ import_node_fs7.default.mkdirSync(absDir, { recursive: true });
3062
3235
  }
3063
3236
  const examplePath = import_node_path8.default.join(absDir, "example.json");
3064
- if (!import_node_fs6.default.existsSync(examplePath)) {
3237
+ if (!import_node_fs7.default.existsSync(examplePath)) {
3065
3238
  const exampleRoutes = [
3066
3239
  {
3067
3240
  method: "GET",
@@ -3085,24 +3258,24 @@ function initMockRoutesDir(routesDir) {
3085
3258
  }
3086
3259
  }
3087
3260
  ];
3088
- import_node_fs6.default.writeFileSync(examplePath, JSON.stringify(exampleRoutes, null, 2), "utf-8");
3261
+ import_node_fs7.default.writeFileSync(examplePath, JSON.stringify(exampleRoutes, null, 2), "utf-8");
3089
3262
  }
3090
3263
  }
3091
3264
 
3092
3265
  // src/services/visual.ts
3093
- var import_node_fs7 = __toESM(require("fs"), 1);
3266
+ var import_node_fs8 = __toESM(require("fs"), 1);
3094
3267
  var import_node_path9 = __toESM(require("path"), 1);
3095
3268
  var import_pixelmatch = __toESM(require("pixelmatch"), 1);
3096
3269
  var import_pngjs = require("pngjs");
3097
3270
  function compareImages(baselinePath, currentPath, diffOutputPath, threshold = 0.1) {
3098
- if (!import_node_fs7.default.existsSync(baselinePath)) {
3271
+ if (!import_node_fs8.default.existsSync(baselinePath)) {
3099
3272
  throw new Error(`\u57FA\u7EBF\u56FE\u7247\u4E0D\u5B58\u5728: ${baselinePath}`);
3100
3273
  }
3101
- if (!import_node_fs7.default.existsSync(currentPath)) {
3274
+ if (!import_node_fs8.default.existsSync(currentPath)) {
3102
3275
  throw new Error(`\u5F53\u524D\u56FE\u7247\u4E0D\u5B58\u5728: ${currentPath}`);
3103
3276
  }
3104
- const baseline = import_pngjs.PNG.sync.read(import_node_fs7.default.readFileSync(baselinePath));
3105
- const current = import_pngjs.PNG.sync.read(import_node_fs7.default.readFileSync(currentPath));
3277
+ const baseline = import_pngjs.PNG.sync.read(import_node_fs8.default.readFileSync(baselinePath));
3278
+ const current = import_pngjs.PNG.sync.read(import_node_fs8.default.readFileSync(currentPath));
3106
3279
  if (baseline.width !== current.width || baseline.height !== current.height) {
3107
3280
  return {
3108
3281
  passed: false,
@@ -3131,10 +3304,10 @@ function compareImages(baselinePath, currentPath, diffOutputPath, threshold = 0.
3131
3304
  let diffPath;
3132
3305
  if (diffPixels > 0) {
3133
3306
  const diffDir = import_node_path9.default.dirname(diffOutputPath);
3134
- if (!import_node_fs7.default.existsSync(diffDir)) {
3135
- import_node_fs7.default.mkdirSync(diffDir, { recursive: true });
3307
+ if (!import_node_fs8.default.existsSync(diffDir)) {
3308
+ import_node_fs8.default.mkdirSync(diffDir, { recursive: true });
3136
3309
  }
3137
- import_node_fs7.default.writeFileSync(diffOutputPath, import_pngjs.PNG.sync.write(diff));
3310
+ import_node_fs8.default.writeFileSync(diffOutputPath, import_pngjs.PNG.sync.write(diff));
3138
3311
  diffPath = diffOutputPath;
3139
3312
  }
3140
3313
  return {
@@ -3148,14 +3321,14 @@ function compareImages(baselinePath, currentPath, diffOutputPath, threshold = 0.
3148
3321
  };
3149
3322
  }
3150
3323
  function createBaseline(currentPath, baselinePath) {
3151
- if (!import_node_fs7.default.existsSync(currentPath)) {
3324
+ if (!import_node_fs8.default.existsSync(currentPath)) {
3152
3325
  throw new Error(`\u5F53\u524D\u622A\u56FE\u4E0D\u5B58\u5728: ${currentPath}`);
3153
3326
  }
3154
3327
  const baselineDir = import_node_path9.default.dirname(baselinePath);
3155
- if (!import_node_fs7.default.existsSync(baselineDir)) {
3156
- import_node_fs7.default.mkdirSync(baselineDir, { recursive: true });
3328
+ if (!import_node_fs8.default.existsSync(baselineDir)) {
3329
+ import_node_fs8.default.mkdirSync(baselineDir, { recursive: true });
3157
3330
  }
3158
- import_node_fs7.default.copyFileSync(currentPath, baselinePath);
3331
+ import_node_fs8.default.copyFileSync(currentPath, baselinePath);
3159
3332
  return baselinePath;
3160
3333
  }
3161
3334
  function updateBaseline(currentPath, baselinePath) {
@@ -3163,69 +3336,69 @@ function updateBaseline(currentPath, baselinePath) {
3163
3336
  }
3164
3337
  function updateAllBaselines(currentDir, baselineDir) {
3165
3338
  const updated = [];
3166
- if (!import_node_fs7.default.existsSync(currentDir)) {
3339
+ if (!import_node_fs8.default.existsSync(currentDir)) {
3167
3340
  return updated;
3168
3341
  }
3169
- const files = import_node_fs7.default.readdirSync(currentDir).filter((f) => f.endsWith(".png"));
3342
+ const files = import_node_fs8.default.readdirSync(currentDir).filter((f) => f.endsWith(".png"));
3170
3343
  for (const file of files) {
3171
3344
  const currentPath = import_node_path9.default.join(currentDir, file);
3172
3345
  const baselinePath = import_node_path9.default.join(baselineDir, file);
3173
3346
  const baselineDirAbs = import_node_path9.default.dirname(baselinePath);
3174
- if (!import_node_fs7.default.existsSync(baselineDirAbs)) {
3175
- import_node_fs7.default.mkdirSync(baselineDirAbs, { recursive: true });
3347
+ if (!import_node_fs8.default.existsSync(baselineDirAbs)) {
3348
+ import_node_fs8.default.mkdirSync(baselineDirAbs, { recursive: true });
3176
3349
  }
3177
- import_node_fs7.default.copyFileSync(currentPath, baselinePath);
3350
+ import_node_fs8.default.copyFileSync(currentPath, baselinePath);
3178
3351
  updated.push(file);
3179
3352
  }
3180
3353
  return updated;
3181
3354
  }
3182
3355
  function cleanBaselines(baselineDir) {
3183
- if (!import_node_fs7.default.existsSync(baselineDir)) {
3356
+ if (!import_node_fs8.default.existsSync(baselineDir)) {
3184
3357
  return 0;
3185
3358
  }
3186
- const files = import_node_fs7.default.readdirSync(baselineDir).filter((f) => f.endsWith(".png"));
3359
+ const files = import_node_fs8.default.readdirSync(baselineDir).filter((f) => f.endsWith(".png"));
3187
3360
  let count = 0;
3188
3361
  for (const file of files) {
3189
- import_node_fs7.default.unlinkSync(import_node_path9.default.join(baselineDir, file));
3362
+ import_node_fs8.default.unlinkSync(import_node_path9.default.join(baselineDir, file));
3190
3363
  count++;
3191
3364
  }
3192
3365
  return count;
3193
3366
  }
3194
3367
  function cleanDiffs(diffDir) {
3195
- if (!import_node_fs7.default.existsSync(diffDir)) {
3368
+ if (!import_node_fs8.default.existsSync(diffDir)) {
3196
3369
  return 0;
3197
3370
  }
3198
- const files = import_node_fs7.default.readdirSync(diffDir).filter((f) => f.endsWith(".png"));
3371
+ const files = import_node_fs8.default.readdirSync(diffDir).filter((f) => f.endsWith(".png"));
3199
3372
  let count = 0;
3200
3373
  for (const file of files) {
3201
- import_node_fs7.default.unlinkSync(import_node_path9.default.join(diffDir, file));
3374
+ import_node_fs8.default.unlinkSync(import_node_path9.default.join(diffDir, file));
3202
3375
  count++;
3203
3376
  }
3204
3377
  return count;
3205
3378
  }
3206
3379
  function listBaselines(baselineDir) {
3207
- if (!import_node_fs7.default.existsSync(baselineDir)) {
3380
+ if (!import_node_fs8.default.existsSync(baselineDir)) {
3208
3381
  return [];
3209
3382
  }
3210
- return import_node_fs7.default.readdirSync(baselineDir).filter((f) => f.endsWith(".png")).sort();
3383
+ return import_node_fs8.default.readdirSync(baselineDir).filter((f) => f.endsWith(".png")).sort();
3211
3384
  }
3212
3385
  function listDiffs(diffDir) {
3213
- if (!import_node_fs7.default.existsSync(diffDir)) {
3386
+ if (!import_node_fs8.default.existsSync(diffDir)) {
3214
3387
  return [];
3215
3388
  }
3216
- return import_node_fs7.default.readdirSync(diffDir).filter((f) => f.endsWith(".png")).sort();
3389
+ return import_node_fs8.default.readdirSync(diffDir).filter((f) => f.endsWith(".png")).sort();
3217
3390
  }
3218
3391
  function compareDirectories(baselineDir, currentDir, diffDir, threshold = 0.1) {
3219
3392
  const results = [];
3220
- if (!import_node_fs7.default.existsSync(currentDir)) {
3393
+ if (!import_node_fs8.default.existsSync(currentDir)) {
3221
3394
  return results;
3222
3395
  }
3223
- const currentFiles = import_node_fs7.default.readdirSync(currentDir).filter((f) => f.endsWith(".png"));
3396
+ const currentFiles = import_node_fs8.default.readdirSync(currentDir).filter((f) => f.endsWith(".png"));
3224
3397
  for (const file of currentFiles) {
3225
3398
  const currentPath = import_node_path9.default.join(currentDir, file);
3226
3399
  const baselinePath = import_node_path9.default.join(baselineDir, file);
3227
3400
  const diffPath = import_node_path9.default.join(diffDir, file);
3228
- if (!import_node_fs7.default.existsSync(baselinePath)) {
3401
+ if (!import_node_fs8.default.existsSync(baselinePath)) {
3229
3402
  createBaseline(currentPath, baselinePath);
3230
3403
  results.push({
3231
3404
  passed: true,
@@ -3257,14 +3430,14 @@ function compareDirectories(baselineDir, currentDir, diffDir, threshold = 0.1) {
3257
3430
  }
3258
3431
 
3259
3432
  // src/services/source-analyzer.ts
3260
- var import_node_fs8 = __toESM(require("fs"), 1);
3433
+ var import_node_fs9 = __toESM(require("fs"), 1);
3261
3434
  var import_node_path10 = __toESM(require("path"), 1);
3262
3435
  function analyzeFile(filePath) {
3263
3436
  const absolutePath = import_node_path10.default.resolve(process.cwd(), filePath);
3264
- if (!import_node_fs8.default.existsSync(absolutePath)) {
3437
+ if (!import_node_fs9.default.existsSync(absolutePath)) {
3265
3438
  return { filePath, exports: [], apiCalls: [] };
3266
3439
  }
3267
- const content = import_node_fs8.default.readFileSync(absolutePath, "utf-8");
3440
+ const content = import_node_fs9.default.readFileSync(absolutePath, "utf-8");
3268
3441
  const ext = import_node_path10.default.extname(filePath);
3269
3442
  const result = {
3270
3443
  filePath,
@@ -3619,13 +3792,13 @@ function cleanTemplateUrl(url) {
3619
3792
  }
3620
3793
  function scanAPICalls(srcDir) {
3621
3794
  const absDir = import_node_path10.default.resolve(process.cwd(), srcDir);
3622
- if (!import_node_fs8.default.existsSync(absDir)) {
3795
+ if (!import_node_fs9.default.existsSync(absDir)) {
3623
3796
  return [];
3624
3797
  }
3625
3798
  const allCalls = [];
3626
3799
  const seen = /* @__PURE__ */ new Set();
3627
3800
  function walk(dir) {
3628
- const entries = import_node_fs8.default.readdirSync(dir, { withFileTypes: true });
3801
+ const entries = import_node_fs9.default.readdirSync(dir, { withFileTypes: true });
3629
3802
  for (const entry of entries) {
3630
3803
  if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist") continue;
3631
3804
  const fullPath = import_node_path10.default.join(dir, entry.name);
@@ -3633,7 +3806,7 @@ function scanAPICalls(srcDir) {
3633
3806
  walk(fullPath);
3634
3807
  } else if (entry.isFile() && /\.(ts|js|vue|mjs)$/.test(entry.name)) {
3635
3808
  try {
3636
- const content = import_node_fs8.default.readFileSync(fullPath, "utf-8");
3809
+ const content = import_node_fs9.default.readFileSync(fullPath, "utf-8");
3637
3810
  const calls = extractAPICalls(content, import_node_path10.default.relative(process.cwd(), fullPath));
3638
3811
  for (const call of calls) {
3639
3812
  const key = `${call.method}:${call.url}`;