@cldmv/vitest-runner 1.4.2 → 1.4.4
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 +14 -9
- package/bin/vitest-runner.mjs +53 -1489
- package/dist/index.cjs +21 -1389
- package/dist/index.mjs +45 -1334
- package/package.json +2 -2
- package/types/src/core/report.d.mts +16 -5
- package/types/src/utils/ansi.d.mts +29 -3
package/bin/vitest-runner.mjs
CHANGED
|
@@ -1,138 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import { createWriteStream, mkdirSync } from "fs";
|
|
5
|
-
import path6 from "path";
|
|
6
|
-
|
|
7
|
-
// src/cli/args.mjs
|
|
8
|
-
var RUNNER_FLAGS = /* @__PURE__ */ new Set([
|
|
9
|
-
"--test-list",
|
|
10
|
-
"--no-error-details",
|
|
11
|
-
"--coverage-quiet",
|
|
12
|
-
"--log-file",
|
|
13
|
-
"--suppress-file-output",
|
|
14
|
-
"--suppress-passing-files",
|
|
15
|
-
"--no-top-summary",
|
|
16
|
-
"--json",
|
|
17
|
-
"--blobs-dir",
|
|
18
|
-
"--no-merge-reports",
|
|
19
|
-
"--workers",
|
|
20
|
-
"--solo-pattern",
|
|
21
|
-
"--file-pattern",
|
|
22
|
-
"--keep-tmp",
|
|
23
|
-
"--scratch-dir",
|
|
24
|
-
"--help",
|
|
25
|
-
"-h"
|
|
26
|
-
]);
|
|
27
|
-
function parseArguments(args2) {
|
|
28
|
-
const vitestPassthroughArgs = [];
|
|
29
|
-
const testPatterns = [];
|
|
30
|
-
const soloPatterns = [];
|
|
31
|
-
let testListFile;
|
|
32
|
-
let showErrorDetails = true;
|
|
33
|
-
let coverageQuiet = false;
|
|
34
|
-
let logFile;
|
|
35
|
-
let suppressFileOutput = false;
|
|
36
|
-
let suppressPassingFiles = false;
|
|
37
|
-
let topSummary = true;
|
|
38
|
-
let json = false;
|
|
39
|
-
let blobsDir;
|
|
40
|
-
let mergeReports = true;
|
|
41
|
-
let workers;
|
|
42
|
-
let help = false;
|
|
43
|
-
let testFilePattern;
|
|
44
|
-
let keepTmp = false;
|
|
45
|
-
let scratchDir;
|
|
46
|
-
for (let i = 0; i < args2.length; i++) {
|
|
47
|
-
const arg = args2[i];
|
|
48
|
-
if (arg === "--test-list") {
|
|
49
|
-
testListFile = args2[++i];
|
|
50
|
-
} else if (arg.startsWith("--test-list=")) {
|
|
51
|
-
testListFile = arg.slice("--test-list=".length);
|
|
52
|
-
} else if (arg === "--no-error-details") {
|
|
53
|
-
showErrorDetails = false;
|
|
54
|
-
} else if (arg === "--coverage-quiet") {
|
|
55
|
-
coverageQuiet = true;
|
|
56
|
-
} else if (arg === "--log-file") {
|
|
57
|
-
logFile = args2[++i];
|
|
58
|
-
} else if (arg.startsWith("--log-file=")) {
|
|
59
|
-
logFile = arg.slice("--log-file=".length);
|
|
60
|
-
} else if (arg === "--suppress-file-output") {
|
|
61
|
-
suppressFileOutput = true;
|
|
62
|
-
} else if (arg === "--suppress-passing-files") {
|
|
63
|
-
suppressPassingFiles = true;
|
|
64
|
-
} else if (arg === "--no-top-summary") {
|
|
65
|
-
topSummary = false;
|
|
66
|
-
} else if (arg === "--json") {
|
|
67
|
-
json = true;
|
|
68
|
-
} else if (arg === "--blobs-dir") {
|
|
69
|
-
blobsDir = args2[++i];
|
|
70
|
-
} else if (arg.startsWith("--blobs-dir=")) {
|
|
71
|
-
blobsDir = arg.slice("--blobs-dir=".length);
|
|
72
|
-
} else if (arg === "--no-merge-reports") {
|
|
73
|
-
mergeReports = false;
|
|
74
|
-
} else if (arg === "--workers") {
|
|
75
|
-
workers = parseInt(args2[++i], 10);
|
|
76
|
-
} else if (arg.startsWith("--workers=")) {
|
|
77
|
-
workers = parseInt(arg.slice("--workers=".length), 10);
|
|
78
|
-
} else if (arg === "--solo-pattern") {
|
|
79
|
-
soloPatterns.push(args2[++i]);
|
|
80
|
-
} else if (arg.startsWith("--solo-pattern=")) {
|
|
81
|
-
soloPatterns.push(arg.slice("--solo-pattern=".length));
|
|
82
|
-
} else if (arg === "--file-pattern") {
|
|
83
|
-
testFilePattern = new RegExp(args2[++i], "i");
|
|
84
|
-
} else if (arg.startsWith("--file-pattern=")) {
|
|
85
|
-
testFilePattern = new RegExp(arg.slice("--file-pattern=".length), "i");
|
|
86
|
-
} else if (arg === "--keep-tmp") {
|
|
87
|
-
keepTmp = true;
|
|
88
|
-
} else if (arg === "--scratch-dir") {
|
|
89
|
-
scratchDir = args2[++i];
|
|
90
|
-
} else if (arg.startsWith("--scratch-dir=")) {
|
|
91
|
-
scratchDir = arg.slice("--scratch-dir=".length);
|
|
92
|
-
} else if (arg === "--help" || arg === "-h") {
|
|
93
|
-
help = true;
|
|
94
|
-
} else if ((arg.startsWith("--") || arg.startsWith("-")) && !RUNNER_FLAGS.has(arg)) {
|
|
95
|
-
vitestPassthroughArgs.push(arg);
|
|
96
|
-
if (i + 1 < args2.length && !args2[i + 1].startsWith("-")) {
|
|
97
|
-
vitestPassthroughArgs.push(args2[++i]);
|
|
98
|
-
}
|
|
99
|
-
} else {
|
|
100
|
-
testPatterns.push(arg);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
return {
|
|
104
|
-
testListFile,
|
|
105
|
-
showErrorDetails,
|
|
106
|
-
coverageQuiet,
|
|
107
|
-
logFile,
|
|
108
|
-
suppressFileOutput,
|
|
109
|
-
suppressPassingFiles,
|
|
110
|
-
topSummary,
|
|
111
|
-
json,
|
|
112
|
-
blobsDir,
|
|
113
|
-
mergeReports,
|
|
114
|
-
help,
|
|
115
|
-
workers,
|
|
116
|
-
soloPatterns,
|
|
117
|
-
testFilePattern,
|
|
118
|
-
vitestPassthroughArgs,
|
|
119
|
-
testPatterns,
|
|
120
|
-
keepTmp,
|
|
121
|
-
scratchDir
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// src/cli/help.mjs
|
|
126
|
-
import chalk from "chalk";
|
|
127
|
-
function showHelp() {
|
|
128
|
-
console.log(`
|
|
129
|
-
${chalk.bold("Vitest Sequential Runner")}
|
|
2
|
+
import{createWriteStream as Wt,mkdirSync as Ut}from"fs";import be from"path";var Mt=new Set(["--test-list","--no-error-details","--coverage-quiet","--log-file","--suppress-file-output","--suppress-passing-files","--no-top-summary","--json","--blobs-dir","--no-merge-reports","--workers","--solo-pattern","--file-pattern","--keep-tmp","--scratch-dir","--help","-h"]);function st(e){let s=[],t=[],n=[],o,r=!0,l=!1,c,g=!1,a=!1,u=!0,f=!1,S,v=!0,h,y=!1,b,d=!1,x;for(let w=0;w<e.length;w++){let p=e[w];p==="--test-list"?o=e[++w]:p.startsWith("--test-list=")?o=p.slice(12):p==="--no-error-details"?r=!1:p==="--coverage-quiet"?l=!0:p==="--log-file"?c=e[++w]:p.startsWith("--log-file=")?c=p.slice(11):p==="--suppress-file-output"?g=!0:p==="--suppress-passing-files"?a=!0:p==="--no-top-summary"?u=!1:p==="--json"?f=!0:p==="--blobs-dir"?S=e[++w]:p.startsWith("--blobs-dir=")?S=p.slice(12):p==="--no-merge-reports"?v=!1:p==="--workers"?h=parseInt(e[++w],10):p.startsWith("--workers=")?h=parseInt(p.slice(10),10):p==="--solo-pattern"?n.push(e[++w]):p.startsWith("--solo-pattern=")?n.push(p.slice(15)):p==="--file-pattern"?b=new RegExp(e[++w],"i"):p.startsWith("--file-pattern=")?b=new RegExp(p.slice(15),"i"):p==="--keep-tmp"?d=!0:p==="--scratch-dir"?x=e[++w]:p.startsWith("--scratch-dir=")?x=p.slice(14):p==="--help"||p==="-h"?y=!0:(p.startsWith("--")||p.startsWith("-"))&&!Mt.has(p)?(s.push(p),w+1<e.length&&!e[w+1].startsWith("-")&&s.push(e[++w])):t.push(p)}return{testListFile:o,showErrorDetails:r,coverageQuiet:l,logFile:c,suppressFileOutput:g,suppressPassingFiles:a,topSummary:u,json:f,blobsDir:S,mergeReports:v,help:y,workers:h,soloPatterns:n,testFilePattern:b,vitestPassthroughArgs:s,testPatterns:t,keepTmp:d,scratchDir:x}}import Z from"chalk";function ot(){console.log(`
|
|
3
|
+
${Z.bold("Vitest Sequential Runner")}
|
|
130
4
|
Runs each test file in its own Vitest process to avoid OOM issues.
|
|
131
5
|
|
|
132
|
-
${
|
|
6
|
+
${Z.bold("USAGE:")}
|
|
133
7
|
vitest-runner [OPTIONS] [PATTERNS]
|
|
134
8
|
|
|
135
|
-
${
|
|
9
|
+
${Z.bold("SPECIAL FLAGS:")}
|
|
136
10
|
--test-list <file> Run only the files listed in a JSON array file
|
|
137
11
|
--file-pattern <regex> Override the file discovery regex (default: \\.test\\.vitest\\.(?:js|mjs|cjs)$)
|
|
138
12
|
--workers <n> Number of parallel workers (default: 4 or VITEST_WORKERS)
|
|
@@ -150,7 +24,7 @@ ${chalk.bold("SPECIAL FLAGS:")}
|
|
|
150
24
|
--scratch-dir <path> Per-run scratch root, relative to cwd (default: tmp/vitest-runner)
|
|
151
25
|
--help, -h Show this help message
|
|
152
26
|
|
|
153
|
-
${
|
|
27
|
+
${Z.bold("TEST PATTERNS:")}
|
|
154
28
|
[file] Run a specific test file (supports partial paths)
|
|
155
29
|
[folder] Run all tests in a folder
|
|
156
30
|
|
|
@@ -159,7 +33,7 @@ ${chalk.bold("TEST PATTERNS:")}
|
|
|
159
33
|
src/tests/metadata
|
|
160
34
|
background.test.vitest.mjs
|
|
161
35
|
|
|
162
|
-
${
|
|
36
|
+
${Z.bold("VITEST FLAGS:")}
|
|
163
37
|
All standard Vitest CLI flags are supported and passed through:
|
|
164
38
|
-t, --testNamePattern Filter tests by name pattern (regex)
|
|
165
39
|
--reporter Change reporter (verbose, dot, json, etc.)
|
|
@@ -168,7 +42,7 @@ ${chalk.bold("VITEST FLAGS:")}
|
|
|
168
42
|
|
|
169
43
|
See the Vitest documentation for the full list.
|
|
170
44
|
|
|
171
|
-
${
|
|
45
|
+
${Z.bold("ENVIRONMENT VARIABLES:")}
|
|
172
46
|
VITEST_HEAP_MB Set max heap size per test (default: Node.js default)
|
|
173
47
|
VITEST_WORKERS Number of parallel workers (default: 4, overridden by --workers)
|
|
174
48
|
VITEST_RUNNER_TMP Set by the runner in each child \u2014 this run's per-file scratch
|
|
@@ -200,1360 +74,50 @@ ${chalk.bold("ENVIRONMENT VARIABLES:")}
|
|
|
200
74
|
|
|
201
75
|
# Custom heap and workers
|
|
202
76
|
VITEST_HEAP_MB=8192 vitest-runner --workers 2 src/tests/heavy
|
|
203
|
-
`);
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
return path.join(
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
"
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// src/core/discover.mjs
|
|
252
|
-
import fs2 from "fs/promises";
|
|
253
|
-
import path2 from "path";
|
|
254
|
-
var DEFAULT_TEST_FILE_PATTERN = /\.test\.vitest\.(?:js|mjs|cjs)$/i;
|
|
255
|
-
async function discoverFilesInDir(dir, cwd2, pattern = DEFAULT_TEST_FILE_PATTERN) {
|
|
256
|
-
const queue = [dir];
|
|
257
|
-
const files = [];
|
|
258
|
-
while (queue.length) {
|
|
259
|
-
const current = queue.pop();
|
|
260
|
-
let entries;
|
|
261
|
-
try {
|
|
262
|
-
entries = await fs2.readdir(current, { withFileTypes: true });
|
|
263
|
-
} catch {
|
|
264
|
-
continue;
|
|
265
|
-
}
|
|
266
|
-
for (const entry of entries) {
|
|
267
|
-
if (entry.isDirectory()) {
|
|
268
|
-
if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
|
|
269
|
-
queue.push(path2.join(current, entry.name));
|
|
270
|
-
continue;
|
|
271
|
-
}
|
|
272
|
-
if (entry.isFile() && pattern.test(entry.name)) {
|
|
273
|
-
files.push(path2.relative(cwd2, path2.join(current, entry.name)));
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
return files;
|
|
278
|
-
}
|
|
279
|
-
function sortWithPriority(files, earlyRunPatterns = []) {
|
|
280
|
-
const early = [];
|
|
281
|
-
const rest = [];
|
|
282
|
-
for (const file of files) {
|
|
283
|
-
const normalized = file.replace(/\\/g, "/");
|
|
284
|
-
const priorityIndex = earlyRunPatterns.findIndex((pat) => normalized.includes(pat));
|
|
285
|
-
if (priorityIndex !== -1) {
|
|
286
|
-
early.push({ file, priorityIndex });
|
|
287
|
-
} else {
|
|
288
|
-
rest.push(file);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
early.sort((a, b) => a.priorityIndex - b.priorityIndex || a.file.localeCompare(b.file));
|
|
292
|
-
rest.sort((a, b) => a.localeCompare(b));
|
|
293
|
-
return [...early.map((e) => e.file), ...rest];
|
|
294
|
-
}
|
|
295
|
-
function computeFilterConflicts(files) {
|
|
296
|
-
const normalized = files.map((f) => f.replace(/\\/g, "/"));
|
|
297
|
-
const conflicts = /* @__PURE__ */ new Map();
|
|
298
|
-
for (let i = 0; i < files.length; i++) {
|
|
299
|
-
const matches = [];
|
|
300
|
-
for (let j = 0; j < files.length; j++) {
|
|
301
|
-
if (i === j) continue;
|
|
302
|
-
if (normalized[j].includes(normalized[i])) matches.push(files[j]);
|
|
303
|
-
}
|
|
304
|
-
conflicts.set(files[i], matches);
|
|
305
|
-
}
|
|
306
|
-
return conflicts;
|
|
307
|
-
}
|
|
308
|
-
async function discoverVitestFiles(opts) {
|
|
309
|
-
const { cwd: cwd2, testDir, testPatterns = [], testListFile, testFilePattern = DEFAULT_TEST_FILE_PATTERN, earlyRunPatterns = [] } = opts;
|
|
310
|
-
const resolvedTestDir = testDir ? path2.isAbsolute(testDir) ? testDir : path2.resolve(cwd2, testDir) : cwd2;
|
|
311
|
-
if (testListFile) {
|
|
312
|
-
const resolvedListPath = path2.isAbsolute(testListFile) ? testListFile : path2.resolve(cwd2, testListFile);
|
|
313
|
-
let testList;
|
|
314
|
-
try {
|
|
315
|
-
const content = await fs2.readFile(resolvedListPath, "utf8");
|
|
316
|
-
testList = JSON.parse(content);
|
|
317
|
-
} catch (err) {
|
|
318
|
-
throw new Error(`Failed to read test list file "${resolvedListPath}": ${err.message}`, { cause: err });
|
|
319
|
-
}
|
|
320
|
-
if (!Array.isArray(testList)) {
|
|
321
|
-
throw new Error(`Test list file "${resolvedListPath}" must contain a JSON array of test file paths`);
|
|
322
|
-
}
|
|
323
|
-
console.log(`\u{1F4CB} Loading test list from: ${path2.relative(cwd2, resolvedListPath)}`);
|
|
324
|
-
return sortWithPriority(testList, earlyRunPatterns);
|
|
325
|
-
}
|
|
326
|
-
if (testPatterns.length === 0) {
|
|
327
|
-
const files2 = await discoverFilesInDir(resolvedTestDir, cwd2, testFilePattern);
|
|
328
|
-
return sortWithPriority(files2, earlyRunPatterns);
|
|
329
|
-
}
|
|
330
|
-
const files = [];
|
|
331
|
-
for (const pattern of testPatterns) {
|
|
332
|
-
const absPath = path2.isAbsolute(pattern) ? pattern : path2.resolve(cwd2, pattern);
|
|
333
|
-
let stat = null;
|
|
334
|
-
try {
|
|
335
|
-
stat = await fs2.stat(absPath);
|
|
336
|
-
} catch {
|
|
337
|
-
}
|
|
338
|
-
if (stat?.isFile()) {
|
|
339
|
-
if (testFilePattern.test(absPath)) {
|
|
340
|
-
files.push(path2.relative(cwd2, absPath));
|
|
341
|
-
}
|
|
342
|
-
} else if (stat?.isDirectory()) {
|
|
343
|
-
files.push(...await discoverFilesInDir(absPath, cwd2, testFilePattern));
|
|
344
|
-
} else {
|
|
345
|
-
const allFiles = await discoverFilesInDir(resolvedTestDir, cwd2, testFilePattern);
|
|
346
|
-
const matched = allFiles.filter((f) => f.replace(/\\/g, "/").includes(pattern.replace(/\\/g, "/")));
|
|
347
|
-
if (matched.length > 0) {
|
|
348
|
-
files.push(...matched);
|
|
349
|
-
} else {
|
|
350
|
-
console.warn(`\u26A0\uFE0F No matches found for: ${pattern}`);
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
return sortWithPriority([...new Set(files)], earlyRunPatterns);
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
// src/core/spawn.mjs
|
|
358
|
-
import { spawn } from "child_process";
|
|
359
|
-
|
|
360
|
-
// src/utils/ansi.mjs
|
|
361
|
-
function stripAnsi(text) {
|
|
362
|
-
return text.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "");
|
|
363
|
-
}
|
|
364
|
-
function colourPct(chalk5, pct) {
|
|
365
|
-
const str = pct.toFixed(2).padStart(6);
|
|
366
|
-
if (pct >= 80) return chalk5.green(str);
|
|
367
|
-
if (pct >= 50) return chalk5.yellow(str);
|
|
368
|
-
return chalk5.red(str);
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
// src/core/parse.mjs
|
|
372
|
-
function parseVitestOutput(output) {
|
|
373
|
-
const cleanOutput = stripAnsi(output);
|
|
374
|
-
const result = {
|
|
375
|
-
testFilesPass: 0,
|
|
376
|
-
testFilesFail: 0,
|
|
377
|
-
testsPass: 0,
|
|
378
|
-
testsFail: 0,
|
|
379
|
-
testsSkip: 0,
|
|
380
|
-
duration: 0,
|
|
381
|
-
heapMb: null,
|
|
382
|
-
errors: []
|
|
383
|
-
};
|
|
384
|
-
const testFilesLineMatch = cleanOutput.match(/Test Files\s+(.+)/);
|
|
385
|
-
if (testFilesLineMatch) {
|
|
386
|
-
const line = testFilesLineMatch[1];
|
|
387
|
-
const passMatch = line.match(/(\d+)\s+passed/);
|
|
388
|
-
const failMatch = line.match(/(\d+)\s+failed/);
|
|
389
|
-
if (passMatch) result.testFilesPass = parseInt(passMatch[1], 10);
|
|
390
|
-
if (failMatch) result.testFilesFail = parseInt(failMatch[1], 10);
|
|
391
|
-
}
|
|
392
|
-
const testsLineMatch = cleanOutput.match(/^\s*Tests\s+(.+)$/m);
|
|
393
|
-
if (testsLineMatch) {
|
|
394
|
-
const line = testsLineMatch[1];
|
|
395
|
-
const passMatch = line.match(/(\d+)\s+passed/);
|
|
396
|
-
const failMatch = line.match(/(\d+)\s+failed/);
|
|
397
|
-
const skipMatch = line.match(/(\d+)\s+skipped/);
|
|
398
|
-
if (passMatch) result.testsPass = parseInt(passMatch[1], 10);
|
|
399
|
-
if (failMatch) result.testsFail = parseInt(failMatch[1], 10);
|
|
400
|
-
if (skipMatch) result.testsSkip = parseInt(skipMatch[1], 10);
|
|
401
|
-
}
|
|
402
|
-
const durationMatch = cleanOutput.match(/Duration\s+([\d.]+)s/);
|
|
403
|
-
if (durationMatch) {
|
|
404
|
-
result.duration = parseFloat(durationMatch[1]) * 1e3;
|
|
405
|
-
}
|
|
406
|
-
const heapMatch = cleanOutput.match(/(\d+)\s*MB\s+heap\s+used/i);
|
|
407
|
-
if (heapMatch) {
|
|
408
|
-
result.heapMb = parseInt(heapMatch[1], 10);
|
|
409
|
-
}
|
|
410
|
-
const failedSectionStart = output.indexOf("Failed Tests");
|
|
411
|
-
if (failedSectionStart !== -1) {
|
|
412
|
-
const failedSectionEnd = output.indexOf("\n Test Files", failedSectionStart);
|
|
413
|
-
const errorSection = failedSectionEnd !== -1 ? output.substring(failedSectionStart, failedSectionEnd) : output.substring(failedSectionStart);
|
|
414
|
-
const failPattern = /FAIL\s*(?:\x1B\[[0-9;]*[a-zA-Z]|\s)*tests\//g;
|
|
415
|
-
const matches = [...errorSection.matchAll(failPattern)];
|
|
416
|
-
for (let i = 0; i < matches.length; i++) {
|
|
417
|
-
const matchPos = matches[i].index;
|
|
418
|
-
const lineStart = errorSection.lastIndexOf("\n", matchPos);
|
|
419
|
-
const actualStart = lineStart === -1 ? 0 : lineStart;
|
|
420
|
-
const matchEnd = i < matches.length - 1 ? matches[i + 1].index : errorSection.length;
|
|
421
|
-
const nextLineStart = errorSection.lastIndexOf("\n", matchEnd);
|
|
422
|
-
const actualEnd = nextLineStart === -1 ? matchEnd : nextLineStart;
|
|
423
|
-
const errorBlock = errorSection.substring(actualStart, actualEnd).trim();
|
|
424
|
-
if (errorBlock) result.errors.push(errorBlock);
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
return result;
|
|
428
|
-
}
|
|
429
|
-
function deduplicateErrors(errors) {
|
|
430
|
-
const fullText = errors.join("\n");
|
|
431
|
-
const lines = fullText.split("\n");
|
|
432
|
-
const failLineMap = /* @__PURE__ */ new Map();
|
|
433
|
-
const lineIndices = /* @__PURE__ */ new Map();
|
|
434
|
-
lines.forEach((line, idx) => {
|
|
435
|
-
if (line.includes("FAIL") && line.includes("Config:")) {
|
|
436
|
-
lineIndices.set(line, idx);
|
|
437
|
-
const cleaned = stripAnsi(line);
|
|
438
|
-
const match = cleaned.match(/^(.+Config:\s+)'*([^'>]+)'*(.+)$/);
|
|
439
|
-
if (match) {
|
|
440
|
-
const [, before, config, after] = match;
|
|
441
|
-
const pattern = `${before.trim()}|||${after.trim()}`;
|
|
442
|
-
if (!failLineMap.has(pattern)) {
|
|
443
|
-
failLineMap.set(pattern, { lines: [], configs: [] });
|
|
444
|
-
}
|
|
445
|
-
failLineMap.get(pattern).lines.push(line);
|
|
446
|
-
failLineMap.get(pattern).configs.push(config.replace(/'/g, ""));
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
});
|
|
450
|
-
const skipIndices = /* @__PURE__ */ new Set();
|
|
451
|
-
for (const [, data] of failLineMap.entries()) {
|
|
452
|
-
if (data.configs.length > 1) {
|
|
453
|
-
for (let i = 1; i < data.lines.length; i++) {
|
|
454
|
-
const idx = lineIndices.get(data.lines[i]);
|
|
455
|
-
skipIndices.add(idx);
|
|
456
|
-
}
|
|
457
|
-
const firstLine = data.lines[0];
|
|
458
|
-
const configArray = `[${data.configs.map((c) => `'${c}'`).join(",")}]`;
|
|
459
|
-
const consolidated = stripAnsi(firstLine).replace(/(Config:\s+)'*[^'>]+'*/, `$1${configArray}`);
|
|
460
|
-
const firstIdx = lineIndices.get(firstLine);
|
|
461
|
-
lines[firstIdx] = consolidated;
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
return lines.filter((_, idx) => !skipIndices.has(idx)).join("\n");
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
// src/utils/env.mjs
|
|
468
|
-
function buildNodeOptions({ maxOldSpaceMb, conditions = [], base = process.env.NODE_OPTIONS ?? "" }) {
|
|
469
|
-
let opts = base;
|
|
470
|
-
for (const cond of conditions) {
|
|
471
|
-
const flag = `--conditions=${cond}`;
|
|
472
|
-
if (!opts.includes(flag)) {
|
|
473
|
-
opts = opts ? `${opts} ${flag}` : flag;
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
if (maxOldSpaceMb && !opts.includes("--max-old-space-size")) {
|
|
477
|
-
const flag = `--max-old-space-size=${maxOldSpaceMb}`;
|
|
478
|
-
opts = opts ? `${opts} ${flag}` : flag;
|
|
479
|
-
}
|
|
480
|
-
return opts.trim();
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
// src/core/spawn.mjs
|
|
484
|
-
function buildEnv({ maxOldSpaceMb, conditions = [], nodeEnv = "development", extraEnv = {} }) {
|
|
485
|
-
const env = { ...process.env };
|
|
486
|
-
if (!env.NODE_ENV) env.NODE_ENV = nodeEnv;
|
|
487
|
-
const nodeOptions = buildNodeOptions({ maxOldSpaceMb, conditions, base: env.NODE_OPTIONS ?? "" });
|
|
488
|
-
if (nodeOptions) env.NODE_OPTIONS = nodeOptions;
|
|
489
|
-
Object.assign(env, extraEnv);
|
|
490
|
-
return env;
|
|
491
|
-
}
|
|
492
|
-
function buildBaseArgs(vitestBin, vitestConfig) {
|
|
493
|
-
const configArgs = vitestConfig ? ["--config", vitestConfig] : [];
|
|
494
|
-
return [vitestBin, ...configArgs, "run"];
|
|
495
|
-
}
|
|
496
|
-
function runSingleFile(filePath, opts) {
|
|
497
|
-
const {
|
|
498
|
-
cwd: cwd2,
|
|
499
|
-
vitestBin,
|
|
500
|
-
vitestConfig,
|
|
501
|
-
maxOldSpaceMb,
|
|
502
|
-
conditions = [],
|
|
503
|
-
nodeEnv = "development",
|
|
504
|
-
vitestArgs: vitestArgs2 = [],
|
|
505
|
-
streamOutput = true,
|
|
506
|
-
// Other discovered files whose path would spuriously also match Vitest's
|
|
507
|
-
// own substring filter on `filePath` (see discover.mjs computeFilterConflicts).
|
|
508
|
-
// Excluded explicitly so this invocation runs exactly `filePath`.
|
|
509
|
-
excludePaths = [],
|
|
510
|
-
// Extra env vars for the child (e.g. VITEST_RUNNER_TMP — see core/scratch.mjs).
|
|
511
|
-
extraEnv = {}
|
|
512
|
-
} = opts;
|
|
513
|
-
return new Promise((resolve) => {
|
|
514
|
-
const startTime = Date.now();
|
|
515
|
-
const excludeArgs = excludePaths.flatMap((p) => ["--exclude", p]);
|
|
516
|
-
const args2 = [...buildBaseArgs(vitestBin, vitestConfig), ...vitestArgs2, ...excludeArgs, filePath];
|
|
517
|
-
const env = buildEnv({ maxOldSpaceMb, conditions, nodeEnv, extraEnv });
|
|
518
|
-
const child = spawn(process.execPath, args2, { cwd: cwd2, stdio: ["ignore", "pipe", "pipe"], env });
|
|
519
|
-
let stdout = "";
|
|
520
|
-
let stderr = "";
|
|
521
|
-
child.stdout?.on("data", (data) => {
|
|
522
|
-
stdout += data.toString();
|
|
523
|
-
if (streamOutput) process.stdout.write(data);
|
|
524
|
-
});
|
|
525
|
-
child.stderr?.on("data", (data) => {
|
|
526
|
-
stderr += data.toString();
|
|
527
|
-
if (streamOutput) process.stderr.write(data);
|
|
528
|
-
});
|
|
529
|
-
child.on("close", (code) => {
|
|
530
|
-
const spawnDuration = Date.now() - startTime;
|
|
531
|
-
const output = `${stdout}
|
|
532
|
-
${stderr}`;
|
|
533
|
-
const parsed = parseVitestOutput(output);
|
|
534
|
-
resolve({
|
|
535
|
-
file: filePath,
|
|
536
|
-
code: code ?? 1,
|
|
537
|
-
// c8 ignore next -- false branch verified manually; V8 ternary probe mismatch inside object literal
|
|
538
|
-
duration: parsed.duration > 0 ? parsed.duration : spawnDuration,
|
|
539
|
-
testFilesPass: parsed.testFilesPass,
|
|
540
|
-
testFilesFail: parsed.testFilesFail,
|
|
541
|
-
testsPass: parsed.testsPass,
|
|
542
|
-
testsFail: parsed.testsFail,
|
|
543
|
-
testsSkip: parsed.testsSkip,
|
|
544
|
-
heapMb: parsed.heapMb,
|
|
545
|
-
errors: parsed.errors,
|
|
546
|
-
rawOutput: output
|
|
547
|
-
});
|
|
548
|
-
});
|
|
549
|
-
child.on("error", (err) => {
|
|
550
|
-
resolve({
|
|
551
|
-
file: filePath,
|
|
552
|
-
code: 1,
|
|
553
|
-
duration: Date.now() - startTime,
|
|
554
|
-
testFilesPass: 0,
|
|
555
|
-
testFilesFail: 1,
|
|
556
|
-
testsPass: 0,
|
|
557
|
-
testsFail: 0,
|
|
558
|
-
testsSkip: 0,
|
|
559
|
-
heapMb: null,
|
|
560
|
-
errors: [err.message],
|
|
561
|
-
rawOutput: err.toString()
|
|
562
|
-
});
|
|
563
|
-
});
|
|
564
|
-
});
|
|
565
|
-
}
|
|
566
|
-
function runMergeReports(blobsDir, opts) {
|
|
567
|
-
const {
|
|
568
|
-
cwd: cwd2,
|
|
569
|
-
vitestBin,
|
|
570
|
-
vitestConfig,
|
|
571
|
-
maxOldSpaceMb,
|
|
572
|
-
conditions = [],
|
|
573
|
-
nodeEnv = "development",
|
|
574
|
-
extraCoverageArgs = [],
|
|
575
|
-
quietOutput = false
|
|
576
|
-
} = opts;
|
|
577
|
-
return new Promise((resolve) => {
|
|
578
|
-
const configArgs = vitestConfig ? ["--config", vitestConfig] : [];
|
|
579
|
-
const mergeReporterArgs = quietOutput ? ["--color"] : [];
|
|
580
|
-
const args2 = [vitestBin, ...configArgs, "--mergeReports", blobsDir, "--run", "--coverage", ...mergeReporterArgs, ...extraCoverageArgs];
|
|
581
|
-
const env = buildEnv({ maxOldSpaceMb, conditions, nodeEnv });
|
|
582
|
-
const child = spawn(process.execPath, args2, {
|
|
583
|
-
cwd: cwd2,
|
|
584
|
-
stdio: quietOutput ? ["ignore", "pipe", "pipe"] : "inherit",
|
|
585
|
-
env
|
|
586
|
-
});
|
|
587
|
-
let stdout = "";
|
|
588
|
-
let stderr = "";
|
|
589
|
-
if (quietOutput) {
|
|
590
|
-
child.stdout?.on("data", (data) => stdout += data.toString());
|
|
591
|
-
child.stderr?.on("data", (data) => stderr += data.toString());
|
|
592
|
-
}
|
|
593
|
-
child.on("close", (code) => resolve({ exitCode: code ?? 1, output: `${stdout}
|
|
594
|
-
${stderr}` }));
|
|
595
|
-
child.on("error", () => resolve({ exitCode: 1, output: "" }));
|
|
596
|
-
});
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
// src/core/progress.mjs
|
|
600
|
-
import chalk2 from "chalk";
|
|
601
|
-
|
|
602
|
-
// src/utils/duration.mjs
|
|
603
|
-
function formatDuration(ms) {
|
|
604
|
-
const totalSeconds = Math.max(0, Math.floor(ms / 1e3));
|
|
605
|
-
const hours = Math.floor(totalSeconds / 3600);
|
|
606
|
-
const minutes = Math.floor(totalSeconds % 3600 / 60);
|
|
607
|
-
const seconds = totalSeconds % 60;
|
|
608
|
-
if (hours > 0) {
|
|
609
|
-
return `${hours}:${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`;
|
|
610
|
-
}
|
|
611
|
-
return `${minutes}:${seconds.toString().padStart(2, "0")}`;
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
// src/core/progress.mjs
|
|
615
|
-
function createCoverageProgressTracker(total) {
|
|
616
|
-
const isTTY = Boolean(process.stdout.isTTY);
|
|
617
|
-
const spinnerFrames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
618
|
-
const barWidth = 26;
|
|
619
|
-
const startTime = Date.now();
|
|
620
|
-
let completed = 0;
|
|
621
|
-
let active = 0;
|
|
622
|
-
let failed = 0;
|
|
623
|
-
let frameIndex = 0;
|
|
624
|
-
let maxLineLength = 0;
|
|
625
|
-
let lastPlainLog = 0;
|
|
626
|
-
let spinnerTimer = null;
|
|
627
|
-
function buildLine() {
|
|
628
|
-
const percent = total === 0 ? 100 : completed / total * 100;
|
|
629
|
-
const elapsedMs = Date.now() - startTime;
|
|
630
|
-
const avgPerFile = completed > 0 ? elapsedMs / completed : 0;
|
|
631
|
-
const etaMs = avgPerFile * Math.max(total - completed, 0);
|
|
632
|
-
const filled = Math.round(percent / 100 * barWidth);
|
|
633
|
-
const spinner = spinnerFrames[frameIndex % spinnerFrames.length];
|
|
634
|
-
const bar = `[${"=".repeat(filled)}${"-".repeat(Math.max(0, barWidth - filled))}]`;
|
|
635
|
-
let _percent = percent.toFixed(1).padStart(5);
|
|
636
|
-
if (percent < 30) {
|
|
637
|
-
_percent = chalk2.red(_percent + "%");
|
|
638
|
-
} else if (percent < 70) {
|
|
639
|
-
_percent = chalk2.rgb(255, 136, 0)(_percent + "%");
|
|
640
|
-
} else if (percent > 99) {
|
|
641
|
-
_percent = chalk2.green(_percent + "%");
|
|
642
|
-
} else {
|
|
643
|
-
_percent = chalk2.yellow(_percent + "%");
|
|
644
|
-
}
|
|
645
|
-
if (isTTY) {
|
|
646
|
-
return `${chalk2.green(spinner)} ${chalk2.green(bar)} ${chalk2.bold(_percent)} ${completed}/${total} | active ${active} | failed ${failed} | ETA ${formatDuration(etaMs)} | elapsed ${formatDuration(elapsedMs)}`;
|
|
647
|
-
}
|
|
648
|
-
return `progress ${percent.toFixed(1)}% ${completed}/${total} | active ${active} | failed ${failed} | eta ${formatDuration(etaMs)} | elapsed ${formatDuration(elapsedMs)}`;
|
|
649
|
-
}
|
|
650
|
-
function render(forcePlainLog = false) {
|
|
651
|
-
const line = buildLine();
|
|
652
|
-
frameIndex++;
|
|
653
|
-
if (isTTY) {
|
|
654
|
-
const visibleLength = line.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "").length;
|
|
655
|
-
maxLineLength = Math.max(maxLineLength, visibleLength);
|
|
656
|
-
process.stdout.write(`\r${line.padEnd(maxLineLength, " ")}`);
|
|
657
|
-
return;
|
|
658
|
-
}
|
|
659
|
-
const now = Date.now();
|
|
660
|
-
if (forcePlainLog || now - lastPlainLog >= 2e3) {
|
|
661
|
-
console.log(line);
|
|
662
|
-
lastPlainLog = now;
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
function startSpinnerLoop() {
|
|
666
|
-
if (!isTTY || spinnerTimer) return;
|
|
667
|
-
spinnerTimer = setInterval(() => {
|
|
668
|
-
if (completed >= total && active === 0) return;
|
|
669
|
-
render();
|
|
670
|
-
}, 120);
|
|
671
|
-
spinnerTimer.unref?.();
|
|
672
|
-
}
|
|
673
|
-
function stopSpinnerLoop() {
|
|
674
|
-
if (!spinnerTimer) return;
|
|
675
|
-
clearInterval(spinnerTimer);
|
|
676
|
-
spinnerTimer = null;
|
|
677
|
-
}
|
|
678
|
-
render(true);
|
|
679
|
-
startSpinnerLoop();
|
|
680
|
-
return {
|
|
681
|
-
/**
|
|
682
|
-
* Call when a file run starts (increments active count).
|
|
683
|
-
* @returns {void}
|
|
684
|
-
*/
|
|
685
|
-
onStart() {
|
|
686
|
-
active++;
|
|
687
|
-
render();
|
|
688
|
-
},
|
|
689
|
-
/**
|
|
690
|
-
* Call when a file run completes.
|
|
691
|
-
* @param {boolean} failedRun - Whether the run exited with a non-zero code.
|
|
692
|
-
* @returns {void}
|
|
693
|
-
*/
|
|
694
|
-
onComplete(failedRun) {
|
|
695
|
-
active = Math.max(0, active - 1);
|
|
696
|
-
completed++;
|
|
697
|
-
if (failedRun) failed++;
|
|
698
|
-
render(true);
|
|
699
|
-
},
|
|
700
|
-
/**
|
|
701
|
-
* Stop the spinner and print the final progress line.
|
|
702
|
-
* @returns {void}
|
|
703
|
-
*/
|
|
704
|
-
finish() {
|
|
705
|
-
stopSpinnerLoop();
|
|
706
|
-
render(true);
|
|
707
|
-
if (isTTY) process.stdout.write("\n");
|
|
708
|
-
}
|
|
709
|
-
};
|
|
710
|
-
}
|
|
711
|
-
var noopProgressTracker = {
|
|
712
|
-
/** @returns {void} */
|
|
713
|
-
onStart() {
|
|
714
|
-
},
|
|
715
|
-
/** @param {boolean} ___failedRun @returns {void} */
|
|
716
|
-
onComplete(___failedRun) {
|
|
717
|
-
},
|
|
718
|
-
/** @returns {void} */
|
|
719
|
-
finish() {
|
|
720
|
-
}
|
|
721
|
-
};
|
|
722
|
-
|
|
723
|
-
// src/core/report.mjs
|
|
724
|
-
import fs3 from "fs/promises";
|
|
725
|
-
import path3 from "path";
|
|
726
|
-
import chalk3 from "chalk";
|
|
727
|
-
function printQuietCoverageFailureDetails(failedResults) {
|
|
728
|
-
if (failedResults.length === 0) return;
|
|
729
|
-
console.log(`
|
|
730
|
-
${"=".repeat(80)}`);
|
|
731
|
-
console.log(chalk3.bold.red("\u2716 FAILED TEST FILES (VERBOSE OUTPUT)"));
|
|
732
|
-
console.log("=".repeat(80));
|
|
733
|
-
for (const r of failedResults) {
|
|
734
|
-
console.log(`
|
|
735
|
-
${chalk3.red("\u2716")} ${chalk3.red(r.file)} ${chalk3.dim(`(exit ${r.code})`)}`);
|
|
736
|
-
if (r.rawOutput?.trim()) {
|
|
737
|
-
console.log(r.rawOutput.trimEnd());
|
|
738
|
-
} else {
|
|
739
|
-
console.log(chalk3.dim("(no child output captured)"));
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
console.log(`
|
|
743
|
-
${"=".repeat(80)}`);
|
|
744
|
-
}
|
|
745
|
-
function printMergeOutput(exitCode, output) {
|
|
746
|
-
if (exitCode === 0) {
|
|
747
|
-
const marker = "% Coverage report from v8";
|
|
748
|
-
const rawLines = output.split("\n");
|
|
749
|
-
const markerLineIndex = rawLines.findIndex((line) => stripAnsi(line).includes(marker));
|
|
750
|
-
if (markerLineIndex >= 0) {
|
|
751
|
-
let endLineIndex = rawLines.length;
|
|
752
|
-
for (let i = markerLineIndex + 1; i < rawLines.length; i++) {
|
|
753
|
-
const line = stripAnsi(rawLines[i]).trimStart();
|
|
754
|
-
if (line.startsWith("stderr |") || line.startsWith("stdout |")) {
|
|
755
|
-
endLineIndex = i;
|
|
756
|
-
break;
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
const coverageBody = rawLines.slice(markerLineIndex + 1, endLineIndex).join("\n").trimEnd();
|
|
760
|
-
if (coverageBody) {
|
|
761
|
-
const fullBlock = rawLines.slice(markerLineIndex, endLineIndex).join("\n").trimEnd();
|
|
762
|
-
console.log(`
|
|
763
|
-
${fullBlock}
|
|
764
|
-
`);
|
|
765
|
-
}
|
|
766
|
-
}
|
|
767
|
-
} else {
|
|
768
|
-
const trimmed = output.trimEnd();
|
|
769
|
-
if (trimmed) console.error(trimmed);
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
function computeSummaryFromFinal(finalData) {
|
|
773
|
-
const pct = (covered, total) => total === 0 ? 100 : parseFloat((covered / total * 100).toFixed(2));
|
|
774
|
-
const summary = {
|
|
775
|
-
total: {
|
|
776
|
-
statements: { total: 0, covered: 0, pct: 0 },
|
|
777
|
-
branches: { total: 0, covered: 0, pct: 0 },
|
|
778
|
-
functions: { total: 0, covered: 0, pct: 0 },
|
|
779
|
-
lines: { total: 0, covered: 0, pct: 0 }
|
|
780
|
-
}
|
|
781
|
-
};
|
|
782
|
-
for (const [filePath, data] of Object.entries(finalData)) {
|
|
783
|
-
const sKeys = Object.keys(data.s ?? {});
|
|
784
|
-
const stmtTotal = sKeys.length;
|
|
785
|
-
const stmtCovered = sKeys.filter((k) => data.s[k] > 0).length;
|
|
786
|
-
const fKeys = Object.keys(data.f ?? {});
|
|
787
|
-
const fnTotal = fKeys.length;
|
|
788
|
-
const fnCovered = fKeys.filter((k) => data.f[k] > 0).length;
|
|
789
|
-
let branchTotal = 0, branchCovered = 0;
|
|
790
|
-
for (const counts of Object.values(data.b ?? {})) {
|
|
791
|
-
branchTotal += counts.length;
|
|
792
|
-
branchCovered += counts.filter((c) => c > 0).length;
|
|
793
|
-
}
|
|
794
|
-
const coveredLines = /* @__PURE__ */ new Set();
|
|
795
|
-
const totalLines = /* @__PURE__ */ new Set();
|
|
796
|
-
for (const [sid, loc] of Object.entries(data.statementMap ?? {})) {
|
|
797
|
-
const line = loc?.start?.line;
|
|
798
|
-
if (line == null) continue;
|
|
799
|
-
totalLines.add(line);
|
|
800
|
-
if ((data.s ?? {})[sid] > 0) coveredLines.add(line);
|
|
801
|
-
}
|
|
802
|
-
const fileStats = {
|
|
803
|
-
statements: { total: stmtTotal, covered: stmtCovered, pct: pct(stmtCovered, stmtTotal) },
|
|
804
|
-
branches: { total: branchTotal, covered: branchCovered, pct: pct(branchCovered, branchTotal) },
|
|
805
|
-
functions: { total: fnTotal, covered: fnCovered, pct: pct(fnCovered, fnTotal) },
|
|
806
|
-
lines: { total: totalLines.size, covered: coveredLines.size, pct: pct(coveredLines.size, totalLines.size) }
|
|
807
|
-
};
|
|
808
|
-
summary[filePath] = fileStats;
|
|
809
|
-
for (const key of ["statements", "branches", "functions", "lines"]) {
|
|
810
|
-
summary.total[key].total += fileStats[key].total;
|
|
811
|
-
summary.total[key].covered += fileStats[key].covered;
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
for (const key of ["statements", "branches", "functions", "lines"]) {
|
|
815
|
-
const { total, covered } = summary.total[key];
|
|
816
|
-
summary.total[key].pct = pct(covered, total);
|
|
817
|
-
}
|
|
818
|
-
return summary;
|
|
819
|
-
}
|
|
820
|
-
async function printCoverageSummary(cwd2, extraCoverageArgs, worstCount = 10, options = {}) {
|
|
821
|
-
const { silent = false } = options;
|
|
822
|
-
let coverageDir = path3.resolve(cwd2, "coverage");
|
|
823
|
-
const repoDirArg = extraCoverageArgs.find((a) => a.startsWith("--coverage.reportsDirectory="));
|
|
824
|
-
if (repoDirArg) {
|
|
825
|
-
const raw = repoDirArg.split("=").slice(1).join("=");
|
|
826
|
-
coverageDir = path3.isAbsolute(raw) ? raw : path3.resolve(cwd2, raw);
|
|
827
|
-
}
|
|
828
|
-
let summary;
|
|
829
|
-
try {
|
|
830
|
-
const content = await fs3.readFile(path3.join(coverageDir, "coverage-summary.json"), "utf8");
|
|
831
|
-
summary = JSON.parse(content);
|
|
832
|
-
} catch {
|
|
833
|
-
try {
|
|
834
|
-
const content = await fs3.readFile(path3.join(coverageDir, "coverage-final.json"), "utf8");
|
|
835
|
-
summary = computeSummaryFromFinal(JSON.parse(content));
|
|
836
|
-
} catch {
|
|
837
|
-
if (!silent) console.log(chalk3.dim(" (no coverage JSON found \u2014 skipping summary)"));
|
|
838
|
-
return null;
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
const { total, ...fileSummaries } = summary;
|
|
842
|
-
if (worstCount > 0) {
|
|
843
|
-
const fileRows = Object.entries(fileSummaries).map(([absFile, data]) => ({
|
|
844
|
-
file: path3.relative(cwd2, absFile),
|
|
845
|
-
lines: data.lines?.pct ?? 0,
|
|
846
|
-
stmts: data.statements?.pct ?? 0,
|
|
847
|
-
fns: data.functions?.pct ?? 0,
|
|
848
|
-
branches: data.branches?.pct ?? 0
|
|
849
|
-
})).filter((r) => Math.min(r.lines, r.stmts, r.fns, r.branches) < 100).sort((a, b) => Math.min(a.lines, a.stmts, a.fns, a.branches) - Math.min(b.lines, b.stmts, b.fns, b.branches));
|
|
850
|
-
const rowsToShow = fileRows.slice(0, worstCount);
|
|
851
|
-
if (!silent) {
|
|
852
|
-
console.log("\n" + chalk3.bold("\u{1F4C9} WORST COVERAGE FILES (lowest metric)"));
|
|
853
|
-
console.log("-".repeat(80));
|
|
854
|
-
rowsToShow.forEach(({ file, lines, stmts, fns, branches }) => {
|
|
855
|
-
const worst = Math.min(lines, stmts, fns, branches);
|
|
856
|
-
const extras = chalk3.dim(
|
|
857
|
-
`lines ${lines.toFixed(0)}% | stmts ${stmts.toFixed(0)}% | fns ${fns.toFixed(0)}% | branches ${branches.toFixed(0)}%`
|
|
858
|
-
);
|
|
859
|
-
console.log(` ${colourPct(chalk3, worst)}% ${chalk3.dim(file)} ${extras}`);
|
|
860
|
-
});
|
|
861
|
-
if (fileRows.length > worstCount) {
|
|
862
|
-
console.log(chalk3.dim(` ... and ${fileRows.length - worstCount} more files`));
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
const tl2 = total.lines?.pct ?? 0;
|
|
866
|
-
const ts2 = total.statements?.pct ?? 0;
|
|
867
|
-
const tf2 = total.functions?.pct ?? 0;
|
|
868
|
-
const tb2 = total.branches?.pct ?? 0;
|
|
869
|
-
if (!silent) {
|
|
870
|
-
console.log(
|
|
871
|
-
`
|
|
872
|
-
${chalk3.bold("Coverage")} ${colourPct(chalk3, tl2)}% lines ${chalk3.dim("|")} ${colourPct(chalk3, ts2)}% statements ${chalk3.dim("|")} ${colourPct(chalk3, tf2)}% functions ${chalk3.dim("|")} ${colourPct(chalk3, tb2)}% branches`
|
|
873
|
-
);
|
|
874
|
-
}
|
|
875
|
-
return {
|
|
876
|
-
coverageDir,
|
|
877
|
-
total,
|
|
878
|
-
worstFiles: rowsToShow,
|
|
879
|
-
worstFilesShown: rowsToShow.length,
|
|
880
|
-
worstFilesTotal: fileRows.length,
|
|
881
|
-
summary
|
|
882
|
-
};
|
|
883
|
-
}
|
|
884
|
-
const tl = total.lines?.pct ?? 0;
|
|
885
|
-
const ts = total.statements?.pct ?? 0;
|
|
886
|
-
const tf = total.functions?.pct ?? 0;
|
|
887
|
-
const tb = total.branches?.pct ?? 0;
|
|
888
|
-
if (!silent) {
|
|
889
|
-
console.log(
|
|
890
|
-
`
|
|
891
|
-
${chalk3.bold("Coverage")} ${colourPct(chalk3, tl)}% lines ${chalk3.dim("|")} ${colourPct(chalk3, ts)}% statements ${chalk3.dim("|")} ${colourPct(chalk3, tf)}% functions ${chalk3.dim("|")} ${colourPct(chalk3, tb)}% branches`
|
|
892
|
-
);
|
|
893
|
-
}
|
|
894
|
-
return {
|
|
895
|
-
coverageDir,
|
|
896
|
-
total,
|
|
897
|
-
worstFiles: [],
|
|
898
|
-
worstFilesShown: 0,
|
|
899
|
-
worstFilesTotal: 0,
|
|
900
|
-
summary
|
|
901
|
-
};
|
|
902
|
-
}
|
|
903
|
-
|
|
904
|
-
// src/core/scratch.mjs
|
|
905
|
-
import fs4 from "fs/promises";
|
|
906
|
-
import fsSync from "fs";
|
|
907
|
-
import path4 from "path";
|
|
908
|
-
var DEFAULT_SCRATCH_DIR = "tmp/vitest-runner";
|
|
909
|
-
var RUN_ROOT_NAME_PATTERN = /^(\d+)-\d+$/;
|
|
910
|
-
function isPidAlive(pid) {
|
|
911
|
-
try {
|
|
912
|
-
process.kill(pid, 0);
|
|
913
|
-
return true;
|
|
914
|
-
} catch (err) {
|
|
915
|
-
return err.code !== "ESRCH";
|
|
916
|
-
}
|
|
917
|
-
}
|
|
918
|
-
async function sweepStaleScratchRoots(cwd2, scratchDir) {
|
|
919
|
-
const base = path4.resolve(cwd2, scratchDir);
|
|
920
|
-
let entries;
|
|
921
|
-
try {
|
|
922
|
-
entries = await fs4.readdir(base, { withFileTypes: true });
|
|
923
|
-
} catch {
|
|
924
|
-
return;
|
|
925
|
-
}
|
|
926
|
-
for (const entry of entries) {
|
|
927
|
-
if (!entry.isDirectory()) continue;
|
|
928
|
-
const match = entry.name.match(RUN_ROOT_NAME_PATTERN);
|
|
929
|
-
if (!match) continue;
|
|
930
|
-
const pid = parseInt(match[1], 10);
|
|
931
|
-
if (isPidAlive(pid)) continue;
|
|
932
|
-
await fs4.rm(path4.join(base, entry.name), { recursive: true, force: true }).catch(() => {
|
|
933
|
-
});
|
|
934
|
-
}
|
|
935
|
-
}
|
|
936
|
-
async function createRunScratchRoot(cwd2, scratchDir) {
|
|
937
|
-
const base = path4.resolve(cwd2, scratchDir);
|
|
938
|
-
const root = path4.join(base, `${process.pid}-${Date.now()}`);
|
|
939
|
-
await fs4.mkdir(root, { recursive: true });
|
|
940
|
-
return root;
|
|
941
|
-
}
|
|
942
|
-
async function createFileScratchDir(runRoot, index) {
|
|
943
|
-
const dir = path4.join(runRoot, `worker-${index}`);
|
|
944
|
-
await fs4.mkdir(dir, { recursive: true });
|
|
945
|
-
return dir;
|
|
946
|
-
}
|
|
947
|
-
async function removeScratchRoot(root) {
|
|
948
|
-
await fs4.rm(root, { recursive: true, force: true }).catch(() => {
|
|
949
|
-
});
|
|
950
|
-
}
|
|
951
|
-
function removeScratchRootSync(root) {
|
|
952
|
-
try {
|
|
953
|
-
fsSync.rmSync(root, { recursive: true, force: true });
|
|
954
|
-
} catch {
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
|
|
958
|
-
// src/runner.mjs
|
|
959
|
-
function getHeapForFile(filePath, globalMaxMb, overrides) {
|
|
960
|
-
const normalized = filePath.replace(/\\/g, "/");
|
|
961
|
-
let perFileMb;
|
|
962
|
-
for (const { pattern, heapMb } of overrides) {
|
|
963
|
-
if (normalized.includes(pattern)) {
|
|
964
|
-
perFileMb = heapMb;
|
|
965
|
-
break;
|
|
966
|
-
}
|
|
967
|
-
}
|
|
968
|
-
if (perFileMb === void 0 && globalMaxMb === void 0) return void 0;
|
|
969
|
-
if (perFileMb === void 0) return globalMaxMb;
|
|
970
|
-
if (globalMaxMb === void 0) return perFileMb;
|
|
971
|
-
return Math.max(perFileMb, globalMaxMb);
|
|
972
|
-
}
|
|
973
|
-
async function run(opts) {
|
|
974
|
-
const cwd2 = opts.cwd ?? process.cwd();
|
|
975
|
-
const scratchDirOpt = opts.scratchDir ?? DEFAULT_SCRATCH_DIR;
|
|
976
|
-
const keepTmp = opts.keepTmp ?? false;
|
|
977
|
-
await sweepStaleScratchRoots(cwd2, scratchDirOpt);
|
|
978
|
-
const scratchRoot = await createRunScratchRoot(cwd2, scratchDirOpt);
|
|
979
|
-
const onSignal = () => {
|
|
980
|
-
removeScratchRootSync(scratchRoot);
|
|
981
|
-
process.exitCode = 130;
|
|
982
|
-
process.exit();
|
|
983
|
-
};
|
|
984
|
-
if (!keepTmp) {
|
|
985
|
-
process.once("SIGINT", onSignal);
|
|
986
|
-
process.once("SIGTERM", onSignal);
|
|
987
|
-
}
|
|
988
|
-
try {
|
|
989
|
-
return await runImpl(opts, scratchRoot);
|
|
990
|
-
} finally {
|
|
991
|
-
process.removeListener("SIGINT", onSignal);
|
|
992
|
-
process.removeListener("SIGTERM", onSignal);
|
|
993
|
-
if (!keepTmp) await removeScratchRoot(scratchRoot);
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
async function runImpl(opts, scratchRoot) {
|
|
997
|
-
const {
|
|
998
|
-
cwd: cwd2 = process.cwd(),
|
|
999
|
-
testDir,
|
|
1000
|
-
testPatterns = [],
|
|
1001
|
-
testListFile,
|
|
1002
|
-
testFilePattern,
|
|
1003
|
-
vitestArgs: rawVitestArgs = [],
|
|
1004
|
-
showErrorDetails = true,
|
|
1005
|
-
coverageQuiet = false,
|
|
1006
|
-
suppressFileOutput = false,
|
|
1007
|
-
suppressPassingFiles = false,
|
|
1008
|
-
topSummary = true,
|
|
1009
|
-
json = false,
|
|
1010
|
-
workers = parseInt(process.env.VITEST_WORKERS ?? "4", 10),
|
|
1011
|
-
worstCoverageCount = 10,
|
|
1012
|
-
blobsDir: blobsDirOpt,
|
|
1013
|
-
mergeReports = true,
|
|
1014
|
-
earlyRunPatterns = [],
|
|
1015
|
-
perFileHeapOverrides = [],
|
|
1016
|
-
conditions = [],
|
|
1017
|
-
nodeEnv = "development",
|
|
1018
|
-
/** @internal Inject pre-built results to bypass discovery and spawn (for testing final-report render paths). */
|
|
1019
|
-
_testResultsOverride = null
|
|
1020
|
-
} = opts;
|
|
1021
|
-
let fileScratchIndex = 0;
|
|
1022
|
-
const maxOldSpaceMb = opts.maxOldSpaceMb ?? (process.env.VITEST_HEAP_MB ? parseInt(process.env.VITEST_HEAP_MB, 10) : void 0);
|
|
1023
|
-
const emitTextOutput = !json;
|
|
1024
|
-
const vitestBin = resolveBin(cwd2, "vitest", "vitest");
|
|
1025
|
-
const vitestConfig = await resolveVitestConfig(cwd2, opts.vitestConfig);
|
|
1026
|
-
const spawnBase = { cwd: cwd2, vitestBin, vitestConfig, conditions, nodeEnv };
|
|
1027
|
-
const vitestArgs2 = [...rawVitestArgs];
|
|
1028
|
-
if (coverageQuiet && !vitestArgs2.some((a) => a === "--coverage" || a.startsWith("--coverage."))) {
|
|
1029
|
-
vitestArgs2.unshift("--coverage");
|
|
1030
|
-
}
|
|
1031
|
-
const hasCoverage = vitestArgs2.some((a) => a === "--coverage" || a.startsWith("--coverage."));
|
|
1032
|
-
if (hasCoverage) {
|
|
1033
|
-
const blobsDir = path5.resolve(cwd2, blobsDirOpt ?? ".vitest-coverage-blobs");
|
|
1034
|
-
const coverageTmpBase = path5.resolve(cwd2, ".vitest-coverage-tmp");
|
|
1035
|
-
await Promise.all([fs5.rm(blobsDir, { recursive: true, force: true }), fs5.rm(coverageTmpBase, { recursive: true, force: true })]);
|
|
1036
|
-
await Promise.all([fs5.mkdir(blobsDir, { recursive: true }), fs5.mkdir(coverageTmpBase, { recursive: true })]);
|
|
1037
|
-
const allTestFiles = await discoverVitestFiles({ cwd: cwd2, testDir, testPatterns, testListFile, testFilePattern, earlyRunPatterns });
|
|
1038
|
-
const filterConflicts2 = computeFilterConflicts(allTestFiles);
|
|
1039
|
-
if (allTestFiles.length === 0) {
|
|
1040
|
-
const noTestsMessage = testPatterns.length > 0 ? `\u274C No Vitest test files found matching: ${testPatterns.join(", ")}` : "\u274C No Vitest test files found";
|
|
1041
|
-
if (emitTextOutput) console.log(noTestsMessage);
|
|
1042
|
-
if (json) {
|
|
1043
|
-
return {
|
|
1044
|
-
exitCode: 1,
|
|
1045
|
-
mode: "coverage",
|
|
1046
|
-
message: noTestsMessage,
|
|
1047
|
-
options: {
|
|
1048
|
-
cwd: cwd2,
|
|
1049
|
-
testDir,
|
|
1050
|
-
testPatterns,
|
|
1051
|
-
testListFile,
|
|
1052
|
-
workers,
|
|
1053
|
-
coverageQuiet,
|
|
1054
|
-
suppressFileOutput,
|
|
1055
|
-
suppressPassingFiles,
|
|
1056
|
-
topSummary,
|
|
1057
|
-
vitestArgs: vitestArgs2
|
|
1058
|
-
}
|
|
1059
|
-
};
|
|
1060
|
-
}
|
|
1061
|
-
return 1;
|
|
1062
|
-
}
|
|
1063
|
-
const extraCoverageArgs = vitestArgs2.filter((a) => a !== "--coverage" && a.startsWith("--coverage."));
|
|
1064
|
-
const nonCoveragePassthrough = vitestArgs2.filter((a) => a !== "--coverage" && !a.startsWith("--coverage."));
|
|
1065
|
-
const soloFiles2 = allTestFiles.filter((f) => earlyRunPatterns.some((p) => f.replace(/\\/g, "/").includes(p)));
|
|
1066
|
-
const parallelFiles2 = allTestFiles.filter((f) => !earlyRunPatterns.some((p) => f.replace(/\\/g, "/").includes(p)));
|
|
1067
|
-
const suppressPerFileCoverageOutput = coverageQuiet || suppressFileOutput || json;
|
|
1068
|
-
const showCoverageProgress = coverageQuiet && !json;
|
|
1069
|
-
if (!coverageQuiet && !suppressFileOutput && emitTextOutput) {
|
|
1070
|
-
console.log(`
|
|
1071
|
-
\u{1F9EA} Running ${allTestFiles.length} test files for coverage (blob + merge mode)`);
|
|
1072
|
-
console.log(`\u2699\uFE0F Workers: ${workers} (${soloFiles2.length} solo first, then parallel)`);
|
|
1073
|
-
if (maxOldSpaceMb) console.log(`\u{1F9E0} Heap limit: ${maxOldSpaceMb} MB`);
|
|
1074
|
-
console.log("");
|
|
1075
|
-
}
|
|
1076
|
-
const progress = showCoverageProgress ? createCoverageProgressTracker(allTestFiles.length) : noopProgressTracker;
|
|
1077
|
-
const coverageResults = [];
|
|
1078
|
-
let blobIndex = 0;
|
|
1079
|
-
const runCoverageFile = async (filePath) => {
|
|
1080
|
-
const blobPath = path5.join(blobsDir, `run-${blobIndex}.blob`);
|
|
1081
|
-
blobIndex++;
|
|
1082
|
-
if (!suppressPerFileCoverageOutput && emitTextOutput) {
|
|
1083
|
-
console.log(`
|
|
1084
|
-
${"=".repeat(80)}`);
|
|
1085
|
-
console.log(`\u25B6\uFE0F ${filePath}`);
|
|
1086
|
-
console.log("=".repeat(80));
|
|
1087
|
-
}
|
|
1088
|
-
progress.onStart();
|
|
1089
|
-
const tmpCoverageDir = path5.join(coverageTmpBase, `run-${blobIndex}`);
|
|
1090
|
-
const blobArgs = [
|
|
1091
|
-
...nonCoveragePassthrough,
|
|
1092
|
-
"--coverage",
|
|
1093
|
-
`--coverage.reportsDirectory=${tmpCoverageDir}`,
|
|
1094
|
-
"--reporter=default",
|
|
1095
|
-
"--reporter=blob",
|
|
1096
|
-
`--outputFile=${blobPath}`
|
|
1097
|
-
];
|
|
1098
|
-
const scratchDir = await createFileScratchDir(scratchRoot, fileScratchIndex++);
|
|
1099
|
-
const result = await runSingleFile(filePath, {
|
|
1100
|
-
...spawnBase,
|
|
1101
|
-
maxOldSpaceMb: getHeapForFile(filePath, maxOldSpaceMb, perFileHeapOverrides),
|
|
1102
|
-
vitestArgs: blobArgs,
|
|
1103
|
-
streamOutput: !suppressPerFileCoverageOutput,
|
|
1104
|
-
excludePaths: filterConflicts2.get(filePath),
|
|
1105
|
-
extraEnv: { VITEST_RUNNER_TMP: scratchDir }
|
|
1106
|
-
});
|
|
1107
|
-
coverageResults.push(result);
|
|
1108
|
-
progress.onComplete(result.code !== 0);
|
|
1109
|
-
if (!suppressPerFileCoverageOutput && emitTextOutput) {
|
|
1110
|
-
const durationSec = (result.duration / 1e3).toFixed(2);
|
|
1111
|
-
if (result.code === 0) {
|
|
1112
|
-
const heapInfo = result.heapMb ? ` | ${result.heapMb} MB heap` : "";
|
|
1113
|
-
console.log(`
|
|
1114
|
-
\u2705 PASSED (${durationSec}s${heapInfo})
|
|
1115
|
-
`);
|
|
1116
|
-
} else {
|
|
1117
|
-
console.log(`
|
|
1118
|
-
\u274C FAILED (exit code ${result.code}, ${durationSec}s)
|
|
1119
|
-
`);
|
|
1120
|
-
}
|
|
1121
|
-
}
|
|
1122
|
-
};
|
|
1123
|
-
for (const filePath of soloFiles2) {
|
|
1124
|
-
await runCoverageFile(filePath).catch((err) => {
|
|
1125
|
-
if (emitTextOutput) console.error(`Error running ${filePath}:`, err);
|
|
1126
|
-
});
|
|
1127
|
-
}
|
|
1128
|
-
let coverageFileIndex = 0;
|
|
1129
|
-
const coverageActivePromises = /* @__PURE__ */ new Set();
|
|
1130
|
-
while (coverageFileIndex < parallelFiles2.length || coverageActivePromises.size > 0) {
|
|
1131
|
-
while (coverageFileIndex < parallelFiles2.length && coverageActivePromises.size < workers) {
|
|
1132
|
-
const filePath = parallelFiles2[coverageFileIndex++];
|
|
1133
|
-
const promise = runCoverageFile(filePath).catch((err) => {
|
|
1134
|
-
if (emitTextOutput) console.error(`Error running ${filePath}:`, err);
|
|
1135
|
-
}).finally(() => coverageActivePromises.delete(promise));
|
|
1136
|
-
coverageActivePromises.add(promise);
|
|
1137
|
-
}
|
|
1138
|
-
if (coverageActivePromises.size > 0) await Promise.race(coverageActivePromises);
|
|
1139
|
-
}
|
|
1140
|
-
progress.finish();
|
|
1141
|
-
const blobFiles = (await fs5.readdir(blobsDir).catch(() => [])).filter((f) => f.endsWith(".blob"));
|
|
1142
|
-
if (blobFiles.length === 0) {
|
|
1143
|
-
const message = "\u274C No coverage blobs were generated \u2014 coverage report cannot be produced";
|
|
1144
|
-
if (emitTextOutput) console.error(message);
|
|
1145
|
-
if (json) {
|
|
1146
|
-
return {
|
|
1147
|
-
exitCode: 1,
|
|
1148
|
-
mode: "coverage",
|
|
1149
|
-
message,
|
|
1150
|
-
options: {
|
|
1151
|
-
cwd: cwd2,
|
|
1152
|
-
testDir,
|
|
1153
|
-
testPatterns,
|
|
1154
|
-
testListFile,
|
|
1155
|
-
workers,
|
|
1156
|
-
coverageQuiet,
|
|
1157
|
-
suppressFileOutput,
|
|
1158
|
-
suppressPassingFiles,
|
|
1159
|
-
topSummary,
|
|
1160
|
-
showErrorDetails,
|
|
1161
|
-
worstCoverageCount,
|
|
1162
|
-
vitestArgs: vitestArgs2
|
|
1163
|
-
},
|
|
1164
|
-
totals: {
|
|
1165
|
-
testFiles: allTestFiles.length,
|
|
1166
|
-
failedFiles: coverageResults.filter((r) => r.code !== 0).length,
|
|
1167
|
-
passedFiles: coverageResults.filter((r) => r.code === 0).length
|
|
1168
|
-
},
|
|
1169
|
-
results: {
|
|
1170
|
-
all: coverageResults,
|
|
1171
|
-
failed: coverageResults.filter((r) => r.code !== 0)
|
|
1172
|
-
},
|
|
1173
|
-
merge: {
|
|
1174
|
-
blobFiles: 0,
|
|
1175
|
-
exitCode: 1,
|
|
1176
|
-
output: ""
|
|
1177
|
-
},
|
|
1178
|
-
coverageSummary: null
|
|
1179
|
-
};
|
|
1180
|
-
}
|
|
1181
|
-
return 1;
|
|
1182
|
-
}
|
|
1183
|
-
let mergeExitCode = 0;
|
|
1184
|
-
let mergeOutput = "";
|
|
1185
|
-
let coverageSummary = null;
|
|
1186
|
-
if (mergeReports) {
|
|
1187
|
-
if (!coverageQuiet && !suppressFileOutput && emitTextOutput) {
|
|
1188
|
-
console.log(`
|
|
1189
|
-
${"=".repeat(80)}`);
|
|
1190
|
-
console.log(`\u{1F4CA} Merging ${blobFiles.length} coverage blobs into final report...`);
|
|
1191
|
-
console.log("=".repeat(80));
|
|
1192
|
-
}
|
|
1193
|
-
({ exitCode: mergeExitCode, output: mergeOutput } = await runMergeReports(blobsDir, {
|
|
1194
|
-
...spawnBase,
|
|
1195
|
-
maxOldSpaceMb,
|
|
1196
|
-
extraCoverageArgs,
|
|
1197
|
-
quietOutput: coverageQuiet || json
|
|
1198
|
-
}));
|
|
1199
|
-
if (coverageQuiet && emitTextOutput) {
|
|
1200
|
-
printMergeOutput(mergeExitCode, mergeOutput);
|
|
1201
|
-
}
|
|
1202
|
-
coverageSummary = await printCoverageSummary(cwd2, extraCoverageArgs, worstCoverageCount, { silent: json });
|
|
1203
|
-
}
|
|
1204
|
-
await Promise.all([
|
|
1205
|
-
...mergeReports ? [fs5.rm(blobsDir, { recursive: true, force: true }).catch(() => {
|
|
1206
|
-
})] : [],
|
|
1207
|
-
fs5.rm(coverageTmpBase, { recursive: true, force: true }).catch(() => {
|
|
1208
|
-
})
|
|
1209
|
-
]);
|
|
1210
|
-
const coverageFailed = coverageResults.filter((r) => r.code !== 0);
|
|
1211
|
-
if (coverageQuiet && emitTextOutput) printQuietCoverageFailureDetails(coverageFailed);
|
|
1212
|
-
const exitCode2 = coverageFailed.length > 0 ? 1 : mergeExitCode;
|
|
1213
|
-
if (json) {
|
|
1214
|
-
const coverageWithHeap = coverageResults.filter((r) => r.heapMb !== null);
|
|
1215
|
-
return {
|
|
1216
|
-
exitCode: exitCode2,
|
|
1217
|
-
mode: "coverage",
|
|
1218
|
-
options: {
|
|
1219
|
-
cwd: cwd2,
|
|
1220
|
-
testDir,
|
|
1221
|
-
testPatterns,
|
|
1222
|
-
testListFile,
|
|
1223
|
-
workers,
|
|
1224
|
-
coverageQuiet,
|
|
1225
|
-
suppressFileOutput,
|
|
1226
|
-
suppressPassingFiles,
|
|
1227
|
-
topSummary,
|
|
1228
|
-
showErrorDetails,
|
|
1229
|
-
worstCoverageCount,
|
|
1230
|
-
vitestArgs: vitestArgs2
|
|
1231
|
-
},
|
|
1232
|
-
totals: {
|
|
1233
|
-
testFiles: allTestFiles.length,
|
|
1234
|
-
failedFiles: coverageFailed.length,
|
|
1235
|
-
passedFiles: coverageResults.length - coverageFailed.length
|
|
1236
|
-
},
|
|
1237
|
-
results: {
|
|
1238
|
-
all: coverageResults,
|
|
1239
|
-
failed: coverageFailed
|
|
1240
|
-
},
|
|
1241
|
-
merge: {
|
|
1242
|
-
blobFiles: blobFiles.length,
|
|
1243
|
-
exitCode: mergeExitCode,
|
|
1244
|
-
output: mergeOutput
|
|
1245
|
-
},
|
|
1246
|
-
coverageSummary,
|
|
1247
|
-
...topSummary ? {
|
|
1248
|
-
topMemoryUsers: [...coverageWithHeap].sort((a, b) => b.heapMb - a.heapMb).slice(0, 10),
|
|
1249
|
-
topDuration: [...coverageResults].sort((a, b) => b.duration - a.duration).slice(0, 10)
|
|
1250
|
-
} : {}
|
|
1251
|
-
};
|
|
1252
|
-
}
|
|
1253
|
-
return exitCode2;
|
|
1254
|
-
}
|
|
1255
|
-
const testFiles = await discoverVitestFiles({ cwd: cwd2, testDir, testPatterns, testListFile, testFilePattern, earlyRunPatterns });
|
|
1256
|
-
const filterConflicts = computeFilterConflicts(testFiles);
|
|
1257
|
-
if (testFiles.length === 0) {
|
|
1258
|
-
const noTestsMessage = testPatterns.length > 0 ? `\u274C No Vitest test files found matching: ${testPatterns.join(", ")}` : "\u274C No Vitest test files found";
|
|
1259
|
-
if (emitTextOutput) console.log(noTestsMessage);
|
|
1260
|
-
if (json) {
|
|
1261
|
-
return {
|
|
1262
|
-
exitCode: 1,
|
|
1263
|
-
mode: "standard",
|
|
1264
|
-
message: noTestsMessage,
|
|
1265
|
-
options: {
|
|
1266
|
-
cwd: cwd2,
|
|
1267
|
-
testDir,
|
|
1268
|
-
testPatterns,
|
|
1269
|
-
testListFile,
|
|
1270
|
-
workers,
|
|
1271
|
-
coverageQuiet,
|
|
1272
|
-
suppressFileOutput,
|
|
1273
|
-
suppressPassingFiles,
|
|
1274
|
-
topSummary,
|
|
1275
|
-
vitestArgs: vitestArgs2
|
|
1276
|
-
}
|
|
1277
|
-
};
|
|
1278
|
-
}
|
|
1279
|
-
return 1;
|
|
1280
|
-
}
|
|
1281
|
-
const soloFiles = testFiles.filter((f) => earlyRunPatterns.some((p) => f.replace(/\\/g, "/").includes(p)));
|
|
1282
|
-
const parallelFiles = testFiles.filter((f) => !earlyRunPatterns.some((p) => f.replace(/\\/g, "/").includes(p)));
|
|
1283
|
-
const scriptStartTime = Date.now();
|
|
1284
|
-
const scriptStartTimeFormatted = (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false });
|
|
1285
|
-
if (emitTextOutput && !suppressFileOutput && testPatterns.length > 0) {
|
|
1286
|
-
console.log(`
|
|
1287
|
-
\u{1F9EA} Running ${testFiles.length} test files matching: ${testPatterns.join(", ")}`);
|
|
1288
|
-
} else if (emitTextOutput && !suppressFileOutput) {
|
|
1289
|
-
console.log(`
|
|
1290
|
-
\u{1F9EA} Running ${testFiles.length} test files (${soloFiles.length} solo first, then parallel)`);
|
|
1291
|
-
}
|
|
1292
|
-
if (emitTextOutput && !suppressFileOutput) {
|
|
1293
|
-
console.log(`\u2699\uFE0F Workers: ${workers}`);
|
|
1294
|
-
if (maxOldSpaceMb) console.log(`\u{1F9E0} Heap limit: ${maxOldSpaceMb} MB`);
|
|
1295
|
-
if (vitestArgs2.length > 0) console.log(`\u{1F527} Vitest args: ${vitestArgs2.join(" ")}`);
|
|
1296
|
-
console.log("");
|
|
1297
|
-
}
|
|
1298
|
-
const results = [..._testResultsOverride ?? []];
|
|
1299
|
-
const runTestFile = async (filePath) => {
|
|
1300
|
-
if (!suppressFileOutput && emitTextOutput) {
|
|
1301
|
-
console.log(`
|
|
1302
|
-
${"=".repeat(80)}`);
|
|
1303
|
-
console.log(`\u25B6\uFE0F ${filePath}`);
|
|
1304
|
-
console.log("=".repeat(80));
|
|
1305
|
-
}
|
|
1306
|
-
const scratchDir = await createFileScratchDir(scratchRoot, fileScratchIndex++);
|
|
1307
|
-
const result = await runSingleFile(filePath, {
|
|
1308
|
-
...spawnBase,
|
|
1309
|
-
maxOldSpaceMb: getHeapForFile(filePath, maxOldSpaceMb, perFileHeapOverrides),
|
|
1310
|
-
vitestArgs: vitestArgs2,
|
|
1311
|
-
streamOutput: !suppressFileOutput && !json,
|
|
1312
|
-
excludePaths: filterConflicts.get(filePath),
|
|
1313
|
-
extraEnv: { VITEST_RUNNER_TMP: scratchDir }
|
|
1314
|
-
});
|
|
1315
|
-
if (!suppressFileOutput && emitTextOutput) {
|
|
1316
|
-
const durationSec = (result.duration / 1e3).toFixed(2);
|
|
1317
|
-
if (result.code === 0) {
|
|
1318
|
-
const heapInfo = result.heapMb ? ` | ${result.heapMb} MB heap` : "";
|
|
1319
|
-
console.log(`
|
|
1320
|
-
\u2705 PASSED (${durationSec}s${heapInfo})
|
|
1321
|
-
`);
|
|
1322
|
-
} else {
|
|
1323
|
-
console.log(`
|
|
1324
|
-
\u274C FAILED (exit code ${result.code}, ${durationSec}s)
|
|
1325
|
-
`);
|
|
1326
|
-
}
|
|
1327
|
-
}
|
|
1328
|
-
return result;
|
|
1329
|
-
};
|
|
1330
|
-
if (!_testResultsOverride) {
|
|
1331
|
-
for (const filePath of soloFiles) {
|
|
1332
|
-
const result = await runTestFile(filePath).catch((err) => {
|
|
1333
|
-
if (emitTextOutput) console.error(`Error running ${filePath}:`, err);
|
|
1334
|
-
return null;
|
|
1335
|
-
});
|
|
1336
|
-
if (result) results.push(result);
|
|
1337
|
-
}
|
|
1338
|
-
let index = 0;
|
|
1339
|
-
const activePromises = /* @__PURE__ */ new Set();
|
|
1340
|
-
while (index < parallelFiles.length || activePromises.size > 0) {
|
|
1341
|
-
while (index < parallelFiles.length && activePromises.size < workers) {
|
|
1342
|
-
const filePath = parallelFiles[index++];
|
|
1343
|
-
const promise = runTestFile(filePath).then((result) => results.push(result)).catch((err) => {
|
|
1344
|
-
if (emitTextOutput) console.error(`Error running ${filePath}:`, err);
|
|
1345
|
-
}).finally(() => activePromises.delete(promise));
|
|
1346
|
-
activePromises.add(promise);
|
|
1347
|
-
}
|
|
1348
|
-
if (activePromises.size > 0) await Promise.race(activePromises);
|
|
1349
|
-
}
|
|
1350
|
-
}
|
|
1351
|
-
const totalTestFilesPass = results.reduce((s, r) => s + r.testFilesPass, 0);
|
|
1352
|
-
const totalTestFilesFail = results.reduce((s, r) => s + r.testFilesFail, 0);
|
|
1353
|
-
const totalTestsPass = results.reduce((s, r) => s + r.testsPass, 0);
|
|
1354
|
-
const totalTestsFail = results.reduce((s, r) => s + r.testsFail, 0);
|
|
1355
|
-
const totalTestsSkip = results.reduce((s, r) => s + (r.testsSkip || 0), 0);
|
|
1356
|
-
const totalDuration = results.reduce((s, r) => s + r.duration, 0);
|
|
1357
|
-
const failedFiles = results.filter((r) => r.code !== 0);
|
|
1358
|
-
const passedFiles = results.filter((r) => r.code === 0);
|
|
1359
|
-
const exitCode = failedFiles.length > 0 ? 1 : 0;
|
|
1360
|
-
const scriptMemory = process.memoryUsage();
|
|
1361
|
-
const scriptHeapMb = Math.round(scriptMemory.heapUsed / 1024 / 1024);
|
|
1362
|
-
const scriptRssMb = Math.round(scriptMemory.rss / 1024 / 1024);
|
|
1363
|
-
const withHeap = results.filter((r) => r.heapMb !== null);
|
|
1364
|
-
const actualDurationSec = ((Date.now() - scriptStartTime) / 1e3).toFixed(2);
|
|
1365
|
-
const testsDurationSec = (totalDuration / 1e3).toFixed(2);
|
|
1366
|
-
if (json) {
|
|
1367
|
-
return {
|
|
1368
|
-
exitCode,
|
|
1369
|
-
mode: "standard",
|
|
1370
|
-
options: {
|
|
1371
|
-
cwd: cwd2,
|
|
1372
|
-
testDir,
|
|
1373
|
-
testPatterns,
|
|
1374
|
-
testListFile,
|
|
1375
|
-
workers,
|
|
1376
|
-
coverageQuiet,
|
|
1377
|
-
suppressFileOutput,
|
|
1378
|
-
suppressPassingFiles,
|
|
1379
|
-
topSummary,
|
|
1380
|
-
showErrorDetails,
|
|
1381
|
-
worstCoverageCount,
|
|
1382
|
-
vitestArgs: vitestArgs2
|
|
1383
|
-
},
|
|
1384
|
-
totals: {
|
|
1385
|
-
testFilesPass: totalTestFilesPass,
|
|
1386
|
-
testFilesFail: totalTestFilesFail,
|
|
1387
|
-
testsPass: totalTestsPass,
|
|
1388
|
-
testsFail: totalTestsFail,
|
|
1389
|
-
testsSkip: totalTestsSkip,
|
|
1390
|
-
totalTests: totalTestsPass + totalTestsFail + totalTestsSkip
|
|
1391
|
-
},
|
|
1392
|
-
timing: {
|
|
1393
|
-
startAt: scriptStartTimeFormatted,
|
|
1394
|
-
wallClockSeconds: Number(actualDurationSec),
|
|
1395
|
-
testsSeconds: Number(testsDurationSec)
|
|
1396
|
-
},
|
|
1397
|
-
heap: {
|
|
1398
|
-
scriptHeapMb,
|
|
1399
|
-
scriptRssMb,
|
|
1400
|
-
maxHeapMb: withHeap.length > 0 ? Math.max(...withHeap.map((r) => r.heapMb)) : null,
|
|
1401
|
-
avgHeapMb: withHeap.length > 0 ? Number((withHeap.reduce((sum, r) => sum + r.heapMb, 0) / withHeap.length).toFixed(0)) : null
|
|
1402
|
-
},
|
|
1403
|
-
results: {
|
|
1404
|
-
all: results,
|
|
1405
|
-
passed: passedFiles,
|
|
1406
|
-
failed: failedFiles
|
|
1407
|
-
},
|
|
1408
|
-
...topSummary ? {
|
|
1409
|
-
topMemoryUsers: [...withHeap].sort((a, b) => b.heapMb - a.heapMb).slice(0, 10),
|
|
1410
|
-
topDuration: [...results].sort((a, b) => b.duration - a.duration).slice(0, 10)
|
|
1411
|
-
} : {}
|
|
1412
|
-
};
|
|
1413
|
-
}
|
|
1414
|
-
console.log("\n" + "=".repeat(80));
|
|
1415
|
-
if (topSummary && withHeap.length > 0) {
|
|
1416
|
-
console.log("\n" + chalk4.bold("\u{1F9E0} TOP MEMORY USERS"));
|
|
1417
|
-
console.log("-".repeat(80));
|
|
1418
|
-
[...withHeap].sort((a, b) => b.heapMb - a.heapMb).slice(0, 10).forEach((r) => {
|
|
1419
|
-
console.log(` ${String(r.heapMb).padStart(4)} MB ${chalk4.dim(r.file)}`);
|
|
1420
|
-
});
|
|
1421
|
-
}
|
|
1422
|
-
if (topSummary && results.length > 0) {
|
|
1423
|
-
console.log("\n" + chalk4.bold("\u23F1\uFE0F TOP DURATION"));
|
|
1424
|
-
console.log("-".repeat(80));
|
|
1425
|
-
[...results].sort((a, b) => b.duration - a.duration).slice(0, 10).forEach((r) => {
|
|
1426
|
-
const sec = (r.duration / 1e3).toFixed(2);
|
|
1427
|
-
console.log(` ${(sec + "s").padStart(8)} ${chalk4.dim(r.file)}`);
|
|
1428
|
-
});
|
|
1429
|
-
}
|
|
1430
|
-
if (passedFiles.length > 0 && !suppressPassingFiles) {
|
|
1431
|
-
console.log("\n" + "=".repeat(80));
|
|
1432
|
-
console.log(chalk4.bold.green("\u2713 PASSED TEST FILES"));
|
|
1433
|
-
console.log("=".repeat(80));
|
|
1434
|
-
passedFiles.forEach((r) => {
|
|
1435
|
-
const durationSec = (r.duration / 1e3).toFixed(2);
|
|
1436
|
-
const statsInfo = [...r.heapMb ? [`${r.heapMb} MB`] : [], `${durationSec}s`];
|
|
1437
|
-
const testInfo = r.testsPass > 0 ? ` - ${r.testsPass} tests` : "";
|
|
1438
|
-
console.log(chalk4.green(`\u2713 ${r.file}${testInfo}`) + chalk4.dim(` (${statsInfo.join(", ")})`));
|
|
1439
|
-
});
|
|
1440
|
-
}
|
|
1441
|
-
console.log("\n" + chalk4.bold("=".repeat(80)));
|
|
1442
|
-
if (failedFiles.length > 0) {
|
|
1443
|
-
console.log(`
|
|
1444
|
-
\u274C ${failedFiles.length} test file(s) failed`);
|
|
1445
|
-
console.log(chalk4.bold.red("\nFailed Test Files:"));
|
|
1446
|
-
failedFiles.forEach((r) => {
|
|
1447
|
-
const durationSec = (r.duration / 1e3).toFixed(2);
|
|
1448
|
-
const testCounts = [
|
|
1449
|
-
...r.testsFail > 0 ? [chalk4.red(`${r.testsFail} failed`)] : [],
|
|
1450
|
-
...r.testsPass > 0 ? [chalk4.green(`${r.testsPass} passed`)] : [],
|
|
1451
|
-
...r.testsSkip > 0 ? [chalk4.yellow(`${r.testsSkip} skipped`)] : []
|
|
1452
|
-
];
|
|
1453
|
-
const statsInfo = [...r.heapMb ? [`${r.heapMb} MB`] : [], `${durationSec}s`];
|
|
1454
|
-
const countStr = testCounts.length > 0 ? ` (${testCounts.join(", ")})` : "";
|
|
1455
|
-
console.log(` ${chalk4.red("\u2716")} ${r.file}${countStr}` + chalk4.dim(` [${statsInfo.join(", ")}]`));
|
|
1456
|
-
if (showErrorDetails && r.errors.length > 0) {
|
|
1457
|
-
const deduped = deduplicateErrors(r.errors).split("\n").map((line) => line.trim() ? ` ${line}` : "").join("\n");
|
|
1458
|
-
console.log(deduped);
|
|
1459
|
-
console.log("");
|
|
1460
|
-
}
|
|
1461
|
-
});
|
|
1462
|
-
console.log("");
|
|
1463
|
-
}
|
|
1464
|
-
console.log(chalk4.bold("=".repeat(80)));
|
|
1465
|
-
if (totalTestFilesFail > 0 && totalTestFilesPass > 0) {
|
|
1466
|
-
console.log(
|
|
1467
|
-
` ${chalk4.bold("Test Files")} ${chalk4.red(`${totalTestFilesFail} failed`)} ${chalk4.dim("|")} ${chalk4.green(`${totalTestFilesPass} passed`)} ${chalk4.dim(`(${totalTestFilesPass + totalTestFilesFail})`)}`
|
|
1468
|
-
);
|
|
1469
|
-
} else if (totalTestFilesFail > 0) {
|
|
1470
|
-
console.log(` ${chalk4.bold("Test Files")} ${chalk4.red(`${totalTestFilesFail} failed`)} ${chalk4.dim(`(${totalTestFilesFail})`)}`);
|
|
1471
|
-
} else {
|
|
1472
|
-
console.log(` ${chalk4.bold("Test Files")} ${chalk4.green(`${totalTestFilesPass} passed`)} ${chalk4.dim(`(${totalTestFilesPass})`)}`);
|
|
1473
|
-
}
|
|
1474
|
-
const totalTests = totalTestsPass + totalTestsFail + totalTestsSkip;
|
|
1475
|
-
const testsParts = [
|
|
1476
|
-
...totalTestsFail > 0 ? [chalk4.red(`${totalTestsFail} failed`)] : [],
|
|
1477
|
-
...totalTestsPass > 0 ? [chalk4.green(`${totalTestsPass} passed`)] : [],
|
|
1478
|
-
...totalTestsSkip > 0 ? [chalk4.yellow(`${totalTestsSkip} skipped`)] : []
|
|
1479
|
-
];
|
|
1480
|
-
console.log(` ${chalk4.bold("Tests")} ${testsParts.join(` ${chalk4.dim("|")} `)} ${chalk4.dim(`(${totalTests})`)}`);
|
|
1481
|
-
console.log(` ${chalk4.bold("Start at")} ${scriptStartTimeFormatted}`);
|
|
1482
|
-
console.log(` ${chalk4.bold("Duration")} ${actualDurationSec}s ${chalk4.dim(`(tests ${testsDurationSec}s)`)}`);
|
|
1483
|
-
if (withHeap.length > 0) {
|
|
1484
|
-
const maxHeap = Math.max(...withHeap.map((r) => r.heapMb));
|
|
1485
|
-
const avgHeap = (withHeap.reduce((s, r) => s + r.heapMb, 0) / withHeap.length).toFixed(0);
|
|
1486
|
-
console.log(` ${chalk4.bold("Heap")} max ${maxHeap} MB | avg ${avgHeap} MB | script ${scriptHeapMb} MB (RSS ${scriptRssMb} MB)`);
|
|
1487
|
-
} else {
|
|
1488
|
-
console.log(` ${chalk4.bold("Heap")} script ${scriptHeapMb} MB (RSS ${scriptRssMb} MB)`);
|
|
1489
|
-
}
|
|
1490
|
-
if (failedFiles.length > 0) return 1;
|
|
1491
|
-
console.log(`
|
|
1492
|
-
\u2705 All ${passedFiles.length} test files passed
|
|
1493
|
-
`);
|
|
1494
|
-
return 0;
|
|
1495
|
-
}
|
|
1496
|
-
|
|
1497
|
-
// src/bin/vitest-runner.mjs
|
|
1498
|
-
var args = parseArguments(process.argv.slice(2));
|
|
1499
|
-
if (args.help) {
|
|
1500
|
-
showHelp();
|
|
1501
|
-
process.exit(0);
|
|
1502
|
-
}
|
|
1503
|
-
if ((args.coverageQuiet || args.logFile) && !args.json) {
|
|
1504
|
-
let isProgressChunk = function(chunk) {
|
|
1505
|
-
const str = chunk.toString();
|
|
1506
|
-
return str.startsWith("\r") || /^progress \d+\.\d+%/.test(str);
|
|
1507
|
-
};
|
|
1508
|
-
const cwd2 = process.cwd();
|
|
1509
|
-
const resolvedLogFile = args.logFile ? path6.isAbsolute(args.logFile) ? args.logFile : path6.resolve(cwd2, args.logFile) : path6.join(cwd2, "coverage", "coverage-run.log");
|
|
1510
|
-
mkdirSync(path6.dirname(resolvedLogFile), { recursive: true });
|
|
1511
|
-
const logStream = createWriteStream(resolvedLogFile, { flags: "a" });
|
|
1512
|
-
const origStdoutWrite = process.stdout.write.bind(process.stdout);
|
|
1513
|
-
const origStderrWrite = process.stderr.write.bind(process.stderr);
|
|
1514
|
-
process.stdout.write = (chunk, enc, cb) => {
|
|
1515
|
-
if (!isProgressChunk(chunk)) logStream.write(stripAnsi(chunk.toString()));
|
|
1516
|
-
return origStdoutWrite(chunk, enc, cb);
|
|
1517
|
-
};
|
|
1518
|
-
process.stderr.write = (chunk, enc, cb) => {
|
|
1519
|
-
if (!isProgressChunk(chunk)) logStream.write(stripAnsi(chunk.toString()));
|
|
1520
|
-
return origStderrWrite(chunk, enc, cb);
|
|
1521
|
-
};
|
|
1522
|
-
process.on("exit", () => logStream.end());
|
|
1523
|
-
}
|
|
1524
|
-
var cwd = process.cwd();
|
|
1525
|
-
var vitestArgs = [...args.vitestPassthroughArgs];
|
|
1526
|
-
if (args.coverageQuiet && !vitestArgs.some((a) => a === "--coverage" || a.startsWith("--coverage."))) {
|
|
1527
|
-
vitestArgs.unshift("--coverage");
|
|
1528
|
-
}
|
|
1529
|
-
try {
|
|
1530
|
-
const runResult = await run({
|
|
1531
|
-
cwd,
|
|
1532
|
-
testPatterns: args.testPatterns,
|
|
1533
|
-
testListFile: args.testListFile,
|
|
1534
|
-
testFilePattern: args.testFilePattern,
|
|
1535
|
-
vitestArgs,
|
|
1536
|
-
showErrorDetails: args.showErrorDetails,
|
|
1537
|
-
coverageQuiet: args.coverageQuiet,
|
|
1538
|
-
suppressFileOutput: args.suppressFileOutput,
|
|
1539
|
-
suppressPassingFiles: args.suppressPassingFiles,
|
|
1540
|
-
topSummary: args.topSummary,
|
|
1541
|
-
json: args.json,
|
|
1542
|
-
mergeReports: args.mergeReports,
|
|
1543
|
-
keepTmp: args.keepTmp,
|
|
1544
|
-
...args.blobsDir !== void 0 && { blobsDir: args.blobsDir },
|
|
1545
|
-
...args.workers !== void 0 && { workers: args.workers },
|
|
1546
|
-
...args.soloPatterns.length > 0 && { earlyRunPatterns: args.soloPatterns },
|
|
1547
|
-
...args.scratchDir !== void 0 && { scratchDir: args.scratchDir }
|
|
1548
|
-
});
|
|
1549
|
-
if (args.json) {
|
|
1550
|
-
process.stdout.write(`${JSON.stringify(runResult, null, 2)}
|
|
1551
|
-
`);
|
|
1552
|
-
process.exit(typeof runResult === "object" && runResult !== null && "exitCode" in runResult ? runResult.exitCode : 1);
|
|
1553
|
-
}
|
|
1554
|
-
process.exit(typeof runResult === "number" ? runResult : 1);
|
|
1555
|
-
} catch (err) {
|
|
1556
|
-
console.error("Fatal error:", err);
|
|
1557
|
-
process.exit(1);
|
|
1558
|
-
}
|
|
77
|
+
`)}import G from"fs/promises";import ve from"path";import $ from"chalk";import{createRequire as Ot}from"module";import X from"path";import Rt from"fs/promises";import{pathToFileURL as kt}from"url";function ke(e,s,t=s){let n=Ot(kt(X.join(e,"package.json"))),o=n.resolve(`${s}/package.json`),r=n(o),l=r.bin?.[t]??r.bin;if(!l)throw new Error(`No bin "${t}" found in ${s}/package.json`);return X.join(X.dirname(o),l)}var Dt=Object.freeze(["vitest.config.ts","vitest.config.mts","vitest.config.cts","vitest.config.mjs","vitest.config.js","vitest.config.cjs","vite.config.ts","vite.config.mts","vite.config.mjs","vite.config.js"]);async function De(e,s){if(s)return X.isAbsolute(s)?s:X.resolve(e,s);for(let t of Dt){let n=X.join(e,t);try{return await Rt.access(n),n}catch{}}}import Ae from"fs/promises";import C from"path";var rt=/\.test\.vitest\.(?:js|mjs|cjs)$/i;async function de(e,s,t=rt){let n=[e],o=[];for(;n.length;){let r=n.pop(),l;try{l=await Ae.readdir(r,{withFileTypes:!0})}catch{continue}for(let c of l){if(c.isDirectory()){if(c.name==="node_modules"||c.name.startsWith("."))continue;n.push(C.join(r,c.name));continue}c.isFile()&&t.test(c.name)&&o.push(C.relative(s,C.join(r,c.name)))}}return o}function ge(e,s=[]){let t=[],n=[];for(let o of e){let r=o.replace(/\\/g,"/"),l=s.findIndex(c=>r.includes(c));l!==-1?t.push({file:o,priorityIndex:l}):n.push(o)}return t.sort((o,r)=>o.priorityIndex-r.priorityIndex||o.file.localeCompare(r.file)),n.sort((o,r)=>o.localeCompare(r)),[...t.map(o=>o.file),...n]}function Ie(e){let s=e.map(n=>n.replace(/\\/g,"/")),t=new Map;for(let n=0;n<e.length;n++){let o=[];for(let r=0;r<e.length;r++)n!==r&&s[r].includes(s[n])&&o.push(e[r]);t.set(e[n],o)}return t}async function me(e){let{cwd:s,testDir:t,testPatterns:n=[],testListFile:o,testFilePattern:r=rt,earlyRunPatterns:l=[]}=e,c=t?C.isAbsolute(t)?t:C.resolve(s,t):s;if(o){let a=C.isAbsolute(o)?o:C.resolve(s,o),u;try{let f=await Ae.readFile(a,"utf8");u=JSON.parse(f)}catch(f){throw new Error(`Failed to read test list file "${a}": ${f.message}`,{cause:f})}if(!Array.isArray(u))throw new Error(`Test list file "${a}" must contain a JSON array of test file paths`);return console.log(`\u{1F4CB} Loading test list from: ${C.relative(s,a)}`),ge(u,l)}if(n.length===0){let a=await de(c,s,r);return ge(a,l)}let g=[];for(let a of n){let u=C.isAbsolute(a)?a:C.resolve(s,a),f=null;try{f=await Ae.stat(u)}catch{}if(f?.isFile())r.test(u)&&g.push(C.relative(s,u));else if(f?.isDirectory())g.push(...await de(u,s,r));else{let v=(await de(c,s,r)).filter(h=>h.replace(/\\/g,"/").includes(a.replace(/\\/g,"/")));v.length>0?g.push(...v):console.warn(`\u26A0\uFE0F No matches found for: ${a}`)}}return ge([...new Set(g)],l)}import{spawn as nt}from"child_process";function N(e){return e.replace(/\x1B\[[0-9;]*[a-zA-Z]/g,"")}function ee(e){return typeof e=="number"&&Number.isFinite(e)}function te(e,s){return ee(e)?e.toFixed(s):"Unknown"}function z(e,s){if(!ee(s))return e.dim(te(s,2).padStart(6));let t=s.toFixed(2).padStart(6);return s>=80?e.green(t):s>=50?e.yellow(t):e.red(t)}function je(e){let s=N(e),t={testFilesPass:0,testFilesFail:0,testsPass:0,testsFail:0,testsSkip:0,duration:0,heapMb:null,errors:[]},n=s.match(/Test Files\s+(.+)/);if(n){let g=n[1],a=g.match(/(\d+)\s+passed/),u=g.match(/(\d+)\s+failed/);a&&(t.testFilesPass=parseInt(a[1],10)),u&&(t.testFilesFail=parseInt(u[1],10))}let o=s.match(/^\s*Tests\s+(.+)$/m);if(o){let g=o[1],a=g.match(/(\d+)\s+passed/),u=g.match(/(\d+)\s+failed/),f=g.match(/(\d+)\s+skipped/);a&&(t.testsPass=parseInt(a[1],10)),u&&(t.testsFail=parseInt(u[1],10)),f&&(t.testsSkip=parseInt(f[1],10))}let r=s.match(/Duration\s+([\d.]+)s/);r&&(t.duration=parseFloat(r[1])*1e3);let l=s.match(/(\d+)\s*MB\s+heap\s+used/i);l&&(t.heapMb=parseInt(l[1],10));let c=e.indexOf("Failed Tests");if(c!==-1){let g=e.indexOf(`
|
|
78
|
+
Test Files`,c),a=g!==-1?e.substring(c,g):e.substring(c),u=/FAIL\s*(?:\x1B\[[0-9;]*[a-zA-Z]|\s)*tests\//g,f=[...a.matchAll(u)];for(let S=0;S<f.length;S++){let v=f[S].index,h=a.lastIndexOf(`
|
|
79
|
+
`,v),y=h===-1?0:h,b=S<f.length-1?f[S+1].index:a.length,d=a.lastIndexOf(`
|
|
80
|
+
`,b),x=d===-1?b:d,w=a.substring(y,x).trim();w&&t.errors.push(w)}}return t}function Ce(e){let t=e.join(`
|
|
81
|
+
`).split(`
|
|
82
|
+
`),n=new Map,o=new Map;t.forEach((l,c)=>{if(l.includes("FAIL")&&l.includes("Config:")){o.set(l,c);let a=N(l).match(/^(.+Config:\s+)'*([^'>]+)'*(.+)$/);if(a){let[,u,f,S]=a,v=`${u.trim()}|||${S.trim()}`;n.has(v)||n.set(v,{lines:[],configs:[]}),n.get(v).lines.push(l),n.get(v).configs.push(f.replace(/'/g,""))}}});let r=new Set;for(let[,l]of n.entries())if(l.configs.length>1){for(let f=1;f<l.lines.length;f++){let S=o.get(l.lines[f]);r.add(S)}let c=l.lines[0],g=`[${l.configs.map(f=>`'${f}'`).join(",")}]`,a=N(c).replace(/(Config:\s+)'*[^'>]+'*/,`$1${g}`),u=o.get(c);t[u]=a}return t.filter((l,c)=>!r.has(c)).join(`
|
|
83
|
+
`)}function Ne({maxOldSpaceMb:e,conditions:s=[],base:t=process.env.NODE_OPTIONS??""}){let n=t;for(let o of s){let r=`--conditions=${o}`;n.includes(r)||(n=n?`${n} ${r}`:r)}if(e&&!n.includes("--max-old-space-size")){let o=`--max-old-space-size=${e}`;n=n?`${n} ${o}`:o}return n.trim()}function it({maxOldSpaceMb:e,conditions:s=[],nodeEnv:t="development",extraEnv:n={}}){let o={...process.env};o.NODE_ENV||(o.NODE_ENV=t);let r=Ne({maxOldSpaceMb:e,conditions:s,base:o.NODE_OPTIONS??""});return r&&(o.NODE_OPTIONS=r),Object.assign(o,n),o}function At(e,s){return[e,...s?["--config",s]:[],"run"]}function he(e,s){let{cwd:t,vitestBin:n,vitestConfig:o,maxOldSpaceMb:r,conditions:l=[],nodeEnv:c="development",vitestArgs:g=[],streamOutput:a=!0,excludePaths:u=[],extraEnv:f={}}=s;return new Promise(S=>{let v=Date.now(),h=u.flatMap(p=>["--exclude",p]),y=[...At(n,o),...g,...h,e],b=it({maxOldSpaceMb:r,conditions:l,nodeEnv:c,extraEnv:f}),d=nt(process.execPath,y,{cwd:t,stdio:["ignore","pipe","pipe"],env:b}),x="",w="";d.stdout?.on("data",p=>{x+=p.toString(),a&&process.stdout.write(p)}),d.stderr?.on("data",p=>{w+=p.toString(),a&&process.stderr.write(p)}),d.on("close",p=>{let V=Date.now()-v,_=`${x}
|
|
84
|
+
${w}`,P=je(_);S({file:e,code:p??1,duration:P.duration>0?P.duration:V,testFilesPass:P.testFilesPass,testFilesFail:P.testFilesFail,testsPass:P.testsPass,testsFail:P.testsFail,testsSkip:P.testsSkip,heapMb:P.heapMb,errors:P.errors,rawOutput:_})}),d.on("error",p=>{S({file:e,code:1,duration:Date.now()-v,testFilesPass:0,testFilesFail:1,testsPass:0,testsFail:0,testsSkip:0,heapMb:null,errors:[p.message],rawOutput:p.toString()})})})}function Le(e,s){let{cwd:t,vitestBin:n,vitestConfig:o,maxOldSpaceMb:r,conditions:l=[],nodeEnv:c="development",extraCoverageArgs:g=[],quietOutput:a=!1}=s;return new Promise(u=>{let f=o?["--config",o]:[],S=a?["--color"]:[],v=[n,...f,"--mergeReports",e,"--run","--coverage",...S,...g],h=it({maxOldSpaceMb:r,conditions:l,nodeEnv:c}),y=nt(process.execPath,v,{cwd:t,stdio:a?["ignore","pipe","pipe"]:"inherit",env:h}),b="",d="";a&&(y.stdout?.on("data",x=>b+=x.toString()),y.stderr?.on("data",x=>d+=x.toString())),y.on("close",x=>u({exitCode:x??1,output:`${b}
|
|
85
|
+
${d}`})),y.on("error",()=>u({exitCode:1,output:""}))})}import q from"chalk";function se(e){let s=Math.max(0,Math.floor(e/1e3)),t=Math.floor(s/3600),n=Math.floor(s%3600/60),o=s%60;return t>0?`${t}:${n.toString().padStart(2,"0")}:${o.toString().padStart(2,"0")}`:`${n}:${o.toString().padStart(2,"0")}`}function _e(e){let s=!!process.stdout.isTTY,t=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"],n=26,o=Date.now(),r=0,l=0,c=0,g=0,a=0,u=0,f=null;function S(){let b=e===0?100:r/e*100,d=Date.now()-o,w=(r>0?d/r:0)*Math.max(e-r,0),p=Math.round(b/100*n),V=t[g%t.length],_=`[${"=".repeat(p)}${"-".repeat(Math.max(0,n-p))}]`,P=b.toFixed(1).padStart(5);return b<30?P=q.red(P+"%"):b<70?P=q.rgb(255,136,0)(P+"%"):b>99?P=q.green(P+"%"):P=q.yellow(P+"%"),s?`${q.green(V)} ${q.green(_)} ${q.bold(P)} ${r}/${e} | active ${l} | failed ${c} | ETA ${se(w)} | elapsed ${se(d)}`:`progress ${b.toFixed(1)}% ${r}/${e} | active ${l} | failed ${c} | eta ${se(w)} | elapsed ${se(d)}`}function v(b=!1){let d=S();if(g++,s){let w=d.replace(/\x1B\[[0-9;]*[a-zA-Z]/g,"").length;a=Math.max(a,w),process.stdout.write(`\r${d.padEnd(a," ")}`);return}let x=Date.now();(b||x-u>=2e3)&&(console.log(d),u=x)}function h(){!s||f||(f=setInterval(()=>{r>=e&&l===0||v()},120),f.unref?.())}function y(){f&&(clearInterval(f),f=null)}return v(!0),h(),{onStart(){l++,v()},onComplete(b){l=Math.max(0,l-1),r++,b&&c++,v(!0)},finish(){y(),v(!0),s&&process.stdout.write(`
|
|
86
|
+
`)}}}var Ve={onStart(){},onComplete(e){},finish(){}};import lt from"fs/promises";import oe from"path";import O from"chalk";function Be(e){if(e.length!==0){console.log(`
|
|
87
|
+
${"=".repeat(80)}`),console.log(O.bold.red("\u2716 FAILED TEST FILES (VERBOSE OUTPUT)")),console.log("=".repeat(80));for(let s of e)console.log(`
|
|
88
|
+
${O.red("\u2716")} ${O.red(s.file)} ${O.dim(`(exit ${s.code})`)}`),s.rawOutput?.trim()?console.log(s.rawOutput.trimEnd()):console.log(O.dim("(no child output captured)"));console.log(`
|
|
89
|
+
${"=".repeat(80)}`)}}function We(e,s){if(e===0){let t="% Coverage report from v8",n=s.split(`
|
|
90
|
+
`),o=n.findIndex(r=>N(r).includes(t));if(o>=0){let r=n.length;for(let c=o+1;c<n.length;c++){let g=N(n[c]).trimStart();if(g.startsWith("stderr |")||g.startsWith("stdout |")){r=c;break}}if(n.slice(o+1,r).join(`
|
|
91
|
+
`).trimEnd()){let c=n.slice(o,r).join(`
|
|
92
|
+
`).trimEnd();console.log(`
|
|
93
|
+
${c}
|
|
94
|
+
`)}}}else{let t=s.trimEnd();t&&console.error(t)}}function It(e){let s=(o,r)=>r===0?100:parseFloat((o/r*100).toFixed(2)),t={total:{statements:{total:0,covered:0,pct:0},branches:{total:0,covered:0,pct:0},functions:{total:0,covered:0,pct:0},lines:{total:0,covered:0,pct:0}}};for(let[o,r]of Object.entries(e)){let l=Object.keys(r.s??{}),c=l.length,g=l.filter(d=>r.s[d]>0).length,a=Object.keys(r.f??{}),u=a.length,f=a.filter(d=>r.f[d]>0).length,S=0,v=0;for(let d of Object.values(r.b??{}))S+=d.length,v+=d.filter(x=>x>0).length;let h=new Set,y=new Set;for(let[d,x]of Object.entries(r.statementMap??{})){let w=x?.start?.line;w!=null&&(y.add(w),(r.s??{})[d]>0&&h.add(w))}let b={statements:{total:c,covered:g,pct:s(g,c)},branches:{total:S,covered:v,pct:s(v,S)},functions:{total:u,covered:f,pct:s(f,u)},lines:{total:y.size,covered:h.size,pct:s(h.size,y.size)}};t[o]=b;for(let d of["statements","branches","functions","lines"])t.total[d].total+=b[d].total,t.total[d].covered+=b[d].covered}let n=Object.keys(e).length>0;for(let o of["statements","branches","functions","lines"]){let{total:r,covered:l}=t.total[o];t.total[o].pct=n?s(l,r):"Unknown"}return t}function jt({lines:e,stmts:s,fns:t,branches:n}){let o=[e,s,t,n].filter(ee);return o.length>0?Math.min(...o):null}function at(e){return e??Number.MAX_VALUE}function Ct({lines:e,stmts:s,fns:t,branches:n}){return![e,s,t,n].every(ee)}function Nt({lines:e,stmts:s,fns:t,branches:n}){console.log(`
|
|
95
|
+
${O.bold("Coverage")} ${z(O,e)}% lines ${O.dim("|")} ${z(O,s)}% statements ${O.dim("|")} ${z(O,t)}% functions ${O.dim("|")} ${z(O,n)}% branches`)}async function Ue(e,s,t=10,n={}){let{silent:o=!1}=n,r=oe.resolve(e,"coverage"),l=s.find(h=>h.startsWith("--coverage.reportsDirectory="));if(l){let h=l.split("=").slice(1).join("=");r=oe.isAbsolute(h)?h:oe.resolve(e,h)}let c;try{let h=await lt.readFile(oe.join(r,"coverage-summary.json"),"utf8");c=JSON.parse(h)}catch{try{let h=await lt.readFile(oe.join(r,"coverage-final.json"),"utf8");c=It(JSON.parse(h))}catch{return o||console.log(O.dim(" (no coverage JSON found \u2014 skipping summary)")),null}}let{total:g,...a}=c,u={lines:g.lines?.pct??0,stmts:g.statements?.pct??0,fns:g.functions?.pct??0,branches:g.branches?.pct??0},f=Object.keys(a).length===0&&!Object.values(u).some(ee),S=[],v=0;if(t>0){let h=Object.entries(a).map(([b,d])=>({file:oe.relative(e,b),lines:d.lines?.pct??0,stmts:d.statements?.pct??0,fns:d.functions?.pct??0,branches:d.branches?.pct??0})).map(b=>({row:b,worst:jt(b)})).filter(({row:b,worst:d})=>Ct(b)||d<100).sort((b,d)=>at(b.worst)-at(d.worst)),y=h.slice(0,t);o||(console.log(`
|
|
96
|
+
`+O.bold("\u{1F4C9} WORST COVERAGE FILES (lowest metric)")),console.log("-".repeat(80)),y.forEach(({row:{file:b,lines:d,stmts:x,fns:w,branches:p},worst:V})=>{let _=O.dim(`lines ${te(d,0)}% | stmts ${te(x,0)}% | fns ${te(w,0)}% | branches ${te(p,0)}%`);console.log(` ${z(O,V)}% ${O.dim(b)} ${_}`)}),h.length>t&&console.log(O.dim(` ... and ${h.length-t} more files`))),S=y.map(({row:b})=>b),v=h.length}return o||(Nt(u),f&&console.log(O.dim(" No files were measured (the coverage include matched nothing), so every percentage is Unknown and coverage thresholds are skipped."))),{coverageDir:r,total:g,noFilesMeasured:f,worstFiles:S,worstFilesShown:S.length,worstFilesTotal:v,summary:c}}import ce from"fs/promises";import Lt from"fs";import pe from"path";var ct="tmp/vitest-runner",_t=/^(\d+)-\d+$/;function Vt(e){try{return process.kill(e,0),!0}catch(s){return s.code!=="ESRCH"}}async function pt(e,s){let t=pe.resolve(e,s),n;try{n=await ce.readdir(t,{withFileTypes:!0})}catch{return}for(let o of n){if(!o.isDirectory())continue;let r=o.name.match(_t);if(!r)continue;let l=parseInt(r[1],10);Vt(l)||await ce.rm(pe.join(t,o.name),{recursive:!0,force:!0}).catch(()=>{})}}async function ut(e,s){let t=pe.resolve(e,s),n=pe.join(t,`${process.pid}-${Date.now()}`);return await ce.mkdir(n,{recursive:!0}),n}async function He(e,s){let t=pe.join(e,`worker-${s}`);return await ce.mkdir(t,{recursive:!0}),t}async function ft(e){await ce.rm(e,{recursive:!0,force:!0}).catch(()=>{})}function dt(e){try{Lt.rmSync(e,{recursive:!0,force:!0})}catch{}}function gt(e,s,t){let n=e.replace(/\\/g,"/"),o;for(let{pattern:r,heapMb:l}of t)if(n.includes(r)){o=l;break}if(!(o===void 0&&s===void 0))return o===void 0?s:s===void 0?o:Math.max(o,s)}async function mt(e){let s=e.cwd??process.cwd(),t=e.scratchDir??ct,n=e.keepTmp??!1;await pt(s,t);let o=await ut(s,t),r=()=>{dt(o),process.exitCode=130,process.exit()};n||(process.once("SIGINT",r),process.once("SIGTERM",r));try{return await Bt(e,o)}finally{process.removeListener("SIGINT",r),process.removeListener("SIGTERM",r),n||await ft(o)}}async function Bt(e,s){let{cwd:t=process.cwd(),testDir:n,testPatterns:o=[],testListFile:r,testFilePattern:l,vitestArgs:c=[],showErrorDetails:g=!0,coverageQuiet:a=!1,suppressFileOutput:u=!1,suppressPassingFiles:f=!1,topSummary:S=!0,json:v=!1,workers:h=parseInt(process.env.VITEST_WORKERS??"4",10),worstCoverageCount:y=10,blobsDir:b,mergeReports:d=!0,earlyRunPatterns:x=[],perFileHeapOverrides:w=[],conditions:p=[],nodeEnv:V="development",_testResultsOverride:_=null}=e,P=0,B=e.maxOldSpaceMb??(process.env.VITEST_HEAP_MB?parseInt(process.env.VITEST_HEAP_MB,10):void 0),R=!v,ht=ke(t,"vitest","vitest"),vt=await De(t,e.vitestConfig),Se={cwd:t,vitestBin:ht,vitestConfig:vt,conditions:p,nodeEnv:V},A=[...c];if(a&&!A.some(i=>i==="--coverage"||i.startsWith("--coverage."))&&A.unshift("--coverage"),A.some(i=>i==="--coverage"||i.startsWith("--coverage."))){let i=ve.resolve(t,b??".vitest-coverage-blobs"),m=ve.resolve(t,".vitest-coverage-tmp");await Promise.all([G.rm(i,{recursive:!0,force:!0}),G.rm(m,{recursive:!0,force:!0})]),await Promise.all([G.mkdir(i,{recursive:!0}),G.mkdir(m,{recursive:!0})]);let T=await me({cwd:t,testDir:n,testPatterns:o,testListFile:r,testFilePattern:l,earlyRunPatterns:x}),k=Ie(T);if(T.length===0){let F=o.length>0?`\u274C No Vitest test files found matching: ${o.join(", ")}`:"\u274C No Vitest test files found";return R&&console.log(F),v?{exitCode:1,mode:"coverage",message:F,options:{cwd:t,testDir:n,testPatterns:o,testListFile:r,workers:h,coverageQuiet:a,suppressFileOutput:u,suppressPassingFiles:f,topSummary:S,vitestArgs:A}}:1}let j=A.filter(F=>F!=="--coverage"&&F.startsWith("--coverage.")),xe=A.filter(F=>F!=="--coverage"&&!F.startsWith("--coverage.")),le=T.filter(F=>x.some(M=>F.replace(/\\/g,"/").includes(M))),Te=T.filter(F=>!x.some(M=>F.replace(/\\/g,"/").includes(M))),Ee=a||u||v,Tt=a&&!v;!a&&!u&&R&&(console.log(`
|
|
97
|
+
\u{1F9EA} Running ${T.length} test files for coverage (blob + merge mode)`),console.log(`\u2699\uFE0F Workers: ${h} (${le.length} solo first, then parallel)`),B&&console.log(`\u{1F9E0} Heap limit: ${B} MB`),console.log(""));let ye=Tt?_e(T.length):Ve,L=[],Pe=0,Ze=async F=>{let M=ve.join(i,`run-${Pe}.blob`);Pe++,!Ee&&R&&(console.log(`
|
|
98
|
+
${"=".repeat(80)}`),console.log(`\u25B6\uFE0F ${F}`),console.log("=".repeat(80))),ye.onStart();let U=ve.join(m,`run-${Pe}`),Et=[...xe,"--coverage",`--coverage.reportsDirectory=${U}`,"--reporter=default","--reporter=blob",`--outputFile=${M}`],yt=await He(s,P++),H=await he(F,{...Se,maxOldSpaceMb:gt(F,B,w),vitestArgs:Et,streamOutput:!Ee,excludePaths:k.get(F),extraEnv:{VITEST_RUNNER_TMP:yt}});if(L.push(H),ye.onComplete(H.code!==0),!Ee&&R){let tt=(H.duration/1e3).toFixed(2);if(H.code===0){let Pt=H.heapMb?` | ${H.heapMb} MB heap`:"";console.log(`
|
|
99
|
+
\u2705 PASSED (${tt}s${Pt})
|
|
100
|
+
`)}else console.log(`
|
|
101
|
+
\u274C FAILED (exit code ${H.code}, ${tt}s)
|
|
102
|
+
`)}};for(let F of le)await Ze(F).catch(M=>{R&&console.error(`Error running ${F}:`,M)});let Me=0,Y=new Set;for(;Me<Te.length||Y.size>0;){for(;Me<Te.length&&Y.size<h;){let F=Te[Me++],M=Ze(F).catch(U=>{R&&console.error(`Error running ${F}:`,U)}).finally(()=>Y.delete(M));Y.add(M)}Y.size>0&&await Promise.race(Y)}ye.finish();let Oe=(await G.readdir(i).catch(()=>[])).filter(F=>F.endsWith(".blob"));if(Oe.length===0){let F="\u274C No coverage blobs were generated \u2014 coverage report cannot be produced";return R&&console.error(F),v?{exitCode:1,mode:"coverage",message:F,options:{cwd:t,testDir:n,testPatterns:o,testListFile:r,workers:h,coverageQuiet:a,suppressFileOutput:u,suppressPassingFiles:f,topSummary:S,showErrorDetails:g,worstCoverageCount:y,vitestArgs:A},totals:{testFiles:T.length,failedFiles:L.filter(M=>M.code!==0).length,passedFiles:L.filter(M=>M.code===0).length},results:{all:L,failed:L.filter(M=>M.code!==0)},merge:{blobFiles:0,exitCode:1,output:""},coverageSummary:null}:1}let fe=0,Re="",Xe=null;d&&(!a&&!u&&R&&(console.log(`
|
|
103
|
+
${"=".repeat(80)}`),console.log(`\u{1F4CA} Merging ${Oe.length} coverage blobs into final report...`),console.log("=".repeat(80))),{exitCode:fe,output:Re}=await Le(i,{...Se,maxOldSpaceMb:B,extraCoverageArgs:j,quietOutput:a||v}),a&&R&&We(fe,Re),Xe=await Ue(t,j,y,{silent:v})),await Promise.all([...d?[G.rm(i,{recursive:!0,force:!0}).catch(()=>{})]:[],G.rm(m,{recursive:!0,force:!0}).catch(()=>{})]);let ae=L.filter(F=>F.code!==0);a&&R&&Be(ae);let et=ae.length>0?1:fe;if(v){let F=L.filter(M=>M.heapMb!==null);return{exitCode:et,mode:"coverage",options:{cwd:t,testDir:n,testPatterns:o,testListFile:r,workers:h,coverageQuiet:a,suppressFileOutput:u,suppressPassingFiles:f,topSummary:S,showErrorDetails:g,worstCoverageCount:y,vitestArgs:A},totals:{testFiles:T.length,failedFiles:ae.length,passedFiles:L.length-ae.length},results:{all:L,failed:ae},merge:{blobFiles:Oe.length,exitCode:fe,output:Re},coverageSummary:Xe,...S?{topMemoryUsers:[...F].sort((M,U)=>U.heapMb-M.heapMb).slice(0,10),topDuration:[...L].sort((M,U)=>U.duration-M.duration).slice(0,10)}:{}}}return et}let J=await me({cwd:t,testDir:n,testPatterns:o,testListFile:r,testFilePattern:l,earlyRunPatterns:x}),bt=Ie(J);if(J.length===0){let i=o.length>0?`\u274C No Vitest test files found matching: ${o.join(", ")}`:"\u274C No Vitest test files found";return R&&console.log(i),v?{exitCode:1,mode:"standard",message:i,options:{cwd:t,testDir:n,testPatterns:o,testListFile:r,workers:h,coverageQuiet:a,suppressFileOutput:u,suppressPassingFiles:f,topSummary:S,vitestArgs:A}}:1}let qe=J.filter(i=>x.some(m=>i.replace(/\\/g,"/").includes(m))),$e=J.filter(i=>!x.some(m=>i.replace(/\\/g,"/").includes(m))),St=Date.now(),Ge=new Date().toLocaleTimeString("en-US",{hour12:!1});R&&!u&&o.length>0?console.log(`
|
|
104
|
+
\u{1F9EA} Running ${J.length} test files matching: ${o.join(", ")}`):R&&!u&&console.log(`
|
|
105
|
+
\u{1F9EA} Running ${J.length} test files (${qe.length} solo first, then parallel)`),R&&!u&&(console.log(`\u2699\uFE0F Workers: ${h}`),B&&console.log(`\u{1F9E0} Heap limit: ${B} MB`),A.length>0&&console.log(`\u{1F527} Vitest args: ${A.join(" ")}`),console.log(""));let D=[..._??[]],Je=async i=>{!u&&R&&(console.log(`
|
|
106
|
+
${"=".repeat(80)}`),console.log(`\u25B6\uFE0F ${i}`),console.log("=".repeat(80)));let m=await He(s,P++),T=await he(i,{...Se,maxOldSpaceMb:gt(i,B,w),vitestArgs:A,streamOutput:!u&&!v,excludePaths:bt.get(i),extraEnv:{VITEST_RUNNER_TMP:m}});if(!u&&R){let k=(T.duration/1e3).toFixed(2);if(T.code===0){let j=T.heapMb?` | ${T.heapMb} MB heap`:"";console.log(`
|
|
107
|
+
\u2705 PASSED (${k}s${j})
|
|
108
|
+
`)}else console.log(`
|
|
109
|
+
\u274C FAILED (exit code ${T.code}, ${k}s)
|
|
110
|
+
`)}return T};if(!_){for(let T of qe){let k=await Je(T).catch(j=>(R&&console.error(`Error running ${T}:`,j),null));k&&D.push(k)}let i=0,m=new Set;for(;i<$e.length||m.size>0;){for(;i<$e.length&&m.size<h;){let T=$e[i++],k=Je(T).then(j=>D.push(j)).catch(j=>{R&&console.error(`Error running ${T}:`,j)}).finally(()=>m.delete(k));m.add(k)}m.size>0&&await Promise.race(m)}}let K=D.reduce((i,m)=>i+m.testFilesPass,0),W=D.reduce((i,m)=>i+m.testFilesFail,0),re=D.reduce((i,m)=>i+m.testsPass,0),ne=D.reduce((i,m)=>i+m.testsFail,0),ie=D.reduce((i,m)=>i+(m.testsSkip||0),0),$t=D.reduce((i,m)=>i+m.duration,0),Q=D.filter(i=>i.code!==0),ue=D.filter(i=>i.code===0),wt=Q.length>0?1:0,Ke=process.memoryUsage(),we=Math.round(Ke.heapUsed/1024/1024),Fe=Math.round(Ke.rss/1024/1024),I=D.filter(i=>i.heapMb!==null),Qe=((Date.now()-St)/1e3).toFixed(2),Ye=($t/1e3).toFixed(2);if(v)return{exitCode:wt,mode:"standard",options:{cwd:t,testDir:n,testPatterns:o,testListFile:r,workers:h,coverageQuiet:a,suppressFileOutput:u,suppressPassingFiles:f,topSummary:S,showErrorDetails:g,worstCoverageCount:y,vitestArgs:A},totals:{testFilesPass:K,testFilesFail:W,testsPass:re,testsFail:ne,testsSkip:ie,totalTests:re+ne+ie},timing:{startAt:Ge,wallClockSeconds:Number(Qe),testsSeconds:Number(Ye)},heap:{scriptHeapMb:we,scriptRssMb:Fe,maxHeapMb:I.length>0?Math.max(...I.map(i=>i.heapMb)):null,avgHeapMb:I.length>0?Number((I.reduce((i,m)=>i+m.heapMb,0)/I.length).toFixed(0)):null},results:{all:D,passed:ue,failed:Q},...S?{topMemoryUsers:[...I].sort((i,m)=>m.heapMb-i.heapMb).slice(0,10),topDuration:[...D].sort((i,m)=>m.duration-i.duration).slice(0,10)}:{}};console.log(`
|
|
111
|
+
`+"=".repeat(80)),S&&I.length>0&&(console.log(`
|
|
112
|
+
`+$.bold("\u{1F9E0} TOP MEMORY USERS")),console.log("-".repeat(80)),[...I].sort((i,m)=>m.heapMb-i.heapMb).slice(0,10).forEach(i=>{console.log(` ${String(i.heapMb).padStart(4)} MB ${$.dim(i.file)}`)})),S&&D.length>0&&(console.log(`
|
|
113
|
+
`+$.bold("\u23F1\uFE0F TOP DURATION")),console.log("-".repeat(80)),[...D].sort((i,m)=>m.duration-i.duration).slice(0,10).forEach(i=>{let m=(i.duration/1e3).toFixed(2);console.log(` ${(m+"s").padStart(8)} ${$.dim(i.file)}`)})),ue.length>0&&!f&&(console.log(`
|
|
114
|
+
`+"=".repeat(80)),console.log($.bold.green("\u2713 PASSED TEST FILES")),console.log("=".repeat(80)),ue.forEach(i=>{let m=(i.duration/1e3).toFixed(2),T=[...i.heapMb?[`${i.heapMb} MB`]:[],`${m}s`],k=i.testsPass>0?` - ${i.testsPass} tests`:"";console.log($.green(`\u2713 ${i.file}${k}`)+$.dim(` (${T.join(", ")})`))})),console.log(`
|
|
115
|
+
`+$.bold("=".repeat(80))),Q.length>0&&(console.log(`
|
|
116
|
+
\u274C ${Q.length} test file(s) failed`),console.log($.bold.red(`
|
|
117
|
+
Failed Test Files:`)),Q.forEach(i=>{let m=(i.duration/1e3).toFixed(2),T=[...i.testsFail>0?[$.red(`${i.testsFail} failed`)]:[],...i.testsPass>0?[$.green(`${i.testsPass} passed`)]:[],...i.testsSkip>0?[$.yellow(`${i.testsSkip} skipped`)]:[]],k=[...i.heapMb?[`${i.heapMb} MB`]:[],`${m}s`],j=T.length>0?` (${T.join(", ")})`:"";if(console.log(` ${$.red("\u2716")} ${i.file}${j}`+$.dim(` [${k.join(", ")}]`)),g&&i.errors.length>0){let xe=Ce(i.errors).split(`
|
|
118
|
+
`).map(le=>le.trim()?` ${le}`:"").join(`
|
|
119
|
+
`);console.log(xe),console.log("")}}),console.log("")),console.log($.bold("=".repeat(80))),W>0&&K>0?console.log(` ${$.bold("Test Files")} ${$.red(`${W} failed`)} ${$.dim("|")} ${$.green(`${K} passed`)} ${$.dim(`(${K+W})`)}`):W>0?console.log(` ${$.bold("Test Files")} ${$.red(`${W} failed`)} ${$.dim(`(${W})`)}`):console.log(` ${$.bold("Test Files")} ${$.green(`${K} passed`)} ${$.dim(`(${K})`)}`);let Ft=re+ne+ie,xt=[...ne>0?[$.red(`${ne} failed`)]:[],...re>0?[$.green(`${re} passed`)]:[],...ie>0?[$.yellow(`${ie} skipped`)]:[]];if(console.log(` ${$.bold("Tests")} ${xt.join(` ${$.dim("|")} `)} ${$.dim(`(${Ft})`)}`),console.log(` ${$.bold("Start at")} ${Ge}`),console.log(` ${$.bold("Duration")} ${Qe}s ${$.dim(`(tests ${Ye}s)`)}`),I.length>0){let i=Math.max(...I.map(T=>T.heapMb)),m=(I.reduce((T,k)=>T+k.heapMb,0)/I.length).toFixed(0);console.log(` ${$.bold("Heap")} max ${i} MB | avg ${m} MB | script ${we} MB (RSS ${Fe} MB)`)}else console.log(` ${$.bold("Heap")} script ${we} MB (RSS ${Fe} MB)`);return Q.length>0?1:(console.log(`
|
|
120
|
+
\u2705 All ${ue.length} test files passed
|
|
121
|
+
`),0)}var E=st(process.argv.slice(2));E.help&&(ot(),process.exit(0));if((E.coverageQuiet||E.logFile)&&!E.json){let r=function(l){let c=l.toString();return c.startsWith("\r")||/^progress \d+\.\d+%/.test(c)},e=process.cwd(),s=E.logFile?be.isAbsolute(E.logFile)?E.logFile:be.resolve(e,E.logFile):be.join(e,"coverage","coverage-run.log");Ut(be.dirname(s),{recursive:!0});let t=Wt(s,{flags:"a"}),n=process.stdout.write.bind(process.stdout),o=process.stderr.write.bind(process.stderr);process.stdout.write=(l,c,g)=>(r(l)||t.write(N(l.toString())),n(l,c,g)),process.stderr.write=(l,c,g)=>(r(l)||t.write(N(l.toString())),o(l,c,g)),process.on("exit",()=>t.end())}var Ht=process.cwd(),ze=[...E.vitestPassthroughArgs];E.coverageQuiet&&!ze.some(e=>e==="--coverage"||e.startsWith("--coverage."))&&ze.unshift("--coverage");try{let e=await mt({cwd:Ht,testPatterns:E.testPatterns,testListFile:E.testListFile,testFilePattern:E.testFilePattern,vitestArgs:ze,showErrorDetails:E.showErrorDetails,coverageQuiet:E.coverageQuiet,suppressFileOutput:E.suppressFileOutput,suppressPassingFiles:E.suppressPassingFiles,topSummary:E.topSummary,json:E.json,mergeReports:E.mergeReports,keepTmp:E.keepTmp,...E.blobsDir!==void 0&&{blobsDir:E.blobsDir},...E.workers!==void 0&&{workers:E.workers},...E.soloPatterns.length>0&&{earlyRunPatterns:E.soloPatterns},...E.scratchDir!==void 0&&{scratchDir:E.scratchDir}});E.json&&(process.stdout.write(`${JSON.stringify(e,null,2)}
|
|
122
|
+
`),process.exit(typeof e=="object"&&e!==null&&"exitCode"in e?e.exitCode:1)),process.exit(typeof e=="number"?e:1)}catch(e){console.error("Fatal error:",e),process.exit(1)}
|
|
1559
123
|
//# sourceMappingURL=vitest-runner.mjs.map
|