@ultimat3/cli 22.15.0 → 24.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 (267) hide show
  1. package/CLAUDE.md +36 -13
  2. package/README.md +31 -8
  3. package/package.json +29 -28
  4. package/src/admin-catalog-keys.ts +16 -0
  5. package/src/admin-registration.ts +192 -0
  6. package/src/affected.ts +27 -35
  7. package/src/api-registration.ts +125 -11
  8. package/src/app-artifacts.ts +1 -1
  9. package/src/app-auth.ts +4 -27
  10. package/src/app-boundaries.ts +6 -20
  11. package/src/app-config-load.ts +51 -0
  12. package/src/app-env.ts +5 -11
  13. package/src/app-load.ts +55 -11
  14. package/src/app-manifest.ts +16 -8
  15. package/src/app-mcp.ts +10 -23
  16. package/src/app-permissions-borrowed.ts +129 -0
  17. package/src/app-permissions-site.ts +24 -0
  18. package/src/app-permissions.ts +0 -0
  19. package/src/app-reload-graph.ts +7 -30
  20. package/src/app-transport.ts +219 -0
  21. package/src/boundary-findings.ts +16 -4
  22. package/src/browser-transport.ts +160 -0
  23. package/src/budgets.ts +27 -34
  24. package/src/cdp-shot-driver.ts +3 -1
  25. package/src/cmd-auth-spec.ts +16 -0
  26. package/src/cmd-auth.ts +65 -0
  27. package/src/cmd-build-spec.ts +10 -3
  28. package/src/cmd-build.ts +107 -11
  29. package/src/cmd-db-backfill.ts +25 -12
  30. package/src/cmd-db.ts +66 -16
  31. package/src/cmd-deploy-helm.ts +7 -6
  32. package/src/cmd-deploy.ts +4 -4
  33. package/src/cmd-dev.ts +9 -8
  34. package/src/cmd-doctor-spec.ts +2 -9
  35. package/src/cmd-doctor.ts +75 -24
  36. package/src/cmd-env.ts +26 -2
  37. package/src/cmd-generate.ts +195 -47
  38. package/src/cmd-help.ts +19 -6
  39. package/src/cmd-i18n.ts +10 -9
  40. package/src/cmd-jobs-spec.ts +19 -3
  41. package/src/cmd-jobs.ts +136 -7
  42. package/src/cmd-manifest-spec.ts +1 -1
  43. package/src/cmd-manifest.ts +6 -25
  44. package/src/cmd-mcp.ts +46 -15
  45. package/src/cmd-new.ts +15 -1
  46. package/src/cmd-planned.ts +15 -8
  47. package/src/cmd-routes.ts +27 -3
  48. package/src/cmd-secrets-ring.ts +76 -0
  49. package/src/cmd-secrets-spec.ts +10 -2
  50. package/src/cmd-secrets.ts +95 -18
  51. package/src/cmd-shot-matrix.ts +1 -3
  52. package/src/cmd-shot.ts +12 -3
  53. package/src/cmd-tasks.ts +84 -15
  54. package/src/cmd-test-spec.ts +1 -7
  55. package/src/cmd-test.ts +13 -29
  56. package/src/cmd-verify.ts +13 -5
  57. package/src/comment-safe.ts +16 -0
  58. package/src/coverage-floor.ts +334 -0
  59. package/src/coverage-lcov.ts +193 -0
  60. package/src/coverage-source.ts +136 -0
  61. package/src/db-object-drift.ts +30 -0
  62. package/src/db-replay-engine.ts +115 -0
  63. package/src/db-schema-dump.ts +150 -0
  64. package/src/db-schema-refresh.ts +122 -0
  65. package/src/db-subscribes.ts +12 -4
  66. package/src/dev-dashboard-guard.ts +76 -0
  67. package/src/dev-dashboard.ts +51 -29
  68. package/src/dev-lock.ts +4 -7
  69. package/src/dev-route-table.ts +9 -0
  70. package/src/dispatch.ts +3 -6
  71. package/src/doctor-app-url.ts +39 -0
  72. package/src/doctor-auth.ts +95 -0
  73. package/src/doctor-coverage.ts +39 -0
  74. package/src/doctor-guards.ts +54 -0
  75. package/src/doctor-sealed.ts +132 -0
  76. package/src/document-graph.ts +82 -0
  77. package/src/drift-replica-identity.ts +42 -0
  78. package/src/error-catalog.ts +6 -9
  79. package/src/error-codes.ts +55 -3
  80. package/src/error-unthrown.ts +29 -11
  81. package/src/errors.ts +54 -83
  82. package/src/framework-schema-apply.ts +78 -0
  83. package/src/framework-schema-stamp.ts +96 -0
  84. package/src/framework-schema.ts +28 -2
  85. package/src/generate-files.ts +79 -12
  86. package/src/generate-grant-findings.ts +76 -0
  87. package/src/generate-grants.ts +77 -3
  88. package/src/generate-kinds.ts +52 -1
  89. package/src/generate-shadow.ts +46 -0
  90. package/src/generate-write.ts +62 -40
  91. package/src/generated-imports.ts +59 -0
  92. package/src/gh-target.ts +12 -7
  93. package/src/guard-sources.ts +104 -0
  94. package/src/guards.ts +23 -7
  95. package/src/handle-registration.ts +217 -0
  96. package/src/i18n-audit.ts +4 -1
  97. package/src/i18n-registration.ts +36 -0
  98. package/src/image-prepare.ts +51 -8
  99. package/src/import-closure.ts +165 -0
  100. package/src/index.ts +12 -5
  101. package/src/island-bundle.ts +3 -15
  102. package/src/island-store.ts +24 -16
  103. package/src/jobs-drain.ts +95 -21
  104. package/src/jobs-json.ts +50 -0
  105. package/src/jobs-report.ts +7 -4
  106. package/src/json-merge.ts +46 -0
  107. package/src/loopback-host.ts +13 -0
  108. package/src/manifest-staleness.ts +62 -0
  109. package/src/mcp-errors.ts +41 -4
  110. package/src/mcp-host.ts +16 -9
  111. package/src/mcp-test-run.ts +38 -0
  112. package/src/messages.ts +35 -0
  113. package/src/metrics-endpoint.ts +95 -24
  114. package/src/migration-extensions.ts +29 -0
  115. package/src/module-imports.ts +47 -0
  116. package/src/output.ts +24 -9
  117. package/src/page-navigation.ts +7 -23
  118. package/src/page-speculation.ts +5 -20
  119. package/src/permission-grants.ts +41 -9
  120. package/src/prerender-out.ts +34 -7
  121. package/src/prerender.ts +8 -5
  122. package/src/publish-closure.ts +101 -0
  123. package/src/pwa-artifacts.ts +26 -94
  124. package/src/registry.ts +2 -0
  125. package/src/revalidated-response.ts +4 -3
  126. package/src/role-load.ts +143 -0
  127. package/src/role-replicator.ts +16 -0
  128. package/src/role-start.ts +27 -5
  129. package/src/role-sync.ts +55 -20
  130. package/src/role-wake.ts +45 -0
  131. package/src/runtime-admin.ts +141 -0
  132. package/src/runtime-assets.ts +11 -7
  133. package/src/runtime-bindings.ts +4 -4
  134. package/src/runtime-cache.ts +38 -47
  135. package/src/runtime-hooks.ts +5 -2
  136. package/src/runtime-idempotency-scope.ts +23 -0
  137. package/src/runtime-isr.ts +36 -0
  138. package/src/runtime-jobs.ts +8 -33
  139. package/src/runtime-mfa-warning.ts +29 -0
  140. package/src/runtime-notify-retention.ts +10 -46
  141. package/src/runtime-overrides.ts +1 -1
  142. package/src/runtime-purge.ts +38 -16
  143. package/src/runtime-queue.ts +39 -11
  144. package/src/runtime-realtime.ts +9 -45
  145. package/src/runtime-render.ts +68 -35
  146. package/src/runtime-services.ts +22 -18
  147. package/src/runtime-storage.ts +10 -5
  148. package/src/scaffold-typecheck.ts +1 -0
  149. package/src/schema-diff.ts +16 -0
  150. package/src/schema-drift.ts +45 -15
  151. package/src/schema-dump-drift.ts +64 -0
  152. package/src/seo-routes.ts +80 -9
  153. package/src/serve-boot.ts +114 -158
  154. package/src/serve-drain.ts +9 -33
  155. package/src/serve-env.ts +9 -6
  156. package/src/serve-prebuilt-paths.ts +19 -0
  157. package/src/serve-prebuilt.ts +84 -0
  158. package/src/serve-web.ts +169 -0
  159. package/src/serve.ts +6 -9
  160. package/src/server-barrels.ts +54 -0
  161. package/src/shot-locale.ts +5 -23
  162. package/src/site-assets.ts +3 -5
  163. package/src/site-config.ts +9 -48
  164. package/src/site-seo.ts +1 -1
  165. package/src/sitemap-lastmod.ts +5 -0
  166. package/src/solid-loader.ts +12 -3
  167. package/src/source-edit.ts +49 -0
  168. package/src/source-files.ts +6 -0
  169. package/src/stored-object-headers.ts +43 -0
  170. package/src/sync-url.ts +2 -2
  171. package/src/tasks-facts.ts +3 -26
  172. package/src/templates/action.ts +147 -11
  173. package/src/templates/admin-catalog.ts +58 -0
  174. package/src/templates/admin-page.ts +36 -8
  175. package/src/templates/admin.ts +57 -17
  176. package/src/templates/backfill.ts +121 -87
  177. package/src/templates/entity.ts +140 -44
  178. package/src/templates/guard-animated-layout-property.ts +4 -11
  179. package/src/templates/guard-bare-error.ts +9 -13
  180. package/src/templates/guard-focus-visible.ts +4 -11
  181. package/src/templates/guard-image-dimensions.ts +6 -6
  182. package/src/templates/guard-island-without-states.ts +2 -4
  183. package/src/templates/guard-raw-breakpoint.ts +210 -0
  184. package/src/templates/guard-raw-colour.ts +4 -11
  185. package/src/templates/guard-raw-length.ts +375 -0
  186. package/src/templates/guard-raw-motion.ts +213 -0
  187. package/src/templates/guard-raw-shadow.ts +187 -0
  188. package/src/templates/guard-raw-z-index.ts +166 -0
  189. package/src/templates/guard-repo-raw-sql.ts +345 -0
  190. package/src/templates/guard-semantic-interactive.ts +6 -6
  191. package/src/templates/guard-undeclared-custom-property.ts +217 -0
  192. package/src/templates/guard-undefined-style-class.ts +215 -0
  193. package/src/templates/guard-untranslated-string.ts +6 -6
  194. package/src/templates/guard-unzoned-date.ts +9 -13
  195. package/src/templates/index.ts +3 -2
  196. package/src/templates/island.ts +34 -51
  197. package/src/templates/job-body-test.ts +121 -0
  198. package/src/templates/job.ts +23 -8
  199. package/src/templates/naming.ts +28 -0
  200. package/src/templates/policy.ts +16 -9
  201. package/src/templates/query.ts +242 -26
  202. package/src/templates/resource-create.ts +62 -18
  203. package/src/templates/resource-form-island.ts +58 -80
  204. package/src/templates/resource-page.ts +269 -0
  205. package/src/templates/resource-service.ts +96 -0
  206. package/src/templates/resource.ts +86 -61
  207. package/src/templates/route.ts +41 -35
  208. package/src/templates/scaffold-api.ts +55 -1
  209. package/src/templates/scaffold-app.ts +177 -53
  210. package/src/templates/scaffold-auth.ts +83 -19
  211. package/src/templates/scaffold-browser-client.ts +90 -0
  212. package/src/templates/scaffold-container-compose.ts +10 -1
  213. package/src/templates/scaffold-container.ts +20 -2
  214. package/src/templates/scaffold-dashboard-bare.ts +64 -2
  215. package/src/templates/scaffold-dashboard-example.ts +124 -31
  216. package/src/templates/scaffold-dashboard-shared.ts +1 -20
  217. package/src/templates/scaffold-dashboard.ts +1 -2
  218. package/src/templates/scaffold-db-client.ts +144 -0
  219. package/src/templates/scaffold-db-package.ts +90 -8
  220. package/src/templates/scaffold-docs.ts +50 -5
  221. package/src/templates/scaffold-guards.ts +54 -11
  222. package/src/templates/scaffold-helm-fences.ts +76 -0
  223. package/src/templates/scaffold-helm-templates.ts +32 -7
  224. package/src/templates/scaffold-helm.ts +31 -1
  225. package/src/templates/scaffold-http.ts +9 -7
  226. package/src/templates/scaffold-repo.ts +61 -3
  227. package/src/templates/scaffold-roles.ts +56 -9
  228. package/src/templates/scaffold-shell.ts +74 -105
  229. package/src/templates/scaffold-site.ts +45 -19
  230. package/src/templates/scaffold-ui-package.ts +18 -0
  231. package/src/templates/slice-foundation.ts +46 -8
  232. package/src/test-counts.ts +14 -2
  233. package/src/test-ignores.ts +25 -0
  234. package/src/test-passes.ts +0 -15
  235. package/src/test-select.ts +5 -14
  236. package/src/test-shards.ts +41 -93
  237. package/src/test-workers.ts +26 -4
  238. package/src/theme-boot.ts +6 -31
  239. package/src/transport-calls.ts +105 -0
  240. package/src/trusted-hops-error.ts +17 -0
  241. package/src/unscanned-admin.ts +54 -0
  242. package/src/unthrown-codes.ts +26 -0
  243. package/src/verify-checks.ts +43 -53
  244. package/src/verify-coverage-run.ts +190 -0
  245. package/src/verify-deadline.ts +353 -0
  246. package/src/verify-floor.ts +36 -9
  247. package/src/verify-merge.ts +171 -45
  248. package/src/verify-part-step.ts +90 -0
  249. package/src/verify-progress.ts +25 -0
  250. package/src/verify-role-load-probe.ts +54 -0
  251. package/src/verify-role-load.ts +148 -0
  252. package/src/verify-run.ts +116 -37
  253. package/src/verify-shard.ts +33 -11
  254. package/src/verify-stalled.ts +152 -0
  255. package/src/verify-step.ts +42 -1
  256. package/src/verify-tests.ts +83 -8
  257. package/src/workspace-checks.ts +50 -8
  258. package/src/workspace-dep-edit.ts +127 -0
  259. package/src/build-errors.ts +0 -54
  260. package/src/cmd-dev-fixture.ts +0 -204
  261. package/src/dev-live-fixture.ts +0 -122
  262. package/src/policy-fixture.ts +0 -78
  263. package/src/realtime-browser-probe-fixture.ts +0 -9
  264. package/src/role-start-fixture.ts +0 -78
  265. package/src/scaffold-fixture.ts +0 -160
  266. package/src/templates/island-fixture.ts +0 -76
  267. package/src/verify-errors.ts +0 -49
@@ -4,6 +4,7 @@
4
4
  // none of them, so each emitted TS2307 in any slice a resource had not been run in first.
5
5
 
6
6
  import { stripComments } from '@ultimat3/core';
7
+ import { adminCatalogFiles } from './admin-catalog';
7
8
  import type { FeatureTarget } from './entity';
8
9
  import { entityFiles } from './entity';
9
10
  import type { GeneratedFile, NameSet } from './naming';
@@ -74,16 +75,24 @@ const ifAbsent = (files: readonly GeneratedFile[]): readonly GeneratedFile[] =>
74
75
  export function sliceFoundation(
75
76
  target: FeatureTarget,
76
77
  needs: readonly SliceModule[],
78
+ /** Every locale the planted entity's admin labels ship for. Defaults to `['en']`. */
79
+ locales?: readonly string[],
77
80
  ): readonly GeneratedFile[] {
78
81
  const feature = names(target.feature);
79
82
  const dir = `${target.surfaceDir}/${target.feature}`;
80
- return ifAbsent([
81
- ...(needs.includes('entity') ? entityFiles(target.feature, target) : []),
82
- ...(needs.includes('policy') ? policyFiles(target.feature, target) : []),
83
- ...(needs.includes('errors')
84
- ? [{ path: `${dir}/errors.ts`, contents: errorsSource(feature) }]
85
- : []),
86
- ]);
83
+ return [
84
+ ...ifAbsent([
85
+ ...(needs.includes('entity') ? entityFiles(target.feature, target) : []),
86
+ ...(needs.includes('policy') ? policyFiles(target.feature, target) : []),
87
+ ...(needs.includes('errors')
88
+ ? [{ path: `${dir}/errors.ts`, contents: errorsSource(feature) }]
89
+ : []),
90
+ ]),
91
+ // A planted entity joins the typed handle, and an entity in the handle IS an admin screen —
92
+ // so it gets the labels `x g entity` writes for one, or its nav entry renders a missing-key
93
+ // marker. A catalog MERGE, never `if-absent`: a key already there keeps the value it has.
94
+ ...(needs.includes('entity') ? adminCatalogFiles(target.feature, target, locales) : []),
95
+ ];
87
96
  }
88
97
 
89
98
  /** The exported names an `export { a, b as c }` list declares — `c`, never `b`. */
@@ -117,10 +126,39 @@ export function sliceExports(source: string, name: string): boolean {
117
126
  const code = stripComments(source);
118
127
  // `(?:async\s+)?` before `function`: an `export async function byId` — every repo function
119
128
  // `x g entity` scaffolds — matched neither this nor `listedExports`, so a caller checking for
120
- // `byId`/`listByOrg` on a real repo.ts always read `false`. `async` has no meaning before
129
+ // `byId`/`list` on a real repo.ts always read `false`. `async` has no meaning before
121
130
  // `class`/`const`/`let`/`var`/`enum`, so it is scoped to `function` only.
122
131
  const declared = new RegExp(
123
132
  `\\bexport\\s+(?:abstract\\s+)?(?:class|const|let|var|(?:async\\s+)?function|enum)\\s+${name}\\b`,
124
133
  );
125
134
  return declared.test(code) || listedExports(code).includes(name);
126
135
  }
136
+
137
+ /** ` title: text({ max: 200 }),` — one column of an entity, as its name and its builder. */
138
+ const COLUMN_LINE = /^ {4}([A-Za-z_$][\w$]*): ([A-Za-z_$][\w$]*)\(/gm;
139
+
140
+ const columnsOf = (entitySource: string): string =>
141
+ [...stripComments(entitySource).matchAll(COLUMN_LINE)]
142
+ .map((match) => `${match[1] ?? ''}: ${match[2] ?? ''}`)
143
+ .join(', ');
144
+
145
+ /**
146
+ * Whether a generated TEST may store a row in this slice: its entity declares exactly the columns
147
+ * `x g entity` scaffolds for this feature — read off the entity this generator would write, never
148
+ * a second list — and its `repo.ts`, when one is on disk, still exports `insert`. A slice an author
149
+ * reshaped has a row this generator cannot spell (one more required column is enough), so its
150
+ * tests assert the branches that need none and leave the stored-row case to the author.
151
+ */
152
+ export function sliceTakesScaffoldRow(
153
+ target: FeatureTarget,
154
+ sliceEntity: string | undefined,
155
+ sliceRepo: string | undefined,
156
+ ): boolean {
157
+ if (sliceEntity === undefined) return false;
158
+ const scaffold = entityFiles(target.feature, target).find((file) =>
159
+ file.path.endsWith('/entity.ts'),
160
+ );
161
+ const wanted = columnsOf(String(scaffold?.contents ?? ''));
162
+ if (wanted === '' || columnsOf(sliceEntity) !== wanted) return false;
163
+ return sliceRepo === undefined || sliceExports(sliceRepo, 'insert');
164
+ }
@@ -5,12 +5,21 @@
5
5
  import type { ExecResult } from './exec';
6
6
  import { execOutput } from './exec';
7
7
  import { parseBunTest } from './mcp-test-output';
8
+ // One reader of bun's `N errors` line, shared with MCP `tests.run` — never a second regex.
9
+ import { errorsIn } from './mcp-test-run';
8
10
 
9
11
  export interface TestCounts {
10
12
  /** Tests that executed — passed plus failed. A failed test is a test that ran. */
11
13
  readonly ran: number;
12
14
  /** Tests bun reported as skipped or todo, which is the same thing here: they did not run. */
13
15
  readonly skipped: number;
16
+ /**
17
+ * Errors raised OUTSIDE any test — a `describe` body or a module that threw on load, a failed
18
+ * hook — which bun counts on a line of their own (`1 error`) beside `0 fail`. Present only when
19
+ * there were some: the exit code already makes the step red, and without this number the step
20
+ * line read "12 ran, 0 skipped" over a run whose file never got as far as declaring a test.
21
+ */
22
+ readonly errors?: number;
14
23
  }
15
24
 
16
25
  /**
@@ -26,10 +35,13 @@ export interface TestCounts {
26
35
  export const countsOf = (results: readonly ExecResult[]): TestCounts => {
27
36
  let ran = 0;
28
37
  let skipped = 0;
38
+ let errors = 0;
29
39
  for (const result of results) {
30
- const run = parseBunTest(execOutput(result), result.durationMs);
40
+ const output = execOutput(result);
41
+ const run = parseBunTest(output, result.durationMs);
31
42
  ran += run.passed + run.failed;
32
43
  skipped += run.skipped;
44
+ errors += errorsIn(output);
33
45
  }
34
- return { ran, skipped };
46
+ return { ran, skipped, ...(errors === 0 ? {} : { errors }) };
35
47
  };
@@ -0,0 +1,25 @@
1
+ // The ONE policy for which paths are never a test the gate runs here, projected twice: the path
2
+ // predicate `discoverTests` filters by and the `--path-ignore-patterns` the serial steps pass to
3
+ // `bun test`. A leaf, so `test-select.ts` and `verify-tests.ts` (which import each other) share it.
4
+
5
+ /** Build output and installed packages: never a test, at ANY depth — a workspace's `dist/` too. */
6
+ const IGNORED_ANYWHERE = ['dist', 'node_modules'] as const;
7
+
8
+ /**
9
+ * Nested projects with their own gate (`examples/`, `dummy/`, run by
10
+ * `scripts/reference-app-gate.ts`) and the root `build/` — skipped AT THE ROOT only. Matched at any
11
+ * depth they swallowed an app's own slice: `x g resource build` lost 14 of its 16 test files and
12
+ * `x g resource example` its `examples/` page tests, from `x test` and the gate alike.
13
+ */
14
+ const IGNORED_AT_ROOT = ['build', 'examples', 'dummy'] as const;
15
+
16
+ /** Whether a root-relative POSIX path is excluded from every test selection. */
17
+ export const isIgnoredTestPath = (path: string): boolean =>
18
+ IGNORED_ANYWHERE.some((dir) => `/${path}`.includes(`/${dir}/`)) ||
19
+ IGNORED_AT_ROOT.some((dir) => path.startsWith(`${dir}/`));
20
+
21
+ /** The same policy as `bun test --path-ignore-patterns` globs, relative to the run's root. */
22
+ export const IGNORED_TEST_PATTERNS: readonly string[] = [
23
+ ...IGNORED_ANYWHERE.map((dir) => `**/${dir}/**`),
24
+ ...IGNORED_AT_ROOT.map((dir) => `${dir}/**`),
25
+ ];
@@ -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
@@ -9,6 +9,7 @@ import { nearestName, renderFixShellArg } from '@ultimat3/core';
9
9
  import { BadFlagError } from './errors';
10
10
  import type { ParsedArgs } from './parse';
11
11
  import { flagString } from './parse';
12
+ import { isIgnoredTestPath } from './test-ignores';
12
13
  import type { TestType } from './verify-tests';
13
14
  import { ownerOf, TEST_TYPES } from './verify-tests';
14
15
 
@@ -27,20 +28,10 @@ const TEST_GLOB = '**/*.test.{ts,tsx}';
27
28
 
28
29
  /**
29
30
  * 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.
31
+ * suite — `test-ignores.ts` holds it, and the serial steps' argv projects the same policy. `e2e/`
32
+ * is NOT on it: an opt-in suite that the gate runs but `x test` silently drops is a suite nobody
33
+ * runs until CI says so.
41
34
  */
42
- const IGNORED = ['/dist/', '/build/', '/node_modules/', '/examples/', '/dummy/'];
43
-
44
35
  /**
45
36
  * File size stands in for duration: cheap to read, and it correlates far better than file count.
46
37
  * `type`, when given, narrows to exactly the files verify-tests.ts would run for that suite — one
@@ -58,7 +49,7 @@ export async function discoverTests(
58
49
  const files: TestFile[] = [];
59
50
  for await (const found of new Bun.Glob(TEST_GLOB).scan({ cwd: root, absolute: false })) {
60
51
  const path = found.split('\\').join('/');
61
- if (IGNORED.some((part) => `/${path}`.includes(part))) continue;
52
+ if (isIgnoredTestPath(path)) continue;
62
53
  if (filters !== undefined && !filters.some((part) => path.includes(part))) continue;
63
54
  if (type !== undefined && !belongsToType(path, type)) continue;
64
55
  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,8 +52,8 @@ 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';
55
+ /** `@ultimat3/testing`'s `ISOLATED_ENV` — the one spelling, which that package's preload reads. */
56
+ export const ISOLATED_TEST_ENV = ISOLATED_ENV;
57
57
 
58
58
  /** Bun's per-file durations, cached under the run's root and refreshed by every parallel run. */
59
59
  export const TEST_TIMINGS_FILE = '.x/test-timings.json';
@@ -68,24 +68,13 @@ import type { TestType } from './verify-tests';
68
68
  * The argv for one run. An explicit file list, never a re-glob: discovery already decided which
69
69
  * files belong to this type, and a child that globs again can pick up a file the selection removed.
70
70
  *
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).
71
+ * `--parallel=N` over the selection, sorted, so two runs over one corpus hand Bun the same list.
72
+ * One CI job's share of the corpus is `x verify --only unit --shard i/n` (`verify-shard.ts`), never
73
+ * a flag here: `x test --worker I` was a second, 0-based, unhashed split of the same suite.
83
74
  */
84
75
  export function testArgs(input: {
85
76
  readonly files: readonly string[];
86
77
  readonly workers: number;
87
- /** 0-based, matching `--worker`. Absent runs the whole selection across `workers` processes. */
88
- readonly shard?: number;
89
78
  /**
90
79
  * Everything after a bare `--`, handed to `bun test` verbatim and BEFORE the file list, which is
91
80
  * where bun reads its flags. `ParsedArgs.passthrough` had no reader anywhere until 2026-09, so
@@ -114,32 +103,23 @@ export function testArgs(input: {
114
103
  const isolate =
115
104
  extra.includes('--isolate') || (input.isolate === true && !extra.includes('--no-isolate'));
116
105
  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
- ];
106
+ return [
107
+ 'bun',
108
+ 'test',
109
+ `--parallel=${String(input.workers)}`,
110
+ ...(isolate ? [] : ['--no-isolate']),
111
+ ...(input.timings === undefined || bare.some((arg) => arg.startsWith('--timings'))
112
+ ? []
113
+ : [`--timings=${input.timings}`, '--update-timings']),
114
+ ...bare,
115
+ ...files,
116
+ ];
137
117
  }
138
118
 
139
119
  /**
140
120
  * The files an argv selects, flags stripped. `bun test` takes its file list positionally, so this
141
121
  * 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
122
+ * — the flag COUNT is not fixed (`--no-isolate` and the timings pair come and go), and a
143
123
  * test slicing a hardcoded prefix length reads a flag as a filename the day that changes.
144
124
  */
145
125
  export const filesIn = (command: readonly string[]): readonly string[] =>
@@ -154,12 +134,9 @@ export interface ReproduceOptions {
154
134
  readonly sample?: number;
155
135
  /**
156
136
  * 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.
137
+ * decides which files exist to run at all, so a rerun without it selects the WHOLE corpus.
159
138
  */
160
139
  readonly affected?: AffectedSelection;
161
- /** 0-based, when reproducing ONE shard. Absent reproduces the whole selection. */
162
- readonly shard?: number;
163
140
  /** What the caller put after `--`. It reaches `bun test`, so a rerun without it runs differently. */
164
141
  readonly passthrough?: readonly string[];
165
142
  /** The run was isolated per file; the rerun must be too. */
@@ -185,7 +162,6 @@ export function reproduceFor(options: ReproduceOptions): string {
185
162
  ...(options.affected?.dirty === true ? ['--dirty'] : []),
186
163
  '--workers',
187
164
  String(options.workers),
188
- ...(options.shard === undefined ? [] : ['--worker', String(options.shard)]),
189
165
  ...(options.isolate === true ? ['--isolate'] : []),
190
166
  // Last, and after a `--` of its own, because that is where the caller typed it and where the
191
167
  // parser will find it again. Quoted for `shell-quote.ts`'s reason: a reproduce line is pasted.
@@ -200,14 +176,11 @@ export interface RunShardsOptions {
200
176
  readonly runner: Runner;
201
177
  readonly files: readonly TestFile[];
202
178
  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
179
  readonly filter?: string;
206
180
  readonly type?: TestType;
207
181
  /**
208
182
  * 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.
183
+ * Carried rather than counted from what ran: a pass runs part of the sample, never the corpus.
211
184
  */
212
185
  readonly sample?: { readonly kept: number; readonly total: number };
213
186
  /** Passed straight to `reproduceFor`: see `ReproduceOptions.affected`. */
@@ -248,7 +221,6 @@ const planOf = (
248
221
  ...(pass.type === undefined ? {} : { type: pass.type }),
249
222
  ...(options.sample === undefined ? {} : { sample: options.sample.kept }),
250
223
  ...(options.affected === undefined ? {} : { affected: options.affected }),
251
- ...(options.only === undefined ? {} : { shard: options.only }),
252
224
  ...(options.isolate === true ? { isolate: true } : {}),
253
225
  ...(options.passthrough === undefined || options.passthrough.length === 0
254
226
  ? {}
@@ -256,17 +228,12 @@ const planOf = (
256
228
  });
257
229
 
258
230
  /**
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`.
231
+ * `X_TEST_FAILED`, whose `fix:` reruns exactly what failed. `X_TEST_SHARD_FAILED` was the
232
+ * `--worker` rerun's and went with it: the code stays registered (codes are forever) and unthrown.
263
233
  */
264
234
  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))`,
235
+ code: 'X_TEST_FAILED',
236
+ cause: `${plan.type ?? 'test'} run exited ${code} across ${plan.workers} worker(s) (${files} file(s))`,
270
237
  fix: reproduceFor(plan),
271
238
  docs: ERROR_DOCS_URL,
272
239
  });
@@ -281,12 +248,10 @@ export const failureOf = (code: number, files: number, plan: ReproduceOptions):
281
248
  * it. And every pass runs even after one fails — the caller asked for a suite, and a report that
282
249
  * stops at the first red step hides the rest of the answer.
283
250
  *
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.
251
+ * No `ULTIMATE_TEST_WORKER` here: a `--parallel` run has N processes and Bun numbers each with
252
+ * `BUN_TEST_WORKER_ID`, which `@ultimat3/testing`'s `workerId` already reads.
287
253
  */
288
254
  export async function runShards(options: RunShardsOptions): Promise<CommandResult> {
289
- const only = options.only;
290
255
  // Computed ONCE per invocation, never per pass: every pass spawns from the same `root` and the
291
256
  // same parent env, so the leaked-key set cannot differ pass to pass.
292
257
  const envOverrides: Record<string, string | undefined> = {
@@ -302,7 +267,6 @@ export async function runShards(options: RunShardsOptions): Promise<CommandResul
302
267
  files: options.files,
303
268
  workers: options.workers,
304
269
  ...(options.type === undefined ? {} : { type: options.type }),
305
- ...(only === undefined ? {} : { shard: only }),
306
270
  });
307
271
  const started = performance.now();
308
272
  const steps: StepResult[] = [];
@@ -312,53 +276,38 @@ export async function runShards(options: RunShardsOptions): Promise<CommandResul
312
276
  let exitCode = 0;
313
277
  for (const pass of passes) {
314
278
  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];
279
+ // `-- --watch` is never split: it never exits, so a second batch would never start. Every
280
+ // other pass is spent in `test-batches.ts`' batches, one `bun test` after another.
281
+ const batches = !watching
282
+ ? testBatches(
283
+ files,
284
+ pass.workers,
285
+ isolatedRun(options) ? BATCH_FILES_PER_WORKER : SHARED_BATCH_FILES_PER_WORKER,
286
+ )
287
+ : [files];
327
288
  const result = await runBatches({
328
289
  runner: options.runner,
329
290
  batches,
330
291
  stopOnFailure: bailing,
331
292
  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
- : {}),
293
+ // A serial pass is one worker: only a default-width parallel pass leases from the pool.
294
+ ...(pass.workers > 1 && options.lease !== undefined ? { lease: options.lease } : {}),
337
295
  argsFor: (batch, width) =>
338
296
  testArgs({
339
297
  files: batch,
340
- workers: only === undefined ? width : pass.workers,
341
- ...(only === undefined ? {} : { shard: only }),
298
+ workers: width,
342
299
  ...(options.passthrough === undefined ? {} : { passthrough: options.passthrough }),
343
300
  ...(options.isolate === undefined ? {} : { isolate: options.isolate }),
344
301
  timings: join(options.root, TEST_TIMINGS_FILE),
345
302
  }),
346
303
  options: {
347
304
  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
- }),
305
+ ...(Object.keys(envOverrides).length === 0 ? {} : { env: envOverrides }),
356
306
  },
357
307
  });
358
308
  batchCount += batches.length;
359
309
  const plan = planOf(options, pass);
360
- const label =
361
- only === undefined ? `${pass.workers} worker(s)` : `shard ${only} of ${pass.workers}`;
310
+ const label = `${pass.workers} worker(s)`;
362
311
  steps.push({
363
312
  name: `${pass.type === undefined ? label : `${pass.type} · ${label}`} · ${files.length} files${
364
313
  batches.length > 1 ? msg('cli.test.batches', { batches: batches.length }) : ''
@@ -403,7 +352,6 @@ export async function runShards(options: RunShardsOptions): Promise<CommandResul
403
352
  durationMs,
404
353
  ...(options.filter === undefined ? {} : { filter: options.filter }),
405
354
  ...(sample === undefined ? {} : { sample: { kept: sample.kept, total: sample.total } }),
406
- ...(only === undefined ? {} : { shard: only }),
407
355
  // Only when the split made more than one, so a single-pass run's JSON is byte-identical to
408
356
  // what it has always been — and a mixed one can never be read as if it were a single run.
409
357
  ...(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.
package/src/theme-boot.ts CHANGED
@@ -1,40 +1,15 @@
1
- // The no-flash theme script, inlined into every document a served or exported process writes,
2
- // with `theme.defaultMode` from `app.config.ts` as its fallback. Before this the key had no reader
3
- // at all (pinned as a dead key in `scripts/lib/config-reader-pins.ts`) and neither of the two
4
- // scripts the framework exported was wired into a document: an app that wanted to open dark had to
5
- // write its own boot script AND admit it to the CSP by hand — which every app forgot in one of the
6
- // two places. Sibling of `app-auth.ts`'s `loadSignInPath` and imports the config for the same
7
- // reason: a regex over the app's source is the pattern `app-load.ts` refuses.
1
+ // The no-flash theme script, inlined into every document a served or exported process writes, with
2
+ // `theme.defaultMode` (off the one loader, `app-config-load.ts`) as its fallback — and the CSP source
3
+ // that admits it, so no app writes its own boot script and forgets to admit it.
8
4
 
9
- // why: Bun exposes no path-join primitive, and the config path is app-root-relative — the same
10
- // necessity `app-auth.ts` records.
11
- import { join } from 'node:path';
12
5
  import type { ThemeMode } from '@ultimat3/core';
13
6
  import { cspHashSource } from '@ultimat3/http';
14
7
  import { renderHead, themeScript, themeScriptBody } from '@ultimat3/render';
15
- import { APP_CONFIG_EXPORT } from './app-auth';
16
- import { APP_CONFIG_FILE } from './app-root';
8
+ import { loadAppConfig } from './app-config-load';
17
9
 
18
- const isRecord = (value: unknown): value is Record<string, unknown> =>
19
- typeof value === 'object' && value !== null;
20
-
21
- const isThemeMode = (value: unknown): value is ThemeMode =>
22
- value === 'light' || value === 'dark' || value === 'system';
23
-
24
- /**
25
- * `theme.defaultMode`, or `'system'` — the value `defineConfig` fills in — when the app declares
26
- * none or the file is absent. Structural, never `instanceof`, for `loadSignInPath`'s reason.
27
- */
10
+ /** `theme.defaultMode`, or `'system'` — core's own default — for a root with no config file. */
28
11
  export async function loadThemeMode(root: string): Promise<ThemeMode> {
29
- const configPath = join(root, APP_CONFIG_FILE);
30
- if (!(await Bun.file(configPath).exists())) return 'system';
31
- const module = (await import(configPath)) as Record<string, unknown>;
32
- const config = module[APP_CONFIG_EXPORT];
33
- if (!isRecord(config)) return 'system';
34
- const theme = config['theme'];
35
- if (!isRecord(theme)) return 'system';
36
- const mode = theme['defaultMode'];
37
- return isThemeMode(mode) ? mode : 'system';
12
+ return (await loadAppConfig(root))?.theme.defaultMode ?? 'system';
38
13
  }
39
14
 
40
15
  export interface ThemeBoot {