@cldmv/vitest-runner 1.1.0 → 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 +91 -68
- package/bin/vitest-runner.mjs +2 -0
- package/package.json +13 -5
- package/src/cli/args.mjs +14 -0
- package/src/cli/help.mjs +3 -1
- package/src/core/discover.mjs +1 -1
- package/src/core/progress.mjs +2 -2
- package/src/runner.mjs +32 -20
- package/types/src/cli/args.d.mts +8 -0
- package/types/src/runner.d.mts +8 -0
package/README.md
CHANGED
|
@@ -45,20 +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
|
-
| `--suppress-file-output`
|
|
58
|
-
| `--suppress-passing-files` | Hide the `PASSED TEST FILES` section in the final summary
|
|
59
|
-
| `--no-top-summary`
|
|
60
|
-
| `--json`
|
|
61
|
-
| `--
|
|
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 |
|
|
62
64
|
|
|
63
65
|
### Test patterns
|
|
64
66
|
|
|
@@ -95,10 +97,10 @@ vitest-runner --bail
|
|
|
95
97
|
|
|
96
98
|
### Environment variables
|
|
97
99
|
|
|
98
|
-
| Variable
|
|
99
|
-
|
|
100
|
-
| `VITEST_HEAP_MB` |
|
|
101
|
-
| `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`) |
|
|
102
104
|
|
|
103
105
|
### Examples
|
|
104
106
|
|
|
@@ -145,10 +147,10 @@ vitest-runner --json --no-top-summary
|
|
|
145
147
|
## Programmatic API
|
|
146
148
|
|
|
147
149
|
```js
|
|
148
|
-
import { run } from
|
|
150
|
+
import { run } from "vitest-runner";
|
|
149
151
|
|
|
150
152
|
// CommonJS
|
|
151
|
-
const { run } = await require(
|
|
153
|
+
const { run } = await require("vitest-runner");
|
|
152
154
|
```
|
|
153
155
|
|
|
154
156
|
### `run(options)` → `Promise<number | object>`
|
|
@@ -156,10 +158,10 @@ const { run } = await require('vitest-runner');
|
|
|
156
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.
|
|
157
159
|
|
|
158
160
|
```js
|
|
159
|
-
import { run } from
|
|
161
|
+
import { run } from "vitest-runner";
|
|
160
162
|
|
|
161
163
|
const code = await run({
|
|
162
|
-
|
|
164
|
+
testDir: "src/tests"
|
|
163
165
|
});
|
|
164
166
|
|
|
165
167
|
process.exit(code);
|
|
@@ -167,33 +169,38 @@ process.exit(code);
|
|
|
167
169
|
|
|
168
170
|
#### Options
|
|
169
171
|
|
|
170
|
-
| Option
|
|
171
|
-
|
|
172
|
-
| `cwd`
|
|
173
|
-
| `testDir`
|
|
174
|
-
| `vitestConfig`
|
|
175
|
-
| `testPatterns`
|
|
176
|
-
| `testListFile`
|
|
177
|
-
| `testFilePattern`
|
|
178
|
-
| `vitestArgs`
|
|
179
|
-
| `showErrorDetails`
|
|
180
|
-
| `coverageQuiet`
|
|
181
|
-
| `suppressFileOutput`
|
|
182
|
-
| `suppressPassingFiles` | `boolean`
|
|
183
|
-
| `topSummary`
|
|
184
|
-
| `json`
|
|
185
|
-
| `workers`
|
|
186
|
-
| `worstCoverageCount`
|
|
187
|
-
| `
|
|
188
|
-
| `
|
|
189
|
-
| `
|
|
190
|
-
| `
|
|
191
|
-
| `
|
|
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 |
|
|
192
196
|
|
|
193
197
|
#### `PerFileHeapOverride`
|
|
194
198
|
|
|
195
199
|
```ts
|
|
196
|
-
{
|
|
200
|
+
{
|
|
201
|
+
pattern: string;
|
|
202
|
+
heapMb: number;
|
|
203
|
+
}
|
|
197
204
|
```
|
|
198
205
|
|
|
199
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.
|
|
@@ -202,42 +209,40 @@ process.exit(code);
|
|
|
202
209
|
|
|
203
210
|
```js
|
|
204
211
|
// Run all tests under src/tests/ (cwd defaults to process.cwd())
|
|
205
|
-
await run({ testDir:
|
|
212
|
+
await run({ testDir: "src/tests" });
|
|
206
213
|
|
|
207
214
|
// Run only the config and metadata suites
|
|
208
215
|
await run({
|
|
209
|
-
|
|
210
|
-
|
|
216
|
+
testDir: "src/tests",
|
|
217
|
+
testPatterns: ["src/tests/config", "src/tests/metadata"]
|
|
211
218
|
});
|
|
212
219
|
|
|
213
220
|
// Coverage run (OOM-safe blob + merge mode)
|
|
214
221
|
await run({
|
|
215
|
-
|
|
216
|
-
|
|
222
|
+
testDir: "src/tests",
|
|
223
|
+
vitestArgs: ["--coverage"]
|
|
217
224
|
});
|
|
218
225
|
|
|
219
226
|
// Quiet coverage with live progress bar
|
|
220
227
|
await run({
|
|
221
|
-
|
|
222
|
-
|
|
228
|
+
testDir: "src/tests",
|
|
229
|
+
coverageQuiet: true
|
|
223
230
|
});
|
|
224
231
|
|
|
225
232
|
// Machine-readable output (no text logs)
|
|
226
233
|
const report = await run({
|
|
227
|
-
|
|
228
|
-
|
|
234
|
+
testDir: "src/tests",
|
|
235
|
+
json: true
|
|
229
236
|
});
|
|
230
237
|
|
|
231
238
|
console.log(report.exitCode);
|
|
232
239
|
|
|
233
240
|
// Give heap-heavy files a larger ceiling while keeping the global limit lower
|
|
234
241
|
await run({
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
{ pattern: 'listener-cleanup/', heapMb: 6144 },
|
|
240
|
-
],
|
|
242
|
+
testDir: "src/tests",
|
|
243
|
+
maxOldSpaceMb: 2048,
|
|
244
|
+
earlyRunPatterns: ["listener-cleanup/"],
|
|
245
|
+
perFileHeapOverrides: [{ pattern: "listener-cleanup/", heapMb: 6144 }]
|
|
241
246
|
});
|
|
242
247
|
```
|
|
243
248
|
|
|
@@ -259,6 +264,28 @@ This avoids the OOM crash that occurs when a single vitest process holds coverag
|
|
|
259
264
|
|
|
260
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).
|
|
261
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.
|
|
288
|
+
|
|
262
289
|
---
|
|
263
290
|
|
|
264
291
|
## Test list files
|
|
@@ -266,11 +293,7 @@ The log file path can be overridden with `--log-file <path>`. Passing `--log-fil
|
|
|
266
293
|
A test list file is a plain JSON array of test file paths (relative to `cwd`):
|
|
267
294
|
|
|
268
295
|
```json
|
|
269
|
-
[
|
|
270
|
-
"src/tests/auth/login.test.vitest.mjs",
|
|
271
|
-
"src/tests/auth/register.test.vitest.mjs",
|
|
272
|
-
"src/tests/config/defaults.test.vitest.mjs"
|
|
273
|
-
]
|
|
296
|
+
["src/tests/auth/login.test.vitest.mjs", "src/tests/auth/register.test.vitest.mjs", "src/tests/config/defaults.test.vitest.mjs"]
|
|
274
297
|
```
|
|
275
298
|
|
|
276
299
|
Pass `--test-list <file>` (CLI) or `testListFile: 'path/to/list.json'` (API) to run exactly those files instead of scanning `testDir`.
|
|
@@ -281,7 +304,7 @@ Pass `--test-list <file>` (CLI) or `testListFile: 'path/to/list.json'` (API) to
|
|
|
281
304
|
|
|
282
305
|
By default, the runner discovers files matching:
|
|
283
306
|
|
|
284
|
-
```
|
|
307
|
+
```text
|
|
285
308
|
*.test.vitest.js
|
|
286
309
|
*.test.vitest.mjs
|
|
287
310
|
*.test.vitest.cjs
|
|
@@ -297,14 +320,14 @@ vitest-runner --file-pattern '\.spec\.ts$'
|
|
|
297
320
|
```
|
|
298
321
|
|
|
299
322
|
```js
|
|
300
|
-
await run({ cwd, testDir:
|
|
323
|
+
await run({ cwd, testDir: "src", testFilePattern: /\.spec\.ts$/i });
|
|
301
324
|
```
|
|
302
325
|
|
|
303
326
|
---
|
|
304
327
|
|
|
305
328
|
## Source layout
|
|
306
329
|
|
|
307
|
-
```
|
|
330
|
+
```text
|
|
308
331
|
index.mjs ← ESM entry (re-exports src/runner.mjs)
|
|
309
332
|
index.cjs ← CJS shim (dynamic import of index.mjs)
|
|
310
333
|
bin/
|
package/bin/vitest-runner.mjs
CHANGED
|
@@ -79,6 +79,8 @@ try {
|
|
|
79
79
|
suppressPassingFiles: args.suppressPassingFiles,
|
|
80
80
|
topSummary: args.topSummary,
|
|
81
81
|
json: args.json,
|
|
82
|
+
mergeReports: args.mergeReports,
|
|
83
|
+
...(args.blobsDir !== undefined && { blobsDir: args.blobsDir }),
|
|
82
84
|
...(args.workers !== undefined && { workers: args.workers }),
|
|
83
85
|
...(args.soloPatterns.length > 0 && { earlyRunPatterns: args.soloPatterns })
|
|
84
86
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cldmv/vitest-runner",
|
|
3
|
-
"version": "1.
|
|
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
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
* @property {boolean} suppressPassingFiles - Suppress the passed-files section in the final summary (`--suppress-passing-files`).
|
|
14
14
|
* @property {boolean} topSummary - Show top-summary sections for memory and duration (`true` by default, disabled by `--no-top-summary`).
|
|
15
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.
|
|
16
18
|
* @property {boolean} help - Whether `--help` / `-h` was passed.
|
|
17
19
|
* @property {number|undefined} workers - Worker count from `--workers <n>`, or undefined.
|
|
18
20
|
* @property {string[]} soloPatterns - Path substrings from `--solo-pattern <pattern>` (repeatable).
|
|
@@ -31,6 +33,8 @@ const RUNNER_FLAGS = new Set([
|
|
|
31
33
|
"--suppress-passing-files",
|
|
32
34
|
"--no-top-summary",
|
|
33
35
|
"--json",
|
|
36
|
+
"--blobs-dir",
|
|
37
|
+
"--no-merge-reports",
|
|
34
38
|
"--workers",
|
|
35
39
|
"--solo-pattern",
|
|
36
40
|
"--file-pattern",
|
|
@@ -63,6 +67,8 @@ export function parseArguments(args) {
|
|
|
63
67
|
let suppressPassingFiles = false;
|
|
64
68
|
let topSummary = true;
|
|
65
69
|
let json = false;
|
|
70
|
+
let blobsDir;
|
|
71
|
+
let mergeReports = true;
|
|
66
72
|
let workers;
|
|
67
73
|
let help = false;
|
|
68
74
|
let testFilePattern;
|
|
@@ -89,6 +95,12 @@ export function parseArguments(args) {
|
|
|
89
95
|
topSummary = false;
|
|
90
96
|
} else if (arg === "--json") {
|
|
91
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;
|
|
92
104
|
} else if (arg === "--workers") {
|
|
93
105
|
workers = parseInt(args[++i], 10);
|
|
94
106
|
} else if (arg.startsWith("--workers=")) {
|
|
@@ -124,6 +136,8 @@ export function parseArguments(args) {
|
|
|
124
136
|
suppressPassingFiles,
|
|
125
137
|
topSummary,
|
|
126
138
|
json,
|
|
139
|
+
blobsDir,
|
|
140
|
+
mergeReports,
|
|
127
141
|
help,
|
|
128
142
|
workers,
|
|
129
143
|
soloPatterns,
|
package/src/cli/help.mjs
CHANGED
|
@@ -21,7 +21,7 @@ ${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)
|
|
@@ -31,6 +31,8 @@ ${chalk.bold("SPECIAL FLAGS:")}
|
|
|
31
31
|
--suppress-passing-files Hide the PASSED TEST FILES section in the final summary
|
|
32
32
|
--no-top-summary Hide TOP MEMORY USERS and TOP DURATION sections
|
|
33
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)
|
|
34
36
|
--help, -h Show this help message
|
|
35
37
|
|
|
36
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/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
|
|
@@ -61,6 +59,8 @@ import { colourPct } from "./utils/ansi.mjs";
|
|
|
61
59
|
* @property {boolean} [json=false] - Return a JSON run report instead of printing text output.
|
|
62
60
|
* @property {number} [workers=4] - Maximum number of parallel worker slots.
|
|
63
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.
|
|
64
64
|
* @property {number} [maxOldSpaceMb] - Global `--max-old-space-size` ceiling; per-file overrides may raise it.
|
|
65
65
|
* @property {string[]} [earlyRunPatterns=[]] - Path substrings — matching files run solo before the worker pool.
|
|
66
66
|
* @property {PerFileHeapOverride[]} [perFileHeapOverrides=[]] - Per-file minimum heap overrides.
|
|
@@ -118,6 +118,8 @@ export async function run(opts) {
|
|
|
118
118
|
json = false,
|
|
119
119
|
workers = parseInt(process.env.VITEST_WORKERS ?? "4", 10),
|
|
120
120
|
worstCoverageCount = 10,
|
|
121
|
+
blobsDir: blobsDirOpt,
|
|
122
|
+
mergeReports = true,
|
|
121
123
|
earlyRunPatterns = [],
|
|
122
124
|
perFileHeapOverrides = [],
|
|
123
125
|
conditions = [],
|
|
@@ -147,7 +149,7 @@ export async function run(opts) {
|
|
|
147
149
|
|
|
148
150
|
// ─── COVERAGE MODE ───────────────────────────────────────────────────────────
|
|
149
151
|
if (hasCoverage) {
|
|
150
|
-
const blobsDir = path.resolve(cwd, ".vitest-coverage-blobs");
|
|
152
|
+
const blobsDir = path.resolve(cwd, blobsDirOpt ?? ".vitest-coverage-blobs");
|
|
151
153
|
// Temp coverage dirs live OUTSIDE blobsDir — vitest --mergeReports errors on
|
|
152
154
|
// any non-blob entry found inside the blobs directory.
|
|
153
155
|
const coverageTmpBase = path.resolve(cwd, ".vitest-coverage-tmp");
|
|
@@ -321,28 +323,38 @@ export async function run(opts) {
|
|
|
321
323
|
return 1;
|
|
322
324
|
}
|
|
323
325
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
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;
|
|
329
332
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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
|
+
}
|
|
336
339
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
+
({ exitCode: mergeExitCode, output: mergeOutput } = await runMergeReports(blobsDir, {
|
|
341
|
+
...spawnBase,
|
|
342
|
+
maxOldSpaceMb,
|
|
343
|
+
extraCoverageArgs,
|
|
344
|
+
quietOutput: coverageQuiet || json
|
|
345
|
+
}));
|
|
340
346
|
|
|
341
|
-
|
|
347
|
+
if (coverageQuiet && emitTextOutput) {
|
|
348
|
+
printMergeOutput(mergeExitCode, mergeOutput);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
coverageSummary = await printCoverageSummary(cwd, extraCoverageArgs, worstCoverageCount, { silent: json });
|
|
352
|
+
}
|
|
342
353
|
|
|
343
|
-
// 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.
|
|
344
356
|
await Promise.all([
|
|
345
|
-
fs.rm(blobsDir, { recursive: true, force: true }).catch(() => {}),
|
|
357
|
+
...(mergeReports ? [fs.rm(blobsDir, { recursive: true, force: true }).catch(() => {})] : []),
|
|
346
358
|
fs.rm(coverageTmpBase, { recursive: true, force: true }).catch(() => {})
|
|
347
359
|
]);
|
|
348
360
|
|
package/types/src/cli/args.d.mts
CHANGED
|
@@ -45,6 +45,14 @@ export type ParsedArgs = {
|
|
|
45
45
|
* - Emit a JSON run report instead of text output (`--json`).
|
|
46
46
|
*/
|
|
47
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;
|
|
48
56
|
/**
|
|
49
57
|
* - Whether `--help` / `-h` was passed.
|
|
50
58
|
*/
|
package/types/src/runner.d.mts
CHANGED
|
@@ -79,6 +79,14 @@ export type RunOptions = {
|
|
|
79
79
|
* - Rows in the worst-coverage table (0 = disable).
|
|
80
80
|
*/
|
|
81
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;
|
|
82
90
|
/**
|
|
83
91
|
* - Global `--max-old-space-size` ceiling; per-file overrides may raise it.
|
|
84
92
|
*/
|