-
Notifications
You must be signed in to change notification settings - Fork 17
Expand file tree
/
Copy pathvitest.config.ts
More file actions
877 lines (871 loc) · 52.9 KB
/
Copy pathvitest.config.ts
File metadata and controls
877 lines (871 loc) · 52.9 KB
1
2
3
4
5
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
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
// This package had NO vitest config until #9832, and that is the fact this
// header exists to keep visible: adding one changes how every test file in
// `packages/cli` is configured, not just the file that needed it. So the
// config is deliberately minimal — anchored `resolve.alias` entries and a
// `test` block that only carries keys with a recorded warrant (`server.deps`,
// and the #10374 console-intercept disarm), because the test files here run on
// vitest's defaults (`globals: false`, `environment: 'node'`) and re-specifying
// THOSE keys would silently flip them. Sibling configs in this repo do carry
// `test: { globals: true, … }`; copying that shape here would have flipped
// `globals` for every existing file in the package.
//
// ## Why the service-cache entry
//
// `serve-observability-registration.test.ts` (#9832) boots the REAL
// `CacheServicePlugin` to prove that a consumer registered after
// `ObservabilityServicePlugin` actually resolves `observability:metrics` and
// EMITS through it. Without an alias that import resolves through the
// package's `exports` to `@objectstack/service-cache`'s **dist**, which makes
// the test a verdict about build state rather than about the source in the
// checkout — and the dangerous half of that is not a loud error but a test
// that passes GREEN against a stale artifact with nothing in the output
// saying so. `scripts/check-test-source-alias.mjs` carries the measured
// history (#7668, #7778, #7849); it named this import and is the gate that
// fails without the entry below.
//
// ⚠️ The registry in that script is SHRINK-ONLY, so widening
// `KNOWN_UNALIASED_TEST_IMPORTS['@objectstack/cli']` was never an option — and
// it is deliberately left untouched here. The other 28 entries in it are real
// and still unaliased; this change removes nothing from that ledger and adds
// nothing to it. Measured before writing this: `@objectstack/service-cache`
// was reachable from NO other file in `packages/cli` (which is why it was
// absent from the ledger in the first place), so this entry re-resolves
// exactly one file — the new test — and cannot move any existing suite.
// Measured again after: unchanged. ⚠️ The file/test COUNT that "unchanged" was
// checked against is deliberately not repeated here. This file used to carry
// this package's population in two separate sections; the two copies drifted
// apart and neither said which was current, so the population is now stated
// ONCE — with the commit it was measured on — in the suite-cost section below.
//
// ## Why the create-objectstack entry (commit 818e02700)
//
// `init.ts` prints its "Created files" summary from a walk of the finished
// project directory rather than a list accumulated while writing the
// template (see the command's own header) — reusing `create-objectstack`'s
// `created-summary.ts`, published as the `create-objectstack/created-summary`
// subpath so both scaffold paths share one renderer instead of drifting
// (commit 6d441e41f closed the earlier drift). Without an alias that bare specifier resolves through
// `create-objectstack`'s `exports` to its **dist**, for the same reason and
// the same danger as the entry above: a stale `dist/created-summary.js`
// would make every test that reaches `init.ts` a verdict about build state.
// `init.ts` is imported (relatively, inside this package) by three existing
// test files — `commands.test.ts`, `init.test.ts`,
// `init-scaffold-authoring-rules.test.ts` — so all three are reachable from
// this entry, none of them new; this alias just keeps them pointed at source.
//
// Crossing into `service-cache/src` also makes ITS value imports reachable to
// the gate's walk. That is one package, `@objectstack/observability`, which is
// already in this package's ledger entry — so the required set is unchanged in
// both directions.
//
// ## WHY THERE IS STILL NO `test` BLOCK — the suite cost, measured (commit ad492e7fd)
//
// This package's suite was the largest single item on the Test Core critical
// path (548.6s / 474.4s in two `merge_group` runs), and the standing theory for
// suite cost in this repo — per-file module-graph re-execution under isolation,
// the proxy `scripts/partition-test-shards.mjs` weights by — predicted the
// cause would be import surface, which would have made a `test` block (`pool`,
// `isolate`) the lever. It is NOT that, and the measurement is recorded here
// because this file is where the next person looking for a lever will arrive.
//
// ⚠️ EVERY FIGURE BELOW COMES FROM ONE RUN ON ONE STATED COMMIT — 2f665a1af,
// re-measured 2026-08-26 (#12499). The first version of this section carried a
// DATE and no commit, and the population moved under it while the figures went
// on reading as precise: 137 files / 1498 tests had become 185 / 2115, and 20
// spawner files had become 39, before anyone checked. A date says when someone
// looked; a commit says WHAT they looked at, and only the second can be
// re-checked. Whoever re-measures next: print your commit here, and keep these
// counts in exactly one place in this file — a second copy is what rotted last
// time, because the two drifted and neither said which was current.
// ⇒ Re-measured on f532630d02 (commit 55519d503): the `cli` POPULATION below is
// superseded by the section further down, which also attributes the `import`
// term per file. The peer rows and every ratio in this section are from the
// 2f665a1af run and were NOT re-taken.
//
// PROTOCOL. One machine, 4 cores, warm build, one `vitest run --maxWorkers=2`
// per package, vitest 4.1.10 on node 22.22.2. ⚠️ Several agents share this
// container, so every run below held the shared heavy-verify lock
// (`scripts/pm/os-verify-lock.sh`): a suite timed beside a neighbour's build is
// a reading about the box, not about the suite.
//
// Contention actually seen, so the absolutes can be discounted honestly: three
// other agents were working this container. The `cli` run waited 4m01s for the
// lock behind a neighbour's build and then held it 13m14s; the five peer runs
// waited 7m03s and held 8m57s. The lock serialises LOCKED work only — a
// neighbour's UNLOCKED gate script was seen at ~130% CPU partway through the
// `cli` run — and 1-minute load averaged 4.36 (peak 6.97) on 4 cores across it.
// ⛔ Also part of the protocol: each package was measured against a tree with
// its own dependency closure BUILT. A package measured without one does not
// report a cheap wall, it fails loudly — `example-showcase` did, first time.
//
// ⚠️ WHICH OF THESE TRAVEL TO ANOTHER BOX. #11707 re-ran this section's
// absolutes elsewhere and they did NOT reproduce, while the ratio it cared
// about did. So: every figure in SECONDS here is box-dependent and comparable
// only inside this one run — the walls, s/file, s/test, the `Duration` split,
// the per-spawn floors. What a reader on another box should expect to reproduce
// are the RATIOS: tests/file, the top-20 concentration, the spawner share of
// the file wall, this package's multiple over its peers, and the ratio between
// the two spawn floors.
//
// Both normalisers, because per-FILE cost alone cannot tell "expensive suite"
// from "more tests per file":
//
// package files tests tests/file wall s/file s/test
// @objectstack/cli 185 2115 11.4 793.31s 4.288 0.3751
// @objectstack/spec 432 11460 26.5 358.46s 0.830 0.0313
// …/service-automation 91 1082 11.9 96.06s 1.056 0.0888
// …/driver-turso 39 1006 25.8 33.89s 0.869 0.0337
// @objectstack/client 25 346 13.8 18.74s 0.750 0.0542
// …/example-showcase 26 364 14.0 32.94s 1.267 0.0905
//
// Normalising per TEST makes this package look WORSE, not better: it has the
// LOWEST tests-per-file of the six, so its 3.4-5.7x per-file cost becomes
// 4.1-12.0x per test. "It just has more tests per file" is falsified. ⚠️ Read
// the SHAPE of that check, not only its answer: the multiple has to WIDEN when
// the normaliser changes, and it does.
//
// The `Duration` split says where the cost is NOT:
//
// cli 793.31s (transform 25.17s, setup 0ms, import 203.72s, tests 1353.09s)
// spec 358.46s (transform 15.68s, setup 0ms, import 68.50s, tests 528.18s)
// svc-auto 96.06s (transform 14.72s, setup 0ms, import 172.68s, tests 4.71s)
// turso 33.89s (transform 14.13s, setup 0ms, import 56.93s, tests 3.62s)
// client 18.74s (transform 10.91s, setup 0ms, import 30.15s, tests 2.38s)
// showcase 32.94s (transform 16.68s, setup 0ms, import 48.76s, tests 12.78s)
//
// Per file this package's import cost is 1.10s and its transform cost 0.136s —
// SECOND-LOWEST of the six on BOTH; only `spec` is below it (0.16s import,
// 0.036s transform), and the tops are `service-automation` at 1.90s import and
// `example-showcase` at 0.642s transform. The wide dependency closure in
// `package.json` is not what the test files import. `setup` is 0ms in all six,
// so setupFiles cost is not it either — and that zero was checked against an
// instrument that can say otherwise: the same reporter, pointed at a
// deliberate 300ms setup file, reports it.
//
// The cost is test-body work, and it is concentrated, not uniform: median file
// 0.05s, 129 of 185 files under 2s, top 20 files = 71.3% of the file wall
// (965.3s of 1353.1s). "File wall" here is the sum of the per-module run
// durations, which is the SAME quantity vitest prints as the `tests` term
// above — said out loud so the two can be checked against each other instead of
// drifting apart, which is how this section went stale the first time.
//
// The 39 files that spawn the real CLI as a subprocess (against a `mkdtemp`
// project) are 89.4% of that file wall (1209.8s) while carrying 319 of the 2115
// tests — and they are the WHOLE of the top 20, all twenty of them. Each spawn
// re-executes the CLI's module graph in a COLD process, which is the standing
// theory after all — relocated out of vitest's worker, where neither its
// transform cache nor its module registry can reach it.
//
// ⚠️ THE SPAWNER SET MOVED IN BOTH DIRECTIONS AT ONCE, which is why the two
// shares here disagree with the 2026-08-20 pair in OPPOSITE directions: 20
// spawner files became 39, so the share of the wall they hold ROSE (56.1% ->
// 89.4%) while the top-20 concentration FELL (87.7% -> 71.3%) — the same kind
// of cost, spread across more files. Either number read alone tells the wrong
// story; the pair is the finding.
//
// ⚠️ COUNTING THE SPAWNERS: match the entry BASENAME, not `bin/run-dev.js`.
// Three of the 39 assemble the path from separate literals
// (`join(…, '..', 'bin', 'run-dev.js')`) and a slash-joined pattern misses all
// three — silently, since the answer it returns is still a plausible number.
// Prose mentions do not count either: at least one file names the entry four
// times while explicitly not spawning it.
//
// By entry point, after #11707 moved three files onto the built one:
//
// 35 files spawn `bin/run-dev.js` (source) 1169.2s 33.4s/file 311 tests
// 4 files spawn `bin/run.js` (built) 40.6s 10.1s/file 8 tests
//
// ⛔ That is NOT a measurement of what the two entries cost. Those four are also
// the smallest files here — 2.0 tests/file against 8.9 — and per TEST the
// ordering REVERSES (5.1s vs 3.8s). File-level shares say where the wall sits,
// not what one spawn costs. What a spawn costs is measured directly, one spawn
// at a time, below.
//
// Floor per spawn, doing nothing but printing a version — 5 timed runs each
// after one discarded warm-up, box idle inside the lock:
//
// tsx bin/run-dev.js --version 5.45-6.07s (the source entry, 35 files)
// node bin/run.js --version 2.46-2.66s (the built entry, 4 files)
// node -e 0 0.025-0.031s (process floor)
//
// ⚠️ The absolutes moved from the 2026-08-20 reading (6.5-6.8 / 2.9-3.2 /
// 0.031); the RATIO did not — source-over-built was 2.18x then and 2.21x here (means of the five runs each).
// That is the #11707 pattern exactly: carry the ratio to another box, never the
// seconds.
//
// ⛔ The exit code is PART of this measurement, not a formality. A nonexistent
// entry (`node bin/run-NOPE.js --version`, exit 1) returns in 0.030s — which
// reads as a better floor than anything real. Timing alone cannot tell "fast"
// from "never ran"; every row above was checked for exit 0.
//
// ⚠️ A port-selection change (#12441) was in flight on three of these spawner
// files (`serve-node-env-production-default`,
// `serve-app-anchored-optional-import`, `helpers/serve-process`) while this was
// measured. It changes which PORT a spawned `serve` binds, not which ENTRY is
// spawned, so the source-vs-built split above is unaffected by it; it can move
// wall times slightly. Recorded so the next reader can tell drift from noise.
//
// ⚠️ Two levers were measured and both are rejected HERE, on this evidence:
//
// `test: { maxWorkers: 4 }` — real but not ours to take. 2->4 workers on this
// box is 793.31s -> 565.86s, but CPU is FLAT (user+sys 2028.5s -> 1971.2s)
// and per-file wall INFLATES (sum 1353.1s -> 1969.8s; longest file 94.0s ->
// 114.1s): the box is saturated, so this is packing, not work. Re-measured on
// the commit above, and the 2026-08-20 verdict reproduced in every term. In CI
// the box is not this package's — `ci.yml` runs `turbo run test
// --concurrency=4` — so pinning a worker count here spends cores belonging to
// whatever else lands on the shard. That OUTER fan-out — how many package
// `test` tasks run at once — is a property of the shard, decided in `ci.yml`,
// not of this config (#10149).
//
// ⚠️ THE INNER POOL IS BOUNDED TOO, AND THE WORDING HERE USED TO DENY IT.
// Vitest's own worker pool, inside THIS package's task, has had a host-sized
// cap since #11958: the root `test` script and the CI test steps export
// `VITEST_MAX_WORKERS` from `scripts/vitest-worker-cap.mjs`, which only ever
// LOWERS vitest's own `cores - 1` default. ⛔ Their call sites are
// deliberately NOT listed here — a list of call sites inside a package config
// is the next thing to rot, and a citation that rotted is why this paragraph
// exists. The script is single-source and carries its own reasoning.
//
// ⭐ Worth keeping is WHY that omission was worse than a gap. The export sits
// in the SAME `run:` block as the `--concurrency` flag quoted above, a few
// lines earlier, under a comment explaining it. Saying "worker allocation is
// decided in `ci.yml`" and then naming only the turbo flag sent the reader to
// the exact place the cap lives and told them what they would find there — so
// they walked past it, on the authority of this file. That is how a one-way
// citation survives: the newer document points back at the older one, and
// nobody re-reads the older one to check that it still holds.
//
// ⚠️ The lever is also INERT wherever that variable is exported, not merely
// unwise. Vitest applies the env var to the RESOLVED config, so it overwrites
// a declared `maxWorkers` rather than being bounded by it. Observed on vitest
// 4.1.10 against a positive control: a `vitest.config.ts` declaring
// `maxWorkers: 8` resolves to 8 with the variable unset, and to 2 under
// `VITEST_MAX_WORKERS=2`. A pin added here would read as taken and change
// nothing in exactly the runs that matter.
//
// `NODE_COMPILE_CACHE` for the spawned processes — re-measured 5.15/5.34/
// 5.16/5.42s cached against 5.46/5.61/5.64/5.71s uncached, for 42MB of cache
// (783 files). ⚠️ Unlike the 2026-08-20 reading, where the two sets
// overlapped and the answer was "inside noise", these four-and-four do not
// overlap: cached is consistently ~0.3s (~5%) faster. It is still not the
// lever — that is an order of magnitude less than the 3.11s the built entry
// already saves per spawn, and it buys 42MB to get it. The per-spawn cost is
// module-graph EXECUTION, not compilation.
//
// So the work is real, the price is fair, and nothing contained in this package
// removes it without changing what the e2e tests assert.
//
// ## THE `import` TERM, ATTRIBUTED PER FILE (commit 55519d503) — f532630d02, 2026-08-31
//
// Commit 55519d503's card asked the one question the section above does not answer. The run it
// filed spent `import 401.08s`, a quarter of its wall, before any assertion
// executed, and nothing said WHERE. This section says where, and the answer
// settles three candidate routes without any of them having to be tried.
//
// ⚠️ SAME PROTOCOL, DIFFERENT RUN AND DIFFERENT COMMIT from the section above,
// so the two are not interchangeable. This one is f532630d02: one
// `pnpm --filter @objectstack/cli exec vitest run --maxWorkers=2` through the
// shared lock, vitest 4.1.10 on node 22.22.2, dependency closure built.
// ⛔ The five PEER packages above were deliberately NOT re-measured — each one
// costs another hold of the very lock this card is about — so their
// cross-package ratios stand on their own run and are not restated here. What
// is superseded above is the `cli` POPULATION, nothing else.
//
// Test Files 220 · Tests 2529 · Duration 1041.38s
// (transform 31.49s, setup 0ms, import 300.68s, tests 1741.68s, environment 30ms)
// os-verify-lock: held 1043s (17m23s) · waited 0s · exit 0
//
// ⚠️ The instrument is vitest's own arithmetic, not a proxy. The printed
// `import` term IS `sum(file.collectDuration)` and the printed `tests` term IS
// `sum(file.result.duration)` (vitest 4.1.10, the summary block in
// `dist/chunks/index.*.js`). Per-file `collectDuration` is therefore a
// DECOMPOSITION of the printed number rather than a correlate of it, and the
// two can be checked against each other — this run's per-file sums reproduce
// both printed terms exactly.
//
// WHERE THE `import` TERM IS: nowhere in particular. It is a per-file FLOOR.
//
// p25 0.08s · p50 0.83s · p75 2.04s · p90 3.36s · p99 4.73s · max 10.66s
// mean 1.37s over 220 files · 112 files under 1s · 63 under 100ms
// top 10 files 17.8% · top 20 29.4% · top 40 50.9%
//
// The most expensive single file is 10.66s — 3.5% of the term. Half the term
// takes FORTY files. ⇒ There is no hotspot to remove, and sharding cannot help
// on this box: it divides files, a floor divides with them, and the wall then
// falls only as far as the cores allow — which here is the binding constraint
// (4 cores, `--maxWorkers=2`, beside other agents' unlocked work).
//
// WHO PAYS IT, by class — "spawner" spawns the real CLI as a child process,
// "kernel" boots `bootSchemaStack` / `ObjectQL` / a real driver in-process:
//
// class files tests import tests
// spawns the real CLI 35 306 4.41s ( 1.5%) 1279.14s (73.4%)
// boots a real kernel in-proc 28 327 58.98s (19.6%) 59.05s ( 3.4%)
// neither 157 1896 237.29s (78.9%) 403.49s (23.2%)
//
// ⭐ THAT TABLE REFUSES TWO OF THE THREE ROUTES THE CARD LISTED, on numbers:
//
// - "move the kernel-booting cases to a narrower double" — those 28 files
// hold 3.4% of the test term. Convert every one of them and the wall moves
// by about half a minute. The route is not wrong, it is not the lever.
// - "shard it" — the floor above.
//
// The third, a slow-suite split, is not refused. It is a ROUTING decision and
// it is priced at the end of this section.
//
// ⛔ ONE HYPOTHESIS THIS RUN ALREADY KILLED, so the next reader does not spend
// a hold on it. The import term is not transform and not bundle size; it is
// the per-file BOUNDARY — each test file re-executes its module graph in its
// own registry, and that cost barely notices how the module arrived. The
// control was already inside the run, because this config externalises exactly
// one package and inlines every other, so both paths are present at once:
//
// packages/types/dist/index.mjs EXTERNAL 33 KB 188.7 ms/file (91 files)
// packages/spec/dist/index.mjs inlined 2090 KB 165.2 ms/file (139 files)
//
// The externalised 33 KB module costs MORE per file than the inlined 2 MB one.
// ⚠️ It is not a paired A/B — two different bundles, not one bundle measured
// both ways — so it BOUNDS the idea rather than settling it. What it is enough
// for is refusing "externalise more workspace `dist/`" as a speed fix for this
// term. Widening `server.deps.external` still has its #11775 resolution
// warrant; it does not have a speed warrant.
//
// Largest single module contributor: `packages/metadata-protocol/dist` at
// 751 ms/file over 52 files (39.06s), then the `packages/spec/dist/*` entries
// together at roughly 104s, each spread over 90-139 files.
//
// WHERE THE `tests` TERM IS: concentrated, and in exactly the files the card
// forbids touching for being slow. The 35 spawner files are 73.4% of it while
// carrying 306 of 2529 cases; top 10 files 42.0%, top 20 65.3%. The cost is a
// product, not a mystery — about 13 spawns in a heavy file times the per-spawn
// floor already measured above (5.45-6.07s for `bin/run-dev.js`, 2.46-2.66s
// for `bin/run.js`) is the 84-96s those files take. And there is no idle time
// to reclaim: the harness waits on a PATTERN, never on a fixed sleep
// (`test/helpers/serve-process.ts`).
//
// ⭐ THE ONE LEVER THIS ATTRIBUTION DOES SURFACE — per-case work, never a
// sweep. All ten of the heaviest spawner files spawn the SOURCE entry
// (`bin/run-dev.js`, through tsx), whose floor is 2.2x the built entry's;
// #11707 moved three files the other way and measured 2.06x. ⛔ The two
// entries are not interchangeable: `bin/run-dev.js` pins `NODE_ENV=development`
// and reads `src/`, and a `dist/` merely BEHIND its source turns a green run
// into a verdict about build state (which is why the four files already on
// `bin/run.js` each state that prerequisite in their own words). So this names
// the lever and stops; taking it is one argument per file.
//
// THE TRADE, PRICED. This is the choice commit 55519d503's card says is being made by the
// suite's runtime rather than by a person. Estimated wall uses this run's own
// measured effective parallelism (2043.29s of per-file work over a 1041s wall
// = 1.96):
//
// lane files tests est. wall share of wall
// the whole suite 220 2529 1041s 100%
// minus every *.e2e.test.ts 177 2195 260s 25%
// only the *.e2e.test.ts 43 334 781s 75%
//
// 13% of the cases hold 75% of the wall. ⛔ This file does not take that trade:
// `pnpm test` here still runs everything, and which lane a card's Definition of
// done owes is a rule that lives in AGENTS.md, which no agent seat may land.
// What this section removes is the option of not knowing the price.
//
// ## THE `test` BLOCK THAT NOW EXISTS, AND WHY IT IS NOT THE ONE REFUSED ABOVE
//
// #11775 added `test.server.deps.external`. Everything above still stands: the
// block deliberately sets NOTHING that has a default a test file can observe —
// no `globals`, no `environment`, no `pool`, no `isolate`, no `maxWorkers`. It
// is not a performance lever (the section above measured those and rejected
// them), and adding it changed no test's configuration except the resolution of
// one dependency.
//
// WHAT IT DOES. Vitest's default `server.deps.external` is `[/\/node_modules\//]`,
// evaluated against a module's REALPATH. A pnpm-linked workspace package's
// realpath is the package directory itself — `packages/types/dist/node.mjs` —
// which contains no `/node_modules/` segment, so every workspace dependency is
// INLINED, even one reached through `exports` to `dist/`. Vite then rewrites the
// `import()` written inside it to `__vite_ssr_dynamic_import__`, which resolves
// from the vitest root instead of from the module physically containing the
// call. Node ESM does the opposite: it anchors a bare specifier at the
// containing module. `@objectstack/types/node`'s `createHostImporter` EXISTS to
// resolve against a specific base, so under vitest it was measuring a base that
// had been flattened out from under it (#11412), and the entry below hands that
// call back to Node.
//
// ⚠️ THE PATTERN MUST MATCH THE REALPATH, AND A NAME-SHAPED ONE MATCHES NOTHING
// — SILENTLY. `/@objectstack[\/]types/` looks like the obvious spelling and is
// the trap: the realpath carries neither the package name nor `/node_modules/`,
// so that pattern matches zero modules and the experiment reads as
// "externalising does not help" rather than as "the pattern was wrong". #11775
// was first measured wrong for exactly that reason. Anything edited here needs a
// POSITIVE CONTROL that the pattern matches something — a pattern that matches
// nothing and a mechanism that does not work are indistinguishable from the
// outcome alone.
//
// ⚠️ IT DOES NOT FIGHT THE `resolve.alias` ENTRIES ABOVE, by construction.
// `resolve.alias` acts in the RESOLVE phase, so an aliased specifier is already
// an absolute `…/src/…` path before this predicate is consulted, and a `/dist/`
// pattern cannot match a `src/` path. That is not a coincidence to be preserved
// by care: `check:test-source-alias` FAILS any alias whose winning entry does
// not land under `src/`, so the gate that was assumed to be in tension with this
// entry is the same gate that keeps the two disjoint. Measured, not argued — see
// `test/vitest-resolution-base-collapse.e2e.test.ts`.
//
// COSTS, so the next person extending this list knows what they buy: an
// externalised package cannot be `vi.mock`ed and is not instrumented for
// coverage. Both were checked against this package when the entry landed, and
// a package added here later must be re-checked for both — so the census that
// re-check starts from is stated below, on the commit it was taken on.
//
// ## THE MOCK-TARGET CENSUS (#13873) — 55519d5036, 2026-08-31
//
// `@objectstack/types`, the one package externalised below, is NOT mocked here.
// Zero sites — and the zero is not vacuous: 10 test files import it (both
// `@objectstack/types` and `@objectstack/types/node`), so a mock of it is a
// thing that could exist here and does not. That half has held since #11775.
//
// What a reader externalising something ELSE needs: 11 mock sites over 8
// distinct targets. Five are relative or node-builtin specifiers, which this
// predicate cannot reach. THREE are workspace packages, which it can:
//
// @objectstack/cloud-connection src/commands/doctor-ledger-read-failure.test.ts
// @objectstack/platform-objects/plugin src/commands/secret/orphans.guards.test.ts
// @objectstack/lint test/i18n-flow-screen-coverage.test.ts
//
// ⭐ THREE, not the one this paragraph named until #13873 — and naming one was
// worse than naming none. The sentence above sends the reader here to do a
// re-check and then tells them what they will find, so a reader who trusted it
// concluded that externalising a workspace package is free of mock conflicts in
// this package. Externalising any of the three breaks the file that mocks it,
// with an error pointing at that test rather than at the entry below that
// caused it. Same shape as #12529 on this file.
//
// ⚠️ RE-TAKING IT: match the capital M, or you silently lose a third of the
// census and get a plausible number back.
//
// git grep -nE "vi[.](do)?[Mm]ock[(]" -- 'packages/cli/**/*.test.ts'
//
// `vi.(do)?mock(` — the obvious spelling — matches no `vi.doMock` call at all,
// so it drops all four `doMock` sites, among them `@objectstack/cloud-connection`:
// the ONE workspace package the old parenthesis did name. #13873 shipped that
// spelling as its reproduce command while its table was taken another way, and
// the two disagree by exactly those two targets — re-run on the card's own
// commit, the command returns 6 where the table says 8. Whoever re-takes this:
// print your commit here, and keep the census in exactly one place in this
// file (#12499).
//
// ## WHY THE SPAWN SWAP IS NOT THIS GATE'S TRADE TO REFUSE (#11707, #12460)
//
// This file used to close by calling a swap of the spawns to the built entry
// “exactly the source-vs-dist trade `scripts/check-test-source-alias.mjs`
// exists to refuse”. It is not one, and naming a gate for a verdict it never
// reaches reads as verification while verifying nothing. What that gate does
// measure: the specifiers written in `import` / `export … from` / `import()` /
// `require()` statements reachable from a package's test files, kept when they
// name a workspace dep whose own entry point resolves under `dist/`, then
// resolved through THIS config's `resolve.alias` table. That is a verdict about
// IN-PROCESS import resolution. It says nothing about which entry a test hands
// to `spawn()`, and its own header signs that second axis over to a different
// mechanism (“A SECOND resolution hazard, which this gate does NOT cover”,
// #11412). The `plugin-auth` entry below is this file's worked example: it
// satisfies the gate while being inert for the child, whose own `exports`
// lookup reaches `dist/` either way.
//
// So the swap was available, and #11707 took it — 2.06x faster in test time,
// measured there. Four files in `test/` consume `packages/cli/dist` today:
// `serve-node-env-production-default` (since #11113) and the three spawners
// #11707 moved onto `node bin/run.js` with `NODE_ENV` unset
// (`serve-mcp-stdio-answers`, `serve-mcp-capability-collision`,
// `serve-stdio-stdout-purity`). Re-running this gate on that commit and on its
// parent returns a byte-identical verdict AND a byte-identical measured
// population: it did not see the swap.
//
// What keeps those four honest is a declaration, not this gate. `turbo.json`
// declares `@objectstack/cli#test` `dependsOn: ["build"]` (commit 918988ad3), so CI
// builds `dist/` before the suite runs, and each of the four refuses an unbuilt
// tree in a sentence of its own. The residual — a `dist/` merely BEHIND its
// source — is real, and those files state it. An in-process import has no such
// declaration standing behind it, which is why the alias entries above exist
// and why the gate does refuse THAT trade — see the note above on why a test
// that passes GREEN against a stale artifact is the dangerous outcome.
//
// Before adding a `test` block for speed, re-measure: if `tests` is still the
// dominant term, the block is not the lever.
//
// ## THE TWO TIERS (commit 44813ba57, #14554) — `unit` and `integration`, DERIVED population
//
// Maintainer ruling (2026-09-01): split this suite into two NAMED tiers — a
// unit-fast tier that is fast to run locally and does not monopolise the shared
// verify lock, and a real-kernel integration tier that is CI-mandatory and run
// locally on demand.
//
// pnpm --filter @objectstack/cli test # BOTH tiers — what CI runs
// pnpm --filter @objectstack/cli exec vitest run --project unit # fast; runs ONLY the unit tier
// pnpm --filter @objectstack/cli exec vitest run --project integration # the real thing, on demand
//
// ⛔ `--project` NARROWS THE RUN, AND A PATH YOU NAME OUTSIDE THE SELECTED TIER
// IS DISCARDED RATHER THAN RUN (commit 08f5f0e5a). The split itself skips, weakens,
// deletes and doubles nothing — `vitest run` with no `--project` runs every
// project, so the POPULATION is intact. ⛔ That sentence is about the
// population and says nothing whatever about one narrowed invocation, and this
// block used to print it three lines above the narrowed commands, which is
// precisely how it got read as a guarantee about the reader's own command line.
// The safe invocation is now printed FIRST, above the two that can lose things.
//
// Naming an integration-tier file while passing `--project unit` selects
// nothing for that path — the two tiers are a partition (`:583`) and each
// project's `include` is an exact-path list (`:613`), so the intersection is
// empty BY CONSTRUCTION, not by accident. Measured on this tree, vitest 4.1.11:
// if that path was the ONLY one you named, vitest is already loud —
// `No test files found, exiting with code 1`. If you named OTHER paths that did
// match, it is dropped in SILENCE: five paths in, `Test Files … (4)` out, the
// discarded name printed nowhere in vitest's own output, and the whole run
// byte-identical to the one that named only the four.
//
// ⇒ A false green in the worst direction, and it has already cost one dispatch
// round (#16872): that dev verified with `--project unit`, read green, pushed,
// and CI went red on `Test Core` with the failing assertion inside the very
// integration-tier file the local run had discarded.
//
// ⇒ `vitest-filter-preflight.ts` runs at config load below and now says so:
// every named path that will select no test file is reported BY NAME, with the
// tier it really lives in and the command that runs it. It prints nothing at
// all when every named path selects something, so a healthy narrowed run is
// byte-identical to what it was before this existed. ⛔ Before accepting a
// narrowed run as pre-delivery verification, read that line — or run the full
// `test` target above, which is the only one of the three whose green is a
// statement about this package rather than about a subset you chose.
//
// ⛔ THE PREDICATE IS WHAT A FILE DOES, NOT WHAT IT IS CALLED. The ACCEPT on
// commit 44813ba57's card fixed that the `*.e2e.test.ts` name disagrees with behaviour, so a
// tier keyed on the name routes coverage to the wrong place. The predicate is
// stated ONCE, in `vitest-tiers.ts` — SPAWN (the real CLI, or this package's
// source in a cold tsx child) or KERNEL (a real kernel or driver booted in
// process), evaluated in code position with comments masked. Type-only
// imports, spelling lists, fixture objects that merely SAY
// `client: 'better-sqlite3'` and prose do not count; that module's header
// carries the regexes and the false positives they were tuned against, and is
// the place to read or change them.
//
// ⛔ AND THERE IS NO LIST HERE TO KEEP IN STEP (#14554). `INTEGRATION_FILES`
// below is DERIVED from that predicate at config load. A hand-maintained list
// is a copy of a fact already on disk, and the copy goes stale whenever
// ANOTHER PR lands a qualifying test file: the pin then fires inside the merge
// queue, against a `main` that is by construction newer than any queued PR's
// own run, and — because GitHub stacks queue entries — ejects every PR behind
// it too. Measured 2026-09-02: five ejections in a rolling 24 hours, ONE
// independent hit, four bystanders touching no `packages/cli` path at all. A
// derived population is classified rather than reported, so that shape is
// gone. What `test/vitest-tiers-partition.test.ts` (unit tier) still holds —
// coverage of the union, that the derivation reaches vitest, and the predicate
// itself against fixture sources — and why a derivation needs a pin at all, is
// set out in its own header.
//
// Population on this branch: 230 files = 158 unit + 72 integration. Deriving
// reproduced the last hand-maintained list EXACTLY: `vitest list --filesOnly
// --project <name>` returns byte-identical lists before and after #14554 (158
// and 72, zero added, zero removed), so nothing moved tier when the list went
// away. Before it did, the list moved twice between the first cut
// (00ff228fe0: 228 = 158 + 70) and 3b5f8168b5, and the pin caught both in the
// merge queue: one NEW spawner file and one EXISTING file that started
// constructing `new ObjectQL(`
// — the second is the shape a name-based tier can never see. Reconciled
// against the #13872 census (f532630d02, 220 files, 35 spawners / 29
// kernel-booters / 1 both):
// on that same tree this predicate finds 46 spawners and 22 kernel-booters.
// The spawner side GROWS by 11 files the basename census could not see — six
// that spawn only through `runServe()` and five through the helper's exported
// `CLI` path constant — and SHRINKS by three that name an entry basename in an
// assertion without importing `child_process` at all. The kernel side shrinks
// because the census counted text matches: two `CONTRACT_ONLY_SPELLINGS`
// lists, a banner fixture, a connection-display formatter, a scaffold
// dependency assertion and two `import type { … } from '@objectstack/driver-*'`
// are not boots. With the behavioural predicate the name-vs-behaviour
// disagreement is 5 files (4 spawn without the `.e2e` name, 1 carries the name
// and spawns plain node), down from the census's 18.
//
// WHAT THE SPLIT COSTS AND BUYS, priced from the #13872 attribution above: the
// 70 integration files hold the 73.4% of test-body time that belongs to the
// spawners plus the 3.4% of the kernel-booters; the unit tier is the remaining
// ~23% of test-body time plus the per-file import floor, which is the part the
// split cannot move. The unit tier's measured wall on this box is recorded in
// the PR that landed this section; re-measure it when the population moves,
// and print the commit here.
//
// ## THE NIGHTLY TIERS (#16455) — a second cut, by NAME, read from `OS_TEST_TIERS`
//
// Maintainer direction (2026-09-07, verbatim): 「我想的是测试会不会太多,是否都是
// 必要的,是不是应该砍,每次修改都要完整的测试吗」. The `e2e` and `live` tiers
// leave the per-PR and merge-queue runs and run nightly on `main`; selection is
// by the EXISTING filename tiers only (`*.e2e.test.*`, `*.live.test.*`) and no
// file is renamed, deleted or edited to move it. Measured on 6eba38f5a3: this
// package owns every one of the tree's 60 `*.e2e.test.ts` files and the tree
// owns no `*.live.test.*` at all, so this is the one config that reads the
// switch today — read through `scripts/nightly-tiers.mjs`, the single reader of
// the variable, so a package that adopts a tier tomorrow imports rather than
// re-spells it.
//
// OS_TEST_TIERS unset / queue this package's population = the 212 non-tier files (180 unit + 32 integration)
// OS_TEST_TIERS=nightly this package's population = exactly the 60 tier files (1 unit + 59 integration)
//
// (272 test files on disk at 6eba38f5a3, read from `vitest list --filesOnly`
// under each setting and each `--project`; 212 + 60 = 272, no file in both.)
//
// The cut is applied in ONE place — `testFilesOnDisk()` in `vitest-tiers.ts`,
// the walk both derivations below read from — so the behavioural partition
// into `unit` / `integration` operates on whatever population the setting
// selected, and the two projects remain a partition of it by construction.
// That is also why the unit tier is now an INCLUDE list rather than
// `exclude: INTEGRATION_FILES`: an exclude-shaped unit project falls back to
// vitest's default `include`, which would collect the tier files the queue
// must not run. `test/vitest-tiers-partition.test.ts` measures all of this
// under whichever setting it runs in (its `vitest list` child inherits the
// switch); under `nightly` it is not itself collected — it carries no tier
// name — which is the ruled behaviour, not a gap.
//
// ⛔ The switch reaches vitest only because `turbo.json` names `OS_TEST_TIERS`
// in the `test` task's `env`: turbo 2.10 runs in STRICT env mode and strips
// every undeclared variable before the task's shell sees it. It sits in `env`
// (hashed) rather than `passThroughEnv` on purpose — a `test` task's cached
// outcome depends on the setting, so the setting is in the hash and a nightly
// can never replay a queue-mode cache entry as `>>> FULL TURBO`.
//
// ⚠️ INLINE PROJECTS INHERIT NOTHING BY DEFAULT — `extends: true` is what
// carries this file's `resolve.alias` table and `test.server.deps.external`
// into each project (vitest 4.1.10: an inline project without it gets a fresh
// Vite config, so the source aliases the gate above guards would be declared
// here and enforced nowhere). `disableConsoleIntercept: true` is repeated
// inside every project because `check:console-intercept-disarm` measured the
// root-level setting inert under projects. Both projects carry an explicit
// `include` of exact paths, so neither reaches vitest's default `include`
// (which would collect the whole tree) and neither needs its own
// `node_modules` exclusion: an exact-path list matches nothing it does not name.
import { defineConfig } from 'vitest/config';
import path from 'path';
import { parseCLI, type TestUserConfig } from 'vitest/node';
import {
runFilterPreflight,
runProjectCliOverridePreflight,
} from '../qa/vitest-filter-preflight/src/index.js';
import { integrationTestFiles, unitTestFiles } from './vitest-tiers.js';
// The two tiers, DERIVED from what the files DO — never written down — over
// the population `OS_TEST_TIERS` selects. `vitest-tiers.ts` holds the
// predicate, the walk and the argument for both; `test/vitest-tiers-partition.test.ts`
// pins what a derivation cannot pin about itself. Package-root-relative,
// POSIX-separated, sorted; each entry is an exact path, which is what lets
// each array serve as its project's `include`.
export const INTEGRATION_FILES = integrationTestFiles(__dirname);
export const UNIT_FILES = unitTestFiles(__dirname, INTEGRATION_FILES);
// Commit 08f5f0e5a / #17978 — say so when a path named on the command line will run no
// tests. It is invoked HERE, at config load, and ⛔ deliberately NOT as a
// `test.reporters` entry: naming that option replaces vitest's own reporter
// defaulting instead of extending it, which measurably changes a healthy run's
// output and would drop the `github-actions` reporter in CI. The ONE shared
// transcription of vitest's `TestProject.filterFiles` —
// `packages/qa/vitest-filter-preflight` — carries both measurements, and it is
// imported by RELATIVE PATH rather than by its package name for a third measured
// reason recorded in its header. It reads the argv through vitest's own exported
// parser and the SAME two derived arrays the projects below take as their
// `include` — ⛔ never a second derivation — and writes nothing whatever unless a
// named path selects nothing.
//
// ⭐ This package is the ONE of the eight that needs no walked population: both
// of its projects take an exact-path `include`, as a by-product of the tier walk
// it already performs for unrelated reasons (commit 44813ba57 / #14554). So it hands the
// two arrays over directly and never calls `exactAndGlobPopulations`. That
// asymmetry is exactly why a port of this package's former local copy could not
// serve the other seven — #17978 carries the measurement.
runFilterPreflight({
argv: process.argv,
root: __dirname,
packageName: '@objectstack/cli',
populations: { unit: UNIT_FILES, integration: INTEGRATION_FILES },
parse: parseCLI,
});
// #18788 — the same narrowing, the same failure direction, a different input.
// `projects` also swallows a CLI TIMEOUT OVERRIDE: vitest 4.1.11 carries only a
// closed twenty-name allowlist into a project config, `hookTimeout` and
// `teardownTimeout` are not on it, and a run that names one uses the DEFAULT
// budget and reports a pass that measured nothing. Measured in THIS package and
// not recalled: a probe `beforeAll` sleeping 500ms passes under
// `--hookTimeout=1` (with and without `--project`), while the same probe body
// under `@objectstack/plugin-dev`, which declares no `projects`, exits 1 with
// `Hook timed out in 1ms.`
//
// ⭐ That flag is the instrument this repo's own prior art reaches for to
// witness that a cold load has left every clocked window — the header of
// `packages/plugins/plugin-dev/src/dev-plugin-security-enforcement-warning.test.ts`
// reads a GREEN under `--hookTimeout=1` as the pass. Here that green cannot
// fail, so it is indistinguishable from one that passed, and this call is what
// stops it being read: the run is REFUSED, loudly, naming the spellings that do
// bite here. ⛔ Not forwarded into the projects below — the shared module's
// header carries the three measured reasons that was rejected.
runProjectCliOverridePreflight({
argv: process.argv,
packageName: '@objectstack/cli',
parse: parseCLI,
});
// #19278 — THE FILE-LEVEL SLICE ARRIVES AS AN ENV VAR, NOT AS A PASSTHROUGH.
// `scripts/partition-test-shards.mjs` slices this package (`FILE_SHARDED_PACKAGES`),
// and Test Core runs each slice as `OS_TEST_SHARD=k/n turbo run test`. The
// value reaches vitest HERE because vitest 4.1.11 reads no shard variable of its
// own (no `VITEST_SHARD`: the variables it reads are enumerable in its dist),
// and it reaches this process at all only because `turbo.json` declares
// `OS_TEST_SHARD` in this package's `test` task `env` — which is also what
// puts the slice in the task hash. Unset (every local run, the whole-package
// leg, the nightly) it is `undefined`, and the run is unsharded as before; a
// `--shard` on the command line still wins, because vitest merges the CLI
// options OVER this block.
//
// Why a passthrough (`-- --shard=k/n`) is no longer the carrier: turbo folds a
// run-level passthrough into the hash of every task in the run, so the slice
// leg had to be `--only`, and `--only` drops the `build` closure out of the
// test's hash — a slice could replay across the very upstream change that put
// it in the affected set. An env declared on the task reaches only the task.
//
// ⚠️ Typed against vitest's CLI-options type (`TestUserConfig`), spread rather
// than written as a literal key: vitest declares `shard` on its CLI options and
// NOT on `InlineConfig`, the type of this `test` block (a literal `shard:` is
// TS2769 here: "'shard' does not exist in type 'InlineConfig'"), yet it
// resolves the two as one object (`deepMerge(configDefaults, test, cliOptions)`)
// — measured on 4.1.11, it honours this key, projects included. The
// partitioner's `--self-test` fails when a sliced package's config stops
// reading the variable — it looks for the read in code position, so the read
// lives at its use site in the `test` block below, not in a helper binding
// that could outlive the spread.
export default defineConfig({
resolve: {
// Array form with an ANCHORED pattern, per the trap the gate documents:
// the object form matches by PREFIX, so a bare key whose replacement is a
// FILE also swallows every subpath and resolves it to `…/index.ts/<sub>`
// (`ENOTDIR`, at run time, in a config that looks right). `service-cache`
// is imported bare and has no subpath exports, but the anchored form is
// what keeps that true when one is added.
alias: [
{
find: /^@objectstack\/service-cache$/,
replacement: path.resolve(__dirname, '../services/service-cache/src/index.ts'),
},
{
find: /^create-objectstack\/created-summary$/,
replacement: path.resolve(__dirname, '../create-objectstack/src/created-summary.ts'),
},
// `test/serve-node-env-production-default.e2e.test.ts` (#11113) writes a
// FIXTURE config file whose text is `import { AuthPlugin } from
// '@objectstack/plugin-auth'` — real code, but code the fixture's own
// SPAWNED CHILD process resolves via bundle-require, never through this
// Vite config. `check-test-source-alias` is a dependency-free text
// reader (this file's own header explains why); it cannot tell that
// occurrence apart from a real import in THIS file, and flags it as an
// unaliased artifact import the same way it would a genuine one. This
// entry satisfies the gate; it is inert for the actual e2e run (the
// child's own dist/-resolving `exports` lookup is what that test
// deliberately exercises — see the file's header for why testing the
// BUILT artifact is the point there).
{
find: /^@objectstack\/plugin-auth$/,
replacement: path.resolve(__dirname, '../plugins/plugin-auth/src/index.ts'),
},
// `test/fixtures/option-b-reader-probe.ts` (#15232) calls
// `@objectstack/plugin-dev`'s shipped i18n auto-detect decision — the
// option-B acceptance pin (#15004) measures readers by CALLING them, and
// a reader resolved through `exports` to plugin-dev's **dist** would make
// that row a verdict about the last build rather than about the reader
// this card changes. The registry in `check-test-source-alias.mjs` is
// SHRINK-ONLY, so widening `KNOWN_UNALIASED_TEST_IMPORTS` was never an
// option; this entry is the sanctioned remedy, in the same anchored form
// as its neighbours (plugin-dev publishes only `"."`, and the anchor is
// what keeps that true if a subpath is ever added). Measured before and
// after: the gate reports the same required set for this package in both
// directions — crossing into `plugin-dev/src` adds no unaliased artifact
// import it did not already carry.
{
find: /^@objectstack\/plugin-dev$/,
replacement: path.resolve(__dirname, '../plugins/plugin-dev/src/index.ts'),
},
// `src/utils/protocol-version-gap.test.ts` (#13860) exercises the upgrade
// advisory, whose verdict comes from `checkProtocolCompat` — the platform's
// single reader of `engines.protocol`. The advisory is a thin direction
// check over that handshake, so a test resolving the handshake through
// `exports` to metadata-core's **dist** would be a verdict about build
// state: the range grammar it actually pins would be whatever was last
// compiled, and the dangerous half is not an error but a green run against
// a stale artifact.
{
find: /^@objectstack\/metadata-core$/,
replacement: path.resolve(__dirname, '../metadata-core/src/index.ts'),
},
// `test/rls-policy-authoring-admission.test.ts` (#20158) runs the metadata
// save door over a REAL engine with real DDL, and a `:memory:` SQLite
// driver is its storage. Resolved to source for the reason the entry above
// gives: a verdict about the checkout, not about the last build. Anchored
// like its neighbours (driver-sqlite-wasm publishes only `"."`).
{
find: /^@objectstack\/driver-sqlite-wasm$/,
replacement: path.resolve(__dirname, '../drivers/driver-sqlite-wasm/src/index.ts'),
},
// [#21120] `utils/data-migration-plugins.ts` now reaches
// `@objectstack/plugin-audit` (the `audit: true` arm boots `AuditPlugin`
// so `os migrate audit-metadata-bodies` can read/rewrite the audit
// tables), and `migrate/meta.stored-flow-resolution.integration.test.ts`
// imports that module. Resolved to source for the reason its neighbours
// give: a test that reaches a sibling package must be a verdict about the
// checkout, not about the last build. Anchored (plugin-audit publishes
// only `"."`). `check:test-source-alias` names this import and is the gate
// that fails without the entry.
{
find: /^@objectstack\/plugin-audit$/,
replacement: path.resolve(__dirname, '../plugins/plugin-audit/src/index.ts'),
},
],
},
test: {
// The file-level slice, when Test Core runs one (#19278) — see the section
// above `export default` for why it is spread and typed this way.
...({ shard: process.env.OS_TEST_SHARD } satisfies Pick<TestUserConfig, 'shard'>),
// A late console.* must not redden a green suite (#10374): vitest's worker
// forwards console output over RPC and discards the promise, and a write
// landing after teardown's rpcDone() snapshot is rejected into an unhandled
// error — a fully green run that exits 1. Disarming removes the mechanism.
// Mechanism + measured costs: examples/app-showcase/vitest.config.ts.
// Enforced repo-wide by scripts/check-console-intercept-disarm.mjs.
disableConsoleIntercept: true,
server: {
deps: {
external: [/packages[\/]types[\/]dist/],
},
},
// The two tiers (commit 44813ba57) — see the header section of the same name, and
// "THE NIGHTLY TIERS" for the population both read. Both `extends: true`
// so each project inherits the `resolve.alias` table and the
// `server.deps.external` entry above; each repeats the console-intercept
// disarm — and, since #17647, the registry log level — because a
// root-level value is inert under projects.
projects: [
{
extends: true,
test: {
name: 'unit',
disableConsoleIntercept: true,
// #13517: quiet the registry's per-item registration chatter — the
// engine's own `OS_REGISTRY_LOG` seam, not a change to its shipped
// default. Enforced by scripts/check-registry-log-declared.mjs.
env: { OS_REGISTRY_LOG: 'warn' },
include: UNIT_FILES,
},
},
{
extends: true,
test: {
name: 'integration',
disableConsoleIntercept: true,
// #13517: quiet the registry's per-item registration chatter — the
// engine's own `OS_REGISTRY_LOG` seam, not a change to its shipped
// default. Enforced by scripts/check-registry-log-declared.mjs.
env: { OS_REGISTRY_LOG: 'warn' },
include: INTEGRATION_FILES,
},
},
],
},
});