qat-cli 0.3.1 → 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/README.md +1 -1
- package/dist/cli.js +1257 -1244
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +228 -122
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +227 -121
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1623,12 +1623,23 @@ function generateHTMLReport(data) {
|
|
|
1623
1623
|
// src/runners/vitest-runner.ts
|
|
1624
1624
|
import { execFile } from "child_process";
|
|
1625
1625
|
import path5 from "path";
|
|
1626
|
+
import fs6 from "fs";
|
|
1627
|
+
import os from "os";
|
|
1628
|
+
var isVerbose = () => process.env.QAT_VERBOSE === "true";
|
|
1629
|
+
function debug(label, ...args) {
|
|
1630
|
+
if (isVerbose()) {
|
|
1631
|
+
console.log(`\x1B[90m [debug:${label}]\x1B[0m`, ...args);
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1626
1634
|
async function runVitest(options) {
|
|
1627
1635
|
const startTime = Date.now();
|
|
1628
1636
|
const args = buildVitestArgs(options);
|
|
1637
|
+
debug("vitest", "\u547D\u4EE4\u53C2\u6570:", args.join(" "));
|
|
1629
1638
|
try {
|
|
1630
1639
|
const result = await execVitest(args);
|
|
1631
1640
|
const endTime = Date.now();
|
|
1641
|
+
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`);
|
|
1642
|
+
debug("vitest", "\u89E3\u6790\u65B9\u5F0F:", result.parseMethod);
|
|
1632
1643
|
return {
|
|
1633
1644
|
type: options.type,
|
|
1634
1645
|
status: result.success ? "passed" : "failed",
|
|
@@ -1693,12 +1704,11 @@ function buildVitestArgs(options) {
|
|
|
1693
1704
|
return args;
|
|
1694
1705
|
}
|
|
1695
1706
|
async function execVitest(args) {
|
|
1696
|
-
const os = await import("os");
|
|
1697
|
-
const fs9 = await import("fs");
|
|
1698
1707
|
const tmpFile = path5.join(os.tmpdir(), `qat-vitest-result-${Date.now()}.json`);
|
|
1699
1708
|
const argsWithOutput = [...args, "--outputFile", tmpFile];
|
|
1700
1709
|
return new Promise((resolve, reject) => {
|
|
1701
1710
|
const npx = process.platform === "win32" ? "npx.cmd" : "npx";
|
|
1711
|
+
debug("vitest", "\u6267\u884C\u547D\u4EE4:", npx, argsWithOutput.join(" "));
|
|
1702
1712
|
const child = execFile(npx, argsWithOutput, {
|
|
1703
1713
|
cwd: process.cwd(),
|
|
1704
1714
|
env: { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" },
|
|
@@ -1706,85 +1716,113 @@ async function execVitest(args) {
|
|
|
1706
1716
|
shell: true
|
|
1707
1717
|
}, (error, stdout, stderr) => {
|
|
1708
1718
|
const rawOutput = stdout || stderr || "";
|
|
1719
|
+
const exitCode = error && "code" in error ? error.code : 0;
|
|
1720
|
+
debug("vitest", `\u9000\u51FA\u7801: ${exitCode}`);
|
|
1721
|
+
debug("vitest", `stdout \u957F\u5EA6: ${stdout?.length || 0}, stderr \u957F\u5EA6: ${stderr?.length || 0}`);
|
|
1709
1722
|
let jsonResult = null;
|
|
1710
1723
|
try {
|
|
1711
|
-
if (
|
|
1712
|
-
jsonResult =
|
|
1713
|
-
|
|
1724
|
+
if (fs6.existsSync(tmpFile)) {
|
|
1725
|
+
jsonResult = fs6.readFileSync(tmpFile, "utf-8");
|
|
1726
|
+
debug("vitest", `\u4ECE\u4E34\u65F6\u6587\u4EF6\u8BFB\u53D6\u5230 JSON (${jsonResult.length} \u5B57\u7B26)`);
|
|
1727
|
+
debug("vitest", "JSON \u524D 500 \u5B57\u7B26:", jsonResult.substring(0, 500));
|
|
1728
|
+
fs6.unlinkSync(tmpFile);
|
|
1729
|
+
} else {
|
|
1730
|
+
debug("vitest", "\u4E34\u65F6\u6587\u4EF6\u4E0D\u5B58\u5728:", tmpFile);
|
|
1714
1731
|
}
|
|
1715
|
-
} catch {
|
|
1732
|
+
} catch (e) {
|
|
1733
|
+
debug("vitest", "\u4E34\u65F6\u6587\u4EF6\u8BFB\u53D6\u5931\u8D25:", e instanceof Error ? e.message : String(e));
|
|
1716
1734
|
}
|
|
1717
1735
|
if (jsonResult) {
|
|
1718
1736
|
try {
|
|
1719
|
-
const parsed =
|
|
1720
|
-
|
|
1737
|
+
const parsed = parseVitestJSON(jsonResult);
|
|
1738
|
+
debug("vitest", "\u4ECE\u4E34\u65F6\u6587\u4EF6\u89E3\u6790\u6210\u529F:", parsed.suites.length, "\u4E2A\u5957\u4EF6");
|
|
1739
|
+
resolve({ ...parsed, rawOutput, parseMethod: "outputFile-JSON" });
|
|
1721
1740
|
return;
|
|
1722
|
-
} catch {
|
|
1741
|
+
} catch (e) {
|
|
1742
|
+
debug("vitest", "\u4E34\u65F6\u6587\u4EF6 JSON \u89E3\u6790\u5931\u8D25:", e instanceof Error ? e.message : String(e));
|
|
1723
1743
|
}
|
|
1724
1744
|
}
|
|
1745
|
+
debug("vitest", "\u5C1D\u8BD5\u4ECE stdout \u63D0\u53D6 JSON...");
|
|
1725
1746
|
try {
|
|
1726
|
-
const parsed =
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1747
|
+
const parsed = parseFromStdout(rawOutput);
|
|
1748
|
+
debug("vitest", "\u4ECE stdout \u89E3\u6790\u6210\u529F:", parsed.suites.length, "\u4E2A\u5957\u4EF6");
|
|
1749
|
+
resolve({ ...parsed, rawOutput, parseMethod: "stdout-JSON" });
|
|
1750
|
+
return;
|
|
1751
|
+
} catch (e) {
|
|
1752
|
+
debug("vitest", "stdout JSON \u89E3\u6790\u5931\u8D25:", e instanceof Error ? e.message : String(e));
|
|
1753
|
+
}
|
|
1754
|
+
debug("vitest", "\u5C1D\u8BD5\u4ECE\u6587\u672C\u8F93\u51FA\u89E3\u6790...");
|
|
1755
|
+
if (rawOutput) {
|
|
1756
|
+
const parsed = parseVitestTextOutput(rawOutput, !!error);
|
|
1757
|
+
debug("vitest", "\u6587\u672C\u89E3\u6790\u7ED3\u679C:", parsed.suites.length, "\u4E2A\u5957\u4EF6");
|
|
1758
|
+
resolve({ ...parsed, rawOutput, parseMethod: "text-fallback" });
|
|
1759
|
+
return;
|
|
1736
1760
|
}
|
|
1761
|
+
if (error && error.message.includes("ENOENT")) {
|
|
1762
|
+
reject(new Error("\u672A\u627E\u5230 vitest\uFF0C\u8BF7\u786E\u4FDD\u5DF2\u5B89\u88C5: npm install -D vitest"));
|
|
1763
|
+
return;
|
|
1764
|
+
}
|
|
1765
|
+
debug("vitest", "\u6240\u6709\u89E3\u6790\u65B9\u5F0F\u5747\u5931\u8D25\uFF0C\u8FD4\u56DE\u7A7A\u7ED3\u679C");
|
|
1766
|
+
resolve({ success: !error, suites: [], rawOutput, parseMethod: "none" });
|
|
1737
1767
|
});
|
|
1738
1768
|
child.on("error", (err) => {
|
|
1739
1769
|
try {
|
|
1740
|
-
|
|
1770
|
+
fs6.unlinkSync(tmpFile);
|
|
1741
1771
|
} catch {
|
|
1742
1772
|
}
|
|
1743
1773
|
reject(new Error(`Vitest \u6267\u884C\u5931\u8D25: ${err.message}`));
|
|
1744
1774
|
});
|
|
1745
1775
|
});
|
|
1746
1776
|
}
|
|
1747
|
-
function
|
|
1777
|
+
function parseVitestJSON(jsonStr) {
|
|
1748
1778
|
const data = JSON.parse(jsonStr);
|
|
1749
1779
|
const suites = [];
|
|
1780
|
+
debug("vitest-json", "JSON \u9876\u5C42\u5B57\u6BB5:", Object.keys(data).join(", "));
|
|
1750
1781
|
if (data.testResults && Array.isArray(data.testResults)) {
|
|
1782
|
+
debug("vitest-json", `testResults \u6570\u91CF: ${data.testResults.length}`);
|
|
1751
1783
|
for (const fileResult of data.testResults) {
|
|
1784
|
+
const suiteTests = parseTestResults(fileResult);
|
|
1785
|
+
suites.push({
|
|
1786
|
+
name: path5.basename(fileResult.name || "unknown"),
|
|
1787
|
+
file: fileResult.name || "unknown",
|
|
1788
|
+
type: "unit",
|
|
1789
|
+
status: mapVitestStatus(fileResult.status),
|
|
1790
|
+
duration: fileResult.duration || 0,
|
|
1791
|
+
tests: suiteTests
|
|
1792
|
+
});
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
if (suites.length === 0 && data.numTotalTests !== void 0) {
|
|
1796
|
+
debug("vitest-json", `\u4F7F\u7528\u6C47\u603B\u683C\u5F0F: total=${data.numTotalTests}, passed=${data.numPassedTests}`);
|
|
1797
|
+
suites.push({
|
|
1798
|
+
name: "Vitest Results",
|
|
1799
|
+
file: "unknown",
|
|
1800
|
+
type: "unit",
|
|
1801
|
+
status: data.numFailedTests > 0 ? "failed" : "passed",
|
|
1802
|
+
duration: 0,
|
|
1803
|
+
tests: buildTestsFromSummary(data)
|
|
1804
|
+
});
|
|
1805
|
+
}
|
|
1806
|
+
if (suites.length === 0 && data.suites && Array.isArray(data.suites)) {
|
|
1807
|
+
debug("vitest-json", `suites \u6570\u91CF: ${data.suites.length}`);
|
|
1808
|
+
for (const suiteData of data.suites) {
|
|
1752
1809
|
const suiteTests = [];
|
|
1753
|
-
const
|
|
1754
|
-
for (const assertion of assertions) {
|
|
1810
|
+
for (const test of suiteData.tests || []) {
|
|
1755
1811
|
suiteTests.push({
|
|
1756
|
-
name:
|
|
1757
|
-
file:
|
|
1758
|
-
status: mapVitestStatus(
|
|
1759
|
-
duration:
|
|
1760
|
-
error:
|
|
1812
|
+
name: test.name || test.title || "unknown",
|
|
1813
|
+
file: test.file || suiteData.file || "unknown",
|
|
1814
|
+
status: mapVitestStatus(test.status || test.result?.status),
|
|
1815
|
+
duration: test.duration || test.result?.duration || 0,
|
|
1816
|
+
error: test.result?.errors?.[0] ? { message: test.result.errors[0].message || String(test.result.errors[0]) } : void 0,
|
|
1761
1817
|
retries: 0
|
|
1762
1818
|
});
|
|
1763
1819
|
}
|
|
1764
|
-
if (suiteTests.length === 0 && fileResult.numPassingTests !== void 0) {
|
|
1765
|
-
const counts = [
|
|
1766
|
-
{ n: fileResult.numPassingTests || 0, s: "passed" },
|
|
1767
|
-
{ n: fileResult.numFailingTests || 0, s: "failed" },
|
|
1768
|
-
{ n: fileResult.numPendingTests || 0, s: "skipped" }
|
|
1769
|
-
];
|
|
1770
|
-
for (const { n, s } of counts) {
|
|
1771
|
-
for (let i = 0; i < n; i++) {
|
|
1772
|
-
suiteTests.push({
|
|
1773
|
-
name: `${s} test ${i + 1}`,
|
|
1774
|
-
file: fileResult.name || "unknown",
|
|
1775
|
-
status: s,
|
|
1776
|
-
duration: 0,
|
|
1777
|
-
retries: 0
|
|
1778
|
-
});
|
|
1779
|
-
}
|
|
1780
|
-
}
|
|
1781
|
-
}
|
|
1782
1820
|
suites.push({
|
|
1783
|
-
name:
|
|
1784
|
-
file:
|
|
1821
|
+
name: suiteData.name || "unknown",
|
|
1822
|
+
file: suiteData.file || "unknown",
|
|
1785
1823
|
type: "unit",
|
|
1786
|
-
status:
|
|
1787
|
-
duration:
|
|
1824
|
+
status: suiteTests.some((t) => t.status === "failed") ? "failed" : "passed",
|
|
1825
|
+
duration: 0,
|
|
1788
1826
|
tests: suiteTests
|
|
1789
1827
|
});
|
|
1790
1828
|
}
|
|
@@ -1793,58 +1831,101 @@ function parseVitestJSONResult(jsonStr) {
|
|
|
1793
1831
|
if (data.coverageMap) {
|
|
1794
1832
|
coverage = extractCoverage(data.coverageMap);
|
|
1795
1833
|
}
|
|
1796
|
-
|
|
1834
|
+
if (!coverage && data.coverage && typeof data.coverage === "object") {
|
|
1835
|
+
const cov = data.coverage;
|
|
1836
|
+
const totals = cov.totals || cov;
|
|
1837
|
+
const getVal = (key) => {
|
|
1838
|
+
const v = totals[key];
|
|
1839
|
+
return typeof v === "number" ? v : typeof v === "object" && v !== null && "pct" in v ? v.pct / 100 : 0;
|
|
1840
|
+
};
|
|
1841
|
+
coverage = {
|
|
1842
|
+
lines: getVal("lines"),
|
|
1843
|
+
statements: getVal("statements"),
|
|
1844
|
+
functions: getVal("functions"),
|
|
1845
|
+
branches: getVal("branches")
|
|
1846
|
+
};
|
|
1847
|
+
}
|
|
1848
|
+
const success = data.success !== false ? data.numFailedTests !== void 0 ? data.numFailedTests === 0 : suites.every((s) => s.status !== "failed") : false;
|
|
1797
1849
|
return { success, suites, coverage };
|
|
1798
1850
|
}
|
|
1799
|
-
function
|
|
1800
|
-
const
|
|
1801
|
-
|
|
1802
|
-
|
|
1851
|
+
function parseTestResults(fileResult) {
|
|
1852
|
+
const tests = [];
|
|
1853
|
+
const assertions = fileResult.assertionResults || fileResult.tests || [];
|
|
1854
|
+
for (const assertion of assertions) {
|
|
1855
|
+
tests.push({
|
|
1856
|
+
name: assertion.title || assertion.fullName || assertion.name || "unknown",
|
|
1857
|
+
file: fileResult.name || "unknown",
|
|
1858
|
+
status: mapVitestStatus(assertion.status),
|
|
1859
|
+
duration: assertion.duration || 0,
|
|
1860
|
+
error: assertion.failureMessages?.length ? { message: assertion.failureMessages[0] } : assertion.failureMessage ? { message: assertion.failureMessage } : void 0,
|
|
1861
|
+
retries: 0
|
|
1862
|
+
});
|
|
1803
1863
|
}
|
|
1804
|
-
|
|
1864
|
+
if (tests.length === 0 && fileResult.numPassingTests !== void 0) {
|
|
1865
|
+
tests.push(...buildTestsFromSummary(fileResult));
|
|
1866
|
+
}
|
|
1867
|
+
return tests;
|
|
1868
|
+
}
|
|
1869
|
+
function buildTestsFromSummary(data) {
|
|
1870
|
+
const tests = [];
|
|
1871
|
+
const counts = [
|
|
1872
|
+
[data.numPassedTests || 0, "passed"],
|
|
1873
|
+
[data.numFailedTests || 0, "failed"],
|
|
1874
|
+
[data.numPendingTests || 0, "skipped"]
|
|
1875
|
+
];
|
|
1876
|
+
for (const [n, s] of counts) {
|
|
1877
|
+
for (let i = 0; i < n; i++) {
|
|
1878
|
+
tests.push({
|
|
1879
|
+
name: `${s} test ${i + 1}`,
|
|
1880
|
+
file: data.name || "unknown",
|
|
1881
|
+
status: s,
|
|
1882
|
+
duration: 0,
|
|
1883
|
+
retries: 0
|
|
1884
|
+
});
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
return tests;
|
|
1888
|
+
}
|
|
1889
|
+
function parseFromStdout(output) {
|
|
1890
|
+
const jsonMatch = output.match(/\{[\s\S]*"testResults"[\s\S]*\}/);
|
|
1891
|
+
if (jsonMatch) {
|
|
1805
1892
|
const data = JSON.parse(jsonMatch[0]);
|
|
1806
1893
|
const suites = [];
|
|
1807
1894
|
if (data.testResults && Array.isArray(data.testResults)) {
|
|
1808
1895
|
for (const fileResult of data.testResults) {
|
|
1809
|
-
|
|
1810
|
-
name: path5.basename(fileResult.name ||
|
|
1896
|
+
suites.push({
|
|
1897
|
+
name: path5.basename(fileResult.name || "unknown"),
|
|
1811
1898
|
file: fileResult.name || "unknown",
|
|
1812
1899
|
type: "unit",
|
|
1813
1900
|
status: mapVitestStatus(fileResult.status),
|
|
1814
1901
|
duration: fileResult.duration || 0,
|
|
1815
|
-
tests: (fileResult
|
|
1816
|
-
|
|
1817
|
-
file: fileResult.name || "unknown",
|
|
1818
|
-
status: mapVitestStatus(assertion.status),
|
|
1819
|
-
duration: assertion.duration || 0,
|
|
1820
|
-
error: assertion.failureMessages?.length ? { message: assertion.failureMessages[0] } : void 0,
|
|
1821
|
-
retries: 0
|
|
1822
|
-
}))
|
|
1823
|
-
};
|
|
1824
|
-
suites.push(suite);
|
|
1902
|
+
tests: parseTestResults(fileResult)
|
|
1903
|
+
});
|
|
1825
1904
|
}
|
|
1826
1905
|
}
|
|
1827
|
-
let coverage;
|
|
1828
|
-
if (data.coverageMap) {
|
|
1829
|
-
coverage = extractCoverage(data.coverageMap);
|
|
1830
|
-
}
|
|
1831
1906
|
const success = data.success !== false && suites.every((s) => s.status !== "failed");
|
|
1907
|
+
let coverage;
|
|
1908
|
+
if (data.coverageMap) coverage = extractCoverage(data.coverageMap);
|
|
1832
1909
|
return { success, suites, coverage };
|
|
1833
|
-
} catch {
|
|
1834
|
-
return parseVitestTextOutput(output, false);
|
|
1835
1910
|
}
|
|
1911
|
+
const anyJsonMatch = output.match(/\{[\s\S]*"numTotalTests"[\s\S]*\}/);
|
|
1912
|
+
if (anyJsonMatch) {
|
|
1913
|
+
return parseVitestJSON(anyJsonMatch[0]);
|
|
1914
|
+
}
|
|
1915
|
+
throw new Error("stdout \u4E2D\u672A\u627E\u5230\u6709\u6548 JSON");
|
|
1836
1916
|
}
|
|
1837
1917
|
function parseVitestTextOutput(output, hasError) {
|
|
1838
1918
|
const suites = [];
|
|
1919
|
+
debug("vitest-text", "\u6587\u672C\u8F93\u51FA\u524D 1000 \u5B57\u7B26:", output.substring(0, 1e3));
|
|
1920
|
+
const suiteRegex = /[✓✗×✕]\s+(.+\.test\.(ts|js)|.+\.spec\.(ts|js))\s*\((\d+)[^)]*\)/i;
|
|
1921
|
+
const lines = output.split("\n");
|
|
1839
1922
|
let totalPassed = 0;
|
|
1840
1923
|
let totalFailed = 0;
|
|
1841
|
-
const suiteRegex = /[✓✗×]\s+(.+\.test\.ts|.+\.spec\.ts)\s*\((\d+)\s+test/i;
|
|
1842
|
-
const lines = output.split("\n");
|
|
1843
1924
|
for (const line of lines) {
|
|
1844
1925
|
const match = line.match(suiteRegex);
|
|
1845
1926
|
if (match) {
|
|
1846
1927
|
const file = match[1];
|
|
1847
|
-
const testCount = parseInt(match[
|
|
1928
|
+
const testCount = parseInt(match[4], 10);
|
|
1848
1929
|
const isPassed = line.includes("\u2713");
|
|
1849
1930
|
if (isPassed) totalPassed += testCount;
|
|
1850
1931
|
else totalFailed += testCount;
|
|
@@ -1864,15 +1945,39 @@ function parseVitestTextOutput(output, hasError) {
|
|
|
1864
1945
|
});
|
|
1865
1946
|
}
|
|
1866
1947
|
}
|
|
1948
|
+
if (suites.length === 0) {
|
|
1949
|
+
const summaryMatch = output.match(/Tests\s+(\d+)\s+(passed|failed)/i);
|
|
1950
|
+
if (summaryMatch) {
|
|
1951
|
+
const count = parseInt(summaryMatch[1], 10);
|
|
1952
|
+
const status = summaryMatch[2].toLowerCase() === "passed" ? "passed" : "failed";
|
|
1953
|
+
suites.push({
|
|
1954
|
+
name: "Vitest Summary",
|
|
1955
|
+
file: "unknown",
|
|
1956
|
+
type: "unit",
|
|
1957
|
+
status,
|
|
1958
|
+
duration: 0,
|
|
1959
|
+
tests: Array.from({ length: count }, (_, i) => ({
|
|
1960
|
+
name: `test ${i + 1}`,
|
|
1961
|
+
file: "unknown",
|
|
1962
|
+
status,
|
|
1963
|
+
duration: 0,
|
|
1964
|
+
retries: 0
|
|
1965
|
+
}))
|
|
1966
|
+
});
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
debug("vitest-text", `\u89E3\u6790\u5230 ${suites.length} \u4E2A\u5957\u4EF6, ${totalPassed} \u901A\u8FC7, ${totalFailed} \u5931\u8D25`);
|
|
1867
1970
|
return {
|
|
1868
1971
|
success: !hasError || totalFailed === 0,
|
|
1869
1972
|
suites
|
|
1870
1973
|
};
|
|
1871
1974
|
}
|
|
1872
1975
|
function mapVitestStatus(status) {
|
|
1976
|
+
if (!status) return "pending";
|
|
1873
1977
|
switch (status) {
|
|
1874
1978
|
case "passed":
|
|
1875
1979
|
case "pass":
|
|
1980
|
+
case "done":
|
|
1876
1981
|
return "passed";
|
|
1877
1982
|
case "failed":
|
|
1878
1983
|
case "fail":
|
|
@@ -1880,6 +1985,7 @@ function mapVitestStatus(status) {
|
|
|
1880
1985
|
case "skipped":
|
|
1881
1986
|
case "skip":
|
|
1882
1987
|
case "pending":
|
|
1988
|
+
case "todo":
|
|
1883
1989
|
return "skipped";
|
|
1884
1990
|
default:
|
|
1885
1991
|
return "pending";
|
|
@@ -2816,7 +2922,7 @@ async function testAIConnection(config) {
|
|
|
2816
2922
|
}
|
|
2817
2923
|
|
|
2818
2924
|
// src/services/mock-server.ts
|
|
2819
|
-
import
|
|
2925
|
+
import fs7 from "fs";
|
|
2820
2926
|
import path7 from "path";
|
|
2821
2927
|
var serverState = {
|
|
2822
2928
|
running: false,
|
|
@@ -2829,18 +2935,18 @@ function getMockServerState() {
|
|
|
2829
2935
|
}
|
|
2830
2936
|
async function loadMockRoutes(routesDir) {
|
|
2831
2937
|
const absDir = path7.resolve(process.cwd(), routesDir);
|
|
2832
|
-
if (!
|
|
2938
|
+
if (!fs7.existsSync(absDir)) {
|
|
2833
2939
|
return [];
|
|
2834
2940
|
}
|
|
2835
2941
|
const routes = [];
|
|
2836
|
-
const entries =
|
|
2942
|
+
const entries = fs7.readdirSync(absDir, { withFileTypes: true });
|
|
2837
2943
|
for (const entry of entries) {
|
|
2838
2944
|
if (!entry.isFile()) continue;
|
|
2839
2945
|
const filePath = path7.join(absDir, entry.name);
|
|
2840
2946
|
const ext = path7.extname(entry.name);
|
|
2841
2947
|
try {
|
|
2842
2948
|
if (ext === ".json") {
|
|
2843
|
-
const content =
|
|
2949
|
+
const content = fs7.readFileSync(filePath, "utf-8");
|
|
2844
2950
|
const parsed = JSON.parse(content);
|
|
2845
2951
|
if (Array.isArray(parsed)) {
|
|
2846
2952
|
routes.push(...parsed);
|
|
@@ -3032,11 +3138,11 @@ function generateMockRouteTemplate(name) {
|
|
|
3032
3138
|
}
|
|
3033
3139
|
function initMockRoutesDir(routesDir) {
|
|
3034
3140
|
const absDir = path7.resolve(process.cwd(), routesDir);
|
|
3035
|
-
if (!
|
|
3036
|
-
|
|
3141
|
+
if (!fs7.existsSync(absDir)) {
|
|
3142
|
+
fs7.mkdirSync(absDir, { recursive: true });
|
|
3037
3143
|
}
|
|
3038
3144
|
const examplePath = path7.join(absDir, "example.json");
|
|
3039
|
-
if (!
|
|
3145
|
+
if (!fs7.existsSync(examplePath)) {
|
|
3040
3146
|
const exampleRoutes = [
|
|
3041
3147
|
{
|
|
3042
3148
|
method: "GET",
|
|
@@ -3060,24 +3166,24 @@ function initMockRoutesDir(routesDir) {
|
|
|
3060
3166
|
}
|
|
3061
3167
|
}
|
|
3062
3168
|
];
|
|
3063
|
-
|
|
3169
|
+
fs7.writeFileSync(examplePath, JSON.stringify(exampleRoutes, null, 2), "utf-8");
|
|
3064
3170
|
}
|
|
3065
3171
|
}
|
|
3066
3172
|
|
|
3067
3173
|
// src/services/visual.ts
|
|
3068
|
-
import
|
|
3174
|
+
import fs8 from "fs";
|
|
3069
3175
|
import path8 from "path";
|
|
3070
3176
|
import pixelmatch from "pixelmatch";
|
|
3071
3177
|
import { PNG } from "pngjs";
|
|
3072
3178
|
function compareImages(baselinePath, currentPath, diffOutputPath, threshold = 0.1) {
|
|
3073
|
-
if (!
|
|
3179
|
+
if (!fs8.existsSync(baselinePath)) {
|
|
3074
3180
|
throw new Error(`\u57FA\u7EBF\u56FE\u7247\u4E0D\u5B58\u5728: ${baselinePath}`);
|
|
3075
3181
|
}
|
|
3076
|
-
if (!
|
|
3182
|
+
if (!fs8.existsSync(currentPath)) {
|
|
3077
3183
|
throw new Error(`\u5F53\u524D\u56FE\u7247\u4E0D\u5B58\u5728: ${currentPath}`);
|
|
3078
3184
|
}
|
|
3079
|
-
const baseline = PNG.sync.read(
|
|
3080
|
-
const current = PNG.sync.read(
|
|
3185
|
+
const baseline = PNG.sync.read(fs8.readFileSync(baselinePath));
|
|
3186
|
+
const current = PNG.sync.read(fs8.readFileSync(currentPath));
|
|
3081
3187
|
if (baseline.width !== current.width || baseline.height !== current.height) {
|
|
3082
3188
|
return {
|
|
3083
3189
|
passed: false,
|
|
@@ -3106,10 +3212,10 @@ function compareImages(baselinePath, currentPath, diffOutputPath, threshold = 0.
|
|
|
3106
3212
|
let diffPath;
|
|
3107
3213
|
if (diffPixels > 0) {
|
|
3108
3214
|
const diffDir = path8.dirname(diffOutputPath);
|
|
3109
|
-
if (!
|
|
3110
|
-
|
|
3215
|
+
if (!fs8.existsSync(diffDir)) {
|
|
3216
|
+
fs8.mkdirSync(diffDir, { recursive: true });
|
|
3111
3217
|
}
|
|
3112
|
-
|
|
3218
|
+
fs8.writeFileSync(diffOutputPath, PNG.sync.write(diff));
|
|
3113
3219
|
diffPath = diffOutputPath;
|
|
3114
3220
|
}
|
|
3115
3221
|
return {
|
|
@@ -3123,14 +3229,14 @@ function compareImages(baselinePath, currentPath, diffOutputPath, threshold = 0.
|
|
|
3123
3229
|
};
|
|
3124
3230
|
}
|
|
3125
3231
|
function createBaseline(currentPath, baselinePath) {
|
|
3126
|
-
if (!
|
|
3232
|
+
if (!fs8.existsSync(currentPath)) {
|
|
3127
3233
|
throw new Error(`\u5F53\u524D\u622A\u56FE\u4E0D\u5B58\u5728: ${currentPath}`);
|
|
3128
3234
|
}
|
|
3129
3235
|
const baselineDir = path8.dirname(baselinePath);
|
|
3130
|
-
if (!
|
|
3131
|
-
|
|
3236
|
+
if (!fs8.existsSync(baselineDir)) {
|
|
3237
|
+
fs8.mkdirSync(baselineDir, { recursive: true });
|
|
3132
3238
|
}
|
|
3133
|
-
|
|
3239
|
+
fs8.copyFileSync(currentPath, baselinePath);
|
|
3134
3240
|
return baselinePath;
|
|
3135
3241
|
}
|
|
3136
3242
|
function updateBaseline(currentPath, baselinePath) {
|
|
@@ -3138,69 +3244,69 @@ function updateBaseline(currentPath, baselinePath) {
|
|
|
3138
3244
|
}
|
|
3139
3245
|
function updateAllBaselines(currentDir, baselineDir) {
|
|
3140
3246
|
const updated = [];
|
|
3141
|
-
if (!
|
|
3247
|
+
if (!fs8.existsSync(currentDir)) {
|
|
3142
3248
|
return updated;
|
|
3143
3249
|
}
|
|
3144
|
-
const files =
|
|
3250
|
+
const files = fs8.readdirSync(currentDir).filter((f) => f.endsWith(".png"));
|
|
3145
3251
|
for (const file of files) {
|
|
3146
3252
|
const currentPath = path8.join(currentDir, file);
|
|
3147
3253
|
const baselinePath = path8.join(baselineDir, file);
|
|
3148
3254
|
const baselineDirAbs = path8.dirname(baselinePath);
|
|
3149
|
-
if (!
|
|
3150
|
-
|
|
3255
|
+
if (!fs8.existsSync(baselineDirAbs)) {
|
|
3256
|
+
fs8.mkdirSync(baselineDirAbs, { recursive: true });
|
|
3151
3257
|
}
|
|
3152
|
-
|
|
3258
|
+
fs8.copyFileSync(currentPath, baselinePath);
|
|
3153
3259
|
updated.push(file);
|
|
3154
3260
|
}
|
|
3155
3261
|
return updated;
|
|
3156
3262
|
}
|
|
3157
3263
|
function cleanBaselines(baselineDir) {
|
|
3158
|
-
if (!
|
|
3264
|
+
if (!fs8.existsSync(baselineDir)) {
|
|
3159
3265
|
return 0;
|
|
3160
3266
|
}
|
|
3161
|
-
const files =
|
|
3267
|
+
const files = fs8.readdirSync(baselineDir).filter((f) => f.endsWith(".png"));
|
|
3162
3268
|
let count = 0;
|
|
3163
3269
|
for (const file of files) {
|
|
3164
|
-
|
|
3270
|
+
fs8.unlinkSync(path8.join(baselineDir, file));
|
|
3165
3271
|
count++;
|
|
3166
3272
|
}
|
|
3167
3273
|
return count;
|
|
3168
3274
|
}
|
|
3169
3275
|
function cleanDiffs(diffDir) {
|
|
3170
|
-
if (!
|
|
3276
|
+
if (!fs8.existsSync(diffDir)) {
|
|
3171
3277
|
return 0;
|
|
3172
3278
|
}
|
|
3173
|
-
const files =
|
|
3279
|
+
const files = fs8.readdirSync(diffDir).filter((f) => f.endsWith(".png"));
|
|
3174
3280
|
let count = 0;
|
|
3175
3281
|
for (const file of files) {
|
|
3176
|
-
|
|
3282
|
+
fs8.unlinkSync(path8.join(diffDir, file));
|
|
3177
3283
|
count++;
|
|
3178
3284
|
}
|
|
3179
3285
|
return count;
|
|
3180
3286
|
}
|
|
3181
3287
|
function listBaselines(baselineDir) {
|
|
3182
|
-
if (!
|
|
3288
|
+
if (!fs8.existsSync(baselineDir)) {
|
|
3183
3289
|
return [];
|
|
3184
3290
|
}
|
|
3185
|
-
return
|
|
3291
|
+
return fs8.readdirSync(baselineDir).filter((f) => f.endsWith(".png")).sort();
|
|
3186
3292
|
}
|
|
3187
3293
|
function listDiffs(diffDir) {
|
|
3188
|
-
if (!
|
|
3294
|
+
if (!fs8.existsSync(diffDir)) {
|
|
3189
3295
|
return [];
|
|
3190
3296
|
}
|
|
3191
|
-
return
|
|
3297
|
+
return fs8.readdirSync(diffDir).filter((f) => f.endsWith(".png")).sort();
|
|
3192
3298
|
}
|
|
3193
3299
|
function compareDirectories(baselineDir, currentDir, diffDir, threshold = 0.1) {
|
|
3194
3300
|
const results = [];
|
|
3195
|
-
if (!
|
|
3301
|
+
if (!fs8.existsSync(currentDir)) {
|
|
3196
3302
|
return results;
|
|
3197
3303
|
}
|
|
3198
|
-
const currentFiles =
|
|
3304
|
+
const currentFiles = fs8.readdirSync(currentDir).filter((f) => f.endsWith(".png"));
|
|
3199
3305
|
for (const file of currentFiles) {
|
|
3200
3306
|
const currentPath = path8.join(currentDir, file);
|
|
3201
3307
|
const baselinePath = path8.join(baselineDir, file);
|
|
3202
3308
|
const diffPath = path8.join(diffDir, file);
|
|
3203
|
-
if (!
|
|
3309
|
+
if (!fs8.existsSync(baselinePath)) {
|
|
3204
3310
|
createBaseline(currentPath, baselinePath);
|
|
3205
3311
|
results.push({
|
|
3206
3312
|
passed: true,
|
|
@@ -3232,14 +3338,14 @@ function compareDirectories(baselineDir, currentDir, diffDir, threshold = 0.1) {
|
|
|
3232
3338
|
}
|
|
3233
3339
|
|
|
3234
3340
|
// src/services/source-analyzer.ts
|
|
3235
|
-
import
|
|
3341
|
+
import fs9 from "fs";
|
|
3236
3342
|
import path9 from "path";
|
|
3237
3343
|
function analyzeFile(filePath) {
|
|
3238
3344
|
const absolutePath = path9.resolve(process.cwd(), filePath);
|
|
3239
|
-
if (!
|
|
3345
|
+
if (!fs9.existsSync(absolutePath)) {
|
|
3240
3346
|
return { filePath, exports: [], apiCalls: [] };
|
|
3241
3347
|
}
|
|
3242
|
-
const content =
|
|
3348
|
+
const content = fs9.readFileSync(absolutePath, "utf-8");
|
|
3243
3349
|
const ext = path9.extname(filePath);
|
|
3244
3350
|
const result = {
|
|
3245
3351
|
filePath,
|
|
@@ -3594,13 +3700,13 @@ function cleanTemplateUrl(url) {
|
|
|
3594
3700
|
}
|
|
3595
3701
|
function scanAPICalls(srcDir) {
|
|
3596
3702
|
const absDir = path9.resolve(process.cwd(), srcDir);
|
|
3597
|
-
if (!
|
|
3703
|
+
if (!fs9.existsSync(absDir)) {
|
|
3598
3704
|
return [];
|
|
3599
3705
|
}
|
|
3600
3706
|
const allCalls = [];
|
|
3601
3707
|
const seen = /* @__PURE__ */ new Set();
|
|
3602
3708
|
function walk(dir) {
|
|
3603
|
-
const entries =
|
|
3709
|
+
const entries = fs9.readdirSync(dir, { withFileTypes: true });
|
|
3604
3710
|
for (const entry of entries) {
|
|
3605
3711
|
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist") continue;
|
|
3606
3712
|
const fullPath = path9.join(dir, entry.name);
|
|
@@ -3608,7 +3714,7 @@ function scanAPICalls(srcDir) {
|
|
|
3608
3714
|
walk(fullPath);
|
|
3609
3715
|
} else if (entry.isFile() && /\.(ts|js|vue|mjs)$/.test(entry.name)) {
|
|
3610
3716
|
try {
|
|
3611
|
-
const content =
|
|
3717
|
+
const content = fs9.readFileSync(fullPath, "utf-8");
|
|
3612
3718
|
const calls = extractAPICalls(content, path9.relative(process.cwd(), fullPath));
|
|
3613
3719
|
for (const call of calls) {
|
|
3614
3720
|
const key = `${call.method}:${call.url}`;
|