@ultimat3/cli 23.0.0 → 25.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (296) hide show
  1. package/CLAUDE.md +28 -13
  2. package/README.md +32 -7
  3. package/package.json +29 -29
  4. package/src/admin-catalog-keys.ts +2 -2
  5. package/src/affected.ts +30 -36
  6. package/src/api-registration.ts +89 -0
  7. package/src/app-artifacts.ts +1 -1
  8. package/src/app-auth.ts +4 -27
  9. package/src/app-boundaries.ts +17 -4
  10. package/src/app-config-load.ts +51 -0
  11. package/src/app-env.ts +26 -25
  12. package/src/app-load.ts +38 -14
  13. package/src/app-manifest.ts +4 -10
  14. package/src/app-mcp.ts +31 -46
  15. package/src/app-name.ts +52 -0
  16. package/src/app-permissions-borrowed.ts +123 -0
  17. package/src/app-permissions-site.ts +48 -0
  18. package/src/app-permissions.ts +7 -17
  19. package/src/app-root.ts +9 -5
  20. package/src/bin.ts +3 -7
  21. package/src/browser-launcher.ts +4 -2
  22. package/src/budgets-charged.ts +60 -0
  23. package/src/budgets-scan.ts +76 -0
  24. package/src/budgets-unmeasured.ts +91 -0
  25. package/src/budgets.ts +117 -154
  26. package/src/cdp-shot-driver.ts +20 -1
  27. package/src/cdp-shot-watch.ts +35 -31
  28. package/src/ci-log.ts +0 -0
  29. package/src/cmd-auth-spec.ts +16 -0
  30. package/src/cmd-auth.ts +65 -0
  31. package/src/cmd-build-spec.ts +7 -1
  32. package/src/cmd-build.ts +151 -22
  33. package/src/cmd-db-backfill.ts +26 -13
  34. package/src/cmd-db-branch.ts +2 -2
  35. package/src/cmd-db.ts +10 -6
  36. package/src/cmd-deploy-compose.ts +94 -0
  37. package/src/cmd-deploy-drain.ts +48 -0
  38. package/src/cmd-deploy-helm.ts +7 -6
  39. package/src/cmd-deploy-start-first.ts +178 -0
  40. package/src/cmd-deploy.ts +158 -36
  41. package/src/cmd-dev.ts +18 -356
  42. package/src/cmd-doctor-spec.ts +2 -9
  43. package/src/cmd-doctor.ts +57 -80
  44. package/src/cmd-env.ts +55 -16
  45. package/src/cmd-generate.ts +89 -32
  46. package/src/cmd-help.ts +19 -6
  47. package/src/cmd-i18n.ts +21 -17
  48. package/src/cmd-jobs-spec.ts +7 -22
  49. package/src/cmd-jobs.ts +33 -140
  50. package/src/cmd-manifest-spec.ts +1 -1
  51. package/src/cmd-manifest.ts +6 -25
  52. package/src/cmd-mcp.ts +71 -22
  53. package/src/cmd-new.ts +36 -8
  54. package/src/cmd-planned.ts +24 -8
  55. package/src/cmd-routes.ts +15 -5
  56. package/src/cmd-secrets.ts +47 -6
  57. package/src/cmd-shot-matrix.ts +22 -7
  58. package/src/cmd-shot-spec.ts +10 -2
  59. package/src/cmd-shot.ts +46 -41
  60. package/src/cmd-tasks.ts +2 -2
  61. package/src/cmd-test-spec.ts +1 -7
  62. package/src/cmd-test.ts +14 -30
  63. package/src/cmd-verify.ts +4 -4
  64. package/src/coverage-lcov.ts +4 -2
  65. package/src/db-backfill.ts +2 -2
  66. package/src/db-replay-engine.ts +4 -10
  67. package/src/db-subscribes.ts +27 -16
  68. package/src/dev-boot.ts +290 -0
  69. package/src/dev-child-watch.ts +63 -12
  70. package/src/dev-dashboard-guard.ts +76 -0
  71. package/src/dev-dashboard.ts +56 -30
  72. package/src/dev-lock.ts +100 -31
  73. package/src/dev-rebuild.ts +80 -0
  74. package/src/dev-restart-retire.ts +46 -0
  75. package/src/dev-route-table.ts +30 -8
  76. package/src/dev-supervisor.ts +114 -22
  77. package/src/dev-traces.ts +53 -20
  78. package/src/dispatch.ts +17 -13
  79. package/src/doctor-app-url.ts +39 -0
  80. package/src/doctor-auth.ts +95 -0
  81. package/src/doctor-offline.ts +6 -5
  82. package/src/document-options.ts +7 -0
  83. package/src/drift-replica-identity.ts +42 -0
  84. package/src/drift.ts +29 -6
  85. package/src/error-catalog.ts +6 -9
  86. package/src/error-codes.ts +29 -3
  87. package/src/error-page-csp.ts +14 -15
  88. package/src/error-unthrown.ts +29 -11
  89. package/src/errors.ts +54 -83
  90. package/src/exec.ts +8 -0
  91. package/src/favicon.ts +2 -2
  92. package/src/fix-command.ts +9 -1
  93. package/src/framework-env.ts +148 -0
  94. package/src/framework-schema-apply.ts +78 -0
  95. package/src/framework-schema-stamp.ts +96 -0
  96. package/src/framework-schema.ts +25 -3
  97. package/src/generate-catalog-locales.ts +79 -0
  98. package/src/generate-files.ts +4 -3
  99. package/src/generate-kinds.ts +18 -0
  100. package/src/generate-plural.ts +50 -0
  101. package/src/generate-shadow.ts +95 -0
  102. package/src/generate-write.ts +47 -13
  103. package/src/gh-target.ts +12 -7
  104. package/src/hold.ts +56 -1
  105. package/src/i18n-audit.ts +25 -9
  106. package/src/i18n-index.ts +5 -6
  107. package/src/i18n-registration.ts +7 -9
  108. package/src/import-scan.ts +28 -3
  109. package/src/index.ts +2 -6
  110. package/src/island-bundle.ts +57 -79
  111. package/src/island-capture.ts +1 -1
  112. package/src/island-duplicate-refusal.ts +57 -0
  113. package/src/island-duplicates.ts +221 -0
  114. package/src/island-entry-missing.ts +64 -0
  115. package/src/island-harness-route.ts +20 -4
  116. package/src/island-harness-script.ts +23 -2
  117. package/src/island-identity.ts +17 -14
  118. package/src/island-package-dedupe.ts +150 -0
  119. package/src/island-realtime.ts +36 -16
  120. package/src/island-routes.ts +2 -2
  121. package/src/island-runtime.ts +241 -0
  122. package/src/island-sources.ts +105 -0
  123. package/src/island-states-load.ts +6 -0
  124. package/src/island-store.ts +90 -6
  125. package/src/jobs-json.ts +0 -26
  126. package/src/jobs-report.ts +36 -20
  127. package/src/local-cli-handoff.ts +90 -0
  128. package/src/loopback-host.ts +13 -0
  129. package/src/manifest-staleness.ts +62 -0
  130. package/src/mcp-errors.ts +20 -4
  131. package/src/mcp-host.ts +22 -14
  132. package/src/mcp-test-run.ts +38 -0
  133. package/src/mcp-ui-inspect.ts +3 -2
  134. package/src/mcp-ui-interact.ts +3 -2
  135. package/src/mcp-ui.ts +8 -7
  136. package/src/measure-scope.ts +15 -10
  137. package/src/messages.ts +29 -16
  138. package/src/metrics-endpoint.ts +28 -3
  139. package/src/migration-extensions.ts +1 -1
  140. package/src/otlp-export.ts +2 -2
  141. package/src/output.ts +24 -0
  142. package/src/page-navigation.ts +7 -23
  143. package/src/page-speculation.ts +19 -31
  144. package/src/parse.ts +0 -8
  145. package/src/permission-grants.ts +3 -3
  146. package/src/posix-path.ts +30 -0
  147. package/src/prerender-locales.ts +6 -4
  148. package/src/prerender-out.ts +34 -7
  149. package/src/prerender-report.ts +58 -0
  150. package/src/prerender-served.ts +52 -0
  151. package/src/prerender.ts +71 -103
  152. package/src/pwa-artifacts.ts +33 -94
  153. package/src/pwa-manifest-locales.ts +9 -9
  154. package/src/registry.ts +2 -0
  155. package/src/retry-memo.ts +1 -1
  156. package/src/revalidated-response.ts +4 -3
  157. package/src/role-realtime.ts +7 -1
  158. package/src/role-replicator.ts +124 -29
  159. package/src/role-start.ts +69 -85
  160. package/src/role-sync.ts +119 -25
  161. package/src/root-env.ts +157 -28
  162. package/src/route-islands.ts +69 -0
  163. package/src/runtime-admin.ts +4 -1
  164. package/src/runtime-assets.ts +26 -9
  165. package/src/runtime-bindings.ts +27 -7
  166. package/src/runtime-cache.ts +46 -55
  167. package/src/runtime-hooks.ts +2 -1
  168. package/src/runtime-idempotency-scope.ts +23 -0
  169. package/src/runtime-isr-outcome.ts +100 -0
  170. package/src/runtime-isr.ts +36 -0
  171. package/src/runtime-jobs.ts +76 -41
  172. package/src/runtime-live-feed.ts +1 -1
  173. package/src/runtime-mail.ts +26 -0
  174. package/src/runtime-mfa-warning.ts +29 -0
  175. package/src/runtime-notify-retention.ts +10 -46
  176. package/src/runtime-overrides.ts +4 -4
  177. package/src/runtime-purge.ts +38 -16
  178. package/src/runtime-queue.ts +43 -22
  179. package/src/runtime-realtime.ts +11 -45
  180. package/src/runtime-render.ts +136 -114
  181. package/src/runtime-replica.ts +3 -8
  182. package/src/runtime-services.ts +49 -26
  183. package/src/runtime-storage-upload.ts +2 -2
  184. package/src/runtime-storage.ts +13 -8
  185. package/src/schema-diff.ts +16 -0
  186. package/src/schema-drift.ts +45 -15
  187. package/src/secrets-rotation.ts +8 -9
  188. package/src/seo-routes.ts +80 -9
  189. package/src/serve-boot.ts +51 -12
  190. package/src/serve-drain.ts +11 -33
  191. package/src/serve-env.ts +9 -6
  192. package/src/serve-retire.ts +95 -0
  193. package/src/serve-web.ts +31 -15
  194. package/src/serve.ts +27 -10
  195. package/src/server-barrels.ts +6 -3
  196. package/src/shot-browser.ts +1 -1
  197. package/src/shot-locale.ts +6 -33
  198. package/src/shot-settle.ts +3 -3
  199. package/src/shot-theme.ts +1 -1
  200. package/src/shot-viewport.ts +152 -0
  201. package/src/signal-shred.ts +26 -12
  202. package/src/site-asset-routes.ts +2 -2
  203. package/src/site-assets.ts +3 -5
  204. package/src/site-config.ts +9 -48
  205. package/src/site-seo.ts +7 -5
  206. package/src/sitemap-lastmod.ts +5 -0
  207. package/src/solid-loader.ts +12 -3
  208. package/src/source-files.ts +19 -3
  209. package/src/static-report.ts +5 -0
  210. package/src/stored-object-headers.ts +43 -0
  211. package/src/style-routes.ts +2 -2
  212. package/src/sw-artifacts.ts +8 -8
  213. package/src/sw-precache-plan.ts +34 -4
  214. package/src/sync-authenticator.ts +3 -3
  215. package/src/sync-url.ts +3 -3
  216. package/src/tasks-facts.ts +3 -29
  217. package/src/templates/action.ts +7 -4
  218. package/src/templates/backfill.ts +4 -4
  219. package/src/templates/entity.ts +2 -2
  220. package/src/templates/github/ci.yml.ts +28 -18
  221. package/src/templates/github/image.yml.ts +92 -0
  222. package/src/templates/github/runner.ts +18 -0
  223. package/src/templates/guard-bare-error.ts +16 -2
  224. package/src/templates/guard-unzoned-date.ts +56 -6
  225. package/src/templates/index.ts +1 -1
  226. package/src/templates/job-body-test.ts +2 -2
  227. package/src/templates/job.ts +6 -6
  228. package/src/templates/naming.ts +28 -0
  229. package/src/templates/query.ts +9 -8
  230. package/src/templates/resource-page.ts +9 -7
  231. package/src/templates/resource-service.ts +2 -2
  232. package/src/templates/route.ts +26 -14
  233. package/src/templates/scaffold-api.ts +2 -3
  234. package/src/templates/scaffold-app.ts +8 -3
  235. package/src/templates/scaffold-auth.ts +205 -70
  236. package/src/templates/scaffold-bin.ts +122 -0
  237. package/src/templates/scaffold-claude-commands.ts +11 -11
  238. package/src/templates/scaffold-container-compose.ts +10 -1
  239. package/src/templates/scaffold-container.ts +16 -8
  240. package/src/templates/scaffold-dashboard-bare.ts +8 -4
  241. package/src/templates/scaffold-dashboard-example-tests.ts +208 -0
  242. package/src/templates/scaffold-dashboard-example.ts +112 -172
  243. package/src/templates/scaffold-dashboard-shared.ts +62 -1
  244. package/src/templates/scaffold-dashboard.ts +9 -5
  245. package/src/templates/scaffold-db-client.ts +4 -2
  246. package/src/templates/scaffold-db-package.ts +2 -2
  247. package/src/templates/scaffold-docs.ts +20 -77
  248. package/src/templates/scaffold-domain-package.ts +0 -4
  249. package/src/templates/scaffold-entries.ts +4 -3
  250. package/src/templates/scaffold-env.ts +6 -3
  251. package/src/templates/scaffold-gitattributes.ts +59 -0
  252. package/src/templates/scaffold-helm-fences.ts +76 -0
  253. package/src/templates/scaffold-helm-templates.ts +100 -19
  254. package/src/templates/scaffold-helm.ts +62 -5
  255. package/src/templates/scaffold-http.ts +1 -1
  256. package/src/templates/scaffold-i18n.ts +15 -2
  257. package/src/templates/scaffold-icon.ts +2 -2
  258. package/src/templates/scaffold-repo.ts +27 -16
  259. package/src/templates/scaffold-shell.ts +55 -37
  260. package/src/test-counts.ts +2 -6
  261. package/src/test-ignores.ts +25 -0
  262. package/src/test-only-shipped.ts +193 -0
  263. package/src/test-passes.ts +0 -15
  264. package/src/test-select.ts +7 -15
  265. package/src/test-shards.ts +40 -95
  266. package/src/test-workers.ts +26 -4
  267. package/src/theme-boot.ts +8 -33
  268. package/src/theme-brand.ts +66 -0
  269. package/src/trusted-hops-error.ts +17 -0
  270. package/src/trusted-hops.ts +49 -0
  271. package/src/unscanned-admin.ts +2 -2
  272. package/src/unthrown-codes.ts +26 -0
  273. package/src/verify-checks.ts +29 -48
  274. package/src/verify-coverage-run.ts +3 -2
  275. package/src/verify-deadline.ts +207 -33
  276. package/src/verify-merge.ts +108 -43
  277. package/src/verify-part-step.ts +98 -0
  278. package/src/verify-role-load-probe.ts +12 -2
  279. package/src/verify-role-load.ts +9 -4
  280. package/src/verify-run.ts +32 -8
  281. package/src/verify-shard.ts +13 -2
  282. package/src/verify-stalled.ts +152 -0
  283. package/src/verify-step.ts +2 -0
  284. package/src/verify-test-run.ts +3 -2
  285. package/src/verify-tests.ts +5 -3
  286. package/src/worker-bundle.ts +2 -2
  287. package/src/workspace-checks.ts +53 -7
  288. package/src/workspace-graph.ts +3 -2
  289. package/src/write-line.ts +82 -7
  290. package/src/browser-launcher-fake-html.ts +0 -80
  291. package/src/browser-launcher-fake.ts +0 -168
  292. package/src/build-errors.ts +0 -54
  293. package/src/jobs-drain.ts +0 -163
  294. package/src/scaffold-typecheck.ts +0 -227
  295. package/src/thrown-by.ts +0 -24
  296. package/src/verify-errors.ts +0 -58
@@ -0,0 +1,193 @@
1
+ // `package-shape`'s third rule of "test code does not ship": a `src/` module only tests (and
2
+ // fixtures) import is test code under a name `files` cannot see. The tarball excludes `*.test.ts`
3
+ // and `*-fixture.ts`, so a fake, a probe or a `bun:test` helper named anything else rode into
4
+ // every install — 20 of them across eleven packages until 2026-10. The defect is the name.
5
+
6
+ import { ERROR_DOCS_URL, renderFixShellArg } from '@ultimat3/core';
7
+ import type { ClosureHost } from './import-closure';
8
+ import { resolveSpec } from './import-closure';
9
+ import type { Finding } from './output';
10
+ import { FIXTURE_SUFFIX } from './publish-closure';
11
+ import { eachSourceFile, isGenerated, isTest } from './source-files';
12
+
13
+ /** A published package: its directory under `packages/` and the files its manifest names. */
14
+ export interface ShippedPackage {
15
+ readonly dir: string;
16
+ /** Package-relative (`src/index.ts`), and may be a glob (`src/icons/glyphs/*.ts`). */
17
+ readonly entries: readonly string[];
18
+ }
19
+
20
+ export interface TestOnlyModule {
21
+ readonly path: string;
22
+ /** Every file that imports or names it — all of them tests, fixtures or test-only modules. */
23
+ readonly importers: readonly string[];
24
+ }
25
+
26
+ /**
27
+ * Every `src/` file a manifest names anywhere — `exports`, `bin`, the `browser` map, a `scripts`
28
+ * command (`bun run src/icons/build-icons.ts`). Read off the whole document rather than field by
29
+ * field: a module the manifest names is shipped on purpose, whichever field names it.
30
+ */
31
+ export function entryPatternsOf(manifest: unknown): readonly string[] {
32
+ const text = JSON.stringify(manifest ?? null);
33
+ const found = [...text.matchAll(/(?:\.\/)?(src\/[\w./*-]+?\.tsx?)(?![\w.])/g)];
34
+ return [...new Set(found.map((match) => match[1] ?? ''))].sort();
35
+ }
36
+
37
+ // Type-only imports count: the package ships `.ts` source, so a consumer's `tsc` opens the file a
38
+ // shipped `import type` names. One pattern for `from '.'`, `import('.')` and `import '.'`, relative
39
+ // specifiers only: over-matching an importer only ever clears a module, never flags one.
40
+ const RELATIVE = /(?:\bfrom|\bimport)\s*\(?\s*(['"])(\.\.?\/[^'"]*)\1/g;
41
+ /**
42
+ * A template-literal `import(\`./drivers/${n}\`)`: the static head up to the first `${` names a
43
+ * DIRECTORY prefix, and any module under it may be the one loaded. With no `${` the closing
44
+ * backtick ends it and it is an ordinary specifier.
45
+ */
46
+ const TEMPLATE = /\bimport\s*\(\s*`(\.\.?\/[^`$]*)(\$\{|`)/g;
47
+ /** A file named in a string — `join(import.meta.dir, 'probe.ts')`, `new URL('./worker.ts', …)`. */
48
+ const NAMED = /['"`/]([\w.-]+\.tsx?)['"`]/g;
49
+
50
+ /**
51
+ * Whether the match at `index` sits on a line that IS a comment — `//`, a doc comment's `*`, a
52
+ * `/*` opener. Line-wise and not core's `stripComments`, which is exact and costs ~1.1 s over the
53
+ * repo's 6,000 files (measured 2026-10-05); asked only of a match, never of every line. The lines
54
+ * this skips are where a commented-out or quoted-in-prose import lives, and one it keeps can only
55
+ * clear a module, never flag one.
56
+ */
57
+ function onCommentLine(text: string, index: number): boolean {
58
+ let at = text.lastIndexOf('\n', index - 1) + 1;
59
+ while (text[at] === ' ' || text[at] === '\t') at += 1;
60
+ const head = text.slice(at, at + 2);
61
+ return head === '//' || head === '/*' || head.startsWith('*');
62
+ }
63
+
64
+ /** `from`'s directory joined with a relative `head`, `.` and `..` folded; a trailing `/` kept. */
65
+ function prefixFrom(from: string, head: string): string {
66
+ const parts = from.split('/').slice(0, -1);
67
+ const segments = head.split('/');
68
+ const last = segments.pop() ?? '';
69
+ for (const segment of segments) {
70
+ if (segment === '..') parts.pop();
71
+ else if (segment !== '.' && segment !== '') parts.push(segment);
72
+ }
73
+ return `${parts.join('/')}/${last}`;
74
+ }
75
+
76
+ const isFixture = (path: string): boolean => path.endsWith(FIXTURE_SUFFIX);
77
+ const baseName = (path: string): string => path.slice(path.lastIndexOf('/') + 1);
78
+
79
+ const add = (map: Map<string, Set<string>>, key: string, value: string): void => {
80
+ const set = map.get(key);
81
+ if (set === undefined) map.set(key, new Set([value]));
82
+ else set.add(value);
83
+ };
84
+
85
+ /** Who imports each file, and which files name each basename in a string. One pass per file. */
86
+ function referrersOf(sources: ReadonlyMap<string, string>): {
87
+ readonly imports: ReadonlyMap<string, ReadonlySet<string>>;
88
+ readonly named: ReadonlyMap<string, ReadonlySet<string>>;
89
+ } {
90
+ const host: ClosureHost = { read: (path) => sources.get(path), alias: () => undefined };
91
+ const imports = new Map<string, Set<string>>();
92
+ const named = new Map<string, Set<string>>();
93
+ const prefixes: { readonly prefix: string; readonly from: string }[] = [];
94
+ for (const [from, text] of sources) {
95
+ for (const match of text.matchAll(RELATIVE)) {
96
+ if (onCommentLine(text, match.index)) continue;
97
+ const target = resolveSpec(host, from, match[2] ?? '');
98
+ if (target !== undefined && target !== from) add(imports, target, from);
99
+ }
100
+ for (const match of text.matchAll(TEMPLATE)) {
101
+ if (onCommentLine(text, match.index)) continue;
102
+ const head = match[1] ?? '';
103
+ if (match[2] === '`') {
104
+ const target = resolveSpec(host, from, head);
105
+ if (target !== undefined && target !== from) add(imports, target, from);
106
+ } else prefixes.push({ prefix: prefixFrom(from, head), from });
107
+ }
108
+ for (const match of text.matchAll(NAMED)) {
109
+ if (!onCommentLine(text, match.index)) add(named, match[1] ?? '', from);
110
+ }
111
+ }
112
+ // Few computed imports in any repo, so a scan of every path per prefix costs nothing measurable.
113
+ for (const { prefix, from } of prefixes) {
114
+ for (const path of sources.keys()) {
115
+ if (path !== from && path.startsWith(prefix)) add(imports, path, from);
116
+ }
117
+ }
118
+ return { imports, named };
119
+ }
120
+
121
+ /**
122
+ * The shipped `.ts` modules of the given packages whose every importer is a test, a fixture or
123
+ * another such module — a greatest fixed point, so a helper only a fake imports is caught with the
124
+ * fake. A module nobody references is not this rule's question, and an entry point is shipped by
125
+ * definition. `.tsx` is not judged: `FIXTURE_EXCLUSION` matches `-fixture.ts` alone.
126
+ */
127
+ export function testOnlyModules(
128
+ sources: ReadonlyMap<string, string>,
129
+ packages: readonly ShippedPackage[],
130
+ ): readonly TestOnlyModule[] {
131
+ const { imports, named } = referrersOf(sources);
132
+ const entryGlobs = new Map(
133
+ packages.map((pkg) => [
134
+ pkg.dir,
135
+ pkg.entries.map((entry) => new Bun.Glob(`packages/${pkg.dir}/${entry}`)),
136
+ ]),
137
+ );
138
+ const referrers = new Map<string, ReadonlySet<string>>();
139
+ for (const path of sources.keys()) {
140
+ const dir = /^packages\/([^/]+)\/src\//.exec(path)?.[1];
141
+ const globs = dir === undefined ? undefined : entryGlobs.get(dir);
142
+ if (globs === undefined || !path.endsWith('.ts') || isGenerated(path)) continue;
143
+ if (isTest(path) || isFixture(path) || globs.some((glob) => glob.match(path))) continue;
144
+ const from = new Set([...(imports.get(path) ?? []), ...(named.get(baseName(path)) ?? [])]);
145
+ from.delete(path);
146
+ if (from.size > 0) referrers.set(path, from);
147
+ }
148
+ const testOnly = new Set(referrers.keys());
149
+ const live = (path: string): boolean => !isTest(path) && !isFixture(path) && !testOnly.has(path);
150
+ for (let changed = true; changed; ) {
151
+ changed = false;
152
+ for (const path of testOnly) {
153
+ if (![...(referrers.get(path) ?? [])].some(live)) continue;
154
+ testOnly.delete(path);
155
+ changed = true;
156
+ }
157
+ }
158
+ return [...testOnly].sort().map((path) => ({
159
+ path,
160
+ importers: [...(referrers.get(path) ?? [])].sort(),
161
+ }));
162
+ }
163
+
164
+ /** `X_PACKAGE_TEST_ONLY_SHIPPED`, its fix the registered rename with the real paths in it. */
165
+ export const testOnlyShippedFinding = (module: TestOnlyModule): Finding => {
166
+ const shown = module.importers.slice(0, 3).join(', ');
167
+ const more = module.importers.length > 3 ? ` and ${module.importers.length - 3} more` : '';
168
+ return {
169
+ code: 'X_PACKAGE_TEST_ONLY_SHIPPED',
170
+ cause: `${module.path} is imported only by tests and fixtures (${shown}${more}), and "files" publishes it — test scaffolding in every install`,
171
+ fix: `git mv ${renderFixShellArg(module.path, '<the module above>')} ${renderFixShellArg(module.path.replace(/\.ts$/, FIXTURE_SUFFIX), '<its name>-fixture.ts')} # and update its importers`,
172
+ docs: ERROR_DOCS_URL,
173
+ at: module.path,
174
+ meta: { importers: [...module.importers] },
175
+ };
176
+ };
177
+
178
+ /**
179
+ * The rule over a repo on disk. Every source file is a possible importer — a script reading a
180
+ * package's module by relative path is a shipped use of it — so the walk is the gate's own
181
+ * (`eachSourceFile`), tests included. A repo with no published package reads nothing.
182
+ */
183
+ export async function checkTestOnlyShipped(
184
+ root: string,
185
+ packages: readonly ShippedPackage[],
186
+ ): Promise<readonly Finding[]> {
187
+ if (packages.length === 0) return [];
188
+ const paths: string[] = [];
189
+ for await (const path of eachSourceFile(root)) if (!isGenerated(path)) paths.push(path);
190
+ const texts = await Promise.all(paths.map((path) => Bun.file(`${root}/${path}`).text()));
191
+ const sources = new Map(paths.map((path, index) => [path, texts[index] ?? '']));
192
+ return testOnlyModules(sources, packages).map(testOnlyShippedFinding);
193
+ }
@@ -24,8 +24,6 @@ export interface PassInput {
24
24
  readonly workers: number;
25
25
  /** The positional, when there was one. A pass over one type keeps it; the split never adds one. */
26
26
  readonly type?: TestType;
27
- /** Set for a `--worker I` rerun, which is one process by construction. */
28
- readonly shard?: number;
29
27
  }
30
28
 
31
29
  const widthFor = (files: readonly TestFile[], workers: number): number =>
@@ -45,22 +43,9 @@ const widthFor = (files: readonly TestFile[], workers: number): number =>
45
43
  *
46
44
  * Each serial type is its own pass rather than one pass over both, because the pass's `type` is
47
45
  * what its failure reproduces with: `x test live --workers 1` selects exactly the files that ran.
48
- *
49
- * A `--worker I` rerun is left whole: it is a single `bun test --isolate --shard=i/N` process, so
50
- * nothing inside it runs beside anything else, and splitting it would make shard i of the rerun a
51
- * different set of files from shard i of the run it reproduces.
52
46
  */
53
47
  export function testPasses(input: PassInput): readonly TestPass[] {
54
48
  const serialTypes = new Set<TestType>(SERIAL_TYPES);
55
- if (input.shard !== undefined) {
56
- return [
57
- {
58
- files: input.files,
59
- workers: widthFor(input.files, input.workers),
60
- ...(input.type === undefined ? {} : { type: input.type }),
61
- },
62
- ];
63
- }
64
49
  const shared = input.files.filter((file) => !serialTypes.has(ownerOf(file.path)));
65
50
  const passes: TestPass[] = [];
66
51
  // Cheapest first, and the widest first: the pool run is most of the corpus and most of the
@@ -6,11 +6,13 @@ import { join } from 'node:path';
6
6
  // Bun ships no equivalent: `join` builds the host-separator path from the scan root to a hit.
7
7
  // Sizing is Bun's own (`Bun.file().size`), so nothing here reaches for `node:fs`.
8
8
  import { nearestName, renderFixShellArg } from '@ultimat3/core';
9
+ import { TEST_TYPES } from '@ultimat3/testing';
9
10
  import { BadFlagError } from './errors';
10
11
  import type { ParsedArgs } from './parse';
11
12
  import { flagString } from './parse';
13
+ import { isIgnoredTestPath } from './test-ignores';
12
14
  import type { TestType } from './verify-tests';
13
- import { ownerOf, TEST_TYPES } from './verify-tests';
15
+ import { ownerOf } from './verify-tests';
14
16
 
15
17
  export interface TestFile {
16
18
  readonly path: string;
@@ -27,20 +29,10 @@ const TEST_GLOB = '**/*.test.{ts,tsx}';
27
29
 
28
30
  /**
29
31
  * The root `test` script's ignore list, kept identical so `x test` and `bun run test` see one
30
- * suite. `e2e/` is NOT on it: an opt-in suite that the gate runs but `x test` silently drops is
31
- * a suite nobody runs until CI says so. `examples/` is, because the reference app is a separate
32
- * project with its own gate — `x verify` there, not `x test` here.
33
- *
34
- * `dummy/` and `build/` complete the list `verify-tests.ts` already excluded (`NEVER_A_TEST`). The
35
- * comment above claimed the two agreed and they did not: `x test unit` discovered 464 files where
36
- * the gate's `unit` step ran 441, so the gate's own test steps — which now select through this
37
- * function — would have started running a nested demo app's suite on the framework's gate.
38
- *
39
- * Both directories are gated where they belong, by `scripts/reference-app-gate.ts` running
40
- * `x verify` inside each app — see `NEVER_A_TEST` for why that is the only place they run.
32
+ * suite — `test-ignores.ts` holds it, and the serial steps' argv projects the same policy. `e2e/`
33
+ * is NOT on it: an opt-in suite that the gate runs but `x test` silently drops is a suite nobody
34
+ * runs until CI says so.
41
35
  */
42
- const IGNORED = ['/dist/', '/build/', '/node_modules/', '/examples/', '/dummy/'];
43
-
44
36
  /**
45
37
  * File size stands in for duration: cheap to read, and it correlates far better than file count.
46
38
  * `type`, when given, narrows to exactly the files verify-tests.ts would run for that suite — one
@@ -58,7 +50,7 @@ export async function discoverTests(
58
50
  const files: TestFile[] = [];
59
51
  for await (const found of new Bun.Glob(TEST_GLOB).scan({ cwd: root, absolute: false })) {
60
52
  const path = found.split('\\').join('/');
61
- if (IGNORED.some((part) => `/${path}`.includes(part))) continue;
53
+ if (isIgnoredTestPath(path)) continue;
62
54
  if (filters !== undefined && !filters.some((part) => path.includes(part))) continue;
63
55
  if (type !== undefined && !belongsToType(path, type)) continue;
64
56
  files.push({ path, bytes: Bun.file(join(root, path)).size });
@@ -39,12 +39,12 @@
39
39
  // `@ultimat3/testing`'s preload clears the plugins after each file (`isolated-plugins.ts`). And the
40
40
  // per-WORKER database survives untouched: `@ultimat3/testing`'s `workerId` already read
41
41
  // `BUN_TEST_WORKER_ID` as its second key, which is exactly what Bun sets, 1..N, one per real
42
- // process (probed on 1.4.0). `ULTIMATE_TEST_WORKER` stays the first key and is what `--worker`
43
- // still sets, so a single-shard rerun keeps naming its own database.
42
+ // process (probed on 1.4.0).
44
43
 
45
44
  // why: Bun ships no path-join primitive; the timings cache lives under the run's root.
46
45
  import { join } from 'node:path';
47
46
  import { ERROR_DOCS_URL } from '@ultimat3/core';
47
+ import { ISOLATED_ENV } from '@ultimat3/testing';
48
48
  import type { AffectedSelection } from './affected';
49
49
  import type { Runner } from './exec';
50
50
  import { msg } from './messages';
@@ -52,9 +52,6 @@ import type { CommandResult, Finding, JsonValue, StepResult } from './output';
52
52
  import { quoteArg } from './shell-quote';
53
53
  import { testEnvOverrides } from './test-dotenv';
54
54
 
55
- /** `@ultimat3/testing`'s `ISOLATED_ENV`, restated: `cli → testing` is a runtime edge, kept to fixtures. */
56
- export const ISOLATED_TEST_ENV = 'ULTIMATE_TEST_ISOLATED';
57
-
58
55
  /** Bun's per-file durations, cached under the run's root and refreshed by every parallel run. */
59
56
  export const TEST_TIMINGS_FILE = '.x/test-timings.json';
60
57
 
@@ -68,24 +65,13 @@ import type { TestType } from './verify-tests';
68
65
  * The argv for one run. An explicit file list, never a re-glob: discovery already decided which
69
66
  * files belong to this type, and a child that globs again can pick up a file the selection removed.
70
67
  *
71
- * `--parallel=N` for the whole selection, `--shard=i+1/N` for one slice of it. The shard form is
72
- * `--worker`'s, and it carries `--isolate` in its own right — only `--parallel` implies it, and a
73
- * partition without a fresh module registry per file is the failure mode this whole design exists
74
- * to remove: measured on this repo, an 8-way split turned 0 failures into 36, every one
75
- * `X_PERMISSION_UNKNOWN` in `@ultimat3/query` because the `packages/cli` file that had been
76
- * declaring `feed:read` for it landed elsewhere. Half a dozen registries here are process-global by
77
- * design — the permission set, the roles, the entity/action/query tables, the error-code titles,
78
- * the fixture bag — and a serial `bun test` only passes because glob order happens to put every
79
- * declaring file before every file that reads what it left behind.
80
- *
81
- * Bun's shard partition is round-robin over the list it is given, so the sorted list this hands it
82
- * makes `--shard=2/8` the same 1/8 on CI and on a laptop (probed on 1.4.0).
68
+ * `--parallel=N` over the selection, sorted, so two runs over one corpus hand Bun the same list.
69
+ * One CI job's share of the corpus is `x verify --only unit --shard i/n` (`verify-shard.ts`), never
70
+ * a flag here: `x test --worker I` was a second, 0-based, unhashed split of the same suite.
83
71
  */
84
72
  export function testArgs(input: {
85
73
  readonly files: readonly string[];
86
74
  readonly workers: number;
87
- /** 0-based, matching `--worker`. Absent runs the whole selection across `workers` processes. */
88
- readonly shard?: number;
89
75
  /**
90
76
  * Everything after a bare `--`, handed to `bun test` verbatim and BEFORE the file list, which is
91
77
  * where bun reads its flags. `ParsedArgs.passthrough` had no reader anywhere until 2026-09, so
@@ -114,32 +100,23 @@ export function testArgs(input: {
114
100
  const isolate =
115
101
  extra.includes('--isolate') || (input.isolate === true && !extra.includes('--no-isolate'));
116
102
  const bare = extra.filter((arg) => arg !== '--isolate' && arg !== '--no-isolate');
117
- return input.shard === undefined
118
- ? [
119
- 'bun',
120
- 'test',
121
- `--parallel=${String(input.workers)}`,
122
- ...(isolate ? [] : ['--no-isolate']),
123
- ...(input.timings === undefined || bare.some((arg) => arg.startsWith('--timings'))
124
- ? []
125
- : [`--timings=${input.timings}`, '--update-timings']),
126
- ...bare,
127
- ...files,
128
- ]
129
- : [
130
- 'bun',
131
- 'test',
132
- ...(isolate ? ['--isolate'] : []),
133
- `--shard=${String(input.shard + 1)}/${String(input.workers)}`,
134
- ...bare,
135
- ...files,
136
- ];
103
+ return [
104
+ 'bun',
105
+ 'test',
106
+ `--parallel=${String(input.workers)}`,
107
+ ...(isolate ? [] : ['--no-isolate']),
108
+ ...(input.timings === undefined || bare.some((arg) => arg.startsWith('--timings'))
109
+ ? []
110
+ : [`--timings=${input.timings}`, '--update-timings']),
111
+ ...bare,
112
+ ...files,
113
+ ];
137
114
  }
138
115
 
139
116
  /**
140
117
  * The files an argv selects, flags stripped. `bun test` takes its file list positionally, so this
141
118
  * is the inverse of `testArgs` and the one thing a test asserting "what did the child get?" needs
142
- * — the flag COUNT is not fixed (`--parallel=N` is one token, a shard run carries two more), and a
119
+ * — the flag COUNT is not fixed (`--no-isolate` and the timings pair come and go), and a
143
120
  * test slicing a hardcoded prefix length reads a flag as a filename the day that changes.
144
121
  */
145
122
  export const filesIn = (command: readonly string[]): readonly string[] =>
@@ -154,12 +131,9 @@ export interface ReproduceOptions {
154
131
  readonly sample?: number;
155
132
  /**
156
133
  * The `--affected` narrowing, when there was one. The input most easily forgotten: `--affected`
157
- * decides which files exist to run at all, so a rerun without it selects the WHOLE corpus and
158
- * its shard 2 is a different shard 2.
134
+ * decides which files exist to run at all, so a rerun without it selects the WHOLE corpus.
159
135
  */
160
136
  readonly affected?: AffectedSelection;
161
- /** 0-based, when reproducing ONE shard. Absent reproduces the whole selection. */
162
- readonly shard?: number;
163
137
  /** What the caller put after `--`. It reaches `bun test`, so a rerun without it runs differently. */
164
138
  readonly passthrough?: readonly string[];
165
139
  /** The run was isolated per file; the rerun must be too. */
@@ -185,7 +159,6 @@ export function reproduceFor(options: ReproduceOptions): string {
185
159
  ...(options.affected?.dirty === true ? ['--dirty'] : []),
186
160
  '--workers',
187
161
  String(options.workers),
188
- ...(options.shard === undefined ? [] : ['--worker', String(options.shard)]),
189
162
  ...(options.isolate === true ? ['--isolate'] : []),
190
163
  // Last, and after a `--` of its own, because that is where the caller typed it and where the
191
164
  // parser will find it again. Quoted for `shell-quote.ts`'s reason: a reproduce line is pasted.
@@ -200,14 +173,11 @@ export interface RunShardsOptions {
200
173
  readonly runner: Runner;
201
174
  readonly files: readonly TestFile[];
202
175
  readonly workers: number;
203
- /** Run exactly one shard of the same N-way split, not a one-worker run of everything. */
204
- readonly only?: number;
205
176
  readonly filter?: string;
206
177
  readonly type?: TestType;
207
178
  /**
208
179
  * Set when `--sample` narrowed `files`: `kept` is what survived, `total` what discovery found.
209
- * `kept` is carried rather than counted from what ran, because `--worker N` runs one shard of the
210
- * sample and would otherwise report that shard's size as the corpus.
180
+ * Carried rather than counted from what ran: a pass runs part of the sample, never the corpus.
211
181
  */
212
182
  readonly sample?: { readonly kept: number; readonly total: number };
213
183
  /** Passed straight to `reproduceFor`: see `ReproduceOptions.affected`. */
@@ -248,7 +218,6 @@ const planOf = (
248
218
  ...(pass.type === undefined ? {} : { type: pass.type }),
249
219
  ...(options.sample === undefined ? {} : { sample: options.sample.kept }),
250
220
  ...(options.affected === undefined ? {} : { affected: options.affected }),
251
- ...(options.only === undefined ? {} : { shard: options.only }),
252
221
  ...(options.isolate === true ? { isolate: true } : {}),
253
222
  ...(options.passthrough === undefined || options.passthrough.length === 0
254
223
  ? {}
@@ -256,17 +225,12 @@ const planOf = (
256
225
  });
257
226
 
258
227
  /**
259
- * `X_TEST_SHARD_FAILED` for a `--worker` run and `X_TEST_FAILED` for a whole one, because the two
260
- * name different reruns: a shard is reproduced by naming it, and a full run by rerunning it. Both
261
- * codes already exist and both are already documented — a third would be a new name for a failed
262
- * `bun test`.
228
+ * `X_TEST_FAILED`, whose `fix:` reruns exactly what failed. `X_TEST_SHARD_FAILED` was the
229
+ * `--worker` rerun's and went with it: the code stays registered (codes are forever) and unthrown.
263
230
  */
264
231
  export const failureOf = (code: number, files: number, plan: ReproduceOptions): Finding => ({
265
- code: plan.shard === undefined ? 'X_TEST_FAILED' : 'X_TEST_SHARD_FAILED',
266
- cause:
267
- plan.shard === undefined
268
- ? `${plan.type ?? 'test'} run exited ${code} across ${plan.workers} worker(s) (${files} file(s))`
269
- : `shard ${plan.shard} of ${plan.workers} exited ${code} (${files} file(s))`,
232
+ code: 'X_TEST_FAILED',
233
+ cause: `${plan.type ?? 'test'} run exited ${code} across ${plan.workers} worker(s) (${files} file(s))`,
270
234
  fix: reproduceFor(plan),
271
235
  docs: ERROR_DOCS_URL,
272
236
  });
@@ -281,19 +245,17 @@ export const failureOf = (code: number, files: number, plan: ReproduceOptions):
281
245
  * it. And every pass runs even after one fails — the caller asked for a suite, and a report that
282
246
  * stops at the first red step hides the rest of the answer.
283
247
  *
284
- * `ULTIMATE_TEST_WORKER` is still set for a `--worker` rerun and only then: that run is one
285
- * process, so naming its database is this file's to do. A `--parallel` run has N of them and Bun
286
- * numbers each with `BUN_TEST_WORKER_ID`, which `@ultimat3/testing`'s `workerId` already reads.
248
+ * No `ULTIMATE_TEST_WORKER` here: a `--parallel` run has N processes and Bun numbers each with
249
+ * `BUN_TEST_WORKER_ID`, which `@ultimat3/testing`'s `workerId` already reads.
287
250
  */
288
251
  export async function runShards(options: RunShardsOptions): Promise<CommandResult> {
289
- const only = options.only;
290
252
  // Computed ONCE per invocation, never per pass: every pass spawns from the same `root` and the
291
253
  // same parent env, so the leaked-key set cannot differ pass to pass.
292
254
  const envOverrides: Record<string, string | undefined> = {
293
255
  ...testEnvOverrides(options.root, options.env ?? Bun.env),
294
256
  // Only an isolated run: the preload clears `Bun.plugin` handlers after each file, which in a
295
257
  // shared global would leave the next `.tsx` compiling with Bun's classic factory.
296
- ...(isolatedRun(options) ? { [ISOLATED_TEST_ENV]: '1' } : {}),
258
+ ...(isolatedRun(options) ? { [ISOLATED_ENV]: '1' } : {}),
297
259
  };
298
260
  const passthrough = options.passthrough ?? [];
299
261
  const watching = passthrough.includes('--watch');
@@ -302,7 +264,6 @@ export async function runShards(options: RunShardsOptions): Promise<CommandResul
302
264
  files: options.files,
303
265
  workers: options.workers,
304
266
  ...(options.type === undefined ? {} : { type: options.type }),
305
- ...(only === undefined ? {} : { shard: only }),
306
267
  });
307
268
  const started = performance.now();
308
269
  const steps: StepResult[] = [];
@@ -312,53 +273,38 @@ export async function runShards(options: RunShardsOptions): Promise<CommandResul
312
273
  let exitCode = 0;
313
274
  for (const pass of passes) {
314
275
  const files = pass.files.map((file) => file.path);
315
- // A `--worker` rerun is one process over one shard and is never split: shard i of the rerun
316
- // must be the same files as shard i of the run it reproduces. Nor is `-- --watch`, which never
317
- // exits, so a second batch would never start. Every other pass is spent in `test-batches.ts`'
318
- // batches, one `bun test` after another.
319
- const batches =
320
- only === undefined && !watching
321
- ? testBatches(
322
- files,
323
- pass.workers,
324
- isolatedRun(options) ? BATCH_FILES_PER_WORKER : SHARED_BATCH_FILES_PER_WORKER,
325
- )
326
- : [files];
276
+ // `-- --watch` is never split: it never exits, so a second batch would never start. Every
277
+ // other pass is spent in `test-batches.ts`' batches, one `bun test` after another.
278
+ const batches = !watching
279
+ ? testBatches(
280
+ files,
281
+ pass.workers,
282
+ isolatedRun(options) ? BATCH_FILES_PER_WORKER : SHARED_BATCH_FILES_PER_WORKER,
283
+ )
284
+ : [files];
327
285
  const result = await runBatches({
328
286
  runner: options.runner,
329
287
  batches,
330
288
  stopOnFailure: bailing,
331
289
  workers: pass.workers,
332
- // A `--worker` rerun is one process whatever its N says, and a serial pass is one worker:
333
- // only a default-width parallel pass leases from the machine pool.
334
- ...(only === undefined && pass.workers > 1 && options.lease !== undefined
335
- ? { lease: options.lease }
336
- : {}),
290
+ // A serial pass is one worker: only a default-width parallel pass leases from the pool.
291
+ ...(pass.workers > 1 && options.lease !== undefined ? { lease: options.lease } : {}),
337
292
  argsFor: (batch, width) =>
338
293
  testArgs({
339
294
  files: batch,
340
- workers: only === undefined ? width : pass.workers,
341
- ...(only === undefined ? {} : { shard: only }),
295
+ workers: width,
342
296
  ...(options.passthrough === undefined ? {} : { passthrough: options.passthrough }),
343
297
  ...(options.isolate === undefined ? {} : { isolate: options.isolate }),
344
298
  timings: join(options.root, TEST_TIMINGS_FILE),
345
299
  }),
346
300
  options: {
347
301
  cwd: options.root,
348
- ...(Object.keys(envOverrides).length === 0 && only === undefined
349
- ? {}
350
- : {
351
- env: {
352
- ...envOverrides,
353
- ...(only === undefined ? {} : { ULTIMATE_TEST_WORKER: String(only) }),
354
- },
355
- }),
302
+ ...(Object.keys(envOverrides).length === 0 ? {} : { env: envOverrides }),
356
303
  },
357
304
  });
358
305
  batchCount += batches.length;
359
306
  const plan = planOf(options, pass);
360
- const label =
361
- only === undefined ? `${pass.workers} worker(s)` : `shard ${only} of ${pass.workers}`;
307
+ const label = `${pass.workers} worker(s)`;
362
308
  steps.push({
363
309
  name: `${pass.type === undefined ? label : `${pass.type} · ${label}`} · ${files.length} files${
364
310
  batches.length > 1 ? msg('cli.test.batches', { batches: batches.length }) : ''
@@ -403,7 +349,6 @@ export async function runShards(options: RunShardsOptions): Promise<CommandResul
403
349
  durationMs,
404
350
  ...(options.filter === undefined ? {} : { filter: options.filter }),
405
351
  ...(sample === undefined ? {} : { sample: { kept: sample.kept, total: sample.total } }),
406
- ...(only === undefined ? {} : { shard: only }),
407
352
  // Only when the split made more than one, so a single-pass run's JSON is byte-identical to
408
353
  // what it has always been — and a mixed one can never be read as if it were a single run.
409
354
  ...(spent.length > 1 ? { passes: spent } : {}),
@@ -1,14 +1,36 @@
1
1
  // How wide a parallel test run goes, decided in one place. `x test --workers`, `x verify
2
2
  // --workers` and every parallel step of the gate read this — a second default would split the same
3
- // suite two different ways, and the `--worker N` reproduction a shard failure prints would then
4
- // name a shard the gate never ran.
3
+ // suite two different ways, and the `--workers N` a failure's reproduction prints would then name
4
+ // a width the gate never ran.
5
5
 
6
6
  // Bun ships no CPU-count or memory primitive: `cpus()` is the fallback when navigator cannot
7
7
  // answer, and `totalmem()` is the only reader of the machine's RAM.
8
8
  // why: Bun ships no CPU-count or total-memory primitive.
9
9
  import { cpus, totalmem } from 'node:os';
10
+ import { UltimateError } from '@ultimat3/core';
10
11
  import type { TestType } from '@ultimat3/testing';
11
- import { TestBudgetInvalidError } from './verify-errors';
12
+
13
+ /**
14
+ * `ULTIMATE_TEST_MEMORY_BUDGET` or `ULTIMATE_TEST_MAX_WORKERS` set to something that does not
15
+ * parse. Refused rather than ignored: a typo read as "the default" is a run sized for a machine
16
+ * the caller said this is not.
17
+ */
18
+ export class TestBudgetInvalidError extends UltimateError {
19
+ constructor(name: string, value: string) {
20
+ super({
21
+ code: 'X_TEST_BUDGET_INVALID',
22
+ cause: `${name}=${JSON.stringify(value)} does not parse — ${
23
+ name === 'ULTIMATE_TEST_MAX_WORKERS'
24
+ ? 'expected a positive integer'
25
+ : 'expected a size such as 3g, 512m or 4GiB'
26
+ }`,
27
+ fix:
28
+ name === 'ULTIMATE_TEST_MAX_WORKERS'
29
+ ? 'ULTIMATE_TEST_MAX_WORKERS=4 x verify'
30
+ : 'ULTIMATE_TEST_MEMORY_BUDGET=3g x verify',
31
+ });
32
+ }
33
+ }
12
34
 
13
35
  /** navigator first: it is the runtime's own answer, and it respects a container's CPU limit. */
14
36
  export function availableCpus(): number {
@@ -219,7 +241,7 @@ export const sharedWorkers = (
219
241
  * Which types run across worker processes, and why the other two cannot.
220
242
  *
221
243
  * Parallel is safe when the only thing a test file shares with another file is the database, and
222
- * the database is per worker by construction (`ULTIMATE_TEST_WORKER` → one clone of the migrated
244
+ * the database is per worker by construction (`BUN_TEST_WORKER_ID` → one clone of the migrated
223
245
  * template, `@ultimat3/testing`'s `acquireWorkerDatabase`). Every other process-global in this
224
246
  * framework — the permission set, the roles, the entity/action/query registries, the error-code
225
247
  * titles, the fixture bag — is handled by `--isolate` giving each FILE its own module registry.