@ultimat3/cli 29.4.0 → 30.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 (448) hide show
  1. package/CLAUDE.md +64 -40
  2. package/README.md +61 -11
  3. package/package.json +42 -28
  4. package/src/affected-tests.ts +97 -0
  5. package/src/affected-unowned.ts +43 -0
  6. package/src/affected.ts +25 -6
  7. package/src/app-boundaries.ts +29 -1
  8. package/src/app-evals.ts +12 -4
  9. package/src/app-load-unresolved.ts +62 -0
  10. package/src/app-load.ts +30 -37
  11. package/src/app-mcp.ts +14 -2
  12. package/src/app-module-graph.ts +232 -0
  13. package/src/app-reload-graph.ts +2 -2
  14. package/src/app-root.ts +37 -1
  15. package/src/bin.ts +1 -0
  16. package/src/boundary-findings.ts +6 -0
  17. package/src/budgets-freshness.ts +32 -0
  18. package/src/budgets.ts +20 -1
  19. package/src/build-capture-env.ts +9 -0
  20. package/src/cdp-shot-a11y.ts +2 -39
  21. package/src/ci-tested.ts +243 -0
  22. package/src/client-emit-customer-runtime.ts +139 -0
  23. package/src/client-emit-customer.ts +108 -0
  24. package/src/client-emit-drift.ts +72 -0
  25. package/src/client-emit-header.ts +67 -0
  26. package/src/client-emit-schemas.ts +175 -0
  27. package/src/client-emit-types.ts +121 -0
  28. package/src/client-emit-vendor.ts +146 -0
  29. package/src/client-errors-routes.ts +229 -0
  30. package/src/client-errors-script.ts +74 -0
  31. package/src/client-vitals-route.ts +197 -0
  32. package/src/client-vitals-script.ts +56 -0
  33. package/src/cmd-affected.ts +23 -0
  34. package/src/cmd-build-spec.ts +7 -1
  35. package/src/cmd-build.ts +35 -9
  36. package/src/cmd-ci-spec.ts +42 -6
  37. package/src/cmd-ci.ts +99 -2
  38. package/src/cmd-client-customer.ts +134 -0
  39. package/src/cmd-client-source.ts +81 -0
  40. package/src/cmd-client-spec.ts +48 -0
  41. package/src/cmd-client-write.ts +41 -0
  42. package/src/cmd-client.ts +111 -0
  43. package/src/cmd-db-recount.ts +120 -0
  44. package/src/cmd-db-spec.ts +36 -6
  45. package/src/cmd-db.ts +11 -3
  46. package/src/cmd-deploy-compose.ts +61 -3
  47. package/src/cmd-deploy-drain.ts +10 -2
  48. package/src/cmd-deploy-helm.ts +11 -0
  49. package/src/cmd-deploy-preflight.ts +118 -0
  50. package/src/cmd-deploy-spec.ts +58 -6
  51. package/src/cmd-deploy-verify.ts +380 -0
  52. package/src/cmd-deploy.ts +42 -9
  53. package/src/cmd-docs.ts +34 -4
  54. package/src/cmd-doctor.ts +66 -9
  55. package/src/cmd-drill-spec.ts +25 -0
  56. package/src/cmd-drill.ts +176 -0
  57. package/src/cmd-env-init.ts +102 -0
  58. package/src/cmd-env-spec.ts +10 -3
  59. package/src/cmd-env.ts +15 -10
  60. package/src/cmd-errors-recent.ts +60 -0
  61. package/src/cmd-errors-spec.ts +9 -1
  62. package/src/cmd-errors.ts +4 -0
  63. package/src/cmd-exercise-spec.ts +19 -0
  64. package/src/cmd-exercise.ts +129 -0
  65. package/src/cmd-generate-spec.ts +9 -1
  66. package/src/cmd-generate.ts +32 -27
  67. package/src/cmd-graph-load.ts +97 -0
  68. package/src/cmd-graph-spec.ts +16 -0
  69. package/src/cmd-graph.ts +73 -0
  70. package/src/cmd-i18n.ts +4 -1
  71. package/src/cmd-impact-spec.ts +13 -0
  72. package/src/cmd-impact.ts +64 -0
  73. package/src/cmd-jobs-resolve.ts +76 -0
  74. package/src/cmd-jobs-spec.ts +21 -2
  75. package/src/cmd-jobs.ts +10 -6
  76. package/src/cmd-mail-spec.ts +28 -0
  77. package/src/cmd-mail.ts +70 -0
  78. package/src/cmd-mcp-lock.ts +69 -0
  79. package/src/cmd-mcp-spec.ts +23 -4
  80. package/src/cmd-mcp.ts +17 -3
  81. package/src/cmd-new-spec.ts +8 -1
  82. package/src/cmd-new.ts +61 -9
  83. package/src/cmd-planned.ts +0 -6
  84. package/src/cmd-repro-spec.ts +31 -0
  85. package/src/cmd-repro.ts +174 -0
  86. package/src/cmd-stop-spec.ts +21 -0
  87. package/src/cmd-stop.ts +166 -0
  88. package/src/cmd-test-spec.ts +17 -4
  89. package/src/cmd-test.ts +70 -14
  90. package/src/cmd-upgrade-spec.ts +26 -0
  91. package/src/cmd-upgrade.ts +274 -0
  92. package/src/cmd-verify-spec.ts +3 -3
  93. package/src/db-backfill.ts +6 -0
  94. package/src/db-diagnostics.ts +106 -0
  95. package/src/db-generate.ts +9 -0
  96. package/src/db-reset-template.ts +1 -1
  97. package/src/db-rolling.ts +45 -0
  98. package/src/db-seed-generate.ts +188 -0
  99. package/src/deploy-verify-findings.ts +98 -0
  100. package/src/deploy-verify-flags.ts +109 -0
  101. package/src/deploy-verify-grade.ts +185 -0
  102. package/src/deploy-verify-queues.ts +53 -0
  103. package/src/dev-boot.ts +35 -15
  104. package/src/dev-child-watch.ts +16 -4
  105. package/src/dev-dashboard-guard.ts +8 -1
  106. package/src/dev-dashboard.ts +7 -0
  107. package/src/dev-lock-identity.ts +61 -0
  108. package/src/dev-lock-port-holder.ts +80 -0
  109. package/src/dev-lock-steal.ts +124 -0
  110. package/src/dev-lock.ts +92 -120
  111. package/src/dev-mail-templates.ts +51 -0
  112. package/src/dev-rebuild.ts +26 -2
  113. package/src/dev-route-table.ts +17 -0
  114. package/src/dev-supervisor.ts +5 -2
  115. package/src/dev-watch-tree.ts +11 -10
  116. package/src/dev-watch.ts +1 -1
  117. package/src/dispatch.ts +29 -6
  118. package/src/docs-section.ts +69 -0
  119. package/src/docs-sources.ts +100 -0
  120. package/src/doctor-auth.ts +11 -2
  121. package/src/doctor-chart-alerts.ts +55 -0
  122. package/src/doctor-pooler.ts +107 -0
  123. package/src/doctor-rolling.ts +35 -0
  124. package/src/doctor-row-security.ts +40 -0
  125. package/src/document-options.ts +13 -0
  126. package/src/drill-sync-lane.ts +25 -0
  127. package/src/drill-targets.ts +39 -0
  128. package/src/duplicate-packages.ts +1 -1
  129. package/src/env-production.ts +93 -0
  130. package/src/error-codes-shot.ts +59 -0
  131. package/src/error-codes-tooling.ts +29 -0
  132. package/src/error-codes.ts +100 -54
  133. package/src/error-contract.ts +2 -2
  134. package/src/errors.ts +22 -3
  135. package/src/exercise-database.ts +99 -0
  136. package/src/exercise-findings.ts +159 -0
  137. package/src/exercise-overrides.ts +42 -0
  138. package/src/exercise-registration.ts +58 -0
  139. package/src/exercise-static.ts +88 -0
  140. package/src/exercise-subject.ts +69 -0
  141. package/src/failure-capture-boot.ts +30 -0
  142. package/src/framework-env.ts +7 -1
  143. package/src/framework-schema.ts +72 -5
  144. package/src/generate-catalog-locales.ts +19 -0
  145. package/src/generate-chart-alerts.ts +156 -0
  146. package/src/generate-feature-required.ts +65 -0
  147. package/src/generate-feature.ts +83 -6
  148. package/src/generate-files.ts +24 -0
  149. package/src/generate-format-written.ts +32 -0
  150. package/src/generate-kinds.ts +6 -0
  151. package/src/generate-sign-in-config.ts +100 -0
  152. package/src/generate-sign-in-manifest.ts +28 -0
  153. package/src/generate-sign-in-mode.ts +101 -0
  154. package/src/generate-sign-in.ts +250 -0
  155. package/src/generate-write.ts +44 -7
  156. package/src/hand-rolled-socket.ts +72 -0
  157. package/src/i18n-app-key-shapes.ts +29 -0
  158. package/src/i18n-audit.ts +10 -3
  159. package/src/i18n-registration.ts +18 -3
  160. package/src/index.ts +2 -0
  161. package/src/island-bundle.ts +4 -3
  162. package/src/island-disposer.ts +116 -0
  163. package/src/island-realtime-state.ts +34 -6
  164. package/src/island-realtime.ts +6 -2
  165. package/src/island-routes.ts +3 -2
  166. package/src/isr-query-advice.ts +6 -11
  167. package/src/isr-query-guard.ts +4 -3
  168. package/src/job-budget.ts +95 -0
  169. package/src/jobs-json.ts +11 -0
  170. package/src/lint-findings.ts +104 -0
  171. package/src/local-cli.ts +34 -4
  172. package/src/loopback-host.ts +2 -2
  173. package/src/mail-preview-findings.ts +24 -0
  174. package/src/mcp-errors.ts +82 -2
  175. package/src/mcp-host.ts +11 -1
  176. package/src/mcp-lock-findings.ts +57 -0
  177. package/src/measure-paths.ts +12 -4
  178. package/src/measure-scope.ts +27 -13
  179. package/src/messages-client.ts +23 -0
  180. package/src/messages-db.ts +56 -0
  181. package/src/messages-gate.ts +107 -0
  182. package/src/messages-repro.ts +11 -0
  183. package/src/messages-shot.ts +51 -0
  184. package/src/messages-upgrade.ts +45 -0
  185. package/src/messages.ts +53 -178
  186. package/src/metrics-endpoint.ts +23 -5
  187. package/src/new-shapes-generate.ts +71 -0
  188. package/src/new-shapes.ts +63 -0
  189. package/src/openapi-compile-ir.ts +68 -0
  190. package/src/openapi-compile-schema.ts +284 -0
  191. package/src/openapi-compile.ts +273 -0
  192. package/src/openapi-refuse.ts +45 -0
  193. package/src/otlp-export.ts +2 -1
  194. package/src/output-findings.ts +123 -0
  195. package/src/output.ts +32 -8
  196. package/src/package-boundaries.ts +110 -0
  197. package/src/page-pins.ts +23 -0
  198. package/src/plain-routes.ts +13 -0
  199. package/src/prerender-entries.ts +59 -0
  200. package/src/prerender-heaviest.ts +27 -0
  201. package/src/prerender-preload.ts +6 -0
  202. package/src/prerender-report.ts +12 -0
  203. package/src/prerender-seo-reads.ts +38 -0
  204. package/src/prerender-served.ts +22 -7
  205. package/src/prerender-vitals.ts +42 -0
  206. package/src/prerender.ts +61 -32
  207. package/src/pwa-artifacts.ts +4 -3
  208. package/src/pwa-manifest-locales.ts +1 -1
  209. package/src/registry.ts +18 -0
  210. package/src/repro-bundle.ts +65 -0
  211. package/src/repro-errors.ts +47 -0
  212. package/src/repro-job.ts +72 -0
  213. package/src/repro-literal.ts +41 -0
  214. package/src/repro-request.ts +48 -0
  215. package/src/repro-write.ts +100 -0
  216. package/src/retain-sweep.ts +27 -0
  217. package/src/retained-assets-boot.ts +113 -0
  218. package/src/retained-assets-layout.ts +110 -0
  219. package/src/retained-assets-read.ts +142 -0
  220. package/src/retained-assets-sweep.ts +70 -0
  221. package/src/retained-assets.ts +171 -0
  222. package/src/revalidated-response.ts +8 -5
  223. package/src/role-replicator.ts +7 -1
  224. package/src/role-start-devices.ts +119 -0
  225. package/src/role-start-types.ts +6 -0
  226. package/src/role-start.ts +22 -5
  227. package/src/role-sync.ts +7 -1
  228. package/src/route-islands.ts +42 -0
  229. package/src/route-table.ts +2 -1
  230. package/src/row-security-boot.ts +58 -0
  231. package/src/row-security-wanted.ts +32 -0
  232. package/src/runtime-admin.ts +0 -1
  233. package/src/runtime-assets-site-cache.ts +55 -0
  234. package/src/runtime-assets-site.ts +136 -0
  235. package/src/runtime-assets.ts +99 -55
  236. package/src/runtime-bindings.ts +19 -3
  237. package/src/runtime-cache.ts +23 -14
  238. package/src/runtime-isr-outcome.ts +114 -60
  239. package/src/runtime-purge.ts +14 -2
  240. package/src/runtime-queue.ts +31 -4
  241. package/src/runtime-render-document.ts +204 -0
  242. package/src/runtime-render.ts +48 -198
  243. package/src/runtime-replica-position.ts +62 -0
  244. package/src/runtime-replica.ts +18 -3
  245. package/src/runtime-s3-path-style.ts +17 -0
  246. package/src/runtime-services.ts +19 -6
  247. package/src/runtime-statement-bound.ts +22 -0
  248. package/src/runtime-storage-upload.ts +9 -0
  249. package/src/runtime-storage.ts +41 -81
  250. package/src/schema-diff-views.ts +54 -0
  251. package/src/schema-diff.ts +21 -3
  252. package/src/schema-drift.ts +28 -9
  253. package/src/schema-dump-drift.ts +91 -7
  254. package/src/script-csp.ts +1 -1
  255. package/src/seo-cards.ts +26 -0
  256. package/src/seo-routes.ts +22 -8
  257. package/src/serve-boot.ts +54 -21
  258. package/src/serve-drain.ts +1 -0
  259. package/src/serve-env.ts +26 -8
  260. package/src/serve-retire.ts +12 -6
  261. package/src/serve-web.ts +42 -4
  262. package/src/serve.ts +9 -7
  263. package/src/site-asset-routes.ts +7 -1
  264. package/src/site-config.ts +4 -0
  265. package/src/site-llms-pages.ts +106 -0
  266. package/src/site-llms.ts +64 -0
  267. package/src/site-seo-write.ts +4 -2
  268. package/src/site-seo.ts +55 -6
  269. package/src/source-mtime.ts +50 -0
  270. package/src/static-capture-errors.ts +41 -0
  271. package/src/static-capture.ts +56 -0
  272. package/src/static-cards-serve.ts +39 -0
  273. package/src/static-cards.ts +93 -0
  274. package/src/static-document.ts +14 -13
  275. package/src/static-images.ts +115 -0
  276. package/src/stream-hole-scope.ts +55 -0
  277. package/src/style-bundle.ts +1 -1
  278. package/src/style-routes.ts +3 -2
  279. package/src/sw-precache-plan.ts +2 -2
  280. package/src/sw-routes.ts +2 -2
  281. package/src/templates/action-tests.ts +306 -0
  282. package/src/templates/action.ts +81 -269
  283. package/src/templates/backfill.ts +1 -1
  284. package/src/templates/entity.ts +21 -5
  285. package/src/templates/github/ci.yml.ts +8 -9
  286. package/src/templates/guard-bare-error.ts +2 -2
  287. package/src/templates/guard-error-declared.ts +263 -0
  288. package/src/templates/guard-native-confirm.ts +132 -0
  289. package/src/templates/guard-raw-fetch.ts +151 -0
  290. package/src/templates/guard-raw-interval.ts +121 -0
  291. package/src/templates/guard-repo-raw-sql.ts +71 -2
  292. package/src/templates/job.ts +1 -1
  293. package/src/templates/naming.ts +5 -0
  294. package/src/templates/pinned/docker-compose.yml +6 -0
  295. package/src/templates/pinned-images.ts +8 -0
  296. package/src/templates/policy.ts +85 -2
  297. package/src/templates/query.ts +70 -13
  298. package/src/templates/resource-create.ts +3 -1
  299. package/src/templates/resource-form-island.ts +15 -4
  300. package/src/templates/resource-page-db.ts +83 -0
  301. package/src/templates/resource-page.ts +161 -52
  302. package/src/templates/route.ts +4 -6
  303. package/src/templates/scaffold-app.ts +3 -0
  304. package/src/templates/scaffold-auth.ts +2 -1
  305. package/src/templates/scaffold-bin.ts +5 -52
  306. package/src/templates/scaffold-claude-agents.ts +19 -19
  307. package/src/templates/scaffold-claude-commands.ts +28 -31
  308. package/src/templates/scaffold-claude.ts +26 -23
  309. package/src/templates/scaffold-container-compose.ts +6 -1
  310. package/src/templates/scaffold-container.ts +24 -4
  311. package/src/templates/scaffold-dashboard-bare.ts +3 -3
  312. package/src/templates/scaffold-dashboard-example-tests.ts +49 -9
  313. package/src/templates/scaffold-dashboard-example-view.ts +156 -0
  314. package/src/templates/scaffold-dashboard-example.ts +43 -131
  315. package/src/templates/scaffold-dashboard-shared.ts +3 -7
  316. package/src/templates/scaffold-db-client.ts +20 -6
  317. package/src/templates/scaffold-db-package.ts +5 -14
  318. package/src/templates/scaffold-docs.ts +73 -57
  319. package/src/templates/scaffold-domain-package.ts +18 -14
  320. package/src/templates/scaffold-entries.ts +7 -2
  321. package/src/templates/scaffold-exercise.ts +37 -0
  322. package/src/templates/scaffold-guards.ts +13 -1
  323. package/src/templates/scaffold-helm-alerts.ts +234 -0
  324. package/src/templates/scaffold-helm-templates.ts +7 -5
  325. package/src/templates/scaffold-helm.ts +46 -12
  326. package/src/templates/scaffold-i18n.ts +6 -2
  327. package/src/templates/scaffold-mcp-endpoint.ts +182 -0
  328. package/src/templates/scaffold-mcp-json.ts +31 -0
  329. package/src/templates/scaffold-mcp-package.ts +59 -56
  330. package/src/templates/scaffold-repo.ts +7 -2
  331. package/src/templates/scaffold-shell.ts +45 -79
  332. package/src/templates/scaffold-test-services.ts +13 -5
  333. package/src/templates/shapes/api-keys.ts +170 -0
  334. package/src/templates/shapes/api.ts +101 -0
  335. package/src/templates/shapes/blog-site.ts +340 -0
  336. package/src/templates/shapes/blog-slice.ts +265 -0
  337. package/src/templates/shapes/blog.ts +114 -0
  338. package/src/templates/shapes/dashboard-detail.ts +242 -0
  339. package/src/templates/shapes/dashboard.ts +53 -0
  340. package/src/templates/shapes/index.ts +17 -0
  341. package/src/templates/shapes/overlay.ts +99 -0
  342. package/src/templates/shapes/shared-edits.ts +46 -0
  343. package/src/templates/sign-in-action-tests.ts +237 -0
  344. package/src/templates/sign-in-authenticator.ts +112 -0
  345. package/src/templates/sign-in-oidc.ts +189 -0
  346. package/src/templates/sign-in-pages.ts +148 -0
  347. package/src/templates/sign-in-password.ts +216 -0
  348. package/src/templates/sign-in-shared.ts +268 -0
  349. package/src/templates/slice-foundation.ts +70 -15
  350. package/src/test-batches.ts +1 -1
  351. package/src/test-database.ts +1 -1
  352. package/src/test-shards.ts +7 -1
  353. package/src/test-workers.ts +18 -12
  354. package/src/tool-gate-names.ts +34 -0
  355. package/src/upgrade-agents-link.ts +49 -0
  356. package/src/upgrade-bun-floor.ts +225 -0
  357. package/src/upgrade-gate.ts +228 -0
  358. package/src/upgrade-pins.ts +63 -0
  359. package/src/upgrade-plan.ts +214 -0
  360. package/src/upgrade-registry.ts +53 -0
  361. package/src/upgrade-report.ts +139 -0
  362. package/src/upgrade-root.ts +74 -0
  363. package/src/verify-budgets-build.ts +118 -0
  364. package/src/verify-checks.ts +67 -24
  365. package/src/verify-coverage-run.ts +1 -1
  366. package/src/verify-e2e-lane.ts +41 -0
  367. package/src/verify-exercise.ts +64 -0
  368. package/src/verify-floor.ts +41 -0
  369. package/src/verify-merge.ts +19 -4
  370. package/src/verify-role-load.ts +2 -0
  371. package/src/verify-run.ts +28 -8
  372. package/src/verify-shard.ts +12 -2
  373. package/src/verify-skip-ceiling.ts +87 -0
  374. package/src/verify-step.ts +5 -0
  375. package/src/verify-tests.ts +25 -5
  376. package/src/view-drift.ts +55 -0
  377. package/src/worker-bundle.ts +3 -2
  378. package/src/workspace-graph.ts +38 -19
  379. package/src/write-generated.ts +43 -0
  380. package/src/zone-boot.ts +20 -0
  381. package/wiki/Actions.md +342 -0
  382. package/wiki/Admin-Dashboard.md +432 -0
  383. package/wiki/Agents.md +958 -0
  384. package/wiki/Auth.md +195 -0
  385. package/wiki/Bare-VM.md +111 -0
  386. package/wiki/Batching-And-Preloading.md +136 -0
  387. package/wiki/Building-Your-Own-Base.md +203 -0
  388. package/wiki/CI-Parallel-Gate.md +220 -0
  389. package/wiki/CLI-Reference.md +1894 -0
  390. package/wiki/Caching-And-Invalidation.md +255 -0
  391. package/wiki/Client-Data.md +194 -0
  392. package/wiki/Client-Navigation.md +513 -0
  393. package/wiki/Compared.md +93 -0
  394. package/wiki/Configuration.md +868 -0
  395. package/wiki/Contributing.md +12 -0
  396. package/wiki/Deploy-Recipes.md +122 -0
  397. package/wiki/Deployment.md +508 -0
  398. package/wiki/Entities-And-Migrations.md +1116 -0
  399. package/wiki/FAQ.md +175 -0
  400. package/wiki/Feature-Flags.md +65 -0
  401. package/wiki/Getting-Started.md +221 -0
  402. package/wiki/Home.md +160 -0
  403. package/wiki/I18n.md +239 -0
  404. package/wiki/Installation.md +206 -0
  405. package/wiki/Interface-Rules.md +339 -0
  406. package/wiki/Jobs-And-Workflows.md +783 -0
  407. package/wiki/Known-Gaps.md +185 -0
  408. package/wiki/MCP-And-AI.md +753 -0
  409. package/wiki/Mail.md +138 -0
  410. package/wiki/Migrating-An-Existing-App.md +594 -0
  411. package/wiki/Migrating-From-Django.md +54 -0
  412. package/wiki/Migrating-From-Laravel.md +53 -0
  413. package/wiki/Migrating-From-Node-Drizzle.md +53 -0
  414. package/wiki/Migrating-From-Rails.md +53 -0
  415. package/wiki/Migrations-And-Backfills.md +552 -0
  416. package/wiki/Money.md +338 -0
  417. package/wiki/N-Plus-One-Detection.md +197 -0
  418. package/wiki/Notify.md +208 -0
  419. package/wiki/Observability.md +245 -0
  420. package/wiki/Outbound-HTTP.md +109 -0
  421. package/wiki/PWA-And-Offline.md +343 -0
  422. package/wiki/Policies-And-Authz.md +251 -0
  423. package/wiki/Project-Layout.md +150 -0
  424. package/wiki/Queries-And-Live-Queries.md +429 -0
  425. package/wiki/Realtime.md +734 -0
  426. package/wiki/Resource-Management.md +121 -0
  427. package/wiki/Routes-And-Render-Modes.md +521 -0
  428. package/wiki/SEO.md +288 -0
  429. package/wiki/Scheduled-Tasks.md +175 -0
  430. package/wiki/Scraping.md +493 -0
  431. package/wiki/Shapes.md +63 -0
  432. package/wiki/Stability.md +97 -0
  433. package/wiki/Static-Assets.md +166 -0
  434. package/wiki/Storage-And-Uploads.md +404 -0
  435. package/wiki/Testing.md +1314 -0
  436. package/wiki/The-Eight-Primitives.md +247 -0
  437. package/wiki/Theming.md +362 -0
  438. package/wiki/Timezones-And-Dates.md +194 -0
  439. package/wiki/Troubleshooting.md +163 -0
  440. package/wiki/Tutorial-01-First-App.md +289 -0
  441. package/wiki/Tutorial-02-First-Feature.md +339 -0
  442. package/wiki/Tutorial-03-Auth-And-Admin.md +379 -0
  443. package/wiki/Tutorial-04-Jobs-And-Realtime.md +263 -0
  444. package/wiki/Tutorial-05-Deploy-Free.md +231 -0
  445. package/wiki/Tutorial-06-Growing-Up.md +158 -0
  446. package/wiki/UI-Components.md +156 -0
  447. package/wiki/Upgrading.md +3667 -0
  448. package/src/stored-object-headers.ts +0 -43
@@ -3,7 +3,9 @@
3
3
  // `entity('reports', { title, price })` nobody asked for, and the `drift` step then demanded a
4
4
  // migration for the table. Only `x g entity` and `x g resource` create a feature's data.
5
5
 
6
- import { existsSync } from 'node:fs'; // why: a synchronous directory probe; Bun.file answers files, not directories.
6
+ import { existsSync, readdirSync } from 'node:fs'; // why: a synchronous directory probe and listing; Bun.file answers files, not directories.
7
+ // why: Bun ships no path helpers.
8
+ import { dirname, join } from 'node:path';
7
9
  import { UltimateError } from '@ultimat3/core';
8
10
  import type { Generator } from './generate-kinds';
9
11
  import { containedPath } from './generate-write';
@@ -16,15 +18,89 @@ const WRITES_INTO_SLICE: ReadonlySet<Generator> = new Set<Generator>([
16
18
  'query',
17
19
  'job',
18
20
  'task',
21
+ 'backfill',
19
22
  ]);
20
23
 
21
- /** Declared beside its one thrower: `errors.ts` sits at its 500-line ceiling. */
24
+ /** What makes a directory a slice rather than a page: a page directory holds a route and no module. */
25
+ const SLICE_MEMBERS = [
26
+ 'entity.ts',
27
+ 'policy.ts',
28
+ 'repo.ts',
29
+ 'service.ts',
30
+ 'actions',
31
+ 'queries',
32
+ 'jobs',
33
+ 'tasks',
34
+ 'live',
35
+ ];
36
+
37
+ /** How far a `--feature` may be from a slice and still read as a typo of it. */
38
+ const NEAR_DISTANCE = 2;
39
+
40
+ const editDistance = (a: string, b: string): number => {
41
+ let previous = Array.from({ length: b.length + 1 }, (_, index) => index);
42
+ for (let i = 1; i <= a.length; i += 1) {
43
+ const row = [i];
44
+ for (let j = 1; j <= b.length; j += 1) {
45
+ const swap = (previous[j - 1] ?? 0) + (a[i - 1] === b[j - 1] ? 0 : 1);
46
+ row[j] = Math.min((previous[j] ?? 0) + 1, (row[j - 1] ?? 0) + 1, swap);
47
+ }
48
+ previous = row;
49
+ }
50
+ return previous[b.length] ?? 0;
51
+ };
52
+
53
+ /** The nearest slice within `NEAR_DISTANCE`, first in the given order on a tie. */
54
+ export function closestSlice(feature: string, slices: readonly string[]): string | undefined {
55
+ let best: string | undefined;
56
+ let bestDistance = NEAR_DISTANCE + 1;
57
+ for (const slice of slices) {
58
+ const distance = editDistance(feature, slice);
59
+ if (distance < bestDistance) {
60
+ best = slice;
61
+ bestDistance = distance;
62
+ }
63
+ }
64
+ return best;
65
+ }
66
+
67
+ /** The surface's slices, sorted. A surface with no directory yet has none. */
68
+ export const slicesBeside = (root: string, slice: string): readonly string[] => {
69
+ const surface = containedPath(root, dirname(slice));
70
+ if (!existsSync(surface)) return [];
71
+ return readdirSync(surface, { withFileTypes: true })
72
+ .filter((entry) => entry.isDirectory())
73
+ .map((entry) => entry.name)
74
+ .filter((name) => SLICE_MEMBERS.some((member) => existsSync(join(surface, name, member))))
75
+ .sort();
76
+ };
77
+
78
+ export interface FeatureUnknownInput {
79
+ readonly kind: Generator;
80
+ readonly name: string;
81
+ readonly feature: string;
82
+ readonly slice: string;
83
+ readonly slices: readonly string[];
84
+ }
85
+
86
+ /**
87
+ * Declared beside its one thrower: `errors.ts` sits at its 500-line ceiling.
88
+ *
89
+ * Near a real slice, the fix is the command the author meant. `x g resource <typo>` first is how a
90
+ * typo became a table. It stays the fix when nothing is near, with the slices in the cause.
91
+ */
22
92
  export class FeatureUnknownError extends UltimateError {
23
- constructor(input: { readonly feature: string; readonly slice: string }) {
93
+ constructor(input: FeatureUnknownInput) {
94
+ const near = closestSlice(input.feature, input.slices);
95
+ const existing =
96
+ input.slices.length === 0 ? 'no slice exists yet' : `slices: ${input.slices.join(', ')}`;
24
97
  super({
25
98
  code: 'X_FEATURE_UNKNOWN',
26
- cause: `--feature ${quoteArg(input.feature)} names ${input.slice}, which does not exist — a generator writing into a slice never invents one, or its entity table`,
27
- fix: `x g resource ${quoteArg(input.feature)}`,
99
+ cause: `--feature ${quoteArg(input.feature)} names ${input.slice}, which does not exist (${existing}) — a generator writing into a slice never invents one, or its entity table`,
100
+ fix:
101
+ near === undefined
102
+ ? `x g resource ${quoteArg(input.feature)}`
103
+ : `x g ${quoteArg(input.kind)} ${quoteArg(input.name)} --feature ${quoteArg(near)}`,
28
104
  });
29
105
  }
30
106
  }
@@ -35,8 +111,9 @@ export function assertFeatureExists(
35
111
  kind: Generator,
36
112
  feature: string | undefined,
37
113
  slice: string,
114
+ name: string,
38
115
  ): void {
39
116
  if (feature === undefined || !WRITES_INTO_SLICE.has(kind)) return;
40
117
  if (existsSync(containedPath(root, slice))) return;
41
- throw new FeatureUnknownError({ feature, slice });
118
+ throw new FeatureUnknownError({ kind, name, feature, slice, slices: slicesBeside(root, slice) });
42
119
  }
@@ -6,6 +6,7 @@ import { NotImplementedError } from '@ultimat3/core';
6
6
  import { BadFlagError } from './errors';
7
7
  import type { Generator } from './generate-kinds';
8
8
  import { assertLiveSupported, assertSurfaceSupported, GENERATORS } from './generate-kinds';
9
+ import type { SignInMode } from './generate-sign-in-mode';
9
10
  import { dedupe } from './generate-write';
10
11
  import { quoteArg } from './shell-quote';
11
12
  import type { GeneratedFile, Surface } from './templates';
@@ -27,6 +28,9 @@ import {
27
28
  taskFiles,
28
29
  } from './templates';
29
30
  import { adminCatalogFiles } from './templates/admin-catalog';
31
+ import { helmMonitoringFiles } from './templates/scaffold-helm-alerts';
32
+ import { oidcSignInFiles } from './templates/sign-in-oidc';
33
+ import { passwordSignInFiles } from './templates/sign-in-password';
30
34
 
31
35
  export interface GenerateOptions {
32
36
  readonly kind: Generator;
@@ -89,6 +93,8 @@ export interface GenerateOptions {
89
93
  * written beside it as `entity-<name>.ts` + `repo-<name>.ts`. Read off the disk by `run`.
90
94
  */
91
95
  readonly sibling?: boolean;
96
+ /** `sign-in` only: which shape — `--password` or `--oidc <provider>`. Required for that kind. */
97
+ readonly signIn?: SignInMode;
92
98
  }
93
99
 
94
100
  const DEFAULT_SURFACE_DIR: Record<Surface, string> = {
@@ -234,6 +240,24 @@ export function generate(options: GenerateOptions): readonly GeneratedFile[] {
234
240
  ...(options.catalogModule === undefined ? {} : { catalogModule: options.catalogModule }),
235
241
  }),
236
242
  );
243
+ case 'sign-in': {
244
+ // The shape's own files only. The edits outside them — the authenticator swap, the config —
245
+ // read the app's disk, so `generate-sign-in.ts` makes them; this stays a pure function.
246
+ const signIn = options.signIn ?? { kind: 'password' };
247
+ const shape = {
248
+ locales: options.locales ?? ['en'],
249
+ ...(options.catalogModule === undefined ? {} : { catalogModule: options.catalogModule }),
250
+ };
251
+ return dedupe(
252
+ signIn.kind === 'password'
253
+ ? passwordSignInFiles(shape)
254
+ : oidcSignInFiles(signIn.provider, shape),
255
+ );
256
+ }
257
+ case 'chart-alerts':
258
+ // `name` is the chart's own (`Chart.yaml`), which its helpers are named after. The values
259
+ // blocks are an edit to a file the app owns, so `generate-chart-alerts.ts` makes it.
260
+ return helmMonitoringFiles(names(options.name));
237
261
  case 'route':
238
262
  // `--locales` reaches the route generator too: its catalog entry is the route's title and
239
263
  // description, and a locale asked for on the command line is a locale that gets a file.
@@ -0,0 +1,32 @@
1
+ // `x g` formats the source it just wrote. Templates are pre-formatted (`generate-format.test.ts`
2
+ // holds them to it), and a template is a string with its line breaks typed by hand, for every name
3
+ // an app can pick: a name long enough to wrap a line none of those runs named landed in an app
4
+ // whose first `lint` was then red over code nobody typed. The app's own Biome, under its own config.
5
+
6
+ import { existsSync } from 'node:fs'; // why: a synchronous file probe; Bun.file answers asynchronously.
7
+ // why: Bun exposes no path-join primitive.
8
+ import { join } from 'node:path';
9
+ import type { Runner } from './exec';
10
+
11
+ const SOURCE = /\.tsx?$/;
12
+
13
+ /**
14
+ * Best effort, and that is the whole contract: the files are already on disk and correct, this only
15
+ * re-wraps them. A binary the app has not installed, a config it has not written or a version that
16
+ * refuses them must not turn `x g` red over a file the `lint` step would have reported anyway, so
17
+ * the verdict is left for that step. The app's own binary or none: `bunx` would download one.
18
+ */
19
+ export async function formatWritten(
20
+ root: string,
21
+ written: readonly string[],
22
+ runner: Runner,
23
+ ): Promise<void> {
24
+ const sources = written.filter((path) => SOURCE.test(path));
25
+ if (sources.length === 0) return;
26
+ if (!['biome.json', 'biome.jsonc'].some((config) => existsSync(join(root, config)))) return;
27
+ const biome = Bun.which('biome', { PATH: join(root, 'node_modules', '.bin') });
28
+ if (biome === null) return;
29
+ await runner([biome, 'check', '--write', '--files-ignore-unknown=true', ...sources], {
30
+ cwd: root,
31
+ });
32
+ }
@@ -27,6 +27,8 @@ export const GENERATORS = [
27
27
  'island',
28
28
  'admin:page',
29
29
  'guard',
30
+ 'sign-in',
31
+ 'chart-alerts',
30
32
  ] as const;
31
33
 
32
34
  export type Generator = (typeof GENERATORS)[number];
@@ -280,4 +282,8 @@ const EXAMPLE_NAME: Readonly<Record<Generator, string>> = {
280
282
  island: 'counter',
281
283
  'admin:page': 'ops',
282
284
  guard: 'migration-safety',
285
+ // Sign-in takes no name: its "example" is the flag that picks a shape, so the line still runs.
286
+ 'sign-in': '--password',
287
+ // No name either: an app has one chart. `--print` runs with or without one.
288
+ 'chart-alerts': '--print',
283
289
  };
@@ -0,0 +1,100 @@
1
+ // The two edits `x g sign-in` makes to the app's `app.config.ts`: `auth.signInPath`, so a 401 page
2
+ // links to the page it wrote, and — for an OIDC sign-in — the issuer and client keys declared in
3
+ // `envSchema`, so `x env example` lists them. Text edits at anchors `x new` writes; a file reshaped
4
+ // past them gets a finding with the exact lines, never a guess.
5
+
6
+ import { ERROR_DOCS_URL, stripComments } from '@ultimat3/core';
7
+ import type { Finding } from './output';
8
+
9
+ export const APP_CONFIG = 'app.config.ts';
10
+
11
+ /** One env key the sign-in needs, as `envSchema` declares it. */
12
+ export interface SignInEnvKey {
13
+ readonly key: string;
14
+ readonly description: string;
15
+ readonly secret?: boolean;
16
+ readonly type?: 'string' | 'url';
17
+ }
18
+
19
+ export interface ConfigEdit {
20
+ readonly contents: string;
21
+ readonly findings: readonly Finding[];
22
+ }
23
+
24
+ /** `lines` is code to paste — the fix opens with it, and says where after a `#`. */
25
+ const unplaced = (what: string, lines: string): Finding => ({
26
+ code: 'X_GENERATE_CONFLICT',
27
+ cause: `${APP_CONFIG} has no ${what} where x new writes one, so x g sign-in did not edit it`,
28
+ fix: `${lines} # paste into ${what} in ${APP_CONFIG}`,
29
+ docs: ERROR_DOCS_URL,
30
+ at: APP_CONFIG,
31
+ });
32
+
33
+ const SIGN_IN_PATH_LINE = (path: string): string => `auth: { signInPath: '${path}' },`;
34
+
35
+ /**
36
+ * `auth: { signInPath }` after the config's `name:` line, unless a sign-in path is declared. Both
37
+ * tests read the code with its comments blanked: a `// signInPath: …` the author parked declares
38
+ * nothing, and skipping the edit over it left a 401 page linking nowhere.
39
+ */
40
+ function withSignInPath(source: string, path: string): ConfigEdit {
41
+ const code = stripComments(source);
42
+ if (/\bsignInPath\s*:/.test(code)) return { contents: source, findings: [] };
43
+ const anchor = /(export const config = defineConfig\(\{\n(\s*)name: [^\n]*\n)/.exec(source);
44
+ if (anchor === null || /\n\s*auth:\s*\{/.test(code)) {
45
+ return {
46
+ contents: source,
47
+ findings: [unplaced('defineConfig({ … })', SIGN_IN_PATH_LINE(path))],
48
+ };
49
+ }
50
+ const indent = anchor[2] ?? ' ';
51
+ const line = `${indent}// The page a refused browser is sent to: x g sign-in wrote it.\n${indent}${SIGN_IN_PATH_LINE(path)}\n`;
52
+ const at = anchor.index + (anchor[1] ?? '').length;
53
+ return { contents: `${source.slice(0, at)}${line}${source.slice(at)}`, findings: [] };
54
+ }
55
+
56
+ const envEntry = (entry: SignInEnvKey): string =>
57
+ [
58
+ ` ${entry.key}: {`,
59
+ ` type: '${entry.type ?? 'string'}',`,
60
+ ' required: false,',
61
+ " role: ['web', 'sync'],",
62
+ ...(entry.secret === true ? [' secret: true,'] : []),
63
+ // Descriptions are this generator's own constants, written without a quote.
64
+ ` description: '${entry.description}',`,
65
+ ' },',
66
+ ].join('\n');
67
+
68
+ /** Each key `envSchema` does not already declare, before its closing `} satisfies EnvSchema;`. */
69
+ function withEnvKeys(source: string, keys: readonly SignInEnvKey[]): ConfigEdit {
70
+ const missing = keys.filter((entry) => !new RegExp(`\\n\\s*${entry.key}\\s*:`).test(source));
71
+ if (missing.length === 0) return { contents: source, findings: [] };
72
+ const close = source.indexOf('\n} satisfies EnvSchema;');
73
+ if (close === -1) {
74
+ return {
75
+ contents: source,
76
+ findings: [
77
+ unplaced(
78
+ 'the envSchema object',
79
+ `export const envSchema = { ${missing.map((entry) => `${entry.key}: { type: '${entry.type ?? 'string'}', required: false }`).join(', ')} }`,
80
+ ),
81
+ ],
82
+ };
83
+ }
84
+ const block = missing.map(envEntry).join('\n');
85
+ return {
86
+ contents: `${source.slice(0, close)}\n${block}${source.slice(close)}`,
87
+ findings: [],
88
+ };
89
+ }
90
+
91
+ /** Both edits, in order, over the file's text as it stands. */
92
+ export function signInConfigEdit(
93
+ source: string,
94
+ signInPath: string,
95
+ keys: readonly SignInEnvKey[],
96
+ ): ConfigEdit {
97
+ const pathed = withSignInPath(source, signInPath);
98
+ const keyed = withEnvKeys(pathed.contents, keys);
99
+ return { contents: keyed.contents, findings: [...pathed.findings, ...keyed.findings] };
100
+ }
@@ -0,0 +1,28 @@
1
+ // The two projections `x g` keeps current after a write, for `x g sign-in`: `x.manifest.json` and
2
+ // `openapi.json`, refreshed only where the app already keeps them — the same rule `cmd-generate.ts`
3
+ // holds, so a generator never hands an app a generated file it did not ask to maintain.
4
+
5
+ import { existsSync } from 'node:fs'; // why: Bun has no synchronous existence check.
6
+ import { MANIFEST_FILENAME } from '@ultimat3/manifest';
7
+ import { writeAppArtifacts } from './app-artifacts';
8
+ import { appManifest } from './app-manifest';
9
+ import { containedPath } from './generate-write';
10
+ import type { Finding } from './output';
11
+
12
+ /** The projections a real run would rewrite: the ones the app already keeps. */
13
+ export const existingArtifacts = async (root: string): Promise<readonly string[]> =>
14
+ [MANIFEST_FILENAME, 'openapi.json'].filter((file) => existsSync(containedPath(root, file)));
15
+
16
+ export interface Refreshed {
17
+ readonly written: readonly string[];
18
+ /** A module that would not load withholds the projection, and says so. */
19
+ readonly findings: readonly Finding[];
20
+ }
21
+
22
+ export async function appManifestIfAny(root: string): Promise<Refreshed> {
23
+ if (!existsSync(containedPath(root, MANIFEST_FILENAME))) return { written: [], findings: [] };
24
+ const { manifest, findings } = await appManifest(root);
25
+ if (findings.length > 0) return { written: [], findings };
26
+ const written = await writeAppArtifacts(root, manifest, { openapi: true, onlyExisting: true });
27
+ return { written, findings: [] };
28
+ }
@@ -0,0 +1,101 @@
1
+ // How `x g sign-in` is spelled: exactly one of `--oidc <provider>` or `--password`, and no `<name>`
2
+ // — an app has one sign-in, so there is nothing to name. Both flags are refused on every other
3
+ // generator: a flag accepted and ignored is a second spelling an agent believes did something.
4
+
5
+ import { BUILTIN_OAUTH_PROVIDER_IDS } from '@ultimat3/auth';
6
+ import { generateSpec } from './cmd-generate-spec';
7
+ import { BadFlagError } from './errors';
8
+ import type { Generator } from './generate-kinds';
9
+ import type { ParsedArgs } from './parse';
10
+ import { flagBool, flagString } from './parse';
11
+
12
+ export type SignInMode =
13
+ | { readonly kind: 'password' }
14
+ | { readonly kind: 'oidc'; readonly provider: string };
15
+
16
+ /** A provider id is one URL segment and two env-key prefixes: lowercase, digits, hyphens. */
17
+ const PROVIDER = /^[a-z][a-z0-9-]{0,62}$/;
18
+
19
+ /** What a run is called in its summary line: `password`, or the provider. */
20
+ export const signInLabel = (mode: SignInMode): string =>
21
+ mode.kind === 'password' ? 'password' : mode.provider;
22
+
23
+ /** The two flags, on a generator that is not `sign-in`, refused before anything is planned. */
24
+ export function refuseSignInFlags(kind: Generator, args: ParsedArgs): void {
25
+ if (kind === 'sign-in') return;
26
+ for (const flag of ['oidc', 'password']) {
27
+ if (!args.flags.has(flag)) continue;
28
+ throw new BadFlagError({
29
+ flag,
30
+ command: `g ${kind}`,
31
+ reason: `--${flag} is the sign-in generator's`,
32
+ fix: flag === 'oidc' ? 'x g sign-in --oidc corp-sso' : 'x g sign-in --password',
33
+ });
34
+ }
35
+ }
36
+
37
+ /** The `x g` flags the sign-in generator reads; every other one would be ignored, so is refused. */
38
+ const SIGN_IN_READS = new Set(['oidc', 'password', 'force', 'dry-run', 'locales']);
39
+
40
+ /**
41
+ * Any other `x g` flag set to something but its default: the generator never reads `--feature`,
42
+ * `--surface`, `--at`, `--permission`, `--live`, `--admin` or `--locales`, so each would be a
43
+ * flag accepted and ignored — the second spelling the header refuses.
44
+ */
45
+ function refuseUnreadFlags(args: ParsedArgs): void {
46
+ for (const flag of generateSpec.flags ?? []) {
47
+ if (SIGN_IN_READS.has(flag.name)) continue;
48
+ const value = args.flags.get(flag.name);
49
+ if (value === undefined || value === flag.default) continue;
50
+ throw new BadFlagError({
51
+ flag: flag.name,
52
+ command: 'g sign-in',
53
+ reason: `the sign-in generator does not read --${flag.name}, so it would change nothing`,
54
+ fix: 'x g sign-in --password',
55
+ });
56
+ }
57
+ }
58
+
59
+ export function readSignInMode(args: ParsedArgs): SignInMode {
60
+ refuseUnreadFlags(args);
61
+ const named = args.positionals[1];
62
+ if (named !== undefined) {
63
+ throw new BadFlagError({
64
+ flag: 'name',
65
+ command: 'g sign-in',
66
+ reason: `an app has one sign-in, so "${named}" names nothing — the shape is a flag`,
67
+ fix: 'x g sign-in --password',
68
+ });
69
+ }
70
+ const provider = flagString(args, 'oidc');
71
+ const passwordFlag = flagBool(args, 'password');
72
+ if ((provider === undefined) !== passwordFlag) {
73
+ throw new BadFlagError({
74
+ flag: passwordFlag ? 'password' : 'oidc',
75
+ command: 'g sign-in',
76
+ reason: passwordFlag
77
+ ? '--password and --oidc are two sign-ins, and the generator writes one'
78
+ : 'needs exactly one of --oidc <provider> or --password',
79
+ fix: 'x g sign-in --password',
80
+ });
81
+ }
82
+ if (provider === undefined) return { kind: 'password' };
83
+ if (!PROVIDER.test(provider)) {
84
+ throw new BadFlagError({
85
+ flag: 'oidc',
86
+ command: 'g sign-in',
87
+ reason: `"${provider}" is not a provider id: lowercase letters, digits and hyphens, a letter first`,
88
+ fix: 'x g sign-in --oidc corp-sso',
89
+ });
90
+ }
91
+ const builtin: readonly string[] = BUILTIN_OAUTH_PROVIDER_IDS;
92
+ if (builtin.includes(provider)) {
93
+ throw new BadFlagError({
94
+ flag: 'oidc',
95
+ command: 'g sign-in',
96
+ reason: `"${provider}" is a built-in provider, registered already; --oidc discovers a new issuer under its own id`,
97
+ fix: 'x g sign-in --oidc corp-sso',
98
+ });
99
+ }
100
+ return { kind: 'oidc', provider };
101
+ }