@savvy-web/vitest 1.4.0 → 1.5.1
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/index.d.ts +523 -556
- package/index.js +783 -517
- package/package.json +49 -59
- package/tsdoc-metadata.json +11 -11
package/index.js
CHANGED
|
@@ -3,520 +3,786 @@ import { cpus } from "node:os";
|
|
|
3
3
|
import { join, relative } from "node:path";
|
|
4
4
|
import { AgentPlugin } from "vitest-agent-reporter";
|
|
5
5
|
import { getWorkspaceManagerRoot, getWorkspacePackagePaths } from "workspace-tools";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
6
|
+
|
|
7
|
+
//#region src/index.ts
|
|
8
|
+
/**
|
|
9
|
+
* Vitest utility functions for automatic project configuration discovery
|
|
10
|
+
* in pnpm monorepo workspaces.
|
|
11
|
+
*
|
|
12
|
+
* @remarks
|
|
13
|
+
* This package provides two main classes:
|
|
14
|
+
*
|
|
15
|
+
* - {@link VitestProject} - Represents a single Vitest project with sensible
|
|
16
|
+
* defaults per test kind (unit, e2e, or custom).
|
|
17
|
+
*
|
|
18
|
+
* - {@link VitestConfig} - Orchestrates workspace discovery, coverage
|
|
19
|
+
* configuration, reporter selection, and callback invocation.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* import { VitestConfig } from "@savvy-web/vitest";
|
|
24
|
+
*
|
|
25
|
+
* export default VitestConfig.create();
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* @packageDocumentation
|
|
29
|
+
*/
|
|
30
|
+
/**
|
|
31
|
+
* Represents a single Vitest project with sensible defaults per test kind.
|
|
32
|
+
*
|
|
33
|
+
* @remarks
|
|
34
|
+
* Instances are created through static factory methods. The private constructor
|
|
35
|
+
* enforces that all projects are built with validated merge semantics.
|
|
36
|
+
*
|
|
37
|
+
* Override merge precedence (highest wins):
|
|
38
|
+
* 1. `name` and `include` from options (always win)
|
|
39
|
+
* 2. `overrides.test` fields
|
|
40
|
+
* 3. Factory defaults for `test`
|
|
41
|
+
* 4. Top-level: `overrides` rest spreads over factory defaults rest
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```typescript
|
|
45
|
+
* import { VitestProject } from "@savvy-web/vitest";
|
|
46
|
+
*
|
|
47
|
+
* const unitProject = VitestProject.unit({
|
|
48
|
+
* name: "@savvy-web/my-lib",
|
|
49
|
+
* include: ["src/**\/*.test.ts"],
|
|
50
|
+
* });
|
|
51
|
+
*
|
|
52
|
+
* const e2eProject = VitestProject.e2e({
|
|
53
|
+
* name: "@savvy-web/my-lib:e2e",
|
|
54
|
+
* include: ["test/e2e/**\/*.test.ts"],
|
|
55
|
+
* });
|
|
56
|
+
*
|
|
57
|
+
* const config = unitProject.toConfig();
|
|
58
|
+
* ```
|
|
59
|
+
*
|
|
60
|
+
* @public
|
|
61
|
+
*/
|
|
62
|
+
var VitestProject = class VitestProject {
|
|
63
|
+
#name;
|
|
64
|
+
#kind;
|
|
65
|
+
#config;
|
|
66
|
+
#coverageExcludes = [];
|
|
67
|
+
constructor(options, defaults) {
|
|
68
|
+
this.#name = options.name;
|
|
69
|
+
this.#kind = options.kind ?? "unit";
|
|
70
|
+
const { test: defaultTest, ...defaultRest } = defaults;
|
|
71
|
+
const { test: overrideTest, ...overrideRest } = options.overrides ?? {};
|
|
72
|
+
this.#config = {
|
|
73
|
+
extends: true,
|
|
74
|
+
...defaultRest,
|
|
75
|
+
...overrideRest,
|
|
76
|
+
test: {
|
|
77
|
+
...defaultTest,
|
|
78
|
+
...overrideTest,
|
|
79
|
+
name: options.name,
|
|
80
|
+
include: options.include
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The project name.
|
|
86
|
+
* @see {@link VitestProjectOptions.name}
|
|
87
|
+
*/
|
|
88
|
+
get name() {
|
|
89
|
+
return this.#name;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* The test kind (e.g., `"unit"`, `"e2e"`, or a custom string).
|
|
93
|
+
* @see {@link VitestProjectKind}
|
|
94
|
+
*/
|
|
95
|
+
get kind() {
|
|
96
|
+
return this.#kind;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Coverage exclusion patterns accumulated via {@link addCoverageExclude}.
|
|
100
|
+
*
|
|
101
|
+
* @remarks
|
|
102
|
+
* These patterns are not embedded in the inline project config but are
|
|
103
|
+
* made available for the workspace-level coverage configuration to consume.
|
|
104
|
+
*/
|
|
105
|
+
get coverageExcludes() {
|
|
106
|
+
return this.#coverageExcludes;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Returns the vitest-native inline configuration object.
|
|
110
|
+
*
|
|
111
|
+
* @returns A {@link https://vitest.dev/config/ | TestProjectInlineConfiguration}
|
|
112
|
+
* with all defaults and overrides merged
|
|
113
|
+
*/
|
|
114
|
+
toConfig() {
|
|
115
|
+
return this.#config;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Creates a clone of this project with independent config state.
|
|
119
|
+
*
|
|
120
|
+
* @remarks
|
|
121
|
+
* The clone has its own config object so mutations via
|
|
122
|
+
* {@link override}, {@link addInclude}, {@link addExclude}, and
|
|
123
|
+
* {@link addCoverageExclude} do not affect the original.
|
|
124
|
+
*
|
|
125
|
+
* @returns A new {@link VitestProject} with the same configuration
|
|
126
|
+
*/
|
|
127
|
+
clone() {
|
|
128
|
+
const { test, ...rest } = this.#config;
|
|
129
|
+
const cloned = new VitestProject({
|
|
130
|
+
name: this.#name,
|
|
131
|
+
include: test?.include ?? [],
|
|
132
|
+
kind: this.#kind
|
|
133
|
+
}, {});
|
|
134
|
+
cloned.#config = {
|
|
135
|
+
...rest,
|
|
136
|
+
test: test ? { ...test } : void 0
|
|
137
|
+
};
|
|
138
|
+
cloned.#coverageExcludes.push(...this.#coverageExcludes);
|
|
139
|
+
return cloned;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Merges additional configuration over the current config.
|
|
143
|
+
*
|
|
144
|
+
* @remarks
|
|
145
|
+
* The {@link VitestProjectOptions.name | name} and
|
|
146
|
+
* {@link VitestProjectOptions.include | include} fields are preserved
|
|
147
|
+
* and cannot be overridden.
|
|
148
|
+
*
|
|
149
|
+
* @param config - Partial configuration to merge
|
|
150
|
+
* @returns `this` for chaining
|
|
151
|
+
*/
|
|
152
|
+
override(config) {
|
|
153
|
+
const { test: overrideTest, ...overrideRest } = config;
|
|
154
|
+
const { test: existingTest, ...existingRest } = this.#config;
|
|
155
|
+
this.#config = {
|
|
156
|
+
...existingRest,
|
|
157
|
+
...overrideRest,
|
|
158
|
+
test: {
|
|
159
|
+
...existingTest,
|
|
160
|
+
...overrideTest,
|
|
161
|
+
name: this.#name,
|
|
162
|
+
include: existingTest?.include
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
return this;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Appends glob patterns to the test include list.
|
|
169
|
+
*
|
|
170
|
+
* @param patterns - Glob patterns to add
|
|
171
|
+
* @returns `this` for chaining
|
|
172
|
+
*/
|
|
173
|
+
addInclude(...patterns) {
|
|
174
|
+
const { test: existingTest, ...rest } = this.#config;
|
|
175
|
+
this.#config = {
|
|
176
|
+
...rest,
|
|
177
|
+
test: {
|
|
178
|
+
...existingTest,
|
|
179
|
+
include: [...existingTest?.include ?? [], ...patterns]
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
return this;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Appends glob patterns to the test exclude list.
|
|
186
|
+
*
|
|
187
|
+
* @param patterns - Glob patterns to add
|
|
188
|
+
* @returns `this` for chaining
|
|
189
|
+
*/
|
|
190
|
+
addExclude(...patterns) {
|
|
191
|
+
const { test: existingTest, ...rest } = this.#config;
|
|
192
|
+
this.#config = {
|
|
193
|
+
...rest,
|
|
194
|
+
test: {
|
|
195
|
+
...existingTest,
|
|
196
|
+
exclude: [...existingTest?.exclude ?? [], ...patterns]
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
return this;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Appends glob patterns to the coverage exclusion list.
|
|
203
|
+
*
|
|
204
|
+
* @remarks
|
|
205
|
+
* These patterns are exposed via {@link coverageExcludes} for the
|
|
206
|
+
* workspace-level coverage configuration to consume.
|
|
207
|
+
*
|
|
208
|
+
* @param patterns - Glob patterns to exclude from coverage
|
|
209
|
+
* @returns `this` for chaining
|
|
210
|
+
*/
|
|
211
|
+
addCoverageExclude(...patterns) {
|
|
212
|
+
this.#coverageExcludes.push(...patterns);
|
|
213
|
+
return this;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Creates a unit test project with sensible defaults.
|
|
217
|
+
*
|
|
218
|
+
* @remarks
|
|
219
|
+
* Defaults applied: `extends: true`, `environment: "node"`.
|
|
220
|
+
*
|
|
221
|
+
* @param options - Project options (the `kind` field is forced to `"unit"`)
|
|
222
|
+
* @returns A new {@link VitestProject} configured for unit tests
|
|
223
|
+
*
|
|
224
|
+
* @example
|
|
225
|
+
* ```typescript
|
|
226
|
+
* import { VitestProject } from "@savvy-web/vitest";
|
|
227
|
+
*
|
|
228
|
+
* const project = VitestProject.unit({
|
|
229
|
+
* name: "@savvy-web/my-lib",
|
|
230
|
+
* include: ["src/**\/*.test.ts"],
|
|
231
|
+
* });
|
|
232
|
+
* ```
|
|
233
|
+
*/
|
|
234
|
+
static unit(options) {
|
|
235
|
+
return new VitestProject({
|
|
236
|
+
...options,
|
|
237
|
+
kind: "unit"
|
|
238
|
+
}, { test: { environment: "node" } });
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Creates an e2e test project with sensible defaults.
|
|
242
|
+
*
|
|
243
|
+
* @remarks
|
|
244
|
+
* Defaults applied: `extends: true`, `environment: "node"`,
|
|
245
|
+
* `testTimeout: 120_000`, `hookTimeout: 60_000`,
|
|
246
|
+
* `maxConcurrency: clamp(floor(cpus / 2), 1, 8)`.
|
|
247
|
+
*
|
|
248
|
+
* @param options - Project options (the `kind` field is forced to `"e2e"`)
|
|
249
|
+
* @returns A new {@link VitestProject} configured for e2e tests
|
|
250
|
+
*
|
|
251
|
+
* @example
|
|
252
|
+
* ```typescript
|
|
253
|
+
* import { VitestProject } from "@savvy-web/vitest";
|
|
254
|
+
*
|
|
255
|
+
* const project = VitestProject.e2e({
|
|
256
|
+
* name: "@savvy-web/my-lib:e2e",
|
|
257
|
+
* include: ["test/e2e/**\/*.test.ts"],
|
|
258
|
+
* });
|
|
259
|
+
* ```
|
|
260
|
+
*/
|
|
261
|
+
static e2e(options) {
|
|
262
|
+
const concurrency = Math.max(1, Math.min(8, Math.floor(cpus().length / 2)));
|
|
263
|
+
return new VitestProject({
|
|
264
|
+
...options,
|
|
265
|
+
kind: "e2e"
|
|
266
|
+
}, { test: {
|
|
267
|
+
environment: "node",
|
|
268
|
+
testTimeout: 12e4,
|
|
269
|
+
hookTimeout: 6e4,
|
|
270
|
+
maxConcurrency: concurrency
|
|
271
|
+
} });
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Creates an integration test project with sensible defaults.
|
|
275
|
+
*
|
|
276
|
+
* @remarks
|
|
277
|
+
* Defaults applied: `extends: true`, `environment: "node"`,
|
|
278
|
+
* `testTimeout: 60_000`, `hookTimeout: 30_000`,
|
|
279
|
+
* `maxConcurrency: clamp(floor(cpus / 2), 1, 8)`.
|
|
280
|
+
*
|
|
281
|
+
* @param options - Project options (the `kind` field is forced to `"int"`)
|
|
282
|
+
* @returns A new {@link VitestProject} configured for integration tests
|
|
283
|
+
*
|
|
284
|
+
* @example
|
|
285
|
+
* ```typescript
|
|
286
|
+
* import { VitestProject } from "@savvy-web/vitest";
|
|
287
|
+
*
|
|
288
|
+
* const project = VitestProject.int({
|
|
289
|
+
* name: "@savvy-web/my-lib:int",
|
|
290
|
+
* include: ["__test__/integration/**\/*.int.test.ts"],
|
|
291
|
+
* });
|
|
292
|
+
* ```
|
|
293
|
+
*/
|
|
294
|
+
static int(options) {
|
|
295
|
+
const concurrency = Math.max(1, Math.min(8, Math.floor(cpus().length / 2)));
|
|
296
|
+
return new VitestProject({
|
|
297
|
+
...options,
|
|
298
|
+
kind: "int"
|
|
299
|
+
}, { test: {
|
|
300
|
+
environment: "node",
|
|
301
|
+
testTimeout: 6e4,
|
|
302
|
+
hookTimeout: 3e4,
|
|
303
|
+
maxConcurrency: concurrency
|
|
304
|
+
} });
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Creates a custom test project with no preset defaults beyond `extends: true`.
|
|
308
|
+
*
|
|
309
|
+
* @remarks
|
|
310
|
+
* Use this factory when the built-in `unit()` and `e2e()` presets do not
|
|
311
|
+
* match your needs. The `kind` string is stored on the instance but does
|
|
312
|
+
* not influence any default configuration.
|
|
313
|
+
*
|
|
314
|
+
* @param kind - A custom kind string (e.g., `"integration"`, `"smoke"`)
|
|
315
|
+
* @param options - Project options
|
|
316
|
+
* @returns A new {@link VitestProject} with no preset defaults
|
|
317
|
+
*
|
|
318
|
+
* @example
|
|
319
|
+
* ```typescript
|
|
320
|
+
* import { VitestProject } from "@savvy-web/vitest";
|
|
321
|
+
*
|
|
322
|
+
* const project = VitestProject.custom("integration", {
|
|
323
|
+
* name: "@savvy-web/my-lib:integration",
|
|
324
|
+
* include: ["test/integration/**\/*.test.ts"],
|
|
325
|
+
* });
|
|
326
|
+
* ```
|
|
327
|
+
*/
|
|
328
|
+
static custom(kind, options) {
|
|
329
|
+
return new VitestProject({
|
|
330
|
+
...options,
|
|
331
|
+
kind
|
|
332
|
+
}, {});
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
/**
|
|
336
|
+
* Utility class for generating Vitest configuration in monorepo workspaces.
|
|
337
|
+
*
|
|
338
|
+
* @remarks
|
|
339
|
+
* This class automatically discovers packages in a workspace that contain a
|
|
340
|
+
* `src/` directory and generates appropriate {@link VitestProject} configurations.
|
|
341
|
+
* Tests are discovered by filename convention:
|
|
342
|
+
*
|
|
343
|
+
* | Pattern | Kind |
|
|
344
|
+
* | --- | --- |
|
|
345
|
+
* | `*.test.ts` / `*.spec.ts` | unit |
|
|
346
|
+
* | `*.e2e.test.ts` / `*.e2e.spec.ts` | e2e |
|
|
347
|
+
*
|
|
348
|
+
* It supports both running all tests and targeting specific projects via the
|
|
349
|
+
* `--project` command line argument.
|
|
350
|
+
*
|
|
351
|
+
* Results are cached in static properties so that repeated config evaluations
|
|
352
|
+
* during watch mode or HMR do not re-scan the filesystem.
|
|
353
|
+
*
|
|
354
|
+
* @example
|
|
355
|
+
* ```typescript
|
|
356
|
+
* import { VitestConfig } from "@savvy-web/vitest";
|
|
357
|
+
*
|
|
358
|
+
* export default VitestConfig.create();
|
|
359
|
+
* ```
|
|
360
|
+
*
|
|
361
|
+
* @public
|
|
362
|
+
*/
|
|
363
|
+
var VitestConfig = class VitestConfig {
|
|
364
|
+
/** Default glob patterns excluded from coverage reporting. */
|
|
365
|
+
static DEFAULT_COVERAGE_EXCLUDE = [
|
|
366
|
+
"**/*.{test,spec}.{ts,tsx,js,jsx}",
|
|
367
|
+
"**/__test__/**",
|
|
368
|
+
"**/generated/**"
|
|
369
|
+
];
|
|
370
|
+
/**
|
|
371
|
+
* Named coverage level presets.
|
|
372
|
+
*
|
|
373
|
+
* @remarks
|
|
374
|
+
* Use a level name with the `coverage` option in {@link VitestConfig.create}
|
|
375
|
+
* to apply a preset. The object is frozen and cannot be mutated.
|
|
376
|
+
*
|
|
377
|
+
* | Level | lines | branches | functions | statements |
|
|
378
|
+
* | -------- | ----- | -------- | --------- | ---------- |
|
|
379
|
+
* | none | 0 | 0 | 0 | 0 |
|
|
380
|
+
* | basic | 50 | 50 | 50 | 50 |
|
|
381
|
+
* | standard | 70 | 65 | 70 | 70 |
|
|
382
|
+
* | strict | 80 | 75 | 80 | 80 |
|
|
383
|
+
* | full | 90 | 85 | 90 | 90 |
|
|
384
|
+
*/
|
|
385
|
+
static COVERAGE_LEVELS = Object.freeze({
|
|
386
|
+
none: {
|
|
387
|
+
lines: 0,
|
|
388
|
+
branches: 0,
|
|
389
|
+
functions: 0,
|
|
390
|
+
statements: 0
|
|
391
|
+
},
|
|
392
|
+
basic: {
|
|
393
|
+
lines: 50,
|
|
394
|
+
branches: 50,
|
|
395
|
+
functions: 50,
|
|
396
|
+
statements: 50
|
|
397
|
+
},
|
|
398
|
+
standard: {
|
|
399
|
+
lines: 70,
|
|
400
|
+
branches: 65,
|
|
401
|
+
functions: 70,
|
|
402
|
+
statements: 70
|
|
403
|
+
},
|
|
404
|
+
strict: {
|
|
405
|
+
lines: 80,
|
|
406
|
+
branches: 75,
|
|
407
|
+
functions: 80,
|
|
408
|
+
statements: 80
|
|
409
|
+
},
|
|
410
|
+
full: {
|
|
411
|
+
lines: 90,
|
|
412
|
+
branches: 85,
|
|
413
|
+
functions: 90,
|
|
414
|
+
statements: 90
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
static cachedProjects = null;
|
|
418
|
+
static cachedVitestProjects = null;
|
|
419
|
+
/**
|
|
420
|
+
* Creates a complete Vitest configuration by discovering workspace projects
|
|
421
|
+
* and generating appropriate settings.
|
|
422
|
+
*
|
|
423
|
+
* @param options - Optional declarative configuration
|
|
424
|
+
* @param postProcess - Optional escape-hatch callback for full config control
|
|
425
|
+
* @returns The assembled Vitest configuration
|
|
426
|
+
*
|
|
427
|
+
* @see {@link VitestConfigOptions} for available options
|
|
428
|
+
* @see {@link PostProcessCallback} for the post-process callback signature
|
|
429
|
+
*/
|
|
430
|
+
static async create(options, postProcess) {
|
|
431
|
+
const specificProjects = VitestConfig.getSpecificProjects();
|
|
432
|
+
const { projects, vitestProjects } = VitestConfig.discoverWorkspaceProjects();
|
|
433
|
+
const thresholds = VitestConfig.resolveThresholds(options);
|
|
434
|
+
const coverageConfig = VitestConfig.getCoverageConfig(specificProjects, projects, options);
|
|
435
|
+
const workingProjects = vitestProjects.map((p) => p.clone());
|
|
436
|
+
VitestConfig.applyKindOverrides(workingProjects, options);
|
|
437
|
+
let config = { test: {
|
|
438
|
+
reporters: Boolean(process.env.GITHUB_ACTIONS) ? ["default", "github-actions"] : ["default"],
|
|
439
|
+
projects: workingProjects.map((p) => p.toConfig()),
|
|
440
|
+
...options?.pool ? { pool: options.pool } : {},
|
|
441
|
+
coverage: {
|
|
442
|
+
provider: "v8",
|
|
443
|
+
...coverageConfig,
|
|
444
|
+
enabled: true
|
|
445
|
+
}
|
|
446
|
+
} };
|
|
447
|
+
if (options?.agentReporter !== false) {
|
|
448
|
+
const agentOpts = typeof options?.agentReporter === "object" ? options.agentReporter : {};
|
|
449
|
+
const targets = VitestConfig.resolveTargets(options);
|
|
450
|
+
const hasExplicitTargets = agentOpts.reporter?.coverageTargets !== void 0;
|
|
451
|
+
if (hasExplicitTargets && options?.coverageTargets !== void 0) console.warn("[@savvy-web/vitest] Both top-level coverageTargets and agentReporter.reporter.coverageTargets are set. Using agentReporter.reporter.coverageTargets.");
|
|
452
|
+
const plugin = AgentPlugin({
|
|
453
|
+
strategy: "own",
|
|
454
|
+
...agentOpts,
|
|
455
|
+
reporter: {
|
|
456
|
+
coverageThresholds: { ...thresholds },
|
|
457
|
+
...!hasExplicitTargets ? { coverageTargets: { ...targets } } : {},
|
|
458
|
+
...agentOpts.reporter
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
config.plugins = [plugin];
|
|
462
|
+
}
|
|
463
|
+
if (postProcess) {
|
|
464
|
+
const result = postProcess(config);
|
|
465
|
+
if (result !== void 0) config = result;
|
|
466
|
+
}
|
|
467
|
+
return config;
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Applies kind-specific overrides to discovered projects.
|
|
471
|
+
*
|
|
472
|
+
* @privateRemarks
|
|
473
|
+
* When the override is an object, it is merged into every project of the
|
|
474
|
+
* matching kind. When it is a callback, it receives a Map of project name
|
|
475
|
+
* to {@link VitestProject} for fine-grained per-project mutation.
|
|
476
|
+
*/
|
|
477
|
+
static applyKindOverrides(vitestProjects, options) {
|
|
478
|
+
if (!options) return;
|
|
479
|
+
const kindOptions = {
|
|
480
|
+
unit: options.unit,
|
|
481
|
+
e2e: options.e2e,
|
|
482
|
+
int: options.int
|
|
483
|
+
};
|
|
484
|
+
for (const [kind, override] of Object.entries(kindOptions)) {
|
|
485
|
+
if (override === void 0) continue;
|
|
486
|
+
const projectsOfKind = vitestProjects.filter((p) => p.kind === kind);
|
|
487
|
+
if (typeof override === "function") {
|
|
488
|
+
const map = /* @__PURE__ */ new Map();
|
|
489
|
+
for (const p of projectsOfKind) map.set(p.name, p);
|
|
490
|
+
override(map);
|
|
491
|
+
} else for (const p of projectsOfKind) p.override({ test: override });
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Extracts all specific project names from command line arguments.
|
|
496
|
+
*
|
|
497
|
+
* @privateRemarks
|
|
498
|
+
* Supports both `--project=value` and `--project value` formats to match
|
|
499
|
+
* Vitest's own argument parsing behavior. All `--project` flags are
|
|
500
|
+
* collected to support multi-project coverage scoping.
|
|
501
|
+
*/
|
|
502
|
+
static getSpecificProjects() {
|
|
503
|
+
const args = process.argv;
|
|
504
|
+
const projects = [];
|
|
505
|
+
for (let i = 0; i < args.length; i++) {
|
|
506
|
+
const arg = args[i];
|
|
507
|
+
if (arg.startsWith("--project=")) {
|
|
508
|
+
const value = arg.split("=")[1];
|
|
509
|
+
if (value) projects.push(value);
|
|
510
|
+
} else if (arg === "--project" && i + 1 < args.length) {
|
|
511
|
+
const value = args[i + 1];
|
|
512
|
+
if (value) projects.push(value);
|
|
513
|
+
i++;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
return projects;
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Reads the `name` field from a package's `package.json`.
|
|
520
|
+
*
|
|
521
|
+
* @privateRemarks
|
|
522
|
+
* Uses try/catch because the package.json may not exist or may be malformed.
|
|
523
|
+
* Returns `null` to signal the caller should skip this package.
|
|
524
|
+
*/
|
|
525
|
+
static getPackageNameFromPath(packagePath) {
|
|
526
|
+
try {
|
|
527
|
+
const content = readFileSync(join(packagePath, "package.json"), "utf8");
|
|
528
|
+
return JSON.parse(content).name ?? null;
|
|
529
|
+
} catch {
|
|
530
|
+
return null;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Checks whether a path is an existing directory.
|
|
535
|
+
*
|
|
536
|
+
* @privateRemarks
|
|
537
|
+
* Consolidates the repeated `statSync` + `isDirectory()` + try/catch
|
|
538
|
+
* pattern used throughout workspace discovery.
|
|
539
|
+
*/
|
|
540
|
+
static isDirectory(dirPath) {
|
|
541
|
+
try {
|
|
542
|
+
return statSync(dirPath).isDirectory();
|
|
543
|
+
} catch {
|
|
544
|
+
return false;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
/** Extensions probed (in order) when detecting a setup file. */
|
|
548
|
+
static SETUP_FILE_EXTENSIONS = [
|
|
549
|
+
"ts",
|
|
550
|
+
"tsx",
|
|
551
|
+
"js",
|
|
552
|
+
"jsx"
|
|
553
|
+
];
|
|
554
|
+
/**
|
|
555
|
+
* Detects a `vitest.setup.{ts,tsx,js,jsx}` file at the package root.
|
|
556
|
+
*
|
|
557
|
+
* @privateRemarks
|
|
558
|
+
* First match wins. Returns just the filename (e.g. `"vitest.setup.ts"`)
|
|
559
|
+
* so the caller can prepend the relative prefix as needed.
|
|
560
|
+
*/
|
|
561
|
+
static detectSetupFile(packagePath) {
|
|
562
|
+
for (const ext of VitestConfig.SETUP_FILE_EXTENSIONS) {
|
|
563
|
+
const candidate = join(packagePath, `vitest.setup.${ext}`);
|
|
564
|
+
try {
|
|
565
|
+
if (statSync(candidate).isFile()) return `vitest.setup.${ext}`;
|
|
566
|
+
} catch {}
|
|
567
|
+
}
|
|
568
|
+
return null;
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Recursively scans a directory for test files and classifies them by kind.
|
|
572
|
+
*
|
|
573
|
+
* @privateRemarks
|
|
574
|
+
* Short-circuits as soon as all three kinds (unit, e2e, and int) are
|
|
575
|
+
* found, avoiding unnecessary filesystem traversal.
|
|
576
|
+
*/
|
|
577
|
+
static scanForTestFiles(dirPath) {
|
|
578
|
+
let hasUnit = false;
|
|
579
|
+
let hasE2e = false;
|
|
580
|
+
let hasInt = false;
|
|
581
|
+
try {
|
|
582
|
+
const entries = readdirSync(dirPath, { withFileTypes: true });
|
|
583
|
+
for (const entry of entries) {
|
|
584
|
+
if (entry.isDirectory()) {
|
|
585
|
+
const sub = VitestConfig.scanForTestFiles(join(dirPath, entry.name));
|
|
586
|
+
hasUnit = hasUnit || sub.hasUnit;
|
|
587
|
+
hasE2e = hasE2e || sub.hasE2e;
|
|
588
|
+
hasInt = hasInt || sub.hasInt;
|
|
589
|
+
} else if (entry.isFile()) {
|
|
590
|
+
if (/\.e2e\.(test|spec)\.(ts|tsx|js|jsx)$/.test(entry.name)) hasE2e = true;
|
|
591
|
+
else if (/\.int\.(test|spec)\.(ts|tsx|js|jsx)$/.test(entry.name)) hasInt = true;
|
|
592
|
+
else if (/\.(test|spec)\.(ts|tsx|js|jsx)$/.test(entry.name)) hasUnit = true;
|
|
593
|
+
}
|
|
594
|
+
if (hasUnit && hasE2e && hasInt) break;
|
|
595
|
+
}
|
|
596
|
+
} catch {}
|
|
597
|
+
return {
|
|
598
|
+
hasUnit,
|
|
599
|
+
hasE2e,
|
|
600
|
+
hasInt
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* Builds include glob patterns for a given relative path and optional
|
|
605
|
+
* test directory.
|
|
606
|
+
*/
|
|
607
|
+
static buildIncludes(srcGlob, testGlob, pattern) {
|
|
608
|
+
const includes = [`${srcGlob}/**/${pattern}`];
|
|
609
|
+
if (testGlob) includes.push(`${testGlob}/**/${pattern}`);
|
|
610
|
+
return includes;
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* Conventional subdirectories under `__test__/` that hold helpers, not
|
|
614
|
+
* test files, and should be excluded from test discovery.
|
|
615
|
+
*/
|
|
616
|
+
static TEST_DIR_EXCLUSIONS = [
|
|
617
|
+
"__test__/fixtures/**",
|
|
618
|
+
"__test__/utils/**",
|
|
619
|
+
"__test__/unit/fixtures/**",
|
|
620
|
+
"__test__/unit/utils/**",
|
|
621
|
+
"__test__/e2e/fixtures/**",
|
|
622
|
+
"__test__/e2e/utils/**",
|
|
623
|
+
"__test__/int/fixtures/**",
|
|
624
|
+
"__test__/int/utils/**",
|
|
625
|
+
"__test__/integration/fixtures/**",
|
|
626
|
+
"__test__/integration/utils/**"
|
|
627
|
+
];
|
|
628
|
+
/**
|
|
629
|
+
* Returns exclusion patterns for fixture/utils directories under
|
|
630
|
+
* `__test__/`, scoped to the given package prefix.
|
|
631
|
+
*
|
|
632
|
+
* @param prefix - Either `"<relativePath>/"` for non-root packages or
|
|
633
|
+
* `""` for the workspace root.
|
|
634
|
+
*/
|
|
635
|
+
static buildTestDirExclusions(prefix) {
|
|
636
|
+
return VitestConfig.TEST_DIR_EXCLUSIONS.map((pattern) => `${prefix}${pattern}`);
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Discovers all packages in the workspace that contain a `src/` directory
|
|
640
|
+
* and generates {@link VitestProject} instances based on filename conventions.
|
|
641
|
+
*
|
|
642
|
+
* @privateRemarks
|
|
643
|
+
* When a package has both unit and e2e test files, projects are suffixed
|
|
644
|
+
* with `:unit` and `:e2e` to disambiguate. Packages with `src/` but no
|
|
645
|
+
* test files still get a unit project entry as a forward-looking placeholder.
|
|
646
|
+
*/
|
|
647
|
+
static discoverWorkspaceProjects() {
|
|
648
|
+
if (VitestConfig.cachedProjects && VitestConfig.cachedVitestProjects) return {
|
|
649
|
+
projects: VitestConfig.cachedProjects,
|
|
650
|
+
vitestProjects: VitestConfig.cachedVitestProjects
|
|
651
|
+
};
|
|
652
|
+
const cwd = process.cwd();
|
|
653
|
+
const workspaceRoot = getWorkspaceManagerRoot(cwd) ?? cwd;
|
|
654
|
+
const workspacePaths = getWorkspacePackagePaths(workspaceRoot) ?? [];
|
|
655
|
+
const projects = {};
|
|
656
|
+
const vitestProjects = [];
|
|
657
|
+
for (const pkgPath of workspacePaths) {
|
|
658
|
+
const packageName = VitestConfig.getPackageNameFromPath(pkgPath);
|
|
659
|
+
if (!packageName) continue;
|
|
660
|
+
const srcDirPath = join(pkgPath, "src");
|
|
661
|
+
if (!VitestConfig.isDirectory(srcDirPath)) continue;
|
|
662
|
+
const relativePath = relative(workspaceRoot, pkgPath) || ".";
|
|
663
|
+
projects[packageName] = relativePath;
|
|
664
|
+
const testDirPath = join(pkgPath, "__test__");
|
|
665
|
+
const hasTestDir = VitestConfig.isDirectory(testDirPath);
|
|
666
|
+
const srcScan = VitestConfig.scanForTestFiles(srcDirPath);
|
|
667
|
+
const testScan = hasTestDir ? VitestConfig.scanForTestFiles(testDirPath) : {
|
|
668
|
+
hasUnit: false,
|
|
669
|
+
hasE2e: false,
|
|
670
|
+
hasInt: false
|
|
671
|
+
};
|
|
672
|
+
const hasUnit = srcScan.hasUnit || testScan.hasUnit;
|
|
673
|
+
const hasE2e = srcScan.hasE2e || testScan.hasE2e;
|
|
674
|
+
const hasInt = srcScan.hasInt || testScan.hasInt;
|
|
675
|
+
const shouldSuffix = [
|
|
676
|
+
hasUnit,
|
|
677
|
+
hasE2e,
|
|
678
|
+
hasInt
|
|
679
|
+
].filter(Boolean).length >= 2;
|
|
680
|
+
const prefix = relativePath === "." ? "" : `${relativePath}/`;
|
|
681
|
+
const srcGlob = `${prefix}src`;
|
|
682
|
+
const testGlob = hasTestDir ? `${prefix}__test__` : null;
|
|
683
|
+
const testDirExcludes = hasTestDir ? VitestConfig.buildTestDirExclusions(prefix) : [];
|
|
684
|
+
const setupFile = VitestConfig.detectSetupFile(pkgPath);
|
|
685
|
+
const setupFiles = setupFile ? [`${prefix}${setupFile}`] : void 0;
|
|
686
|
+
if (hasUnit) vitestProjects.push(VitestProject.unit({
|
|
687
|
+
name: shouldSuffix ? `${packageName}:unit` : packageName,
|
|
688
|
+
include: VitestConfig.buildIncludes(srcGlob, testGlob, "*.{test,spec}.{ts,tsx,js,jsx}"),
|
|
689
|
+
overrides: { test: {
|
|
690
|
+
...setupFiles ? { setupFiles } : {},
|
|
691
|
+
exclude: [
|
|
692
|
+
"**/*.e2e.{test,spec}.*",
|
|
693
|
+
"**/*.int.{test,spec}.*",
|
|
694
|
+
...testDirExcludes
|
|
695
|
+
]
|
|
696
|
+
} }
|
|
697
|
+
}));
|
|
698
|
+
if (hasE2e) vitestProjects.push(VitestProject.e2e({
|
|
699
|
+
name: shouldSuffix ? `${packageName}:e2e` : packageName,
|
|
700
|
+
include: VitestConfig.buildIncludes(srcGlob, testGlob, "*.e2e.{test,spec}.{ts,tsx,js,jsx}"),
|
|
701
|
+
overrides: { test: {
|
|
702
|
+
...setupFiles ? { setupFiles } : {},
|
|
703
|
+
exclude: [...testDirExcludes]
|
|
704
|
+
} }
|
|
705
|
+
}));
|
|
706
|
+
if (hasInt) vitestProjects.push(VitestProject.int({
|
|
707
|
+
name: shouldSuffix ? `${packageName}:int` : packageName,
|
|
708
|
+
include: VitestConfig.buildIncludes(srcGlob, testGlob, "*.int.{test,spec}.{ts,tsx,js,jsx}"),
|
|
709
|
+
overrides: { test: {
|
|
710
|
+
...setupFiles ? { setupFiles } : {},
|
|
711
|
+
exclude: [...testDirExcludes]
|
|
712
|
+
} }
|
|
713
|
+
}));
|
|
714
|
+
if (!hasUnit && !hasE2e && !hasInt) vitestProjects.push(VitestProject.unit({
|
|
715
|
+
name: packageName,
|
|
716
|
+
include: VitestConfig.buildIncludes(srcGlob, testGlob, "*.{test,spec}.{ts,tsx,js,jsx}"),
|
|
717
|
+
overrides: { test: {
|
|
718
|
+
...setupFiles ? { setupFiles } : {},
|
|
719
|
+
exclude: [...testDirExcludes]
|
|
720
|
+
} }
|
|
721
|
+
}));
|
|
722
|
+
}
|
|
723
|
+
VitestConfig.cachedProjects = projects;
|
|
724
|
+
VitestConfig.cachedVitestProjects = vitestProjects;
|
|
725
|
+
return {
|
|
726
|
+
projects,
|
|
727
|
+
vitestProjects
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* Resolves coverage thresholds from options.
|
|
732
|
+
*
|
|
733
|
+
* @privateRemarks
|
|
734
|
+
* Priority: `options.coverage` (name or object) \> `COVERAGE_LEVELS.none`.
|
|
735
|
+
*/
|
|
736
|
+
static resolveThresholds(options) {
|
|
737
|
+
if (options?.coverage === void 0) return { ...VitestConfig.COVERAGE_LEVELS.none };
|
|
738
|
+
if (typeof options.coverage === "string") return { ...VitestConfig.COVERAGE_LEVELS[options.coverage] };
|
|
739
|
+
return { ...options.coverage };
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* Resolves coverage targets from options.
|
|
743
|
+
*
|
|
744
|
+
* @privateRemarks
|
|
745
|
+
* Priority: `options.coverageTargets` (name or object) \> `COVERAGE_LEVELS.basic`.
|
|
746
|
+
*/
|
|
747
|
+
static resolveTargets(options) {
|
|
748
|
+
if (options?.coverageTargets === void 0) return { ...VitestConfig.COVERAGE_LEVELS.basic };
|
|
749
|
+
if (typeof options.coverageTargets === "string") return { ...VitestConfig.COVERAGE_LEVELS[options.coverageTargets] };
|
|
750
|
+
return { ...options.coverageTargets };
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* Generates coverage configuration including thresholds.
|
|
754
|
+
*
|
|
755
|
+
* @privateRemarks
|
|
756
|
+
* Strips `:unit`/`:e2e`/`:int` suffix when looking up project paths for
|
|
757
|
+
* `--project` filtering, since coverage applies to the entire package
|
|
758
|
+
* regardless of test kind. When multiple `--project` flags are provided,
|
|
759
|
+
* coverage includes are unioned across all matched packages.
|
|
760
|
+
*/
|
|
761
|
+
static getCoverageConfig(specificProjects, projects, options) {
|
|
762
|
+
const exclude = [...VitestConfig.DEFAULT_COVERAGE_EXCLUDE, ...options?.coverageExclude ?? []];
|
|
763
|
+
const thresholds = VitestConfig.resolveThresholds(options);
|
|
764
|
+
const toSrcGlob = (relPath) => {
|
|
765
|
+
return `${relPath === "." ? "" : `${relPath}/`}src/**/*.ts`;
|
|
766
|
+
};
|
|
767
|
+
if (specificProjects.length > 0) {
|
|
768
|
+
const includes = [];
|
|
769
|
+
for (const sp of specificProjects) {
|
|
770
|
+
const relPath = projects[sp.replace(/:(unit|e2e|int)$/, "")];
|
|
771
|
+
if (relPath !== void 0) includes.push(toSrcGlob(relPath));
|
|
772
|
+
}
|
|
773
|
+
if (includes.length > 0) return {
|
|
774
|
+
include: includes,
|
|
775
|
+
exclude,
|
|
776
|
+
thresholds
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
return {
|
|
780
|
+
include: Object.values(projects).map(toSrcGlob),
|
|
781
|
+
exclude,
|
|
782
|
+
thresholds
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
};
|
|
786
|
+
|
|
787
|
+
//#endregion
|
|
788
|
+
export { VitestConfig, VitestProject };
|