@cldmv/vitest-runner 1.0.3 → 1.2.0
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 +106 -61
- package/bin/vitest-runner.mjs +31 -20
- package/package.json +13 -5
- package/src/cli/args.mjs +38 -0
- package/src/cli/help.mjs +8 -2
- package/src/core/discover.mjs +1 -1
- package/src/core/progress.mjs +2 -2
- package/src/core/report.mjs +58 -19
- package/src/runner.mjs +272 -67
- package/types/src/cli/args.d.mts +24 -0
- package/types/src/core/report.d.mts +25 -2
- package/types/src/runner.d.mts +26 -2
package/README.md
CHANGED
|
@@ -45,16 +45,22 @@ vitest-runner [OPTIONS] [PATTERNS...]
|
|
|
45
45
|
|
|
46
46
|
### Runner flags
|
|
47
47
|
|
|
48
|
-
| Flag
|
|
49
|
-
|
|
50
|
-
| `--test-list <file>`
|
|
51
|
-
| `--file-pattern <regex>`
|
|
52
|
-
| `--workers <n>`
|
|
53
|
-
| `--solo-pattern <pat>`
|
|
54
|
-
| `--no-error-details`
|
|
55
|
-
| `--coverage-quiet`
|
|
56
|
-
| `--log-file <path>`
|
|
57
|
-
| `--
|
|
48
|
+
| Flag | Description |
|
|
49
|
+
| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
|
50
|
+
| `--test-list <file>` | Run only the files listed in a JSON array file instead of scanning |
|
|
51
|
+
| `--file-pattern <regex>` | Override the file discovery regex (default: `\.test\.vitest\.(?:js\|mjs\|cjs)$`) |
|
|
52
|
+
| `--workers <n>` | Number of parallel workers (default: `4` or `VITEST_WORKERS`) |
|
|
53
|
+
| `--solo-pattern <pat>` | Run files matching this path substring solo (one at a time) before the worker pool; repeatable |
|
|
54
|
+
| `--no-error-details` | Hide inline error blocks — show only counts in the summary |
|
|
55
|
+
| `--coverage-quiet` | Implies `--coverage`; suppress per-file output and show only a live progress bar and final summaries |
|
|
56
|
+
| `--log-file <path>` | Write a clean (ANSI-stripped) copy of all output to this file. Defaults to `coverage/coverage-run.log` when `--coverage-quiet` is active |
|
|
57
|
+
| `--suppress-file-output` | Suppress per-file runner output blocks in any mode |
|
|
58
|
+
| `--suppress-passing-files` | Hide the `PASSED TEST FILES` section in the final summary |
|
|
59
|
+
| `--no-top-summary` | Hide `TOP MEMORY USERS` and `TOP DURATION` summary sections |
|
|
60
|
+
| `--json` | Print a JSON run report (no runner text output) |
|
|
61
|
+
| `--blobs-dir <path>` | Directory for per-file coverage blobs (default: `.vitest-coverage-blobs`, relative to `cwd`) |
|
|
62
|
+
| `--no-merge-reports` | Produce the coverage blobs but skip the merge and summary, leaving them in `--blobs-dir` for an external merge step |
|
|
63
|
+
| `--help`, `-h` | Print this help and exit |
|
|
58
64
|
|
|
59
65
|
### Test patterns
|
|
60
66
|
|
|
@@ -91,10 +97,10 @@ vitest-runner --bail
|
|
|
91
97
|
|
|
92
98
|
### Environment variables
|
|
93
99
|
|
|
94
|
-
| Variable
|
|
95
|
-
|
|
96
|
-
| `VITEST_HEAP_MB` |
|
|
97
|
-
| `VITEST_WORKERS` | `4`
|
|
100
|
+
| Variable | Default | Description |
|
|
101
|
+
| ---------------- | -------- | ------------------------------------------------------------------------------- |
|
|
102
|
+
| `VITEST_HEAP_MB` | _(none)_ | `--max-old-space-size` ceiling passed to every child process |
|
|
103
|
+
| `VITEST_WORKERS` | `4` | Maximum parallel worker slots in the non-solo phase (overridden by `--workers`) |
|
|
98
104
|
|
|
99
105
|
### Examples
|
|
100
106
|
|
|
@@ -128,6 +134,12 @@ VITEST_HEAP_MB=8192 vitest-runner --workers 2 src/tests/heavy
|
|
|
128
134
|
|
|
129
135
|
# Suppress error details in the summary
|
|
130
136
|
vitest-runner --no-error-details
|
|
137
|
+
|
|
138
|
+
# JSON output for automation
|
|
139
|
+
vitest-runner --json
|
|
140
|
+
|
|
141
|
+
# JSON output without top summary arrays
|
|
142
|
+
vitest-runner --json --no-top-summary
|
|
131
143
|
```
|
|
132
144
|
|
|
133
145
|
---
|
|
@@ -135,21 +147,21 @@ vitest-runner --no-error-details
|
|
|
135
147
|
## Programmatic API
|
|
136
148
|
|
|
137
149
|
```js
|
|
138
|
-
import { run } from
|
|
150
|
+
import { run } from "vitest-runner";
|
|
139
151
|
|
|
140
152
|
// CommonJS
|
|
141
|
-
const { run } = await require(
|
|
153
|
+
const { run } = await require("vitest-runner");
|
|
142
154
|
```
|
|
143
155
|
|
|
144
|
-
### `run(options)` → `Promise<number>`
|
|
156
|
+
### `run(options)` → `Promise<number | object>`
|
|
145
157
|
|
|
146
|
-
Runs the test suite and resolves with an exit code (`0` = all passed, `1` = any failure).
|
|
158
|
+
Runs the test suite and resolves with an exit code (`0` = all passed, `1` = any failure) by default. When `json: true` is passed, it returns a structured JSON report object (including `exitCode`) instead of printing runner text output.
|
|
147
159
|
|
|
148
160
|
```js
|
|
149
|
-
import { run } from
|
|
161
|
+
import { run } from "vitest-runner";
|
|
150
162
|
|
|
151
163
|
const code = await run({
|
|
152
|
-
|
|
164
|
+
testDir: "src/tests"
|
|
153
165
|
});
|
|
154
166
|
|
|
155
167
|
process.exit(code);
|
|
@@ -157,29 +169,38 @@ process.exit(code);
|
|
|
157
169
|
|
|
158
170
|
#### Options
|
|
159
171
|
|
|
160
|
-
| Option
|
|
161
|
-
|
|
162
|
-
| `cwd`
|
|
163
|
-
| `testDir`
|
|
164
|
-
| `vitestConfig`
|
|
165
|
-
| `testPatterns`
|
|
166
|
-
| `testListFile`
|
|
167
|
-
| `testFilePattern`
|
|
168
|
-
| `vitestArgs`
|
|
169
|
-
| `showErrorDetails`
|
|
170
|
-
| `coverageQuiet`
|
|
171
|
-
| `
|
|
172
|
-
| `
|
|
173
|
-
| `
|
|
174
|
-
| `
|
|
175
|
-
| `
|
|
176
|
-
| `
|
|
177
|
-
| `
|
|
172
|
+
| Option | Type | Default | Description |
|
|
173
|
+
| ---------------------- | ----------------------- | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
174
|
+
| `cwd` | `string` | `process.cwd()` | Absolute project root directory |
|
|
175
|
+
| `testDir` | `string` | `cwd` | Directory (absolute or relative to `cwd`) to scan for `*.test.vitest.{js,mjs}` files |
|
|
176
|
+
| `vitestConfig` | `string` | auto-detect | Explicit vitest config path; when omitted the runner walks standard config names (`vitest.config.ts`, `vite.config.ts`, etc.) relative to `cwd` |
|
|
177
|
+
| `testPatterns` | `string[]` | `[]` | File / folder patterns to filter — empty means all files in `testDir` |
|
|
178
|
+
| `testListFile` | `string` | `undefined` | Path to a JSON array of test file paths; when set, scanning is skipped entirely |
|
|
179
|
+
| `testFilePattern` | `RegExp` | `DEFAULT_TEST_FILE_PATTERN` | Regex matched against file names during discovery (`*.test.vitest.{js,mjs,cjs}` by default) |
|
|
180
|
+
| `vitestArgs` | `string[]` | `[]` | Extra CLI args forwarded verbatim to every vitest invocation |
|
|
181
|
+
| `showErrorDetails` | `boolean` | `true` | Print inline error blocks under each failed file in the summary |
|
|
182
|
+
| `coverageQuiet` | `boolean` | `false` | Suppress per-file output; show only the progress bar and final summaries |
|
|
183
|
+
| `suppressFileOutput` | `boolean` | `false` | Suppress per-file runner output blocks in all modes |
|
|
184
|
+
| `suppressPassingFiles` | `boolean` | `false` | Hide passed-file rows in the final summary |
|
|
185
|
+
| `topSummary` | `boolean` | `true` | Show or hide top memory/duration summary sections (and JSON arrays) |
|
|
186
|
+
| `json` | `boolean` | `false` | Return a JSON report object instead of printing text output |
|
|
187
|
+
| `workers` | `number` | `4` | Maximum parallel worker slots (overrides `VITEST_WORKERS`) |
|
|
188
|
+
| `worstCoverageCount` | `number` | `10` | Rows in the worst-coverage table after a coverage run (`0` disables it) |
|
|
189
|
+
| `blobsDir` | `string` | `<cwd>/.vitest-coverage-blobs` | Directory for per-file coverage blobs. Relative paths resolve against `cwd`. Always cleared at the start of a coverage run |
|
|
190
|
+
| `mergeReports` | `boolean` | `true` | When `true`, blobs are merged via `vitest --mergeReports`, the coverage summary is printed, and `blobsDir` is deleted. When `false`, the run stops after producing blobs — no merge, no summary — and `blobsDir` is left populated for an external merge |
|
|
191
|
+
| `maxOldSpaceMb` | `number` | `undefined` | Global `--max-old-space-size` ceiling in MB (overrides `VITEST_HEAP_MB`) |
|
|
192
|
+
| `earlyRunPatterns` | `string[]` | `[]` | Path substrings — matching files run solo (one at a time) before the parallel worker pool starts |
|
|
193
|
+
| `perFileHeapOverrides` | `PerFileHeapOverride[]` | `[]` | Per-file minimum heap ceilings; the maximum of this and `maxOldSpaceMb` wins |
|
|
194
|
+
| `conditions` | `string[]` | `[]` | Additional `--conditions` Node flags forwarded to children |
|
|
195
|
+
| `nodeEnv` | `string` | `'development'` | Value written to `NODE_ENV` in child processes |
|
|
178
196
|
|
|
179
197
|
#### `PerFileHeapOverride`
|
|
180
198
|
|
|
181
199
|
```ts
|
|
182
|
-
{
|
|
200
|
+
{
|
|
201
|
+
pattern: string;
|
|
202
|
+
heapMb: number;
|
|
203
|
+
}
|
|
183
204
|
```
|
|
184
205
|
|
|
185
206
|
`pattern` is a substring matched against the normalised (forward-slash) file path. The first match wins and is compared against the global `maxOldSpaceMb`; the larger value is used.
|
|
@@ -188,34 +209,40 @@ process.exit(code);
|
|
|
188
209
|
|
|
189
210
|
```js
|
|
190
211
|
// Run all tests under src/tests/ (cwd defaults to process.cwd())
|
|
191
|
-
await run({ testDir:
|
|
212
|
+
await run({ testDir: "src/tests" });
|
|
192
213
|
|
|
193
214
|
// Run only the config and metadata suites
|
|
194
215
|
await run({
|
|
195
|
-
|
|
196
|
-
|
|
216
|
+
testDir: "src/tests",
|
|
217
|
+
testPatterns: ["src/tests/config", "src/tests/metadata"]
|
|
197
218
|
});
|
|
198
219
|
|
|
199
220
|
// Coverage run (OOM-safe blob + merge mode)
|
|
200
221
|
await run({
|
|
201
|
-
|
|
202
|
-
|
|
222
|
+
testDir: "src/tests",
|
|
223
|
+
vitestArgs: ["--coverage"]
|
|
203
224
|
});
|
|
204
225
|
|
|
205
226
|
// Quiet coverage with live progress bar
|
|
206
227
|
await run({
|
|
207
|
-
|
|
208
|
-
|
|
228
|
+
testDir: "src/tests",
|
|
229
|
+
coverageQuiet: true
|
|
209
230
|
});
|
|
210
231
|
|
|
232
|
+
// Machine-readable output (no text logs)
|
|
233
|
+
const report = await run({
|
|
234
|
+
testDir: "src/tests",
|
|
235
|
+
json: true
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
console.log(report.exitCode);
|
|
239
|
+
|
|
211
240
|
// Give heap-heavy files a larger ceiling while keeping the global limit lower
|
|
212
241
|
await run({
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
{ pattern: 'listener-cleanup/', heapMb: 6144 },
|
|
218
|
-
],
|
|
242
|
+
testDir: "src/tests",
|
|
243
|
+
maxOldSpaceMb: 2048,
|
|
244
|
+
earlyRunPatterns: ["listener-cleanup/"],
|
|
245
|
+
perFileHeapOverrides: [{ pattern: "listener-cleanup/", heapMb: 6144 }]
|
|
219
246
|
});
|
|
220
247
|
```
|
|
221
248
|
|
|
@@ -235,7 +262,29 @@ This avoids the OOM crash that occurs when a single vitest process holds coverag
|
|
|
235
262
|
|
|
236
263
|
`--coverage-quiet` / `coverageQuiet: true` suppresses all per-file output and renders a live progress bar instead. On completion it prints the coverage table and any failures verbosely. When running in this mode, output is also mirrored to `coverage/coverage-run.log` (CLI only) with ANSI colour codes stripped so the file is human-readable in any editor.
|
|
237
264
|
|
|
238
|
-
The log file path can be overridden with `--log-file <path>`. Passing `--log-file`
|
|
265
|
+
The log file path can be overridden with `--log-file <path>`. Passing `--log-file` by itself only enables log mirroring (it does not enable coverage mode).
|
|
266
|
+
|
|
267
|
+
### Producing blobs for an external merge
|
|
268
|
+
|
|
269
|
+
Set `mergeReports: false` (CLI: `--no-merge-reports`) to stop the run after the per-file blobs are written. The internal `vitest --mergeReports` call and the coverage summary are skipped, and `blobsDir` is left intact instead of being deleted. The blobs directory is still cleared at the **start** of each run, so it only ever contains the current run's output.
|
|
270
|
+
|
|
271
|
+
This is useful when a second coverage blob set (for example, a browser-mode run with a different coverage transform) needs to be merged together with the node-mode blobs. Point both runs at known directories with `blobsDir`, then merge them in one external `vitest --mergeReports` step:
|
|
272
|
+
|
|
273
|
+
```js
|
|
274
|
+
// Node-mode blobs, no internal merge
|
|
275
|
+
await run({
|
|
276
|
+
testDir: "src/tests",
|
|
277
|
+
vitestArgs: ["--coverage"],
|
|
278
|
+
blobsDir: ".coverage-blobs/node",
|
|
279
|
+
mergeReports: false
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// (separately produce browser-mode blobs into .coverage-blobs/browser)
|
|
283
|
+
// then merge both blob sets in a single external step:
|
|
284
|
+
// vitest --mergeReports .coverage-blobs --coverage
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
The exit code still reflects test pass/fail; there is just no coverage-merge result to fold in.
|
|
239
288
|
|
|
240
289
|
---
|
|
241
290
|
|
|
@@ -244,11 +293,7 @@ The log file path can be overridden with `--log-file <path>`. Passing `--log-fil
|
|
|
244
293
|
A test list file is a plain JSON array of test file paths (relative to `cwd`):
|
|
245
294
|
|
|
246
295
|
```json
|
|
247
|
-
[
|
|
248
|
-
"src/tests/auth/login.test.vitest.mjs",
|
|
249
|
-
"src/tests/auth/register.test.vitest.mjs",
|
|
250
|
-
"src/tests/config/defaults.test.vitest.mjs"
|
|
251
|
-
]
|
|
296
|
+
["src/tests/auth/login.test.vitest.mjs", "src/tests/auth/register.test.vitest.mjs", "src/tests/config/defaults.test.vitest.mjs"]
|
|
252
297
|
```
|
|
253
298
|
|
|
254
299
|
Pass `--test-list <file>` (CLI) or `testListFile: 'path/to/list.json'` (API) to run exactly those files instead of scanning `testDir`.
|
|
@@ -259,7 +304,7 @@ Pass `--test-list <file>` (CLI) or `testListFile: 'path/to/list.json'` (API) to
|
|
|
259
304
|
|
|
260
305
|
By default, the runner discovers files matching:
|
|
261
306
|
|
|
262
|
-
```
|
|
307
|
+
```text
|
|
263
308
|
*.test.vitest.js
|
|
264
309
|
*.test.vitest.mjs
|
|
265
310
|
*.test.vitest.cjs
|
|
@@ -275,14 +320,14 @@ vitest-runner --file-pattern '\.spec\.ts$'
|
|
|
275
320
|
```
|
|
276
321
|
|
|
277
322
|
```js
|
|
278
|
-
await run({ cwd, testDir:
|
|
323
|
+
await run({ cwd, testDir: "src", testFilePattern: /\.spec\.ts$/i });
|
|
279
324
|
```
|
|
280
325
|
|
|
281
326
|
---
|
|
282
327
|
|
|
283
328
|
## Source layout
|
|
284
329
|
|
|
285
|
-
```
|
|
330
|
+
```text
|
|
286
331
|
index.mjs ← ESM entry (re-exports src/runner.mjs)
|
|
287
332
|
index.cjs ← CJS shim (dynamic import of index.mjs)
|
|
288
333
|
bin/
|
package/bin/vitest-runner.mjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* @fileoverview CLI entry point for the vitest-runner binary.
|
|
4
4
|
* @module vitest-runner/bin/vitest-runner
|
|
5
5
|
*
|
|
6
|
-
* Mirrors its own output to a log file when --coverage-quiet is set, then
|
|
6
|
+
* Mirrors its own output to a log file when --coverage-quiet or --log-file is set, then
|
|
7
7
|
* delegates all logic to `src/runner.mjs` via the programmatic `run()` API.
|
|
8
8
|
*/
|
|
9
9
|
|
|
@@ -23,7 +23,7 @@ if (args.help) {
|
|
|
23
23
|
|
|
24
24
|
// Mirror all output (excluding progress bar lines) to a log file
|
|
25
25
|
// when running in --coverage-quiet mode (or when --log-file is set).
|
|
26
|
-
if (args.coverageQuiet || args.logFile) {
|
|
26
|
+
if ((args.coverageQuiet || args.logFile) && !args.json) {
|
|
27
27
|
const cwd = process.cwd();
|
|
28
28
|
const resolvedLogFile = args.logFile
|
|
29
29
|
? path.isAbsolute(args.logFile)
|
|
@@ -62,25 +62,36 @@ if (args.coverageQuiet || args.logFile) {
|
|
|
62
62
|
const cwd = process.cwd();
|
|
63
63
|
|
|
64
64
|
const vitestArgs = [...args.vitestPassthroughArgs];
|
|
65
|
-
if (
|
|
65
|
+
if (args.coverageQuiet && !vitestArgs.some((a) => a === "--coverage" || a.startsWith("--coverage."))) {
|
|
66
66
|
vitestArgs.unshift("--coverage");
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
69
|
+
try {
|
|
70
|
+
const runResult = await run({
|
|
71
|
+
cwd,
|
|
72
|
+
testPatterns: args.testPatterns,
|
|
73
|
+
testListFile: args.testListFile,
|
|
74
|
+
testFilePattern: args.testFilePattern,
|
|
75
|
+
vitestArgs,
|
|
76
|
+
showErrorDetails: args.showErrorDetails,
|
|
77
|
+
coverageQuiet: args.coverageQuiet,
|
|
78
|
+
suppressFileOutput: args.suppressFileOutput,
|
|
79
|
+
suppressPassingFiles: args.suppressPassingFiles,
|
|
80
|
+
topSummary: args.topSummary,
|
|
81
|
+
json: args.json,
|
|
82
|
+
mergeReports: args.mergeReports,
|
|
83
|
+
...(args.blobsDir !== undefined && { blobsDir: args.blobsDir }),
|
|
84
|
+
...(args.workers !== undefined && { workers: args.workers }),
|
|
85
|
+
...(args.soloPatterns.length > 0 && { earlyRunPatterns: args.soloPatterns })
|
|
86
86
|
});
|
|
87
|
+
|
|
88
|
+
if (args.json) {
|
|
89
|
+
process.stdout.write(`${JSON.stringify(runResult, null, 2)}\n`);
|
|
90
|
+
process.exit(typeof runResult === "object" && runResult !== null && "exitCode" in runResult ? runResult.exitCode : 1);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
process.exit(typeof runResult === "number" ? runResult : 1);
|
|
94
|
+
} catch (err) {
|
|
95
|
+
console.error("Fatal error:", err);
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cldmv/vitest-runner",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Sequential Vitest runner to avoid OOM issues with large test suites",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.cjs",
|
|
@@ -24,7 +24,9 @@
|
|
|
24
24
|
"bin/"
|
|
25
25
|
],
|
|
26
26
|
"scripts": {
|
|
27
|
-
"lint": "eslint
|
|
27
|
+
"lint": "eslint --config .configs/eslint.config.mjs .",
|
|
28
|
+
"format": "prettier --write . --config .configs/.prettierrc",
|
|
29
|
+
"format:check": "prettier --check . --config .configs/.prettierrc",
|
|
28
30
|
"test": "vitest run --config .configs/vitest.config.mjs",
|
|
29
31
|
"test:watch": "vitest --config .configs/vitest.config.mjs",
|
|
30
32
|
"test:coverage": "vitest run --coverage --config .configs/vitest.config.mjs",
|
|
@@ -39,10 +41,16 @@
|
|
|
39
41
|
"oom"
|
|
40
42
|
],
|
|
41
43
|
"devDependencies": {
|
|
44
|
+
"@eslint/js": "^10.0.1",
|
|
45
|
+
"@eslint/json": "^2.0.0",
|
|
46
|
+
"@eslint/markdown": "^8.0.2",
|
|
42
47
|
"@types/node": "^25.3.0",
|
|
43
|
-
"@vitest/coverage-v8": "^4.
|
|
48
|
+
"@vitest/coverage-v8": "^4.1.9",
|
|
49
|
+
"eslint": "^10.5.0",
|
|
50
|
+
"globals": "^17.6.0",
|
|
51
|
+
"prettier": "^3.8.4",
|
|
44
52
|
"typescript": "^5.9.3",
|
|
45
|
-
"vitest": "^4.
|
|
53
|
+
"vitest": "^4.1.9"
|
|
46
54
|
},
|
|
47
55
|
"dependencies": {
|
|
48
56
|
"chalk": "^5.4.1"
|
|
@@ -51,7 +59,7 @@
|
|
|
51
59
|
"vitest": ">=1.0.0"
|
|
52
60
|
},
|
|
53
61
|
"engines": {
|
|
54
|
-
"node": ">=
|
|
62
|
+
"node": ">=20.19.0"
|
|
55
63
|
},
|
|
56
64
|
"publishConfig": {
|
|
57
65
|
"access": "public"
|
package/src/cli/args.mjs
CHANGED
|
@@ -9,6 +9,12 @@
|
|
|
9
9
|
* @property {boolean} showErrorDetails - `false` when `--no-error-details` was passed.
|
|
10
10
|
* @property {boolean} coverageQuiet - Whether `--coverage-quiet` was passed.
|
|
11
11
|
* @property {string|undefined} logFile - Path for the coverage run log (`--log-file`); defaults to `coverage/coverage-run.log`.
|
|
12
|
+
* @property {boolean} suppressFileOutput - Suppress per-file runner output blocks (`--suppress-file-output`).
|
|
13
|
+
* @property {boolean} suppressPassingFiles - Suppress the passed-files section in the final summary (`--suppress-passing-files`).
|
|
14
|
+
* @property {boolean} topSummary - Show top-summary sections for memory and duration (`true` by default, disabled by `--no-top-summary`).
|
|
15
|
+
* @property {boolean} json - Emit a JSON run report instead of text output (`--json`).
|
|
16
|
+
* @property {string|undefined} blobsDir - Directory for per-file coverage blobs (`--blobs-dir`); defaults to `<cwd>/.vitest-coverage-blobs`.
|
|
17
|
+
* @property {boolean} mergeReports - `false` when `--no-merge-reports` was passed; leaves blobs in `blobsDir` without merging.
|
|
12
18
|
* @property {boolean} help - Whether `--help` / `-h` was passed.
|
|
13
19
|
* @property {number|undefined} workers - Worker count from `--workers <n>`, or undefined.
|
|
14
20
|
* @property {string[]} soloPatterns - Path substrings from `--solo-pattern <pattern>` (repeatable).
|
|
@@ -23,6 +29,12 @@ const RUNNER_FLAGS = new Set([
|
|
|
23
29
|
"--no-error-details",
|
|
24
30
|
"--coverage-quiet",
|
|
25
31
|
"--log-file",
|
|
32
|
+
"--suppress-file-output",
|
|
33
|
+
"--suppress-passing-files",
|
|
34
|
+
"--no-top-summary",
|
|
35
|
+
"--json",
|
|
36
|
+
"--blobs-dir",
|
|
37
|
+
"--no-merge-reports",
|
|
26
38
|
"--workers",
|
|
27
39
|
"--solo-pattern",
|
|
28
40
|
"--file-pattern",
|
|
@@ -51,6 +63,12 @@ export function parseArguments(args) {
|
|
|
51
63
|
let showErrorDetails = true;
|
|
52
64
|
let coverageQuiet = false;
|
|
53
65
|
let logFile;
|
|
66
|
+
let suppressFileOutput = false;
|
|
67
|
+
let suppressPassingFiles = false;
|
|
68
|
+
let topSummary = true;
|
|
69
|
+
let json = false;
|
|
70
|
+
let blobsDir;
|
|
71
|
+
let mergeReports = true;
|
|
54
72
|
let workers;
|
|
55
73
|
let help = false;
|
|
56
74
|
let testFilePattern;
|
|
@@ -69,6 +87,20 @@ export function parseArguments(args) {
|
|
|
69
87
|
logFile = args[++i];
|
|
70
88
|
} else if (arg.startsWith("--log-file=")) {
|
|
71
89
|
logFile = arg.slice("--log-file=".length);
|
|
90
|
+
} else if (arg === "--suppress-file-output") {
|
|
91
|
+
suppressFileOutput = true;
|
|
92
|
+
} else if (arg === "--suppress-passing-files") {
|
|
93
|
+
suppressPassingFiles = true;
|
|
94
|
+
} else if (arg === "--no-top-summary") {
|
|
95
|
+
topSummary = false;
|
|
96
|
+
} else if (arg === "--json") {
|
|
97
|
+
json = true;
|
|
98
|
+
} else if (arg === "--blobs-dir") {
|
|
99
|
+
blobsDir = args[++i];
|
|
100
|
+
} else if (arg.startsWith("--blobs-dir=")) {
|
|
101
|
+
blobsDir = arg.slice("--blobs-dir=".length);
|
|
102
|
+
} else if (arg === "--no-merge-reports") {
|
|
103
|
+
mergeReports = false;
|
|
72
104
|
} else if (arg === "--workers") {
|
|
73
105
|
workers = parseInt(args[++i], 10);
|
|
74
106
|
} else if (arg.startsWith("--workers=")) {
|
|
@@ -100,6 +132,12 @@ export function parseArguments(args) {
|
|
|
100
132
|
showErrorDetails,
|
|
101
133
|
coverageQuiet,
|
|
102
134
|
logFile,
|
|
135
|
+
suppressFileOutput,
|
|
136
|
+
suppressPassingFiles,
|
|
137
|
+
topSummary,
|
|
138
|
+
json,
|
|
139
|
+
blobsDir,
|
|
140
|
+
mergeReports,
|
|
103
141
|
help,
|
|
104
142
|
workers,
|
|
105
143
|
soloPatterns,
|
package/src/cli/help.mjs
CHANGED
|
@@ -21,12 +21,18 @@ ${chalk.bold("USAGE:")}
|
|
|
21
21
|
|
|
22
22
|
${chalk.bold("SPECIAL FLAGS:")}
|
|
23
23
|
--test-list <file> Run only the files listed in a JSON array file
|
|
24
|
-
--file-pattern <regex> Override the file discovery regex (default:
|
|
24
|
+
--file-pattern <regex> Override the file discovery regex (default: \\.test\\.vitest\\.(?:js|mjs|cjs)$)
|
|
25
25
|
--workers <n> Number of parallel workers (default: 4 or VITEST_WORKERS)
|
|
26
26
|
--solo-pattern <pat> Run files matching this path substring solo first (repeatable)
|
|
27
27
|
--no-error-details Hide detailed error output (show only counts)
|
|
28
28
|
--coverage-quiet Implies --coverage; show progress bar + final summaries only
|
|
29
|
-
--log-file <path> Path for
|
|
29
|
+
--log-file <path> Path for mirrored runner output (default: coverage/coverage-run.log with --coverage-quiet)
|
|
30
|
+
--suppress-file-output Suppress per-file runner output blocks
|
|
31
|
+
--suppress-passing-files Hide the PASSED TEST FILES section in the final summary
|
|
32
|
+
--no-top-summary Hide TOP MEMORY USERS and TOP DURATION sections
|
|
33
|
+
--json Emit JSON summary output instead of text logs
|
|
34
|
+
--blobs-dir <path> Directory for per-file coverage blobs (default: .vitest-coverage-blobs)
|
|
35
|
+
--no-merge-reports Leave coverage blobs in --blobs-dir without merging (for an external merge)
|
|
30
36
|
--help, -h Show this help message
|
|
31
37
|
|
|
32
38
|
${chalk.bold("TEST PATTERNS:")}
|
package/src/core/discover.mjs
CHANGED
|
@@ -116,7 +116,7 @@ export async function discoverVitestFiles(opts) {
|
|
|
116
116
|
const content = await fs.readFile(resolvedListPath, "utf8");
|
|
117
117
|
testList = JSON.parse(content);
|
|
118
118
|
} catch (err) {
|
|
119
|
-
throw new Error(`Failed to read test list file "${resolvedListPath}": ${err.message}
|
|
119
|
+
throw new Error(`Failed to read test list file "${resolvedListPath}": ${err.message}`, { cause: err });
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
if (!Array.isArray(testList)) {
|
package/src/core/progress.mjs
CHANGED
|
@@ -157,8 +157,8 @@ export function createCoverageProgressTracker(total) {
|
|
|
157
157
|
export const noopProgressTracker = {
|
|
158
158
|
/** @returns {void} */
|
|
159
159
|
onStart() {},
|
|
160
|
-
/** @param {boolean}
|
|
161
|
-
onComplete(
|
|
160
|
+
/** @param {boolean} ___failedRun @returns {void} */
|
|
161
|
+
onComplete(___failedRun) {},
|
|
162
162
|
/** @returns {void} */
|
|
163
163
|
finish() {}
|
|
164
164
|
};
|
package/src/core/report.mjs
CHANGED
|
@@ -150,9 +150,18 @@ export function computeSummaryFromFinal(finalData) {
|
|
|
150
150
|
* @param {string} cwd - Project root (used to make absolute file paths relative).
|
|
151
151
|
* @param {string[]} extraCoverageArgs - Passthrough `--coverage.*` args (checked for `reportsDirectory`).
|
|
152
152
|
* @param {number} [worstCount=10] - Number of worst-coverage files to show (0 = skip table).
|
|
153
|
-
* @
|
|
153
|
+
* @param {{silent?: boolean}} [options] - Output controls.
|
|
154
|
+
* @returns {Promise<{
|
|
155
|
+
* coverageDir: string,
|
|
156
|
+
* total: object,
|
|
157
|
+
* worstFiles: Array<{file: string, lines: number, stmts: number, fns: number, branches: number}>,
|
|
158
|
+
* worstFilesShown: number,
|
|
159
|
+
* worstFilesTotal: number,
|
|
160
|
+
* summary: object
|
|
161
|
+
* }|null>}
|
|
154
162
|
*/
|
|
155
|
-
export async function printCoverageSummary(cwd, extraCoverageArgs, worstCount = 10) {
|
|
163
|
+
export async function printCoverageSummary(cwd, extraCoverageArgs, worstCount = 10, options = {}) {
|
|
164
|
+
const { silent = false } = options;
|
|
156
165
|
let coverageDir = path.resolve(cwd, "coverage");
|
|
157
166
|
const repoDirArg = extraCoverageArgs.find((a) => a.startsWith("--coverage.reportsDirectory="));
|
|
158
167
|
if (repoDirArg) {
|
|
@@ -170,8 +179,8 @@ export async function printCoverageSummary(cwd, extraCoverageArgs, worstCount =
|
|
|
170
179
|
const content = await fs.readFile(path.join(coverageDir, "coverage-final.json"), "utf8");
|
|
171
180
|
summary = computeSummaryFromFinal(JSON.parse(content));
|
|
172
181
|
} catch {
|
|
173
|
-
console.log(chalk.dim(" (no coverage JSON found — skipping summary)"));
|
|
174
|
-
return;
|
|
182
|
+
if (!silent) console.log(chalk.dim(" (no coverage JSON found — skipping summary)"));
|
|
183
|
+
return null;
|
|
175
184
|
}
|
|
176
185
|
}
|
|
177
186
|
|
|
@@ -188,29 +197,59 @@ export async function printCoverageSummary(cwd, extraCoverageArgs, worstCount =
|
|
|
188
197
|
}))
|
|
189
198
|
.filter((r) => Math.min(r.lines, r.stmts, r.fns, r.branches) < 100)
|
|
190
199
|
.sort((a, b) => Math.min(a.lines, a.stmts, a.fns, a.branches) - Math.min(b.lines, b.stmts, b.fns, b.branches));
|
|
200
|
+
const rowsToShow = fileRows.slice(0, worstCount);
|
|
191
201
|
|
|
192
|
-
|
|
193
|
-
|
|
202
|
+
if (!silent) {
|
|
203
|
+
console.log("\n" + chalk.bold("📉 WORST COVERAGE FILES (lowest metric)"));
|
|
204
|
+
console.log("-".repeat(80));
|
|
194
205
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
206
|
+
rowsToShow.forEach(({ file, lines, stmts, fns, branches }) => {
|
|
207
|
+
const worst = Math.min(lines, stmts, fns, branches);
|
|
208
|
+
const extras = chalk.dim(
|
|
209
|
+
`lines ${lines.toFixed(0)}% | stmts ${stmts.toFixed(0)}% | fns ${fns.toFixed(0)}% | branches ${branches.toFixed(0)}%`
|
|
210
|
+
);
|
|
211
|
+
console.log(` ${colourPct(chalk, worst)}% ${chalk.dim(file)} ${extras}`);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
if (fileRows.length > worstCount) {
|
|
215
|
+
console.log(chalk.dim(` ... and ${fileRows.length - worstCount} more files`));
|
|
216
|
+
}
|
|
217
|
+
}
|
|
203
218
|
|
|
204
|
-
|
|
205
|
-
|
|
219
|
+
const tl = total.lines?.pct ?? 0;
|
|
220
|
+
const ts = total.statements?.pct ?? 0;
|
|
221
|
+
const tf = total.functions?.pct ?? 0;
|
|
222
|
+
const tb = total.branches?.pct ?? 0;
|
|
223
|
+
if (!silent) {
|
|
224
|
+
console.log(
|
|
225
|
+
`\n ${chalk.bold("Coverage")} ${colourPct(chalk, tl)}% lines ${chalk.dim("|")} ${colourPct(chalk, ts)}% statements ${chalk.dim("|")} ${colourPct(chalk, tf)}% functions ${chalk.dim("|")} ${colourPct(chalk, tb)}% branches`
|
|
226
|
+
);
|
|
206
227
|
}
|
|
228
|
+
return {
|
|
229
|
+
coverageDir,
|
|
230
|
+
total,
|
|
231
|
+
worstFiles: rowsToShow,
|
|
232
|
+
worstFilesShown: rowsToShow.length,
|
|
233
|
+
worstFilesTotal: fileRows.length,
|
|
234
|
+
summary
|
|
235
|
+
};
|
|
207
236
|
}
|
|
208
237
|
|
|
209
238
|
const tl = total.lines?.pct ?? 0;
|
|
210
239
|
const ts = total.statements?.pct ?? 0;
|
|
211
240
|
const tf = total.functions?.pct ?? 0;
|
|
212
241
|
const tb = total.branches?.pct ?? 0;
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
242
|
+
if (!silent) {
|
|
243
|
+
console.log(
|
|
244
|
+
`\n ${chalk.bold("Coverage")} ${colourPct(chalk, tl)}% lines ${chalk.dim("|")} ${colourPct(chalk, ts)}% statements ${chalk.dim("|")} ${colourPct(chalk, tf)}% functions ${chalk.dim("|")} ${colourPct(chalk, tb)}% branches`
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
return {
|
|
248
|
+
coverageDir,
|
|
249
|
+
total,
|
|
250
|
+
worstFiles: [],
|
|
251
|
+
worstFilesShown: 0,
|
|
252
|
+
worstFilesTotal: 0,
|
|
253
|
+
summary
|
|
254
|
+
};
|
|
216
255
|
}
|
package/src/runner.mjs
CHANGED
|
@@ -35,8 +35,6 @@ import { runSingleFile, runMergeReports } from "./core/spawn.mjs";
|
|
|
35
35
|
import { deduplicateErrors } from "./core/parse.mjs";
|
|
36
36
|
import { createCoverageProgressTracker, noopProgressTracker } from "./core/progress.mjs";
|
|
37
37
|
import { printQuietCoverageFailureDetails, printMergeOutput, printCoverageSummary } from "./core/report.mjs";
|
|
38
|
-
import { formatDuration } from "./utils/duration.mjs";
|
|
39
|
-
import { colourPct } from "./utils/ansi.mjs";
|
|
40
38
|
|
|
41
39
|
/**
|
|
42
40
|
* @typedef {Object} PerFileHeapOverride
|
|
@@ -55,8 +53,14 @@ import { colourPct } from "./utils/ansi.mjs";
|
|
|
55
53
|
* @property {string[]} [vitestArgs=[]] - Extra CLI args forwarded verbatim to every vitest invocation.
|
|
56
54
|
* @property {boolean} [showErrorDetails=true] - Print inline error blocks under each failed file.
|
|
57
55
|
* @property {boolean} [coverageQuiet=false] - Suppress per-file output; show only progress bar + summaries.
|
|
56
|
+
* @property {boolean} [suppressFileOutput=false] - Suppress per-file runner output blocks in all modes.
|
|
57
|
+
* @property {boolean} [suppressPassingFiles=false] - Hide passed-file rows in the final summary.
|
|
58
|
+
* @property {boolean} [topSummary=true] - Show top memory and duration summary sections.
|
|
59
|
+
* @property {boolean} [json=false] - Return a JSON run report instead of printing text output.
|
|
58
60
|
* @property {number} [workers=4] - Maximum number of parallel worker slots.
|
|
59
61
|
* @property {number} [worstCoverageCount=10] - Rows in the worst-coverage table (0 = disable).
|
|
62
|
+
* @property {string} [blobsDir] - Directory for per-file coverage blobs (default `<cwd>/.vitest-coverage-blobs`). Relative paths resolve against `cwd`. Always cleared at the start of a coverage run.
|
|
63
|
+
* @property {boolean} [mergeReports=true] - When `true`, blobs are merged via `vitest --mergeReports`, the coverage summary is printed, and `blobsDir` is deleted at the end. When `false`, the run stops after producing blobs: no merge, no summary, and `blobsDir` is left populated for an external merge step.
|
|
60
64
|
* @property {number} [maxOldSpaceMb] - Global `--max-old-space-size` ceiling; per-file overrides may raise it.
|
|
61
65
|
* @property {string[]} [earlyRunPatterns=[]] - Path substrings — matching files run solo before the worker pool.
|
|
62
66
|
* @property {PerFileHeapOverride[]} [perFileHeapOverrides=[]] - Per-file minimum heap overrides.
|
|
@@ -96,7 +100,7 @@ function getHeapForFile(filePath, globalMaxMb, overrides) {
|
|
|
96
100
|
* pool for the non-solo phase) and return an exit code.
|
|
97
101
|
*
|
|
98
102
|
* @param {RunOptions} opts
|
|
99
|
-
* @returns {Promise<number>} `0`
|
|
103
|
+
* @returns {Promise<number|object>} `0`/`1` by default; JSON report object when `opts.json` is true.
|
|
100
104
|
*/
|
|
101
105
|
export async function run(opts) {
|
|
102
106
|
const {
|
|
@@ -108,8 +112,14 @@ export async function run(opts) {
|
|
|
108
112
|
vitestArgs: rawVitestArgs = [],
|
|
109
113
|
showErrorDetails = true,
|
|
110
114
|
coverageQuiet = false,
|
|
115
|
+
suppressFileOutput = false,
|
|
116
|
+
suppressPassingFiles = false,
|
|
117
|
+
topSummary = true,
|
|
118
|
+
json = false,
|
|
111
119
|
workers = parseInt(process.env.VITEST_WORKERS ?? "4", 10),
|
|
112
120
|
worstCoverageCount = 10,
|
|
121
|
+
blobsDir: blobsDirOpt,
|
|
122
|
+
mergeReports = true,
|
|
113
123
|
earlyRunPatterns = [],
|
|
114
124
|
perFileHeapOverrides = [],
|
|
115
125
|
conditions = [],
|
|
@@ -119,6 +129,7 @@ export async function run(opts) {
|
|
|
119
129
|
} = opts;
|
|
120
130
|
|
|
121
131
|
const maxOldSpaceMb = opts.maxOldSpaceMb ?? (process.env.VITEST_HEAP_MB ? parseInt(process.env.VITEST_HEAP_MB, 10) : undefined);
|
|
132
|
+
const emitTextOutput = !json;
|
|
122
133
|
|
|
123
134
|
// Resolve vitest binary and config
|
|
124
135
|
const vitestBin = resolveBin(cwd, "vitest", "vitest");
|
|
@@ -138,7 +149,7 @@ export async function run(opts) {
|
|
|
138
149
|
|
|
139
150
|
// ─── COVERAGE MODE ───────────────────────────────────────────────────────────
|
|
140
151
|
if (hasCoverage) {
|
|
141
|
-
const blobsDir = path.resolve(cwd, ".vitest-coverage-blobs");
|
|
152
|
+
const blobsDir = path.resolve(cwd, blobsDirOpt ?? ".vitest-coverage-blobs");
|
|
142
153
|
// Temp coverage dirs live OUTSIDE blobsDir — vitest --mergeReports errors on
|
|
143
154
|
// any non-blob entry found inside the blobs directory.
|
|
144
155
|
const coverageTmpBase = path.resolve(cwd, ".vitest-coverage-tmp");
|
|
@@ -149,9 +160,28 @@ export async function run(opts) {
|
|
|
149
160
|
const allTestFiles = await discoverVitestFiles({ cwd, testDir, testPatterns, testListFile, testFilePattern, earlyRunPatterns });
|
|
150
161
|
|
|
151
162
|
if (allTestFiles.length === 0) {
|
|
152
|
-
|
|
153
|
-
testPatterns.length > 0 ? `❌ No Vitest test files found matching: ${testPatterns.join(", ")}` : "❌ No Vitest test files found"
|
|
154
|
-
);
|
|
163
|
+
const noTestsMessage =
|
|
164
|
+
testPatterns.length > 0 ? `❌ No Vitest test files found matching: ${testPatterns.join(", ")}` : "❌ No Vitest test files found";
|
|
165
|
+
if (emitTextOutput) console.log(noTestsMessage);
|
|
166
|
+
if (json) {
|
|
167
|
+
return {
|
|
168
|
+
exitCode: 1,
|
|
169
|
+
mode: "coverage",
|
|
170
|
+
message: noTestsMessage,
|
|
171
|
+
options: {
|
|
172
|
+
cwd,
|
|
173
|
+
testDir,
|
|
174
|
+
testPatterns,
|
|
175
|
+
testListFile,
|
|
176
|
+
workers,
|
|
177
|
+
coverageQuiet,
|
|
178
|
+
suppressFileOutput,
|
|
179
|
+
suppressPassingFiles,
|
|
180
|
+
topSummary,
|
|
181
|
+
vitestArgs
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
}
|
|
155
185
|
return 1;
|
|
156
186
|
}
|
|
157
187
|
|
|
@@ -161,15 +191,17 @@ export async function run(opts) {
|
|
|
161
191
|
|
|
162
192
|
const soloFiles = allTestFiles.filter((f) => earlyRunPatterns.some((p) => f.replace(/\\/g, "/").includes(p)));
|
|
163
193
|
const parallelFiles = allTestFiles.filter((f) => !earlyRunPatterns.some((p) => f.replace(/\\/g, "/").includes(p)));
|
|
194
|
+
const suppressPerFileCoverageOutput = coverageQuiet || suppressFileOutput || json;
|
|
195
|
+
const showCoverageProgress = coverageQuiet && !json;
|
|
164
196
|
|
|
165
|
-
if (!coverageQuiet) {
|
|
197
|
+
if (!coverageQuiet && !suppressFileOutput && emitTextOutput) {
|
|
166
198
|
console.log(`\n🧪 Running ${allTestFiles.length} test files for coverage (blob + merge mode)`);
|
|
167
199
|
console.log(`⚙️ Workers: ${workers} (${soloFiles.length} solo first, then parallel)`);
|
|
168
200
|
if (maxOldSpaceMb) console.log(`🧠 Heap limit: ${maxOldSpaceMb} MB`);
|
|
169
201
|
console.log("");
|
|
170
202
|
}
|
|
171
203
|
|
|
172
|
-
const progress =
|
|
204
|
+
const progress = showCoverageProgress ? createCoverageProgressTracker(allTestFiles.length) : noopProgressTracker;
|
|
173
205
|
const coverageResults = [];
|
|
174
206
|
let blobIndex = 0;
|
|
175
207
|
|
|
@@ -182,7 +214,7 @@ export async function run(opts) {
|
|
|
182
214
|
const blobPath = path.join(blobsDir, `run-${blobIndex}.blob`);
|
|
183
215
|
blobIndex++;
|
|
184
216
|
|
|
185
|
-
if (!
|
|
217
|
+
if (!suppressPerFileCoverageOutput && emitTextOutput) {
|
|
186
218
|
console.log(`\n${"=".repeat(80)}`);
|
|
187
219
|
console.log(`▶️ ${filePath}`);
|
|
188
220
|
console.log("=".repeat(80));
|
|
@@ -204,13 +236,13 @@ export async function run(opts) {
|
|
|
204
236
|
...spawnBase,
|
|
205
237
|
maxOldSpaceMb: getHeapForFile(filePath, maxOldSpaceMb, perFileHeapOverrides),
|
|
206
238
|
vitestArgs: blobArgs,
|
|
207
|
-
streamOutput: !
|
|
239
|
+
streamOutput: !suppressPerFileCoverageOutput
|
|
208
240
|
});
|
|
209
241
|
|
|
210
242
|
coverageResults.push(result);
|
|
211
243
|
progress.onComplete(result.code !== 0);
|
|
212
244
|
|
|
213
|
-
if (!
|
|
245
|
+
if (!suppressPerFileCoverageOutput && emitTextOutput) {
|
|
214
246
|
const durationSec = (result.duration / 1000).toFixed(2);
|
|
215
247
|
if (result.code === 0) {
|
|
216
248
|
const heapInfo = result.heapMb ? ` | ${result.heapMb} MB heap` : "";
|
|
@@ -223,7 +255,9 @@ export async function run(opts) {
|
|
|
223
255
|
|
|
224
256
|
// Phase 1: solo files — one at a time
|
|
225
257
|
for (const filePath of soloFiles) {
|
|
226
|
-
await runCoverageFile(filePath).catch((err) =>
|
|
258
|
+
await runCoverageFile(filePath).catch((err) => {
|
|
259
|
+
if (emitTextOutput) console.error(`Error running ${filePath}:`, err);
|
|
260
|
+
});
|
|
227
261
|
}
|
|
228
262
|
|
|
229
263
|
// Phase 2: parallel files with worker pool
|
|
@@ -234,7 +268,9 @@ export async function run(opts) {
|
|
|
234
268
|
while (coverageFileIndex < parallelFiles.length && coverageActivePromises.size < workers) {
|
|
235
269
|
const filePath = parallelFiles[coverageFileIndex++];
|
|
236
270
|
const promise = runCoverageFile(filePath)
|
|
237
|
-
.catch((err) =>
|
|
271
|
+
.catch((err) => {
|
|
272
|
+
if (emitTextOutput) console.error(`Error running ${filePath}:`, err);
|
|
273
|
+
})
|
|
238
274
|
.finally(() => coverageActivePromises.delete(promise));
|
|
239
275
|
coverageActivePromises.add(promise);
|
|
240
276
|
}
|
|
@@ -246,48 +282,158 @@ export async function run(opts) {
|
|
|
246
282
|
|
|
247
283
|
const blobFiles = (await fs.readdir(blobsDir).catch(() => [])).filter((f) => f.endsWith(".blob"));
|
|
248
284
|
if (blobFiles.length === 0) {
|
|
249
|
-
|
|
285
|
+
const message = "❌ No coverage blobs were generated — coverage report cannot be produced";
|
|
286
|
+
if (emitTextOutput) console.error(message);
|
|
287
|
+
if (json) {
|
|
288
|
+
return {
|
|
289
|
+
exitCode: 1,
|
|
290
|
+
mode: "coverage",
|
|
291
|
+
message,
|
|
292
|
+
options: {
|
|
293
|
+
cwd,
|
|
294
|
+
testDir,
|
|
295
|
+
testPatterns,
|
|
296
|
+
testListFile,
|
|
297
|
+
workers,
|
|
298
|
+
coverageQuiet,
|
|
299
|
+
suppressFileOutput,
|
|
300
|
+
suppressPassingFiles,
|
|
301
|
+
topSummary,
|
|
302
|
+
showErrorDetails,
|
|
303
|
+
worstCoverageCount,
|
|
304
|
+
vitestArgs
|
|
305
|
+
},
|
|
306
|
+
totals: {
|
|
307
|
+
testFiles: allTestFiles.length,
|
|
308
|
+
failedFiles: coverageResults.filter((r) => r.code !== 0).length,
|
|
309
|
+
passedFiles: coverageResults.filter((r) => r.code === 0).length
|
|
310
|
+
},
|
|
311
|
+
results: {
|
|
312
|
+
all: coverageResults,
|
|
313
|
+
failed: coverageResults.filter((r) => r.code !== 0)
|
|
314
|
+
},
|
|
315
|
+
merge: {
|
|
316
|
+
blobFiles: 0,
|
|
317
|
+
exitCode: 1,
|
|
318
|
+
output: ""
|
|
319
|
+
},
|
|
320
|
+
coverageSummary: null
|
|
321
|
+
};
|
|
322
|
+
}
|
|
250
323
|
return 1;
|
|
251
324
|
}
|
|
252
325
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
326
|
+
// When mergeReports is false, stop after producing the blobs: skip the merge
|
|
327
|
+
// step and the coverage summary, and leave blobsDir intact so an external step
|
|
328
|
+
// can merge these blobs together with a separately-produced blob set.
|
|
329
|
+
let mergeExitCode = 0;
|
|
330
|
+
let mergeOutput = "";
|
|
331
|
+
let coverageSummary = null;
|
|
258
332
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
333
|
+
if (mergeReports) {
|
|
334
|
+
if (!coverageQuiet && !suppressFileOutput && emitTextOutput) {
|
|
335
|
+
console.log(`\n${"=".repeat(80)}`);
|
|
336
|
+
console.log(`📊 Merging ${blobFiles.length} coverage blobs into final report...`);
|
|
337
|
+
console.log("=".repeat(80));
|
|
338
|
+
}
|
|
265
339
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
340
|
+
({ exitCode: mergeExitCode, output: mergeOutput } = await runMergeReports(blobsDir, {
|
|
341
|
+
...spawnBase,
|
|
342
|
+
maxOldSpaceMb,
|
|
343
|
+
extraCoverageArgs,
|
|
344
|
+
quietOutput: coverageQuiet || json
|
|
345
|
+
}));
|
|
346
|
+
|
|
347
|
+
if (coverageQuiet && emitTextOutput) {
|
|
348
|
+
printMergeOutput(mergeExitCode, mergeOutput);
|
|
349
|
+
}
|
|
269
350
|
|
|
270
|
-
|
|
351
|
+
coverageSummary = await printCoverageSummary(cwd, extraCoverageArgs, worstCoverageCount, { silent: json });
|
|
352
|
+
}
|
|
271
353
|
|
|
272
|
-
// Clean up
|
|
354
|
+
// Clean up temp dirs always; only delete blobsDir when we consumed it via merge.
|
|
355
|
+
// With mergeReports:false the blobs must survive for the caller's external merge.
|
|
273
356
|
await Promise.all([
|
|
274
|
-
fs.rm(blobsDir, { recursive: true, force: true }).catch(() => {}),
|
|
357
|
+
...(mergeReports ? [fs.rm(blobsDir, { recursive: true, force: true }).catch(() => {})] : []),
|
|
275
358
|
fs.rm(coverageTmpBase, { recursive: true, force: true }).catch(() => {})
|
|
276
359
|
]);
|
|
277
360
|
|
|
278
361
|
const coverageFailed = coverageResults.filter((r) => r.code !== 0);
|
|
279
|
-
if (coverageQuiet) printQuietCoverageFailureDetails(coverageFailed);
|
|
362
|
+
if (coverageQuiet && emitTextOutput) printQuietCoverageFailureDetails(coverageFailed);
|
|
363
|
+
|
|
364
|
+
const exitCode = coverageFailed.length > 0 ? 1 : mergeExitCode;
|
|
365
|
+
if (json) {
|
|
366
|
+
const coverageWithHeap = coverageResults.filter((r) => r.heapMb !== null);
|
|
367
|
+
return {
|
|
368
|
+
exitCode,
|
|
369
|
+
mode: "coverage",
|
|
370
|
+
options: {
|
|
371
|
+
cwd,
|
|
372
|
+
testDir,
|
|
373
|
+
testPatterns,
|
|
374
|
+
testListFile,
|
|
375
|
+
workers,
|
|
376
|
+
coverageQuiet,
|
|
377
|
+
suppressFileOutput,
|
|
378
|
+
suppressPassingFiles,
|
|
379
|
+
topSummary,
|
|
380
|
+
showErrorDetails,
|
|
381
|
+
worstCoverageCount,
|
|
382
|
+
vitestArgs
|
|
383
|
+
},
|
|
384
|
+
totals: {
|
|
385
|
+
testFiles: allTestFiles.length,
|
|
386
|
+
failedFiles: coverageFailed.length,
|
|
387
|
+
passedFiles: coverageResults.length - coverageFailed.length
|
|
388
|
+
},
|
|
389
|
+
results: {
|
|
390
|
+
all: coverageResults,
|
|
391
|
+
failed: coverageFailed
|
|
392
|
+
},
|
|
393
|
+
merge: {
|
|
394
|
+
blobFiles: blobFiles.length,
|
|
395
|
+
exitCode: mergeExitCode,
|
|
396
|
+
output: mergeOutput
|
|
397
|
+
},
|
|
398
|
+
coverageSummary,
|
|
399
|
+
...(topSummary
|
|
400
|
+
? {
|
|
401
|
+
topMemoryUsers: [...coverageWithHeap].sort((a, b) => b.heapMb - a.heapMb).slice(0, 10),
|
|
402
|
+
topDuration: [...coverageResults].sort((a, b) => b.duration - a.duration).slice(0, 10)
|
|
403
|
+
}
|
|
404
|
+
: {})
|
|
405
|
+
};
|
|
406
|
+
}
|
|
280
407
|
|
|
281
|
-
return
|
|
408
|
+
return exitCode;
|
|
282
409
|
}
|
|
283
410
|
|
|
284
411
|
// ─── STANDARD (NON-COVERAGE) MODE ────────────────────────────────────────────
|
|
285
412
|
const testFiles = await discoverVitestFiles({ cwd, testDir, testPatterns, testListFile, testFilePattern, earlyRunPatterns });
|
|
286
413
|
|
|
287
414
|
if (testFiles.length === 0) {
|
|
288
|
-
|
|
289
|
-
testPatterns.length > 0 ? `❌ No Vitest test files found matching: ${testPatterns.join(", ")}` : "❌ No Vitest test files found"
|
|
290
|
-
);
|
|
415
|
+
const noTestsMessage =
|
|
416
|
+
testPatterns.length > 0 ? `❌ No Vitest test files found matching: ${testPatterns.join(", ")}` : "❌ No Vitest test files found";
|
|
417
|
+
if (emitTextOutput) console.log(noTestsMessage);
|
|
418
|
+
if (json) {
|
|
419
|
+
return {
|
|
420
|
+
exitCode: 1,
|
|
421
|
+
mode: "standard",
|
|
422
|
+
message: noTestsMessage,
|
|
423
|
+
options: {
|
|
424
|
+
cwd,
|
|
425
|
+
testDir,
|
|
426
|
+
testPatterns,
|
|
427
|
+
testListFile,
|
|
428
|
+
workers,
|
|
429
|
+
coverageQuiet,
|
|
430
|
+
suppressFileOutput,
|
|
431
|
+
suppressPassingFiles,
|
|
432
|
+
topSummary,
|
|
433
|
+
vitestArgs
|
|
434
|
+
}
|
|
435
|
+
};
|
|
436
|
+
}
|
|
291
437
|
return 1;
|
|
292
438
|
}
|
|
293
439
|
|
|
@@ -297,15 +443,17 @@ export async function run(opts) {
|
|
|
297
443
|
const scriptStartTime = Date.now();
|
|
298
444
|
const scriptStartTimeFormatted = new Date().toLocaleTimeString("en-US", { hour12: false });
|
|
299
445
|
|
|
300
|
-
if (testPatterns.length > 0) {
|
|
446
|
+
if (emitTextOutput && !suppressFileOutput && testPatterns.length > 0) {
|
|
301
447
|
console.log(`\n🧪 Running ${testFiles.length} test files matching: ${testPatterns.join(", ")}`);
|
|
302
|
-
} else {
|
|
448
|
+
} else if (emitTextOutput && !suppressFileOutput) {
|
|
303
449
|
console.log(`\n🧪 Running ${testFiles.length} test files (${soloFiles.length} solo first, then parallel)`);
|
|
304
450
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
451
|
+
if (emitTextOutput && !suppressFileOutput) {
|
|
452
|
+
console.log(`⚙️ Workers: ${workers}`);
|
|
453
|
+
if (maxOldSpaceMb) console.log(`🧠 Heap limit: ${maxOldSpaceMb} MB`);
|
|
454
|
+
if (vitestArgs.length > 0) console.log(`🔧 Vitest args: ${vitestArgs.join(" ")}`);
|
|
455
|
+
console.log("");
|
|
456
|
+
}
|
|
309
457
|
|
|
310
458
|
const results = [...(_testResultsOverride ?? [])];
|
|
311
459
|
|
|
@@ -315,22 +463,27 @@ export async function run(opts) {
|
|
|
315
463
|
* @returns {Promise<import('./core/spawn.mjs').SingleFileResult>}
|
|
316
464
|
*/
|
|
317
465
|
const runTestFile = async (filePath) => {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
466
|
+
if (!suppressFileOutput && emitTextOutput) {
|
|
467
|
+
console.log(`\n${"=".repeat(80)}`);
|
|
468
|
+
console.log(`▶️ ${filePath}`);
|
|
469
|
+
console.log("=".repeat(80));
|
|
470
|
+
}
|
|
321
471
|
|
|
322
472
|
const result = await runSingleFile(filePath, {
|
|
323
473
|
...spawnBase,
|
|
324
474
|
maxOldSpaceMb: getHeapForFile(filePath, maxOldSpaceMb, perFileHeapOverrides),
|
|
325
|
-
vitestArgs
|
|
475
|
+
vitestArgs,
|
|
476
|
+
streamOutput: !suppressFileOutput && !json
|
|
326
477
|
});
|
|
327
478
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
479
|
+
if (!suppressFileOutput && emitTextOutput) {
|
|
480
|
+
const durationSec = (result.duration / 1000).toFixed(2);
|
|
481
|
+
if (result.code === 0) {
|
|
482
|
+
const heapInfo = result.heapMb ? ` | ${result.heapMb} MB heap` : "";
|
|
483
|
+
console.log(`\n✅ PASSED (${durationSec}s${heapInfo})\n`);
|
|
484
|
+
} else {
|
|
485
|
+
console.log(`\n❌ FAILED (exit code ${result.code}, ${durationSec}s)\n`);
|
|
486
|
+
}
|
|
334
487
|
}
|
|
335
488
|
|
|
336
489
|
return result;
|
|
@@ -340,7 +493,7 @@ export async function run(opts) {
|
|
|
340
493
|
// Phase 1: solo files
|
|
341
494
|
for (const filePath of soloFiles) {
|
|
342
495
|
const result = await runTestFile(filePath).catch((err) => {
|
|
343
|
-
console.error(`Error running ${filePath}:`, err);
|
|
496
|
+
if (emitTextOutput) console.error(`Error running ${filePath}:`, err);
|
|
344
497
|
return null;
|
|
345
498
|
});
|
|
346
499
|
if (result) results.push(result);
|
|
@@ -355,7 +508,9 @@ export async function run(opts) {
|
|
|
355
508
|
const filePath = parallelFiles[index++];
|
|
356
509
|
const promise = runTestFile(filePath)
|
|
357
510
|
.then((result) => results.push(result))
|
|
358
|
-
.catch((err) =>
|
|
511
|
+
.catch((err) => {
|
|
512
|
+
if (emitTextOutput) console.error(`Error running ${filePath}:`, err);
|
|
513
|
+
})
|
|
359
514
|
.finally(() => activePromises.delete(promise));
|
|
360
515
|
activePromises.add(promise);
|
|
361
516
|
}
|
|
@@ -373,12 +528,69 @@ export async function run(opts) {
|
|
|
373
528
|
const totalDuration = results.reduce((s, r) => s + r.duration, 0);
|
|
374
529
|
const failedFiles = results.filter((r) => r.code !== 0);
|
|
375
530
|
const passedFiles = results.filter((r) => r.code === 0);
|
|
531
|
+
const exitCode = failedFiles.length > 0 ? 1 : 0;
|
|
532
|
+
const scriptMemory = process.memoryUsage();
|
|
533
|
+
const scriptHeapMb = Math.round(scriptMemory.heapUsed / 1024 / 1024);
|
|
534
|
+
const scriptRssMb = Math.round(scriptMemory.rss / 1024 / 1024);
|
|
535
|
+
const withHeap = results.filter((r) => r.heapMb !== null);
|
|
536
|
+
const actualDurationSec = ((Date.now() - scriptStartTime) / 1000).toFixed(2);
|
|
537
|
+
const testsDurationSec = (totalDuration / 1000).toFixed(2);
|
|
538
|
+
|
|
539
|
+
if (json) {
|
|
540
|
+
return {
|
|
541
|
+
exitCode,
|
|
542
|
+
mode: "standard",
|
|
543
|
+
options: {
|
|
544
|
+
cwd,
|
|
545
|
+
testDir,
|
|
546
|
+
testPatterns,
|
|
547
|
+
testListFile,
|
|
548
|
+
workers,
|
|
549
|
+
coverageQuiet,
|
|
550
|
+
suppressFileOutput,
|
|
551
|
+
suppressPassingFiles,
|
|
552
|
+
topSummary,
|
|
553
|
+
showErrorDetails,
|
|
554
|
+
worstCoverageCount,
|
|
555
|
+
vitestArgs
|
|
556
|
+
},
|
|
557
|
+
totals: {
|
|
558
|
+
testFilesPass: totalTestFilesPass,
|
|
559
|
+
testFilesFail: totalTestFilesFail,
|
|
560
|
+
testsPass: totalTestsPass,
|
|
561
|
+
testsFail: totalTestsFail,
|
|
562
|
+
testsSkip: totalTestsSkip,
|
|
563
|
+
totalTests: totalTestsPass + totalTestsFail + totalTestsSkip
|
|
564
|
+
},
|
|
565
|
+
timing: {
|
|
566
|
+
startAt: scriptStartTimeFormatted,
|
|
567
|
+
wallClockSeconds: Number(actualDurationSec),
|
|
568
|
+
testsSeconds: Number(testsDurationSec)
|
|
569
|
+
},
|
|
570
|
+
heap: {
|
|
571
|
+
scriptHeapMb,
|
|
572
|
+
scriptRssMb,
|
|
573
|
+
maxHeapMb: withHeap.length > 0 ? Math.max(...withHeap.map((r) => r.heapMb)) : null,
|
|
574
|
+
avgHeapMb: withHeap.length > 0 ? Number((withHeap.reduce((sum, r) => sum + r.heapMb, 0) / withHeap.length).toFixed(0)) : null
|
|
575
|
+
},
|
|
576
|
+
results: {
|
|
577
|
+
all: results,
|
|
578
|
+
passed: passedFiles,
|
|
579
|
+
failed: failedFiles
|
|
580
|
+
},
|
|
581
|
+
...(topSummary
|
|
582
|
+
? {
|
|
583
|
+
topMemoryUsers: [...withHeap].sort((a, b) => b.heapMb - a.heapMb).slice(0, 10),
|
|
584
|
+
topDuration: [...results].sort((a, b) => b.duration - a.duration).slice(0, 10)
|
|
585
|
+
}
|
|
586
|
+
: {})
|
|
587
|
+
};
|
|
588
|
+
}
|
|
376
589
|
|
|
377
590
|
console.log("\n" + "=".repeat(80));
|
|
378
591
|
|
|
379
592
|
// Top memory users
|
|
380
|
-
|
|
381
|
-
if (withHeap.length > 0) {
|
|
593
|
+
if (topSummary && withHeap.length > 0) {
|
|
382
594
|
console.log("\n" + chalk.bold("🧠 TOP MEMORY USERS"));
|
|
383
595
|
console.log("-".repeat(80));
|
|
384
596
|
[...withHeap]
|
|
@@ -390,7 +602,7 @@ export async function run(opts) {
|
|
|
390
602
|
}
|
|
391
603
|
|
|
392
604
|
// Top duration
|
|
393
|
-
if (results.length > 0) {
|
|
605
|
+
if (topSummary && results.length > 0) {
|
|
394
606
|
console.log("\n" + chalk.bold("⏱️ TOP DURATION"));
|
|
395
607
|
console.log("-".repeat(80));
|
|
396
608
|
[...results]
|
|
@@ -403,7 +615,7 @@ export async function run(opts) {
|
|
|
403
615
|
}
|
|
404
616
|
|
|
405
617
|
// Passed files
|
|
406
|
-
if (passedFiles.length > 0) {
|
|
618
|
+
if (passedFiles.length > 0 && !suppressPassingFiles) {
|
|
407
619
|
console.log("\n" + "=".repeat(80));
|
|
408
620
|
console.log(chalk.bold.green("✓ PASSED TEST FILES"));
|
|
409
621
|
console.log("=".repeat(80));
|
|
@@ -468,13 +680,8 @@ export async function run(opts) {
|
|
|
468
680
|
console.log(` ${chalk.bold("Tests")} ${testsParts.join(` ${chalk.dim("|")} `)} ${chalk.dim(`(${totalTests})`)}`);
|
|
469
681
|
console.log(` ${chalk.bold("Start at")} ${scriptStartTimeFormatted}`);
|
|
470
682
|
|
|
471
|
-
const actualDurationSec = ((Date.now() - scriptStartTime) / 1000).toFixed(2);
|
|
472
|
-
const testsDurationSec = (totalDuration / 1000).toFixed(2);
|
|
473
683
|
console.log(` ${chalk.bold("Duration")} ${actualDurationSec}s ${chalk.dim(`(tests ${testsDurationSec}s)`)}`);
|
|
474
684
|
|
|
475
|
-
const scriptMemory = process.memoryUsage();
|
|
476
|
-
const scriptHeapMb = Math.round(scriptMemory.heapUsed / 1024 / 1024);
|
|
477
|
-
const scriptRssMb = Math.round(scriptMemory.rss / 1024 / 1024);
|
|
478
685
|
if (withHeap.length > 0) {
|
|
479
686
|
const maxHeap = Math.max(...withHeap.map((r) => r.heapMb));
|
|
480
687
|
const avgHeap = (withHeap.reduce((s, r) => s + r.heapMb, 0) / withHeap.length).toFixed(0);
|
|
@@ -483,9 +690,7 @@ export async function run(opts) {
|
|
|
483
690
|
console.log(` ${chalk.bold("Heap")} script ${scriptHeapMb} MB (RSS ${scriptRssMb} MB)`);
|
|
484
691
|
}
|
|
485
692
|
|
|
486
|
-
if (failedFiles.length > 0)
|
|
487
|
-
return 1;
|
|
488
|
-
}
|
|
693
|
+
if (failedFiles.length > 0) return 1;
|
|
489
694
|
|
|
490
695
|
console.log(`\n✅ All ${passedFiles.length} test files passed\n`);
|
|
491
696
|
return 0;
|
package/types/src/cli/args.d.mts
CHANGED
|
@@ -29,6 +29,30 @@ export type ParsedArgs = {
|
|
|
29
29
|
* - Path for the coverage run log (`--log-file`); defaults to `coverage/coverage-run.log`.
|
|
30
30
|
*/
|
|
31
31
|
logFile: string | undefined;
|
|
32
|
+
/**
|
|
33
|
+
* - Suppress per-file runner output blocks (`--suppress-file-output`).
|
|
34
|
+
*/
|
|
35
|
+
suppressFileOutput: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* - Suppress the passed-files section in the final summary (`--suppress-passing-files`).
|
|
38
|
+
*/
|
|
39
|
+
suppressPassingFiles: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* - Show top-summary sections for memory and duration (`true` by default, disabled by `--no-top-summary`).
|
|
42
|
+
*/
|
|
43
|
+
topSummary: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* - Emit a JSON run report instead of text output (`--json`).
|
|
46
|
+
*/
|
|
47
|
+
json: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* - Directory for per-file coverage blobs (`--blobs-dir`); defaults to `<cwd>/.vitest-coverage-blobs`.
|
|
50
|
+
*/
|
|
51
|
+
blobsDir: string | undefined;
|
|
52
|
+
/**
|
|
53
|
+
* - `false` when `--no-merge-reports` was passed; leaves blobs in `blobsDir` without merging.
|
|
54
|
+
*/
|
|
55
|
+
mergeReports: boolean;
|
|
32
56
|
/**
|
|
33
57
|
* - Whether `--help` / `-h` was passed.
|
|
34
58
|
*/
|
|
@@ -42,6 +42,29 @@ export function computeSummaryFromFinal(finalData: Record<string, object>): {
|
|
|
42
42
|
* @param {string} cwd - Project root (used to make absolute file paths relative).
|
|
43
43
|
* @param {string[]} extraCoverageArgs - Passthrough `--coverage.*` args (checked for `reportsDirectory`).
|
|
44
44
|
* @param {number} [worstCount=10] - Number of worst-coverage files to show (0 = skip table).
|
|
45
|
-
* @
|
|
45
|
+
* @param {{silent?: boolean}} [options] - Output controls.
|
|
46
|
+
* @returns {Promise<{
|
|
47
|
+
* coverageDir: string,
|
|
48
|
+
* total: object,
|
|
49
|
+
* worstFiles: Array<{file: string, lines: number, stmts: number, fns: number, branches: number}>,
|
|
50
|
+
* worstFilesShown: number,
|
|
51
|
+
* worstFilesTotal: number,
|
|
52
|
+
* summary: object
|
|
53
|
+
* }|null>}
|
|
46
54
|
*/
|
|
47
|
-
export function printCoverageSummary(cwd: string, extraCoverageArgs: string[], worstCount?: number
|
|
55
|
+
export function printCoverageSummary(cwd: string, extraCoverageArgs: string[], worstCount?: number, options?: {
|
|
56
|
+
silent?: boolean;
|
|
57
|
+
}): Promise<{
|
|
58
|
+
coverageDir: string;
|
|
59
|
+
total: object;
|
|
60
|
+
worstFiles: Array<{
|
|
61
|
+
file: string;
|
|
62
|
+
lines: number;
|
|
63
|
+
stmts: number;
|
|
64
|
+
fns: number;
|
|
65
|
+
branches: number;
|
|
66
|
+
}>;
|
|
67
|
+
worstFilesShown: number;
|
|
68
|
+
worstFilesTotal: number;
|
|
69
|
+
summary: object;
|
|
70
|
+
} | null>;
|
package/types/src/runner.d.mts
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
* pool for the non-solo phase) and return an exit code.
|
|
4
4
|
*
|
|
5
5
|
* @param {RunOptions} opts
|
|
6
|
-
* @returns {Promise<number>} `0`
|
|
6
|
+
* @returns {Promise<number|object>} `0`/`1` by default; JSON report object when `opts.json` is true.
|
|
7
7
|
*/
|
|
8
|
-
export function run(opts: RunOptions): Promise<number>;
|
|
8
|
+
export function run(opts: RunOptions): Promise<number | object>;
|
|
9
9
|
export { formatDuration } from "./utils/duration.mjs";
|
|
10
10
|
export { buildNodeOptions } from "./utils/env.mjs";
|
|
11
11
|
export type PerFileHeapOverride = {
|
|
@@ -55,6 +55,22 @@ export type RunOptions = {
|
|
|
55
55
|
* - Suppress per-file output; show only progress bar + summaries.
|
|
56
56
|
*/
|
|
57
57
|
coverageQuiet?: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* - Suppress per-file runner output blocks in all modes.
|
|
60
|
+
*/
|
|
61
|
+
suppressFileOutput?: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* - Hide passed-file rows in the final summary.
|
|
64
|
+
*/
|
|
65
|
+
suppressPassingFiles?: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* - Show top memory and duration summary sections.
|
|
68
|
+
*/
|
|
69
|
+
topSummary?: boolean;
|
|
70
|
+
/**
|
|
71
|
+
* - Return a JSON run report instead of printing text output.
|
|
72
|
+
*/
|
|
73
|
+
json?: boolean;
|
|
58
74
|
/**
|
|
59
75
|
* - Maximum number of parallel worker slots.
|
|
60
76
|
*/
|
|
@@ -63,6 +79,14 @@ export type RunOptions = {
|
|
|
63
79
|
* - Rows in the worst-coverage table (0 = disable).
|
|
64
80
|
*/
|
|
65
81
|
worstCoverageCount?: number;
|
|
82
|
+
/**
|
|
83
|
+
* - Directory for per-file coverage blobs (default `<cwd>/.vitest-coverage-blobs`). Relative paths resolve against `cwd`. Always cleared at the start of a coverage run.
|
|
84
|
+
*/
|
|
85
|
+
blobsDir?: string;
|
|
86
|
+
/**
|
|
87
|
+
* - When `true`, blobs are merged via `vitest --mergeReports`, the coverage summary is printed, and `blobsDir` is deleted at the end. When `false`, the run stops after producing blobs: no merge, no summary, and `blobsDir` is left populated for an external merge step.
|
|
88
|
+
*/
|
|
89
|
+
mergeReports?: boolean;
|
|
66
90
|
/**
|
|
67
91
|
* - Global `--max-old-space-size` ceiling; per-file overrides may raise it.
|
|
68
92
|
*/
|