@cldmv/vitest-runner 1.2.0 → 1.4.3
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 +50 -11
- package/bin/vitest-runner.mjs +110 -83
- package/dist/index.cjs +21 -0
- package/dist/index.mjs +47 -0
- package/package.json +31 -13
- package/types/src/bin/vitest-runner.d.mts +2 -0
- package/types/src/cli/args.d.mts +8 -0
- package/types/src/core/discover.d.mts +23 -0
- package/types/src/core/scratch.d.mts +64 -0
- package/types/src/core/spawn.d.mts +3 -1
- package/types/src/runner.d.mts +15 -0
- package/types/src/utils/ansi.d.mts +12 -0
- package/types/src/utils/duration.d.mts +12 -0
- package/types/src/utils/env.d.mts +12 -0
- package/index.cjs +0 -17
- package/index.mjs +0 -5
- package/src/cli/args.mjs +0 -148
- package/src/cli/help.mjs +0 -86
- package/src/core/discover.mjs +0 -167
- package/src/core/parse.mjs +0 -167
- package/src/core/progress.mjs +0 -164
- package/src/core/report.mjs +0 -255
- package/src/core/spawn.mjs +0 -219
- package/src/runner.mjs +0 -708
- package/src/utils/ansi.mjs +0 -32
- package/src/utils/duration.mjs +0 -25
- package/src/utils/env.mjs +0 -38
- package/src/utils/resolve.mjs +0 -86
package/types/src/cli/args.d.mts
CHANGED
|
@@ -77,4 +77,12 @@ export type ParsedArgs = {
|
|
|
77
77
|
* - Non-flag positional arguments (file / folder patterns).
|
|
78
78
|
*/
|
|
79
79
|
testPatterns: string[];
|
|
80
|
+
/**
|
|
81
|
+
* - Keep the run's scratch directory instead of removing it on completion (`--keep-tmp`).
|
|
82
|
+
*/
|
|
83
|
+
keepTmp: boolean;
|
|
84
|
+
/**
|
|
85
|
+
* - Per-run scratch root, relative to `cwd` (`--scratch-dir`); defaults to `tmp/vitest-runner`.
|
|
86
|
+
*/
|
|
87
|
+
scratchDir: string | undefined;
|
|
80
88
|
};
|
|
@@ -21,6 +21,29 @@ export function discoverFilesInDir(dir: string, cwd: string, pattern?: RegExp):
|
|
|
21
21
|
* sortWithPriority(files, ['listener-cleanup/']);
|
|
22
22
|
*/
|
|
23
23
|
export function sortWithPriority(files: string[], earlyRunPatterns?: string[]): string[];
|
|
24
|
+
/**
|
|
25
|
+
* Compute, for every file in `files`, the set of *other* files that Vitest's
|
|
26
|
+
* own CLI filter would spuriously also match when that file's path is passed
|
|
27
|
+
* as the filter argument.
|
|
28
|
+
*
|
|
29
|
+
* Vitest's `vitest run <filter>` matches any discovered test file whose path
|
|
30
|
+
* *contains* `<filter>` as a substring — not just an exact-path match. Two
|
|
31
|
+
* files that share the same basename (and immediate parent directory) at
|
|
32
|
+
* different depths — e.g. `tests/contract.test.vitest.mjs` and
|
|
33
|
+
* `packages/a/tests/contract.test.vitest.mjs` — collide because the shorter
|
|
34
|
+
* path is a trailing substring of the longer one, so filtering on the shorter
|
|
35
|
+
* path's exact string also matches the longer path's file. Passing an
|
|
36
|
+
* absolute path does not help: Vitest normalises to a root-relative path
|
|
37
|
+
* before matching.
|
|
38
|
+
*
|
|
39
|
+
* @param {string[]} files - File paths relative to the project root, as returned by discovery.
|
|
40
|
+
* @returns {Map<string, string[]>} Map from each file to the other files it would spuriously match (empty array when unambiguous).
|
|
41
|
+
* @example
|
|
42
|
+
* const conflicts = computeFilterConflicts(["tests/a.mjs", "pkg/tests/a.mjs"]);
|
|
43
|
+
* conflicts.get("tests/a.mjs"); // ["pkg/tests/a.mjs"]
|
|
44
|
+
* conflicts.get("pkg/tests/a.mjs"); // []
|
|
45
|
+
*/
|
|
46
|
+
export function computeFilterConflicts(files: string[]): Map<string, string[]>;
|
|
24
47
|
/**
|
|
25
48
|
* @typedef {Object} DiscoverOptions
|
|
26
49
|
* @property {string} cwd - Project root directory.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check whether a process is still alive.
|
|
3
|
+
*
|
|
4
|
+
* @param {number} pid
|
|
5
|
+
* @returns {boolean}
|
|
6
|
+
*/
|
|
7
|
+
export function isPidAlive(pid: number): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Remove scratch roots left behind by runs whose owning process is no longer alive
|
|
10
|
+
* (a crash or `kill -9` skips the normal exit-time cleanup).
|
|
11
|
+
*
|
|
12
|
+
* @param {string} cwd - Project root.
|
|
13
|
+
* @param {string} scratchDir - Scratch base directory, relative to `cwd` (or absolute).
|
|
14
|
+
* @returns {Promise<void>}
|
|
15
|
+
*/
|
|
16
|
+
export function sweepStaleScratchRoots(cwd: string, scratchDir: string): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Create this run's scratch root: `<cwd>/<scratchDir>/<pid>-<timestamp>/`.
|
|
19
|
+
*
|
|
20
|
+
* @param {string} cwd - Project root.
|
|
21
|
+
* @param {string} scratchDir - Scratch base directory, relative to `cwd` (or absolute).
|
|
22
|
+
* @returns {Promise<string>} Absolute path to the created root.
|
|
23
|
+
*/
|
|
24
|
+
export function createRunScratchRoot(cwd: string, scratchDir: string): Promise<string>;
|
|
25
|
+
/**
|
|
26
|
+
* Create a scratch subdirectory under the run root for one file invocation.
|
|
27
|
+
*
|
|
28
|
+
* @param {string} runRoot - This run's scratch root, from `createRunScratchRoot`.
|
|
29
|
+
* @param {number} index - A unique index for this invocation within the run.
|
|
30
|
+
* @returns {Promise<string>} Absolute path to the created directory.
|
|
31
|
+
*/
|
|
32
|
+
export function createFileScratchDir(runRoot: string, index: number): Promise<string>;
|
|
33
|
+
/**
|
|
34
|
+
* Remove a run's scratch root (async, best-effort).
|
|
35
|
+
*
|
|
36
|
+
* @param {string} root
|
|
37
|
+
* @returns {Promise<void>}
|
|
38
|
+
*/
|
|
39
|
+
export function removeScratchRoot(root: string): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Remove a run's scratch root synchronously (best-effort) — used from a signal
|
|
42
|
+
* handler right before `process.exit()`, where async cleanup can't be awaited.
|
|
43
|
+
*
|
|
44
|
+
* @param {string} root
|
|
45
|
+
* @returns {void}
|
|
46
|
+
*/
|
|
47
|
+
export function removeScratchRootSync(root: string): void;
|
|
48
|
+
/**
|
|
49
|
+
* Create a fresh, uniquely-named scratch subdirectory for the calling test file.
|
|
50
|
+
*
|
|
51
|
+
* Reads the run's scratch directory from `process.env.VITEST_RUNNER_TMP`, which
|
|
52
|
+
* vitest-runner sets in every spawned child. Call this from a test file to get
|
|
53
|
+
* isolated scratch space per test case without managing your own `mkdtemp` base.
|
|
54
|
+
*
|
|
55
|
+
* @param {string} label - A short, human-readable label used as the directory name prefix.
|
|
56
|
+
* @returns {string} Absolute path to the newly created directory.
|
|
57
|
+
* @throws {Error} When `VITEST_RUNNER_TMP` is not set (not running under vitest-runner).
|
|
58
|
+
* @example
|
|
59
|
+
* import { makeRunTmpDir } from "@cldmv/vitest-runner";
|
|
60
|
+
* const dir = makeRunTmpDir("my-fixture");
|
|
61
|
+
*/
|
|
62
|
+
export function makeRunTmpDir(label: string): string;
|
|
63
|
+
/** Default scratch root, relative to `cwd`. */
|
|
64
|
+
export const DEFAULT_SCRATCH_DIR: "tmp/vitest-runner";
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* Run a single Vitest test file in a child process and return parsed results.
|
|
17
17
|
*
|
|
18
18
|
* @param {string} filePath - Test file path (relative to `cwd` or absolute).
|
|
19
|
-
* @param {SpawnBaseOptions & { vitestArgs?: string[], streamOutput?: boolean }} opts
|
|
19
|
+
* @param {SpawnBaseOptions & { vitestArgs?: string[], streamOutput?: boolean, excludePaths?: string[], extraEnv?: NodeJS.ProcessEnv }} opts
|
|
20
20
|
* @returns {Promise<SingleFileResult>}
|
|
21
21
|
* @example
|
|
22
22
|
* const result = await runSingleFile('src/tests/foo.test.vitest.mjs', {
|
|
@@ -28,6 +28,8 @@
|
|
|
28
28
|
export function runSingleFile(filePath: string, opts: SpawnBaseOptions & {
|
|
29
29
|
vitestArgs?: string[];
|
|
30
30
|
streamOutput?: boolean;
|
|
31
|
+
excludePaths?: string[];
|
|
32
|
+
extraEnv?: NodeJS.ProcessEnv;
|
|
31
33
|
}): Promise<SingleFileResult>;
|
|
32
34
|
/**
|
|
33
35
|
* Run Vitest directly (all files in one process) with inherited stdio.
|
package/types/src/runner.d.mts
CHANGED
|
@@ -2,12 +2,19 @@
|
|
|
2
2
|
* Run all discovered Vitest test files sequentially (with a configurable worker
|
|
3
3
|
* pool for the non-solo phase) and return an exit code.
|
|
4
4
|
*
|
|
5
|
+
* Owns the run's scratch directory lifecycle (`scratchDir`/`keepTmp`): sweeps stale
|
|
6
|
+
* roots from dead prior runs, creates this run's root, and removes it on every exit
|
|
7
|
+
* path — normal completion, a thrown error, or SIGINT/SIGTERM — unless `keepTmp` is
|
|
8
|
+
* set. The actual run logic lives in {@link runImpl}; this wrapper only exists to
|
|
9
|
+
* guarantee that cleanup regardless of how `runImpl` returns or throws.
|
|
10
|
+
*
|
|
5
11
|
* @param {RunOptions} opts
|
|
6
12
|
* @returns {Promise<number|object>} `0`/`1` by default; JSON report object when `opts.json` is true.
|
|
7
13
|
*/
|
|
8
14
|
export function run(opts: RunOptions): Promise<number | object>;
|
|
9
15
|
export { formatDuration } from "./utils/duration.mjs";
|
|
10
16
|
export { buildNodeOptions } from "./utils/env.mjs";
|
|
17
|
+
export { makeRunTmpDir } from "./core/scratch.mjs";
|
|
11
18
|
export type PerFileHeapOverride = {
|
|
12
19
|
/**
|
|
13
20
|
* - Substring matched against the normalised file path.
|
|
@@ -107,6 +114,14 @@ export type RunOptions = {
|
|
|
107
114
|
* - Value for `NODE_ENV` in child processes.
|
|
108
115
|
*/
|
|
109
116
|
nodeEnv?: string;
|
|
117
|
+
/**
|
|
118
|
+
* - Per-run scratch root, relative to `cwd` (or absolute). A subdirectory is created per file invocation and exposed to it via `VITEST_RUNNER_TMP`.
|
|
119
|
+
*/
|
|
120
|
+
scratchDir?: string;
|
|
121
|
+
/**
|
|
122
|
+
* - Keep the run's scratch root instead of removing it on completion (normal exit, failure, or SIGINT/SIGTERM).
|
|
123
|
+
*/
|
|
124
|
+
keepTmp?: boolean;
|
|
110
125
|
/**
|
|
111
126
|
* -
|
|
112
127
|
*/
|
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @Project: @cldmv/vitest-runner
|
|
3
|
+
* @Filename: /src/utils/ansi.mjs
|
|
4
|
+
* @Date: 2026-02-24T22:33:55-08:00 (1772001235)
|
|
5
|
+
* @Author: Shinrai <CLDMV>
|
|
6
|
+
* @Email: <Shinrai@users.noreply.github.com>
|
|
7
|
+
* -----
|
|
8
|
+
* @Last modified by: Shinrai <CLDMV> (Shinrai@users.noreply.github.com)
|
|
9
|
+
* @Last modified time: 2026-09-27 08:51:31 -07:00 (1790524291)
|
|
10
|
+
* -----
|
|
11
|
+
* @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved.
|
|
12
|
+
*/
|
|
1
13
|
/**
|
|
2
14
|
* @fileoverview ANSI escape-code helpers.
|
|
3
15
|
* @module vitest-runner/src/utils/ansi
|
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @Project: @cldmv/vitest-runner
|
|
3
|
+
* @Filename: /src/utils/duration.mjs
|
|
4
|
+
* @Date: 2026-02-24T22:33:55-08:00 (1772001235)
|
|
5
|
+
* @Author: Shinrai <CLDMV>
|
|
6
|
+
* @Email: <Shinrai@users.noreply.github.com>
|
|
7
|
+
* -----
|
|
8
|
+
* @Last modified by: Shinrai <CLDMV> (Shinrai@users.noreply.github.com)
|
|
9
|
+
* @Last modified time: 2026-09-27 08:51:31 -07:00 (1790524291)
|
|
10
|
+
* -----
|
|
11
|
+
* @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved.
|
|
12
|
+
*/
|
|
1
13
|
/**
|
|
2
14
|
* @fileoverview Duration formatting utilities.
|
|
3
15
|
* @module vitest-runner/src/utils/duration
|
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @Project: @cldmv/vitest-runner
|
|
3
|
+
* @Filename: /src/utils/env.mjs
|
|
4
|
+
* @Date: 2026-02-24T22:33:55-08:00 (1772001235)
|
|
5
|
+
* @Author: Shinrai <CLDMV>
|
|
6
|
+
* @Email: <Shinrai@users.noreply.github.com>
|
|
7
|
+
* -----
|
|
8
|
+
* @Last modified by: Shinrai <CLDMV> (Shinrai@users.noreply.github.com)
|
|
9
|
+
* @Last modified time: 2026-09-27 08:51:31 -07:00 (1790524291)
|
|
10
|
+
* -----
|
|
11
|
+
* @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved.
|
|
12
|
+
*/
|
|
1
13
|
/**
|
|
2
14
|
* @fileoverview NODE_OPTIONS / environment helpers.
|
|
3
15
|
* @module vitest-runner/src/utils/env
|
package/index.cjs
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview CJS shim — dynamically imports the ESM entry point so that
|
|
3
|
-
* CommonJS callers can `require('vitest-runner')`.
|
|
4
|
-
*
|
|
5
|
-
* Because the package is pure ESM (`"type": "module"`) we cannot use `module.exports =`
|
|
6
|
-
* directly; instead we export a promise and re-attach named exports once resolved.
|
|
7
|
-
*
|
|
8
|
-
* @example
|
|
9
|
-
* // CommonJS usage
|
|
10
|
-
* const { run } = await require('vitest-runner');
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
"use strict";
|
|
14
|
-
|
|
15
|
-
// Async shim: re-export everything from the ESM module.
|
|
16
|
-
// Callers must await the result or use .then().
|
|
17
|
-
module.exports = import("./index.mjs");
|
package/index.mjs
DELETED
package/src/cli/args.mjs
DELETED
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview CLI argument parsing for the vitest-runner binary.
|
|
3
|
-
* @module vitest-runner/src/cli/args
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* @typedef {Object} ParsedArgs
|
|
8
|
-
* @property {string|undefined} testListFile - Path to a JSON file of test paths to run (`--test-list`).
|
|
9
|
-
* @property {boolean} showErrorDetails - `false` when `--no-error-details` was passed.
|
|
10
|
-
* @property {boolean} coverageQuiet - Whether `--coverage-quiet` was passed.
|
|
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.
|
|
18
|
-
* @property {boolean} help - Whether `--help` / `-h` was passed.
|
|
19
|
-
* @property {number|undefined} workers - Worker count from `--workers <n>`, or undefined.
|
|
20
|
-
* @property {string[]} soloPatterns - Path substrings from `--solo-pattern <pattern>` (repeatable).
|
|
21
|
-
* @property {RegExp|undefined} testFilePattern - Compiled regex from `--file-pattern <regex>`, or undefined.
|
|
22
|
-
* @property {string[]} vitestPassthroughArgs - Flags forwarded verbatim to vitest.
|
|
23
|
-
* @property {string[]} testPatterns - Non-flag positional arguments (file / folder patterns).
|
|
24
|
-
*/
|
|
25
|
-
|
|
26
|
-
/** Runner-owned flags that must not be forwarded to vitest. */
|
|
27
|
-
const RUNNER_FLAGS = new Set([
|
|
28
|
-
"--test-list",
|
|
29
|
-
"--no-error-details",
|
|
30
|
-
"--coverage-quiet",
|
|
31
|
-
"--log-file",
|
|
32
|
-
"--suppress-file-output",
|
|
33
|
-
"--suppress-passing-files",
|
|
34
|
-
"--no-top-summary",
|
|
35
|
-
"--json",
|
|
36
|
-
"--blobs-dir",
|
|
37
|
-
"--no-merge-reports",
|
|
38
|
-
"--workers",
|
|
39
|
-
"--solo-pattern",
|
|
40
|
-
"--file-pattern",
|
|
41
|
-
"--help",
|
|
42
|
-
"-h"
|
|
43
|
-
]);
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Parse raw CLI arguments into structured runner options.
|
|
47
|
-
*
|
|
48
|
-
* Runner-specific flags are extracted; everything else (flags and their
|
|
49
|
-
* optional values) is forwarded to vitest as passthrough args.
|
|
50
|
-
* A flag that takes a value (where the next token does not start with `-`)
|
|
51
|
-
* consumes that token too.
|
|
52
|
-
*
|
|
53
|
-
* @param {string[]} args - Raw argument array (typically `process.argv.slice(2)`).
|
|
54
|
-
* @returns {ParsedArgs}
|
|
55
|
-
* @example
|
|
56
|
-
* parseArguments(['--test-list', 'tests.json', '--workers', '2', '--reporter=verbose']);
|
|
57
|
-
*/
|
|
58
|
-
export function parseArguments(args) {
|
|
59
|
-
const vitestPassthroughArgs = [];
|
|
60
|
-
const testPatterns = [];
|
|
61
|
-
const soloPatterns = [];
|
|
62
|
-
let testListFile;
|
|
63
|
-
let showErrorDetails = true;
|
|
64
|
-
let coverageQuiet = false;
|
|
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;
|
|
72
|
-
let workers;
|
|
73
|
-
let help = false;
|
|
74
|
-
let testFilePattern;
|
|
75
|
-
for (let i = 0; i < args.length; i++) {
|
|
76
|
-
const arg = args[i];
|
|
77
|
-
|
|
78
|
-
if (arg === "--test-list") {
|
|
79
|
-
testListFile = args[++i];
|
|
80
|
-
} else if (arg.startsWith("--test-list=")) {
|
|
81
|
-
testListFile = arg.slice("--test-list=".length);
|
|
82
|
-
} else if (arg === "--no-error-details") {
|
|
83
|
-
showErrorDetails = false;
|
|
84
|
-
} else if (arg === "--coverage-quiet") {
|
|
85
|
-
coverageQuiet = true;
|
|
86
|
-
} else if (arg === "--log-file") {
|
|
87
|
-
logFile = args[++i];
|
|
88
|
-
} else if (arg.startsWith("--log-file=")) {
|
|
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;
|
|
104
|
-
} else if (arg === "--workers") {
|
|
105
|
-
workers = parseInt(args[++i], 10);
|
|
106
|
-
} else if (arg.startsWith("--workers=")) {
|
|
107
|
-
workers = parseInt(arg.slice("--workers=".length), 10);
|
|
108
|
-
} else if (arg === "--solo-pattern") {
|
|
109
|
-
soloPatterns.push(args[++i]);
|
|
110
|
-
} else if (arg.startsWith("--solo-pattern=")) {
|
|
111
|
-
soloPatterns.push(arg.slice("--solo-pattern=".length));
|
|
112
|
-
} else if (arg === "--file-pattern") {
|
|
113
|
-
testFilePattern = new RegExp(args[++i], "i");
|
|
114
|
-
} else if (arg.startsWith("--file-pattern=")) {
|
|
115
|
-
testFilePattern = new RegExp(arg.slice("--file-pattern=".length), "i");
|
|
116
|
-
} else if (arg === "--help" || arg === "-h") {
|
|
117
|
-
help = true;
|
|
118
|
-
} else if ((arg.startsWith("--") || arg.startsWith("-")) && !RUNNER_FLAGS.has(arg)) {
|
|
119
|
-
vitestPassthroughArgs.push(arg);
|
|
120
|
-
// Consume the next token if it looks like a value (not another flag)
|
|
121
|
-
if (i + 1 < args.length && !args[i + 1].startsWith("-")) {
|
|
122
|
-
vitestPassthroughArgs.push(args[++i]);
|
|
123
|
-
}
|
|
124
|
-
} else {
|
|
125
|
-
// Any remaining token (cannot start with '-'; those are caught above)
|
|
126
|
-
testPatterns.push(arg);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
return {
|
|
131
|
-
testListFile,
|
|
132
|
-
showErrorDetails,
|
|
133
|
-
coverageQuiet,
|
|
134
|
-
logFile,
|
|
135
|
-
suppressFileOutput,
|
|
136
|
-
suppressPassingFiles,
|
|
137
|
-
topSummary,
|
|
138
|
-
json,
|
|
139
|
-
blobsDir,
|
|
140
|
-
mergeReports,
|
|
141
|
-
help,
|
|
142
|
-
workers,
|
|
143
|
-
soloPatterns,
|
|
144
|
-
testFilePattern,
|
|
145
|
-
vitestPassthroughArgs,
|
|
146
|
-
testPatterns
|
|
147
|
-
};
|
|
148
|
-
}
|
package/src/cli/help.mjs
DELETED
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview CLI help text for the vitest-runner binary.
|
|
3
|
-
* @module vitest-runner/src/cli/help
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import chalk from "chalk";
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Print the full CLI help message to stdout.
|
|
10
|
-
* @returns {void}
|
|
11
|
-
* @example
|
|
12
|
-
* showHelp();
|
|
13
|
-
*/
|
|
14
|
-
export function showHelp() {
|
|
15
|
-
console.log(`
|
|
16
|
-
${chalk.bold("Vitest Sequential Runner")}
|
|
17
|
-
Runs each test file in its own Vitest process to avoid OOM issues.
|
|
18
|
-
|
|
19
|
-
${chalk.bold("USAGE:")}
|
|
20
|
-
vitest-runner [OPTIONS] [PATTERNS]
|
|
21
|
-
|
|
22
|
-
${chalk.bold("SPECIAL FLAGS:")}
|
|
23
|
-
--test-list <file> Run only the files listed in a JSON array file
|
|
24
|
-
--file-pattern <regex> Override the file discovery regex (default: \\.test\\.vitest\\.(?:js|mjs|cjs)$)
|
|
25
|
-
--workers <n> Number of parallel workers (default: 4 or VITEST_WORKERS)
|
|
26
|
-
--solo-pattern <pat> Run files matching this path substring solo first (repeatable)
|
|
27
|
-
--no-error-details Hide detailed error output (show only counts)
|
|
28
|
-
--coverage-quiet Implies --coverage; show progress bar + final summaries only
|
|
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)
|
|
36
|
-
--help, -h Show this help message
|
|
37
|
-
|
|
38
|
-
${chalk.bold("TEST PATTERNS:")}
|
|
39
|
-
[file] Run a specific test file (supports partial paths)
|
|
40
|
-
[folder] Run all tests in a folder
|
|
41
|
-
|
|
42
|
-
Examples:
|
|
43
|
-
src/tests/config/background.test.vitest.mjs
|
|
44
|
-
src/tests/metadata
|
|
45
|
-
background.test.vitest.mjs
|
|
46
|
-
|
|
47
|
-
${chalk.bold("VITEST FLAGS:")}
|
|
48
|
-
All standard Vitest CLI flags are supported and passed through:
|
|
49
|
-
-t, --testNamePattern Filter tests by name pattern (regex)
|
|
50
|
-
--reporter Change reporter (verbose, dot, json, etc.)
|
|
51
|
-
--coverage Run full-suite coverage (blob-per-file + mergeReports)
|
|
52
|
-
--bail Stop on first failure
|
|
53
|
-
|
|
54
|
-
See the Vitest documentation for the full list.
|
|
55
|
-
|
|
56
|
-
${chalk.bold("ENVIRONMENT VARIABLES:")}
|
|
57
|
-
VITEST_HEAP_MB Set max heap size per test (default: Node.js default)
|
|
58
|
-
VITEST_WORKERS Number of parallel workers (default: 4, overridden by --workers)
|
|
59
|
-
# Run all tests
|
|
60
|
-
vitest-runner
|
|
61
|
-
|
|
62
|
-
# Run a specific file (partial path ok)
|
|
63
|
-
vitest-runner src/tests/config/background.test.vitest.mjs
|
|
64
|
-
|
|
65
|
-
# Run only the files listed in a JSON file
|
|
66
|
-
vitest-runner --test-list my-tests.json
|
|
67
|
-
|
|
68
|
-
# Filter by test name
|
|
69
|
-
vitest-runner src/tests/metadata -t "lazy materialization"
|
|
70
|
-
|
|
71
|
-
# Run with 2 workers
|
|
72
|
-
vitest-runner --workers 2
|
|
73
|
-
|
|
74
|
-
# Run files matching a pattern solo first, then the rest in parallel
|
|
75
|
-
vitest-runner --solo-pattern listener-cleanup/ --solo-pattern heavy/
|
|
76
|
-
|
|
77
|
-
# Hide detailed errors
|
|
78
|
-
vitest-runner --no-error-details
|
|
79
|
-
|
|
80
|
-
# Coverage with quiet output + progress bar
|
|
81
|
-
vitest-runner --coverage --coverage-quiet
|
|
82
|
-
|
|
83
|
-
# Custom heap and workers
|
|
84
|
-
VITEST_HEAP_MB=8192 vitest-runner --workers 2 src/tests/heavy
|
|
85
|
-
`);
|
|
86
|
-
}
|
package/src/core/discover.mjs
DELETED
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Test-file discovery utilities.
|
|
3
|
-
* @module vitest-runner/src/core/discover
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import fs from "node:fs/promises";
|
|
7
|
-
import path from "node:path";
|
|
8
|
-
|
|
9
|
-
/** Default pattern matching all supported Vitest test file extensions. */
|
|
10
|
-
export const DEFAULT_TEST_FILE_PATTERN = /\.test\.vitest\.(?:js|mjs|cjs)$/i;
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Recursively discover all Vitest test files under a directory.
|
|
14
|
-
* Skips `node_modules` and hidden directories (names starting with `.`).
|
|
15
|
-
*
|
|
16
|
-
* @param {string} dir - Absolute path of the directory to scan.
|
|
17
|
-
* @param {string} cwd - Project root used to compute relative paths.
|
|
18
|
-
* @param {RegExp} [pattern=DEFAULT_TEST_FILE_PATTERN] - Regex tested against the file name.
|
|
19
|
-
* @returns {Promise<string[]>} Paths relative to `cwd`.
|
|
20
|
-
* @example
|
|
21
|
-
* const files = await discoverFilesInDir('/project/src/tests', '/project');
|
|
22
|
-
*/
|
|
23
|
-
export async function discoverFilesInDir(dir, cwd, pattern = DEFAULT_TEST_FILE_PATTERN) {
|
|
24
|
-
const queue = [dir];
|
|
25
|
-
const files = [];
|
|
26
|
-
|
|
27
|
-
while (queue.length) {
|
|
28
|
-
const current = queue.pop();
|
|
29
|
-
let entries;
|
|
30
|
-
try {
|
|
31
|
-
entries = await fs.readdir(current, { withFileTypes: true });
|
|
32
|
-
} catch {
|
|
33
|
-
continue;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
for (const entry of entries) {
|
|
37
|
-
if (entry.isDirectory()) {
|
|
38
|
-
if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
|
|
39
|
-
queue.push(path.join(current, entry.name));
|
|
40
|
-
continue;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (entry.isFile() && pattern.test(entry.name)) {
|
|
44
|
-
files.push(path.relative(cwd, path.join(current, entry.name)));
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return files;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Sort test files alphabetically while hoisting files matching `earlyRunPatterns`
|
|
54
|
-
* to the front (in pattern-declaration order, then alphabetically within each group).
|
|
55
|
-
*
|
|
56
|
-
* @param {string[]} files - File paths to sort.
|
|
57
|
-
* @param {string[]} [earlyRunPatterns=[]] - Substrings — files whose path contains one run first.
|
|
58
|
-
* @returns {string[]} Sorted file paths.
|
|
59
|
-
* @example
|
|
60
|
-
* sortWithPriority(files, ['listener-cleanup/']);
|
|
61
|
-
*/
|
|
62
|
-
export function sortWithPriority(files, earlyRunPatterns = []) {
|
|
63
|
-
const early = [];
|
|
64
|
-
const rest = [];
|
|
65
|
-
|
|
66
|
-
for (const file of files) {
|
|
67
|
-
const normalized = file.replace(/\\/g, "/");
|
|
68
|
-
const priorityIndex = earlyRunPatterns.findIndex((pat) => normalized.includes(pat));
|
|
69
|
-
if (priorityIndex !== -1) {
|
|
70
|
-
early.push({ file, priorityIndex });
|
|
71
|
-
} else {
|
|
72
|
-
rest.push(file);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
early.sort((a, b) => a.priorityIndex - b.priorityIndex || a.file.localeCompare(b.file));
|
|
77
|
-
rest.sort((a, b) => a.localeCompare(b));
|
|
78
|
-
|
|
79
|
-
return [...early.map((e) => e.file), ...rest];
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* @typedef {Object} DiscoverOptions
|
|
84
|
-
* @property {string} cwd - Project root directory.
|
|
85
|
-
* @property {string} [testDir] - Root directory to search for test files (defaults to `cwd`).
|
|
86
|
-
* @property {string[]} [testPatterns=[]] - File / folder patterns to filter (empty = all files).
|
|
87
|
-
* @property {string} [testListFile] - Path to a JSON array of test file paths to run instead of scanning.
|
|
88
|
-
* @property {RegExp} [testFilePattern] - Regex to match file names (default: `DEFAULT_TEST_FILE_PATTERN`).
|
|
89
|
-
* @property {string[]} [earlyRunPatterns=[]] - Path substrings for files that must run solo first.
|
|
90
|
-
*/
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Discover Vitest test files according to the provided options.
|
|
94
|
-
*
|
|
95
|
-
* | Scenario | Behaviour |
|
|
96
|
-
* |---|---|
|
|
97
|
-
* | `testListFile` set | Reads the exact file list from that JSON file. |
|
|
98
|
-
* | Patterns provided | Resolves each as file / directory, falls back to partial-path match. |
|
|
99
|
-
* | No patterns | Returns all test files found under `testDir`. |
|
|
100
|
-
*
|
|
101
|
-
* @param {DiscoverOptions} opts
|
|
102
|
-
* @returns {Promise<string[]>} Sorted array of test file paths relative to `cwd`.
|
|
103
|
-
* @example
|
|
104
|
-
* const files = await discoverVitestFiles({ cwd: '/project', testDir: '/project/src/tests' });
|
|
105
|
-
*/
|
|
106
|
-
export async function discoverVitestFiles(opts) {
|
|
107
|
-
const { cwd, testDir, testPatterns = [], testListFile, testFilePattern = DEFAULT_TEST_FILE_PATTERN, earlyRunPatterns = [] } = opts;
|
|
108
|
-
|
|
109
|
-
const resolvedTestDir = testDir ? (path.isAbsolute(testDir) ? testDir : path.resolve(cwd, testDir)) : cwd;
|
|
110
|
-
|
|
111
|
-
if (testListFile) {
|
|
112
|
-
const resolvedListPath = path.isAbsolute(testListFile) ? testListFile : path.resolve(cwd, testListFile);
|
|
113
|
-
|
|
114
|
-
let testList;
|
|
115
|
-
try {
|
|
116
|
-
const content = await fs.readFile(resolvedListPath, "utf8");
|
|
117
|
-
testList = JSON.parse(content);
|
|
118
|
-
} catch (err) {
|
|
119
|
-
throw new Error(`Failed to read test list file "${resolvedListPath}": ${err.message}`, { cause: err });
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
if (!Array.isArray(testList)) {
|
|
123
|
-
throw new Error(`Test list file "${resolvedListPath}" must contain a JSON array of test file paths`);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
console.log(`📋 Loading test list from: ${path.relative(cwd, resolvedListPath)}`);
|
|
127
|
-
return sortWithPriority(testList, earlyRunPatterns);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
if (testPatterns.length === 0) {
|
|
131
|
-
const files = await discoverFilesInDir(resolvedTestDir, cwd, testFilePattern);
|
|
132
|
-
return sortWithPriority(files, earlyRunPatterns);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const files = [];
|
|
136
|
-
|
|
137
|
-
for (const pattern of testPatterns) {
|
|
138
|
-
const absPath = path.isAbsolute(pattern) ? pattern : path.resolve(cwd, pattern);
|
|
139
|
-
|
|
140
|
-
let stat = null;
|
|
141
|
-
try {
|
|
142
|
-
stat = await fs.stat(absPath);
|
|
143
|
-
} catch {
|
|
144
|
-
// path doesn't exist — fall through to partial-match
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if (stat?.isFile()) {
|
|
148
|
-
if (testFilePattern.test(absPath)) {
|
|
149
|
-
files.push(path.relative(cwd, absPath));
|
|
150
|
-
}
|
|
151
|
-
} else if (stat?.isDirectory()) {
|
|
152
|
-
files.push(...(await discoverFilesInDir(absPath, cwd, testFilePattern)));
|
|
153
|
-
} else {
|
|
154
|
-
// Partial-path matching against all files in testDir
|
|
155
|
-
const allFiles = await discoverFilesInDir(resolvedTestDir, cwd, testFilePattern);
|
|
156
|
-
const matched = allFiles.filter((f) => f.replace(/\\/g, "/").includes(pattern.replace(/\\/g, "/")));
|
|
157
|
-
|
|
158
|
-
if (matched.length > 0) {
|
|
159
|
-
files.push(...matched);
|
|
160
|
-
} else {
|
|
161
|
-
console.warn(`⚠️ No matches found for: ${pattern}`);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
return sortWithPriority([...new Set(files)], earlyRunPatterns);
|
|
167
|
-
}
|