@ultimat3/cli 29.4.0 → 30.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 (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 +1313 -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
package/src/dev-boot.ts CHANGED
@@ -10,6 +10,7 @@ import { setStatementObserver } from '@ultimat3/db';
10
10
  import type { OverlayNotice, RequestContext } from '@ultimat3/http';
11
11
  import { asCtx } from '@ultimat3/http';
12
12
  import { loadSignInPath } from './app-auth';
13
+ import { loadAppConfig } from './app-config-load';
13
14
  import { appManifest } from './app-manifest';
14
15
  import type { StalePin } from './app-reload-graph';
15
16
  import { enableReloadTracking } from './app-reload-graph';
@@ -23,11 +24,14 @@ import { devRebuilder } from './dev-rebuild';
23
24
  import { devRouteTable } from './dev-route-table';
24
25
  import { createTraceRecorder } from './dev-traces';
25
26
  import { watchTree } from './dev-watch-tree';
27
+ import { armFailureCapture } from './failure-capture-boot';
26
28
  import { buildIslands } from './island-bundle';
27
29
  import { FRAME_STYLE } from './island-harness';
28
30
  import type { Finding } from './output';
31
+ import { plainRoutes } from './plain-routes';
29
32
  import type { RunningRoles } from './role-start';
30
33
  import { DEV_ROLES, startRoles } from './role-start';
34
+ import { armRowSecurity, rowSecurityDeclared } from './row-security-boot';
31
35
  import type { DevServices } from './runtime-bindings';
32
36
  import { resolveServices, withRealtimeEvents } from './runtime-bindings';
33
37
  import { attachedIsr } from './runtime-isr';
@@ -175,6 +179,16 @@ async function bootDev(
175
179
  appFindings: app.findings,
176
180
  islands: await buildIslands(options.root),
177
181
  };
182
+ // `serve-boot.ts`'s guard, at its point: after the app's modules, before any role serves. On
183
+ // PGlite the boot serves as `x_app`; on an external database a role that bypasses is refused.
184
+ const appConfig = await loadAppConfig(options.root);
185
+ acquired.push(
186
+ await armRowSecurity(rowSecurityDeclared(appConfig), {
187
+ embedded: services.db.mode === 'embedded',
188
+ }),
189
+ );
190
+ // ON in `x dev` whatever `app.config.ts` says: a 5xx here is one `x repro <requestId>` away.
191
+ acquired.push(armFailureCapture(appConfig, { force: true }));
178
192
  // The manifest's build id is a content hash of every fact below it, so a dev document's
179
193
  // `x-ultimate-build` header names the exact shape the client was served against. Pinned at
180
194
  // boot on purpose: the header is handed to the HTTP config and the render modes once, and a
@@ -210,19 +224,21 @@ async function bootDev(
210
224
  // ttl); attached, so a tag bust reaches it too. Released with the roles.
211
225
  const { isr, release: releaseIsr } = attachedIsr({ buildId });
212
226
  acquired.push(releaseIsr);
213
- const { routes, theme, speculation, inlineStyles, mcpPath, mcpPaths } = await devRouteTable({
214
- isr,
215
- root: options.root,
216
- env: options.env,
217
- buildId,
218
- storage: runtime.storage,
219
- dashboard,
220
- islands: () => state.islands,
221
- realtime: runtime.realtime,
222
- rateLimitStore: appRuntime?.rateLimitStore ?? runtime.rateLimitStore,
223
- appRoutes: appRuntime?.routes,
224
- images: appRuntime?.images,
225
- });
227
+ const { routes, theme, speculation, zone, inlineStyles, mcpPath, mcpPaths } = await devRouteTable(
228
+ {
229
+ isr,
230
+ root: options.root,
231
+ env: options.env,
232
+ buildId,
233
+ storage: runtime.storage,
234
+ dashboard,
235
+ islands: () => state.islands,
236
+ realtime: runtime.realtime,
237
+ rateLimitStore: appRuntime?.rateLimitStore ?? runtime.rateLimitStore,
238
+ appRoutes: plainRoutes(appRuntime?.routes),
239
+ images: appRuntime?.images,
240
+ },
241
+ );
226
242
 
227
243
  // The app's `apps/<app>/runtime.ts`, composed exactly as `runRole` composes a caller's
228
244
  // `runtime`: the replica scope in front, the app's own middleware behind it. Before this the
@@ -247,7 +263,11 @@ async function bootDev(
247
263
  // The `/_x` shell, the harness's frame, the app's own error pages and its brand — the inline
248
264
  // bodies this process serves; the app's surfaces are content-hashed files `'self'` admits.
249
265
  inlineStyles: [await devShellStyle(), FRAME_STYLE, ...inlineStyles],
250
- inlineScripts: [theme.cspSource, ...(speculation === undefined ? [] : [speculation.cspSource])],
266
+ inlineScripts: [
267
+ theme.cspSource,
268
+ ...(speculation === undefined ? [] : [speculation.cspSource]),
269
+ ...(zone === undefined ? [] : [zone.cspSource]),
270
+ ],
251
271
  // The overlay renders this request's own loops under the error it is already showing.
252
272
  // `serve.ts` boots through the same `startRoles` and passes nothing (axiom 6).
253
273
  devNotices: (ctx: RequestContext): readonly OverlayNotice[] =>
@@ -259,7 +279,7 @@ async function bootDev(
259
279
  });
260
280
  acquired.push(() => running.stop());
261
281
 
262
- const rebuild = devRebuilder(options, state, isr);
282
+ const rebuild = devRebuilder(options, state, isr, () => running.refreshDevices());
263
283
  // Watched one directory at a time, so an ignored one costs no descriptor at all — `dev-watch.ts`
264
284
  // decides which, from the app's own `.gitignore`. A recursive watch on the root registered one
265
285
  // inotify descriptor per directory in the tree, `.git/`, `node_modules/` and the `.x/` this
@@ -28,6 +28,13 @@ export const devHardExitMs = (): number => drainDeadlineMs() + MIN_RELEASE_MS +
28
28
 
29
29
  export type DevChildGone = 'supervisor' | 'root';
30
30
 
31
+ /**
32
+ * Why a supervised child is leaving, as its restart bookkeeping records it: `signal` is a stop it
33
+ * was sent (SIGINT, SIGTERM, the supervisor's drain message — `x stop`), which a save landing during
34
+ * the drain must never turn into a restart.
35
+ */
36
+ export type DevChildLeaving = DevChildGone | 'signal';
37
+
31
38
  export interface DevChildWatchInput {
32
39
  readonly root: string;
33
40
  readonly env: Readonly<Record<string, string | undefined>>;
@@ -169,16 +176,21 @@ export const signalExitCode = (signal: 'SIGINT' | 'SIGTERM'): number =>
169
176
  export function startDevChildWatch(
170
177
  root: string,
171
178
  env: Readonly<Record<string, string | undefined>>,
172
- onGone: (why: DevChildGone) => void,
179
+ onGone: (why: DevChildLeaving) => void,
173
180
  ): { readonly stopping: (code: number) => void } {
174
181
  const stopping = hardExit((code) => process.exit(code));
175
182
  // Beside core's own handlers, never instead of them: the drain is theirs, the deadline is this.
176
- process.on('SIGINT', () => stopping(signalExitCode('SIGINT')));
177
- process.on('SIGTERM', () => stopping(signalExitCode('SIGTERM')));
183
+ // `onGone('signal')` first, so a save during this drain cannot become a restart (`childRestart`).
184
+ const stop = (signal: 'SIGINT' | 'SIGTERM'): void => {
185
+ onGone('signal');
186
+ stopping(signalExitCode(signal));
187
+ };
188
+ process.on('SIGINT', () => stop('SIGINT'));
189
+ process.on('SIGTERM', () => stop('SIGTERM'));
178
190
  // The supervisor's stop, on every platform: the same drain a signal begins, by message.
179
191
  process.on('message', (message: unknown) => {
180
192
  if (!isDevDrainMessage(message)) return;
181
- stopping(signalExitCode(message.signal));
193
+ stop(message.signal);
182
194
  void drain(message.signal);
183
195
  });
184
196
  watchDevChild({
@@ -57,7 +57,14 @@ export function assertSameOrigin(request: UltimateRequest): void {
57
57
  listed: () => false,
58
58
  listName: 'no list — /_x admits its own origin only',
59
59
  });
60
- if (!verdict.ok) throw csrfBlocked(request.pathname, verdict.reason, request.ctx.ip);
60
+ if (!verdict.ok) {
61
+ throw csrfBlocked(
62
+ request.pathname,
63
+ verdict.reason,
64
+ request.ctx.ip,
65
+ selfOrigin(request.url, request.ctx.https),
66
+ );
67
+ }
61
68
  }
62
69
 
63
70
  /** The SQL panel's one input, from a form or a JSON body — `request.body` parses either. */
@@ -8,6 +8,7 @@ import type {
8
8
  DevSources,
9
9
  InvalidationFact,
10
10
  MailFact,
11
+ MailTemplateFact,
11
12
  ManifestFact,
12
13
  PolicyFact,
13
14
  RequestTrace,
@@ -25,12 +26,14 @@ import { recentInvalidations } from '@ultimat3/cache';
25
26
  import type { Role } from '@ultimat3/core';
26
27
  import type { Route, UltimateRequest } from '@ultimat3/http';
27
28
  import { jsonResponse } from '@ultimat3/http';
29
+ import { appLocaleSet } from '@ultimat3/i18n/app-catalogs';
28
30
  import type { MemoryMailDriver } from '@ultimat3/mail';
29
31
  import { isMemoryDriver } from '@ultimat3/mail';
30
32
  import type { Manifest } from '@ultimat3/manifest';
31
33
  import { appManifest, readAppManifest } from './app-manifest';
32
34
  import { appBoundaryFindings } from './boundary-findings';
33
35
  import { assertSameOrigin, hostRefusal, SQL_BODY, sqlOnGet } from './dev-dashboard-guard';
36
+ import { mailTemplateFacts } from './dev-mail-templates';
34
37
  import type { StatementLedger } from './dev-n-plus-one';
35
38
  import { devPolicyMatrix } from './dev-policy';
36
39
  import type { TraceRecorder } from './dev-traces';
@@ -137,6 +140,10 @@ export function devSources(input: DevDashboardInput): DevSources {
137
140
  ...(outbox === undefined
138
141
  ? {}
139
142
  : { mail: (): Promise<readonly MailFact[]> => Promise.resolve(mailFacts(outbox)) }),
143
+ // Every declared mail's `preview`, rendered in every locale the app ships — independent of the
144
+ // transport, so a template is checked before anything is sent anywhere.
145
+ mailTemplates: async (): Promise<readonly MailTemplateFact[]> =>
146
+ mailTemplateFacts((await appLocaleSet(input.root)).locales),
140
147
  manifest: (): Promise<ManifestFact> => manifestFact(input.root),
141
148
  invalidations: (): Promise<readonly InvalidationFact[]> =>
142
149
  Promise.resolve(invalidationFacts()),
@@ -0,0 +1,61 @@
1
+ // Which process a pid is, beyond the number: its start time, recorded in `.x/dev.lock` beside the
2
+ // pid and compared before `x stop` signals. A kill -9 leaves the lock behind and the kernel reuses
3
+ // pids, so a pid alone once named an unrelated process for `x stop` to end.
4
+
5
+ // why: /proc files report size 0, and Bun.file reads a file by its size — it answers ''.
6
+ import { readFile } from 'node:fs/promises';
7
+ import type { DevLock } from './dev-lock';
8
+ import type { Runner } from './exec';
9
+ import { exec } from './exec';
10
+
11
+ /** Field 22 of `/proc/<pid>/stat` (`starttime`), read past the `comm` field, which may hold `)`. */
12
+ export function startFromProcStat(stat: string): string | undefined {
13
+ const close = stat.lastIndexOf(')');
14
+ if (close === -1) return undefined;
15
+ // Field 3 (`state`) is the first after the comm; `starttime` is field 22, so index 19 from it.
16
+ const start = stat
17
+ .slice(close + 1)
18
+ .trim()
19
+ .split(/\s+/)[19];
20
+ return start !== undefined && /^\d+$/.test(start) ? start : undefined;
21
+ }
22
+
23
+ /**
24
+ * The start time of `pid` as the OS reports it, or `undefined` when it will not say (no such
25
+ * process, or Windows, which has no `ps`). Linux reads `/proc`; elsewhere `ps -o lstart=`.
26
+ * Only ever compared for equality with a value this same function wrote.
27
+ */
28
+ export async function processStartTime(
29
+ pid: number,
30
+ runner: Runner = exec,
31
+ ): Promise<string | undefined> {
32
+ if (!Number.isInteger(pid) || pid <= 0 || process.platform === 'win32') return undefined;
33
+ if (process.platform === 'linux') {
34
+ const stat = await readFile(`/proc/${String(pid)}/stat`, 'utf8').catch(() => undefined);
35
+ return stat === undefined ? undefined : startFromProcStat(stat);
36
+ }
37
+ try {
38
+ const ps = await runner(['ps', '-o', 'lstart=', '-p', String(pid)], { cwd: process.cwd() });
39
+ const text = ps.stdout.trim();
40
+ return ps.ok && text !== '' ? text : undefined;
41
+ } catch {
42
+ // `ps` missing is the same answer as a pid it will not describe: no identity to compare.
43
+ return undefined;
44
+ }
45
+ }
46
+
47
+ /**
48
+ * The lock's pid is alive but is not the process that wrote it: its start time differs from the one
49
+ * recorded. Compared only where both are known (no start time on Windows, or in a pre-30 lock).
50
+ */
51
+ export const reusedPid = async (lock: DevLock): Promise<boolean> => {
52
+ if (lock.started === undefined) return false;
53
+ const now = await processStartTime(lock.pid);
54
+ return now !== undefined && now !== lock.started;
55
+ };
56
+
57
+ /** `{ started }` for `pid`, or nothing where the OS will not say (Windows). */
58
+ export const startedOf = async (pid: number): Promise<{ readonly started?: string }> => {
59
+ const started = await processStartTime(pid);
60
+ return started === undefined ? {} : { started };
61
+ };
@@ -0,0 +1,80 @@
1
+ // Who holds a taken port, as far as the OS will say: `ss`, then `lsof`, then `netstat` + `tasklist`.
2
+ // Split from `dev-lock.ts`, which re-exports it, so the lock file stays within the file ceiling.
3
+
4
+ import { exec, type Runner } from './exec';
5
+
6
+ /** Whatever is listening, as far as the OS will say. Both fields absent when it will not say. */
7
+ export interface PortHolder {
8
+ readonly pid?: number;
9
+ /** The process name, e.g. `bun`. Enough to recognise "that is my other project". */
10
+ readonly command?: string;
11
+ }
12
+
13
+ /**
14
+ * Who holds the port. `ss` first because it is present on every Linux box the framework targets and
15
+ * needs no elevation for your own processes; `lsof` is the macOS answer; `netstat -ano` plus
16
+ * `tasklist` the Windows one.
17
+ *
18
+ * Through `exec.ts`, like every other subprocess the CLI runs, so a test injects a fake `Runner`
19
+ * rather than racing a real socket.
20
+ *
21
+ * EVERY PROBE IS CAUGHT SEPARATELY. `exec` refuses a missing program with `X_CLI_UNEXPECTED`, and
22
+ * letting that escape would mean a box without `ss` gets the CLI's catch-all instead of
23
+ * `X_PORT_IN_USE` — the exact substitution this module exists to end. A missing `ss` must fall
24
+ * through to `lsof`, and a box with none still gets the refusal, just without a pid in it.
25
+ */
26
+ export const portHolder = async (port: number, runner: Runner = exec): Promise<PortHolder> => {
27
+ const probe = async (command: readonly string[]): Promise<string> => {
28
+ try {
29
+ const result = await runner(command, { cwd: process.cwd() });
30
+ return result.stdout;
31
+ } catch {
32
+ return '';
33
+ }
34
+ };
35
+
36
+ const parsed = /users:\(\("([^"]+)",pid=(\d+)/.exec(
37
+ await probe(['ss', '-lptnH', `sport = :${port}`]),
38
+ );
39
+ const name = parsed?.[1];
40
+ if (parsed !== null && name !== undefined) return { command: name, pid: Number(parsed[2]) };
41
+
42
+ const lines = (
43
+ await probe(['lsof', '-nP', `-iTCP:${String(port)}`, '-sTCP:LISTEN', '-Fpc'])
44
+ ).split('\n');
45
+ const pid = lines.find((line) => line.startsWith('p'))?.slice(1);
46
+ const command = lines.find((line) => line.startsWith('c'))?.slice(1);
47
+ if (pid !== undefined && /^\d+$/.test(pid)) {
48
+ return command === undefined ? { pid: Number(pid) } : { command, pid: Number(pid) };
49
+ }
50
+
51
+ // Windows has neither: `netstat -ano` names the listening pid, `tasklist` its image.
52
+ const listener = netstatListener(await probe(['netstat', '-ano', '-p', 'TCP']), port);
53
+ if (listener === undefined) return {};
54
+ const image = tasklistImage(
55
+ await probe(['tasklist', '/FI', `PID eq ${String(listener)}`, '/FO', 'CSV', '/NH']),
56
+ );
57
+ return image === undefined ? { pid: listener } : { command: image, pid: listener };
58
+ };
59
+
60
+ /**
61
+ * The pid `netstat -ano` reports LISTENING on `port`, any local address: `TCP 127.0.0.1:3000
62
+ * 0.0.0.0:0 LISTENING 4242`, and `[::1]:3000` for IPv6. The state column is the word Windows
63
+ * prints in English on every locale this was read on; an unrecognised line is skipped.
64
+ */
65
+ export function netstatListener(text: string, port: number): number | undefined {
66
+ for (const line of text.split(/\r?\n/)) {
67
+ const [proto, local, , state, pid] = line.trim().split(/\s+/);
68
+ if (proto !== 'TCP' || state !== 'LISTENING' || pid === undefined || !/^\d+$/.test(pid)) {
69
+ continue;
70
+ }
71
+ if (local?.endsWith(`:${String(port)}`) === true) return Number(pid);
72
+ }
73
+ return undefined;
74
+ }
75
+
76
+ /** The image name off `tasklist /FO CSV /NH`'s one row — `"bun.exe","4242",…` — or `undefined`. */
77
+ export function tasklistImage(text: string): string | undefined {
78
+ const name = /^"([^"]+)","\d+"/m.exec(text.trim())?.[1];
79
+ return name === undefined || name === '' ? undefined : name;
80
+ }
@@ -0,0 +1,124 @@
1
+ // `x dev`'s stale-lock removal, under an exclusive guard: only the boot holding `dev.lock.steal`
2
+ // may delete the lock, and only while it still holds the bytes that boot judged stale. An unlink by
3
+ // path after an unguarded read deleted whatever was there by then, a rival's fresh claim included,
4
+ // so two boots over one stale lock both won.
5
+
6
+ // why: the guard and the take are synchronous syscalls (link, rename, unlink, stat) with no Bun
7
+ // equivalent, and a sync read keeps the check and the act in one turn of the event loop.
8
+ import { linkSync, readFileSync, renameSync, statSync, unlinkSync } from 'node:fs';
9
+
10
+ /**
11
+ * `cleared`: the lock judged stale is gone, so the claim may be retried. `changed`: the lock there
12
+ * now is not the one judged, because another boot cleared it and claimed, so judge again.
13
+ * `busy`: another boot is clearing it right now.
14
+ */
15
+ export type StealOutcome = 'cleared' | 'changed' | 'busy';
16
+
17
+ /** The guard's suffix: `dev.lock.steal`, beside the lock it guards. */
18
+ export const STEAL_GUARD_SUFFIX = '.steal';
19
+
20
+ /**
21
+ * Past this age a guard is a crashed steal, whatever its pid says: the steal is three syscalls, and
22
+ * a pid the kernel reused would otherwise hold every later steal off forever.
23
+ */
24
+ export const STEAL_GUARD_STALE_MS = 10_000;
25
+
26
+ export interface StealInput {
27
+ /** The lock file. */
28
+ readonly path: string;
29
+ /** The lock's bytes as the caller read them and found them stale. */
30
+ readonly judged: string;
31
+ /** Create `path` exclusively, holding this process's lock record; `false` when it exists. */
32
+ readonly claim: (path: string) => boolean;
33
+ /** Whether a pid is a live process: the guard's holder is always a real one. */
34
+ readonly alive: (pid: number) => boolean;
35
+ /** Test seam for the guard's age. */
36
+ readonly now?: () => number;
37
+ }
38
+
39
+ const readText = (path: string): string | undefined => {
40
+ try {
41
+ return readFileSync(path, 'utf8');
42
+ } catch {
43
+ return undefined;
44
+ }
45
+ };
46
+
47
+ const discard = (path: string): void => {
48
+ try {
49
+ unlinkSync(path);
50
+ } catch {
51
+ // Already gone. Each caller below has already decided the file is its own to remove.
52
+ }
53
+ };
54
+
55
+ /**
56
+ * Remove `path` only if it still holds `judged`: renamed to a name unique to this call (one
57
+ * syscall, so of two boots exactly one gets the file), then compared, and linked back when it is
58
+ * not the one judged. Linked, never renamed back: a rename would overwrite a claim made meanwhile.
59
+ * `true` when the file judged is gone.
60
+ */
61
+ function takeAway(path: string, judged: string): boolean {
62
+ const taken = `${path}.${process.pid}.${crypto.randomUUID().slice(0, 8)}.stale`;
63
+ try {
64
+ renameSync(path, taken);
65
+ } catch {
66
+ return true;
67
+ }
68
+ const found = readText(taken);
69
+ if (found !== judged) {
70
+ try {
71
+ linkSync(taken, path);
72
+ } catch {
73
+ // A claim landed on the emptied name in that instant; it is the one that stands.
74
+ }
75
+ }
76
+ discard(taken);
77
+ return found === judged;
78
+ }
79
+
80
+ /** A guard left by a steal that never finished: its pid is gone, it is too old, or unreadable. */
81
+ function guardAbandoned(guard: string, held: string, input: StealInput): boolean {
82
+ let pid: unknown;
83
+ try {
84
+ pid = (JSON.parse(held) as { pid?: unknown }).pid;
85
+ } catch {
86
+ return true;
87
+ }
88
+ if (typeof pid !== 'number' || !input.alive(pid)) return true;
89
+ try {
90
+ return (input.now ?? Date.now)() - statSync(guard).mtimeMs > STEAL_GUARD_STALE_MS;
91
+ } catch {
92
+ return false;
93
+ }
94
+ }
95
+
96
+ function takeGuard(guard: string, input: StealInput): boolean {
97
+ if (input.claim(guard)) return true;
98
+ const held = readText(guard);
99
+ // Released between the claim and the read: the steal it guarded is over, so ask once more.
100
+ if (held === undefined) return input.claim(guard);
101
+ if (!guardAbandoned(guard, held, input)) return false;
102
+ // Two boots may both judge one guard abandoned: `takeAway` lets only the one that still finds
103
+ // THOSE bytes remove it, so the second never deletes the first's fresh guard.
104
+ return takeAway(guard, held) && input.claim(guard);
105
+ }
106
+
107
+ /**
108
+ * Delete the lock at `input.path` if it still holds `input.judged`, holding the guard throughout.
109
+ * Under the guard the check-then-act is safe: a lock that exists is removed only by the guard's
110
+ * holder, and its stale owner writes nothing, so the bytes read are the bytes unlinked.
111
+ */
112
+ export function stealStale(input: StealInput): StealOutcome {
113
+ const guard = `${input.path}${STEAL_GUARD_SUFFIX}`;
114
+ if (!takeGuard(guard, input)) return 'busy';
115
+ try {
116
+ const found = readText(input.path);
117
+ if (found === undefined) return 'cleared';
118
+ if (found !== input.judged) return 'changed';
119
+ discard(input.path);
120
+ return 'cleared';
121
+ } finally {
122
+ discard(guard);
123
+ }
124
+ }