@ultimat3/cli 21.0.0 → 22.1.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 (236) hide show
  1. package/CLAUDE.md +84 -1650
  2. package/README.md +40 -2
  3. package/package.json +30 -30
  4. package/src/api-registration.ts +124 -0
  5. package/src/app-artifacts.ts +39 -0
  6. package/src/app-boundaries.ts +5 -10
  7. package/src/app-load.ts +37 -5
  8. package/src/app-openapi.ts +27 -0
  9. package/src/app-permissions.ts +0 -0
  10. package/src/app-root.ts +1 -1
  11. package/src/async-pages.ts +32 -0
  12. package/src/bin.ts +7 -1
  13. package/src/boundary-findings.ts +36 -0
  14. package/src/browser-launcher-fake-html.ts +80 -0
  15. package/src/browser-launcher-fake.ts +165 -0
  16. package/src/browser-launcher-port.ts +159 -0
  17. package/src/browser-launcher.ts +26 -133
  18. package/src/budgets.ts +11 -1
  19. package/src/cdp-shot-a11y.ts +77 -0
  20. package/src/cdp-shot-clock.ts +14 -0
  21. package/src/cdp-shot-driver.ts +150 -0
  22. package/src/cdp-shot-element.ts +147 -0
  23. package/src/cdp-shot-errors.ts +62 -0
  24. package/src/cdp-shot-keys.ts +152 -0
  25. package/src/cdp-shot-page.ts +230 -0
  26. package/src/cdp-shot-watch.ts +241 -0
  27. package/src/cmd-affected-spec.ts +24 -0
  28. package/src/cmd-affected.ts +3 -19
  29. package/src/cmd-build-spec.ts +16 -0
  30. package/src/cmd-build.ts +46 -20
  31. package/src/cmd-ci-spec.ts +24 -0
  32. package/src/cmd-ci.ts +17 -21
  33. package/src/cmd-db-branch.ts +2 -2
  34. package/src/cmd-db-spec.ts +99 -0
  35. package/src/cmd-db.ts +15 -94
  36. package/src/cmd-deploy-helm.ts +136 -0
  37. package/src/cmd-deploy-spec.ts +40 -0
  38. package/src/cmd-deploy.ts +111 -43
  39. package/src/cmd-dev-spec.ts +25 -0
  40. package/src/cmd-dev.ts +55 -36
  41. package/src/cmd-docs-spec.ts +16 -0
  42. package/src/cmd-docs.ts +2 -11
  43. package/src/cmd-doctor-spec.ts +21 -0
  44. package/src/cmd-doctor.ts +4 -18
  45. package/src/cmd-env-spec.ts +18 -0
  46. package/src/cmd-env.ts +4 -13
  47. package/src/cmd-errors-spec.ts +23 -0
  48. package/src/cmd-errors.ts +3 -17
  49. package/src/cmd-fix-spec.ts +18 -0
  50. package/src/cmd-fix.ts +4 -13
  51. package/src/cmd-generate-spec.ts +31 -0
  52. package/src/cmd-generate.ts +46 -42
  53. package/src/cmd-i18n-spec.ts +16 -0
  54. package/src/cmd-i18n.ts +7 -14
  55. package/src/cmd-jobs-spec.ts +56 -0
  56. package/src/cmd-jobs.ts +3 -50
  57. package/src/cmd-manifest-spec.ts +15 -0
  58. package/src/cmd-manifest.ts +19 -20
  59. package/src/cmd-mcp-spec.ts +21 -0
  60. package/src/cmd-mcp.ts +2 -15
  61. package/src/cmd-new-spec.ts +33 -0
  62. package/src/cmd-new.ts +38 -33
  63. package/src/cmd-policy-spec.ts +13 -0
  64. package/src/cmd-policy.ts +2 -9
  65. package/src/cmd-pr-spec.ts +39 -0
  66. package/src/cmd-pr.ts +5 -65
  67. package/src/cmd-registries-spec.ts +32 -0
  68. package/src/cmd-registries.ts +4 -25
  69. package/src/cmd-routes-spec.ts +12 -0
  70. package/src/cmd-routes.ts +2 -7
  71. package/src/cmd-secrets-spec.ts +19 -0
  72. package/src/cmd-secrets.ts +22 -34
  73. package/src/cmd-shot-island.ts +12 -13
  74. package/src/cmd-shot-spec.ts +53 -0
  75. package/src/cmd-shot.ts +11 -60
  76. package/src/cmd-tasks-spec.ts +21 -0
  77. package/src/cmd-tasks.ts +2 -16
  78. package/src/cmd-test-spec.ts +54 -0
  79. package/src/cmd-test.ts +4 -49
  80. package/src/cmd-verify-spec.ts +28 -0
  81. package/src/cmd-verify.ts +3 -23
  82. package/src/db-seed.ts +4 -2
  83. package/src/dev-dashboard.ts +4 -4
  84. package/src/dev-lock.ts +18 -2
  85. package/src/dev-port.ts +21 -0
  86. package/src/dev-route-table.ts +3 -3
  87. package/src/dispatch.ts +12 -5
  88. package/src/document-styles.ts +1 -1
  89. package/src/drift.ts +4 -18
  90. package/src/error-catalog.ts +16 -9
  91. package/src/error-codes.ts +28 -30
  92. package/src/error-pages.ts +19 -7
  93. package/src/errors.ts +14 -0
  94. package/src/favicon.ts +2 -2
  95. package/src/fix-imports.ts +1 -1
  96. package/src/fix-scan.ts +2 -9
  97. package/src/flag-reads.ts +1 -2
  98. package/src/foreign-text.ts +36 -0
  99. package/src/framework-schema.ts +5 -6
  100. package/src/generate-feature.ts +42 -0
  101. package/src/generate-files.ts +7 -3
  102. package/src/generate-grants.ts +83 -0
  103. package/src/generate-kinds.ts +56 -4
  104. package/src/i18n-index.ts +59 -10
  105. package/src/icon-assets.ts +1 -1
  106. package/src/image-prepare.ts +14 -0
  107. package/src/import-scan.ts +63 -0
  108. package/src/index.ts +67 -348
  109. package/src/invocation-flags.ts +26 -0
  110. package/src/island-bundle.ts +6 -1
  111. package/src/island-capture.ts +6 -6
  112. package/src/island-harness-script.ts +6 -2
  113. package/src/island-realtime.ts +15 -8
  114. package/src/island-shot.ts +5 -0
  115. package/src/island-store.ts +131 -0
  116. package/src/island-verdict.ts +1 -1
  117. package/src/job-registration.ts +42 -0
  118. package/src/jobs-driver.ts +2 -2
  119. package/src/load-findings.ts +51 -0
  120. package/src/mcp-db-target.ts +1 -1
  121. package/src/mcp-errors.ts +28 -27
  122. package/src/mcp-host.ts +27 -12
  123. package/src/mcp-ui-diff.ts +27 -0
  124. package/src/mcp-ui-inspect.ts +4 -4
  125. package/src/mcp-ui-interact.ts +21 -15
  126. package/src/mcp-ui.ts +23 -17
  127. package/src/measure-database.ts +73 -0
  128. package/src/measure-paths.ts +74 -0
  129. package/src/measure-scope.ts +74 -0
  130. package/src/messages.ts +1 -3
  131. package/src/metrics-endpoint.ts +1 -1
  132. package/src/otlp-export.ts +1 -1
  133. package/src/output.ts +6 -0
  134. package/src/permission-grants.ts +86 -0
  135. package/src/prerender-out.ts +25 -0
  136. package/src/prerender.ts +121 -102
  137. package/src/pwa-artifacts.ts +3 -3
  138. package/src/reexport-manifest.ts +2 -1
  139. package/src/registry.ts +80 -56
  140. package/src/role-realtime.ts +36 -0
  141. package/src/{dev-replicator.ts → role-replicator.ts} +1 -1
  142. package/src/{dev-roles-fixture.ts → role-start-fixture.ts} +9 -5
  143. package/src/role-start-types.ts +112 -0
  144. package/src/{dev-roles.ts → role-start.ts} +42 -115
  145. package/src/{dev-sync.ts → role-sync.ts} +22 -8
  146. package/src/root-env.ts +67 -0
  147. package/src/{dev-assets.ts → runtime-assets.ts} +7 -7
  148. package/src/{dev-services.ts → runtime-bindings.ts} +39 -16
  149. package/src/{dev-cache.ts → runtime-cache.ts} +2 -2
  150. package/src/runtime-jobs.ts +87 -0
  151. package/src/{dev-live-feed.ts → runtime-live-feed.ts} +18 -5
  152. package/src/{dev-notify-retention.ts → runtime-notify-retention.ts} +1 -1
  153. package/src/{dev-purge.ts → runtime-purge.ts} +2 -2
  154. package/src/{dev-queue.ts → runtime-queue.ts} +4 -4
  155. package/src/runtime-realtime.ts +55 -0
  156. package/src/{dev-replica.ts → runtime-replica.ts} +2 -2
  157. package/src/{dev-runtime.ts → runtime-services.ts} +41 -17
  158. package/src/{dev-storage.ts → runtime-storage.ts} +4 -4
  159. package/src/scaffold-fixture.ts +28 -6
  160. package/src/scaffold-typecheck.ts +6 -3
  161. package/src/schema-drift.ts +7 -1
  162. package/src/script-csp.ts +5 -2
  163. package/src/secrets-rotation.ts +59 -0
  164. package/src/serve-boot.ts +192 -0
  165. package/src/serve-drain.ts +24 -0
  166. package/src/serve-entry.ts +6 -0
  167. package/src/serve-env.ts +116 -0
  168. package/src/serve-types.ts +55 -0
  169. package/src/serve.ts +44 -347
  170. package/src/shot-server.ts +2 -2
  171. package/src/shot-settle.ts +10 -1
  172. package/src/shot-theme.ts +3 -3
  173. package/src/shot-verdict.ts +16 -7
  174. package/src/signal-shred.ts +27 -0
  175. package/src/solid-loader.ts +26 -2
  176. package/src/static-report.ts +8 -1
  177. package/src/sync-url.ts +21 -1
  178. package/src/templates/action.ts +30 -16
  179. package/src/templates/entity.ts +12 -7
  180. package/src/templates/index.ts +1 -1
  181. package/src/templates/job.ts +10 -7
  182. package/src/templates/policy.ts +20 -2
  183. package/src/templates/resource-create.ts +127 -0
  184. package/src/templates/resource-form-island.ts +49 -12
  185. package/src/templates/resource.ts +10 -4
  186. package/src/templates/scaffold-app.ts +4 -1
  187. package/src/templates/scaffold-auth.ts +3 -1
  188. package/src/templates/scaffold-container-compose.ts +184 -0
  189. package/src/templates/scaffold-container.ts +24 -159
  190. package/src/templates/scaffold-dashboard-example.ts +2 -2
  191. package/src/templates/scaffold-db-package.ts +16 -3
  192. package/src/templates/scaffold-demo-org.ts +41 -0
  193. package/src/templates/scaffold-entries.ts +1 -1
  194. package/src/templates/scaffold-env.ts +6 -0
  195. package/src/templates/scaffold-helm-templates.ts +66 -7
  196. package/src/templates/scaffold-helm.ts +27 -5
  197. package/src/templates/scaffold-i18n.ts +15 -10
  198. package/src/templates/scaffold-repo.ts +13 -10
  199. package/src/templates/scaffold-roles.ts +38 -10
  200. package/src/templates/slice-foundation.ts +1 -1
  201. package/src/templates/wrap.ts +4 -1
  202. package/src/test-passes.ts +2 -1
  203. package/src/test-workers.ts +26 -0
  204. package/src/ts-scan.ts +3 -6
  205. package/src/tsconfig-references.ts +1 -2
  206. package/src/verify-checks.ts +41 -39
  207. package/src/verify-e2e.ts +10 -7
  208. package/src/verify-run.ts +11 -1
  209. package/src/verify-step.ts +3 -3
  210. package/src/verify-tests.ts +1 -26
  211. package/src/verify-typecheck.ts +28 -0
  212. package/src/web-binding.ts +2 -2
  213. package/src/workspace-graph.ts +10 -33
  214. package/src/cdp-browser.ts +0 -94
  215. package/src/cdp-connection.ts +0 -247
  216. package/src/cdp-e2e-page.ts +0 -180
  217. package/src/cdp-e2e-session.ts +0 -199
  218. package/src/cdp-errors.ts +0 -56
  219. package/src/cdp-launch.ts +0 -193
  220. package/src/cdp-offline-script.ts +0 -73
  221. package/src/cdp-pipe.ts +0 -77
  222. package/src/e2e-app.ts +0 -103
  223. package/src/e2e-browser-handle.ts +0 -55
  224. package/src/e2e-dom-fixture.ts +0 -117
  225. package/src/e2e-driver.ts +0 -117
  226. package/src/e2e-errors.ts +0 -117
  227. package/src/e2e-evaluate.ts +0 -156
  228. package/src/e2e-locator.ts +0 -86
  229. package/src/e2e-page.ts +0 -153
  230. package/src/e2e-preload.ts +0 -64
  231. package/src/e2e-probe.ts +0 -23
  232. package/src/e2e-selection.ts +0 -182
  233. package/src/e2e-spawn.ts +0 -169
  234. package/src/measurement-actor.ts +0 -26
  235. /package/src/{dev-hooks.ts → runtime-hooks.ts} +0 -0
  236. /package/src/{dev-render.ts → runtime-render.ts} +0 -0
package/src/i18n-index.ts CHANGED
@@ -10,7 +10,8 @@
10
10
  // whether to write at all, before any await the caller could interleave with.
11
11
  import { existsSync } from 'node:fs';
12
12
  import { containedPath } from './generate-write';
13
- import { CATALOG_ROOT, i18nIndex } from './templates';
13
+ import type { Finding } from './output';
14
+ import { CATALOG_ROOT, i18nIndex, localeEntry, localeImport } from './templates';
14
15
 
15
16
  export const I18N_INDEX_PATH = 'packages/i18n/src/index.ts';
16
17
 
@@ -25,16 +26,64 @@ export async function catalogLocales(root: string): Promise<readonly string[]> {
25
26
  return locales.sort();
26
27
  }
27
28
 
29
+ /** What a sync did: whether every catalog on disk is now selectable, and why not when it is not. */
30
+ export interface IndexSync {
31
+ /** False for an app with no i18n package, or an index this writer refused to edit. */
32
+ readonly registered: boolean;
33
+ readonly findings: readonly Finding[];
34
+ }
35
+
36
+ const CATALOG_IMPORT = /^import (\w+) from '\.\.\/catalogs\/([^']+)\.json';$/gm;
37
+ const LOCALES_OBJECT = /(locales:\s*\{)([^{}]*)(\})/;
38
+
39
+ /** The tags the index imports a catalog for, in file order. */
40
+ const importedLocales = (source: string): readonly string[] =>
41
+ [...source.matchAll(CATALOG_IMPORT)].map((match) => match[2] ?? '');
42
+
28
43
  /**
29
- * Re-derives the FULL locale set from `packages/i18n/catalogs/` — never just the locale one
30
- * invocation asked for — and rewrites the index to match. It bypasses `writeFiles` on purpose:
31
- * this file is a projection of the catalog directory, never app-authored content a conflict check
32
- * should protect. An app with no i18n package (deleted, or never scaffolded) is left alone, and
33
- * that is what `written` reports.
44
+ * Makes every catalog on disk selectable, and touches the index no more than that takes.
45
+ *
46
+ * An index that IS the template's output (for the locales it imports) is the framework's file and
47
+ * is re-derived whole. Anything else is the author's: each missing locale gets its import and its
48
+ * `locales: { … }` entry and nothing else moves — the declared `default`, and any code beside it,
49
+ * stay. A shape with no `locales: { … }` object to add to is refused with the edit named, never
50
+ * overwritten: it was, on every `x g`, with a template hard-coding `default: 'en'`.
34
51
  */
35
- export async function syncI18nIndex(root: string): Promise<boolean> {
52
+ export async function syncI18nIndex(root: string): Promise<IndexSync> {
36
53
  const indexAbsolute = containedPath(root, I18N_INDEX_PATH);
37
- if (!existsSync(indexAbsolute)) return false;
38
- await Bun.write(indexAbsolute, i18nIndex(await catalogLocales(root)));
39
- return true;
54
+ if (!existsSync(indexAbsolute)) return { registered: false, findings: [] };
55
+ const current = await Bun.file(indexAbsolute).text();
56
+ const onDisk = await catalogLocales(root);
57
+ const imported = importedLocales(current);
58
+ if (current === i18nIndex(imported)) {
59
+ await Bun.write(indexAbsolute, i18nIndex(onDisk));
60
+ return { registered: true, findings: [] };
61
+ }
62
+ const missing = onDisk.filter((locale) => !imported.includes(locale));
63
+ if (missing.length === 0) return { registered: true, findings: [] };
64
+ const edited = withLocales(current, missing);
65
+ if (edited === undefined) return { registered: false, findings: [refusal(missing)] };
66
+ await Bun.write(indexAbsolute, edited);
67
+ return { registered: true, findings: [] };
68
+ }
69
+
70
+ /** Each import after the last catalog import (or at the top), each entry at the object's end. */
71
+ function withLocales(source: string, locales: readonly string[]): string | undefined {
72
+ const object = LOCALES_OBJECT.exec(source);
73
+ if (object === null) return undefined;
74
+ const body = (object[2] ?? '').trim().replace(/,$/, '');
75
+ const entries = [body, ...locales.map(localeEntry)].filter((part) => part !== '').join(', ');
76
+ const withEntries = source.replace(LOCALES_OBJECT, `$1 ${entries} $3`);
77
+ const imports = locales.map(localeImport).join('\n');
78
+ const lastImport = [...withEntries.matchAll(CATALOG_IMPORT)].at(-1);
79
+ if (lastImport === undefined) return `${imports}\n${withEntries}`;
80
+ const at = lastImport.index + lastImport[0].length;
81
+ return `${withEntries.slice(0, at)}\n${imports}${withEntries.slice(at)}`;
40
82
  }
83
+
84
+ const refusal = (locales: readonly string[]): Finding => ({
85
+ code: 'X_CATALOG_UNREGISTERED',
86
+ cause: `${I18N_INDEX_PATH} is hand-written in a shape this writer cannot add ${locales.join(', ')} to, so those catalogs are on disk and not selectable`,
87
+ fix: `edit ${I18N_INDEX_PATH} — add ${locales.map(localeImport).join(' ')} and ${locales.map(localeEntry).join(', ')} to the locales passed to defineCatalogs`,
88
+ at: I18N_INDEX_PATH,
89
+ });
@@ -1,6 +1,6 @@
1
1
  // Single responsibility: the one source image every generated icon derives from, where the matrix
2
2
  // is served, and the renderer that turns one into the other. Its own module so the two things that
3
- // need it — `dev-assets.ts`, which SERVES the matrix, and `pwa-artifacts.ts`, which NAMES it in the
3
+ // need it — `runtime-assets.ts`, which SERVES the matrix, and `pwa-artifacts.ts`, which NAMES it in the
4
4
  // web manifest — can share it without importing each other.
5
5
 
6
6
  // why: Bun exposes no path-join primitive, and `ICON_SOURCE` is app-root-relative, so resolving it
@@ -0,0 +1,14 @@
1
+ // What `x build --target docker` does before `docker build`: stamp the image with the manifest's
2
+ // build id and write the island chunks it will serve (`island-store.ts`), so a container boot
3
+ // neither re-derives the one nor rebuilds the other.
4
+
5
+ import { appManifest } from './app-manifest';
6
+ import { buildIslands } from './island-bundle';
7
+ import { writeIslandStore } from './island-store';
8
+
9
+ /** Writes `.x/islands/` and answers the build id the image is stamped with. */
10
+ export async function prepareImage(root: string): Promise<string> {
11
+ const { manifest } = await appManifest(root);
12
+ await writeIslandStore(root, await buildIslands(root));
13
+ return manifest.buildId;
14
+ }
@@ -0,0 +1,63 @@
1
+ // The one import scanner: Bun's transpiler, fed the source with type-only imports rewritten so it
2
+ // reports them too. A leaf — it imports nothing — so `scripts/boundaries.ts`, which must run with
3
+ // no `node_modules`, reads it by path. A regex scanner beside it read a nested template's
4
+ // `import … from '@ultimat3/ui'` as the generator's own import (#493); the transpiler never does.
5
+
6
+ /** A file as the scanners take it: repo-relative POSIX path and its text. */
7
+ export interface ScannedFile {
8
+ readonly path: string;
9
+ readonly source: string;
10
+ }
11
+
12
+ /** The transpiler rejects a shebang, and a `bin.ts` legitimately has one. */
13
+ export const stripShebang = (source: string): string =>
14
+ source.startsWith('#!') ? source.slice(source.indexOf('\n') + 1) : source;
15
+
16
+ /**
17
+ * `scanImports` ERASES `import type` / `export type`. Dropping the keyword before the parse makes
18
+ * the transpiler report one as an ordinary import. The lookahead keeps `export type Foo = string`
19
+ * — an alias, not an import — from becoming a syntax error.
20
+ */
21
+ const TYPE_ONLY_CLAUSE = /\b(import|export)\s+type\s+(?=[{*]|[A-Za-z_$][\w$]*\s+from\b)/g;
22
+
23
+ export const dropTypeKeyword = (source: string): string => source.replace(TYPE_ONLY_CLAUSE, '$1 ');
24
+
25
+ /**
26
+ * The inline spelling `import { type Foo } from 'x'`, whose whole list is type-only and so erased.
27
+ * The list is DELETED rather than de-`type`d — deciding which `type` is a modifier is the parser's
28
+ * job — and a side-effect import carries the one thing a scan reads, the specifier.
29
+ */
30
+ const BRACE_IMPORT =
31
+ /(^|[\s;}])(?:import|export)\s+(?:[A-Za-z_$][\w$]*\s*,\s*)?\{[^{}]*\}\s*from\s*(['"])([^'"\n]+)\2/g;
32
+
33
+ export const asSideEffectImports = (source: string): string =>
34
+ source.replace(BRACE_IMPORT, '$1import $2$3$2');
35
+
36
+ const loaderOf = (path: string): 'ts' | 'tsx' => (path.endsWith('x') ? 'tsx' : 'ts');
37
+
38
+ /** What survives type erasure: static, re-export, side-effect and dynamic imports, and `require`. */
39
+ export function scanRuntimeImports(file: ScannedFile): readonly string[] {
40
+ return new Bun.Transpiler({ loader: loaderOf(file.path) })
41
+ .scanImports(stripShebang(file.source))
42
+ .map((entry) => entry.path);
43
+ }
44
+
45
+ /**
46
+ * Every specifier the file names, type-only ones included. Throws when the file does not parse —
47
+ * a scan that answered `[]` there would read as a file importing nothing. The rewrite pass alone
48
+ * is forgiving: a rewrite the parser refuses falls back to the runtime scan.
49
+ */
50
+ export function scanAllImports(file: ScannedFile): readonly string[] {
51
+ const runtime = scanRuntimeImports(file);
52
+ let typed: readonly string[] = [];
53
+ try {
54
+ const rewritten = asSideEffectImports(dropTypeKeyword(stripShebang(file.source)));
55
+ typed = new Bun.Transpiler({ loader: loaderOf(file.path) })
56
+ .scanImports(rewritten)
57
+ .map((entry) => entry.path);
58
+ } catch {
59
+ typed = [];
60
+ }
61
+ // The rewritten pass first: it holds every import in SOURCE order, which callers report in.
62
+ return [...new Set([...typed, ...runtime])];
63
+ }