@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.
- package/CLAUDE.md +64 -40
- package/README.md +61 -11
- package/package.json +42 -28
- package/src/affected-tests.ts +97 -0
- package/src/affected-unowned.ts +43 -0
- package/src/affected.ts +25 -6
- package/src/app-boundaries.ts +29 -1
- package/src/app-evals.ts +12 -4
- package/src/app-load-unresolved.ts +62 -0
- package/src/app-load.ts +30 -37
- package/src/app-mcp.ts +14 -2
- package/src/app-module-graph.ts +232 -0
- package/src/app-reload-graph.ts +2 -2
- package/src/app-root.ts +37 -1
- package/src/bin.ts +1 -0
- package/src/boundary-findings.ts +6 -0
- package/src/budgets-freshness.ts +32 -0
- package/src/budgets.ts +20 -1
- package/src/build-capture-env.ts +9 -0
- package/src/cdp-shot-a11y.ts +2 -39
- package/src/ci-tested.ts +243 -0
- package/src/client-emit-customer-runtime.ts +139 -0
- package/src/client-emit-customer.ts +108 -0
- package/src/client-emit-drift.ts +72 -0
- package/src/client-emit-header.ts +67 -0
- package/src/client-emit-schemas.ts +175 -0
- package/src/client-emit-types.ts +121 -0
- package/src/client-emit-vendor.ts +146 -0
- package/src/client-errors-routes.ts +229 -0
- package/src/client-errors-script.ts +74 -0
- package/src/client-vitals-route.ts +197 -0
- package/src/client-vitals-script.ts +56 -0
- package/src/cmd-affected.ts +23 -0
- package/src/cmd-build-spec.ts +7 -1
- package/src/cmd-build.ts +35 -9
- package/src/cmd-ci-spec.ts +42 -6
- package/src/cmd-ci.ts +99 -2
- package/src/cmd-client-customer.ts +134 -0
- package/src/cmd-client-source.ts +81 -0
- package/src/cmd-client-spec.ts +48 -0
- package/src/cmd-client-write.ts +41 -0
- package/src/cmd-client.ts +111 -0
- package/src/cmd-db-recount.ts +120 -0
- package/src/cmd-db-spec.ts +36 -6
- package/src/cmd-db.ts +11 -3
- package/src/cmd-deploy-compose.ts +61 -3
- package/src/cmd-deploy-drain.ts +10 -2
- package/src/cmd-deploy-helm.ts +11 -0
- package/src/cmd-deploy-preflight.ts +118 -0
- package/src/cmd-deploy-spec.ts +58 -6
- package/src/cmd-deploy-verify.ts +380 -0
- package/src/cmd-deploy.ts +42 -9
- package/src/cmd-docs.ts +34 -4
- package/src/cmd-doctor.ts +66 -9
- package/src/cmd-drill-spec.ts +25 -0
- package/src/cmd-drill.ts +176 -0
- package/src/cmd-env-init.ts +102 -0
- package/src/cmd-env-spec.ts +10 -3
- package/src/cmd-env.ts +15 -10
- package/src/cmd-errors-recent.ts +60 -0
- package/src/cmd-errors-spec.ts +9 -1
- package/src/cmd-errors.ts +4 -0
- package/src/cmd-exercise-spec.ts +19 -0
- package/src/cmd-exercise.ts +129 -0
- package/src/cmd-generate-spec.ts +9 -1
- package/src/cmd-generate.ts +32 -27
- package/src/cmd-graph-load.ts +97 -0
- package/src/cmd-graph-spec.ts +16 -0
- package/src/cmd-graph.ts +73 -0
- package/src/cmd-i18n.ts +4 -1
- package/src/cmd-impact-spec.ts +13 -0
- package/src/cmd-impact.ts +64 -0
- package/src/cmd-jobs-resolve.ts +76 -0
- package/src/cmd-jobs-spec.ts +21 -2
- package/src/cmd-jobs.ts +10 -6
- package/src/cmd-mail-spec.ts +28 -0
- package/src/cmd-mail.ts +70 -0
- package/src/cmd-mcp-lock.ts +69 -0
- package/src/cmd-mcp-spec.ts +23 -4
- package/src/cmd-mcp.ts +17 -3
- package/src/cmd-new-spec.ts +8 -1
- package/src/cmd-new.ts +61 -9
- package/src/cmd-planned.ts +0 -6
- package/src/cmd-repro-spec.ts +31 -0
- package/src/cmd-repro.ts +174 -0
- package/src/cmd-stop-spec.ts +21 -0
- package/src/cmd-stop.ts +166 -0
- package/src/cmd-test-spec.ts +17 -4
- package/src/cmd-test.ts +70 -14
- package/src/cmd-upgrade-spec.ts +26 -0
- package/src/cmd-upgrade.ts +274 -0
- package/src/cmd-verify-spec.ts +3 -3
- package/src/db-backfill.ts +6 -0
- package/src/db-diagnostics.ts +106 -0
- package/src/db-generate.ts +9 -0
- package/src/db-reset-template.ts +1 -1
- package/src/db-rolling.ts +45 -0
- package/src/db-seed-generate.ts +188 -0
- package/src/deploy-verify-findings.ts +98 -0
- package/src/deploy-verify-flags.ts +109 -0
- package/src/deploy-verify-grade.ts +185 -0
- package/src/deploy-verify-queues.ts +53 -0
- package/src/dev-boot.ts +35 -15
- package/src/dev-child-watch.ts +16 -4
- package/src/dev-dashboard-guard.ts +8 -1
- package/src/dev-dashboard.ts +7 -0
- package/src/dev-lock-identity.ts +61 -0
- package/src/dev-lock-port-holder.ts +80 -0
- package/src/dev-lock-steal.ts +124 -0
- package/src/dev-lock.ts +92 -120
- package/src/dev-mail-templates.ts +51 -0
- package/src/dev-rebuild.ts +26 -2
- package/src/dev-route-table.ts +17 -0
- package/src/dev-supervisor.ts +5 -2
- package/src/dev-watch-tree.ts +11 -10
- package/src/dev-watch.ts +1 -1
- package/src/dispatch.ts +29 -6
- package/src/docs-section.ts +69 -0
- package/src/docs-sources.ts +100 -0
- package/src/doctor-auth.ts +11 -2
- package/src/doctor-chart-alerts.ts +55 -0
- package/src/doctor-pooler.ts +107 -0
- package/src/doctor-rolling.ts +35 -0
- package/src/doctor-row-security.ts +40 -0
- package/src/document-options.ts +13 -0
- package/src/drill-sync-lane.ts +25 -0
- package/src/drill-targets.ts +39 -0
- package/src/duplicate-packages.ts +1 -1
- package/src/env-production.ts +93 -0
- package/src/error-codes-shot.ts +59 -0
- package/src/error-codes-tooling.ts +29 -0
- package/src/error-codes.ts +100 -54
- package/src/error-contract.ts +2 -2
- package/src/errors.ts +22 -3
- package/src/exercise-database.ts +99 -0
- package/src/exercise-findings.ts +159 -0
- package/src/exercise-overrides.ts +42 -0
- package/src/exercise-registration.ts +58 -0
- package/src/exercise-static.ts +88 -0
- package/src/exercise-subject.ts +69 -0
- package/src/failure-capture-boot.ts +30 -0
- package/src/framework-env.ts +7 -1
- package/src/framework-schema.ts +72 -5
- package/src/generate-catalog-locales.ts +19 -0
- package/src/generate-chart-alerts.ts +156 -0
- package/src/generate-feature-required.ts +65 -0
- package/src/generate-feature.ts +83 -6
- package/src/generate-files.ts +24 -0
- package/src/generate-format-written.ts +32 -0
- package/src/generate-kinds.ts +6 -0
- package/src/generate-sign-in-config.ts +100 -0
- package/src/generate-sign-in-manifest.ts +28 -0
- package/src/generate-sign-in-mode.ts +101 -0
- package/src/generate-sign-in.ts +250 -0
- package/src/generate-write.ts +44 -7
- package/src/hand-rolled-socket.ts +72 -0
- package/src/i18n-app-key-shapes.ts +29 -0
- package/src/i18n-audit.ts +10 -3
- package/src/i18n-registration.ts +18 -3
- package/src/index.ts +2 -0
- package/src/island-bundle.ts +4 -3
- package/src/island-disposer.ts +116 -0
- package/src/island-realtime-state.ts +34 -6
- package/src/island-realtime.ts +6 -2
- package/src/island-routes.ts +3 -2
- package/src/isr-query-advice.ts +6 -11
- package/src/isr-query-guard.ts +4 -3
- package/src/job-budget.ts +95 -0
- package/src/jobs-json.ts +11 -0
- package/src/lint-findings.ts +104 -0
- package/src/local-cli.ts +34 -4
- package/src/loopback-host.ts +2 -2
- package/src/mail-preview-findings.ts +24 -0
- package/src/mcp-errors.ts +82 -2
- package/src/mcp-host.ts +11 -1
- package/src/mcp-lock-findings.ts +57 -0
- package/src/measure-paths.ts +12 -4
- package/src/measure-scope.ts +27 -13
- package/src/messages-client.ts +23 -0
- package/src/messages-db.ts +56 -0
- package/src/messages-gate.ts +107 -0
- package/src/messages-repro.ts +11 -0
- package/src/messages-shot.ts +51 -0
- package/src/messages-upgrade.ts +45 -0
- package/src/messages.ts +53 -178
- package/src/metrics-endpoint.ts +23 -5
- package/src/new-shapes-generate.ts +71 -0
- package/src/new-shapes.ts +63 -0
- package/src/openapi-compile-ir.ts +68 -0
- package/src/openapi-compile-schema.ts +284 -0
- package/src/openapi-compile.ts +273 -0
- package/src/openapi-refuse.ts +45 -0
- package/src/otlp-export.ts +2 -1
- package/src/output-findings.ts +123 -0
- package/src/output.ts +32 -8
- package/src/package-boundaries.ts +110 -0
- package/src/page-pins.ts +23 -0
- package/src/plain-routes.ts +13 -0
- package/src/prerender-entries.ts +59 -0
- package/src/prerender-heaviest.ts +27 -0
- package/src/prerender-preload.ts +6 -0
- package/src/prerender-report.ts +12 -0
- package/src/prerender-seo-reads.ts +38 -0
- package/src/prerender-served.ts +22 -7
- package/src/prerender-vitals.ts +42 -0
- package/src/prerender.ts +61 -32
- package/src/pwa-artifacts.ts +4 -3
- package/src/pwa-manifest-locales.ts +1 -1
- package/src/registry.ts +18 -0
- package/src/repro-bundle.ts +65 -0
- package/src/repro-errors.ts +47 -0
- package/src/repro-job.ts +72 -0
- package/src/repro-literal.ts +41 -0
- package/src/repro-request.ts +48 -0
- package/src/repro-write.ts +100 -0
- package/src/retain-sweep.ts +27 -0
- package/src/retained-assets-boot.ts +113 -0
- package/src/retained-assets-layout.ts +110 -0
- package/src/retained-assets-read.ts +142 -0
- package/src/retained-assets-sweep.ts +70 -0
- package/src/retained-assets.ts +171 -0
- package/src/revalidated-response.ts +8 -5
- package/src/role-replicator.ts +7 -1
- package/src/role-start-devices.ts +119 -0
- package/src/role-start-types.ts +6 -0
- package/src/role-start.ts +22 -5
- package/src/role-sync.ts +7 -1
- package/src/route-islands.ts +42 -0
- package/src/route-table.ts +2 -1
- package/src/row-security-boot.ts +58 -0
- package/src/row-security-wanted.ts +32 -0
- package/src/runtime-admin.ts +0 -1
- package/src/runtime-assets-site-cache.ts +55 -0
- package/src/runtime-assets-site.ts +136 -0
- package/src/runtime-assets.ts +99 -55
- package/src/runtime-bindings.ts +19 -3
- package/src/runtime-cache.ts +23 -14
- package/src/runtime-isr-outcome.ts +114 -60
- package/src/runtime-purge.ts +14 -2
- package/src/runtime-queue.ts +31 -4
- package/src/runtime-render-document.ts +204 -0
- package/src/runtime-render.ts +48 -198
- package/src/runtime-replica-position.ts +62 -0
- package/src/runtime-replica.ts +18 -3
- package/src/runtime-s3-path-style.ts +17 -0
- package/src/runtime-services.ts +19 -6
- package/src/runtime-statement-bound.ts +22 -0
- package/src/runtime-storage-upload.ts +9 -0
- package/src/runtime-storage.ts +41 -81
- package/src/schema-diff-views.ts +54 -0
- package/src/schema-diff.ts +21 -3
- package/src/schema-drift.ts +28 -9
- package/src/schema-dump-drift.ts +91 -7
- package/src/script-csp.ts +1 -1
- package/src/seo-cards.ts +26 -0
- package/src/seo-routes.ts +22 -8
- package/src/serve-boot.ts +54 -21
- package/src/serve-drain.ts +1 -0
- package/src/serve-env.ts +26 -8
- package/src/serve-retire.ts +12 -6
- package/src/serve-web.ts +42 -4
- package/src/serve.ts +9 -7
- package/src/site-asset-routes.ts +7 -1
- package/src/site-config.ts +4 -0
- package/src/site-llms-pages.ts +106 -0
- package/src/site-llms.ts +64 -0
- package/src/site-seo-write.ts +4 -2
- package/src/site-seo.ts +55 -6
- package/src/source-mtime.ts +50 -0
- package/src/static-capture-errors.ts +41 -0
- package/src/static-capture.ts +56 -0
- package/src/static-cards-serve.ts +39 -0
- package/src/static-cards.ts +93 -0
- package/src/static-document.ts +14 -13
- package/src/static-images.ts +115 -0
- package/src/stream-hole-scope.ts +55 -0
- package/src/style-bundle.ts +1 -1
- package/src/style-routes.ts +3 -2
- package/src/sw-precache-plan.ts +2 -2
- package/src/sw-routes.ts +2 -2
- package/src/templates/action-tests.ts +306 -0
- package/src/templates/action.ts +81 -269
- package/src/templates/backfill.ts +1 -1
- package/src/templates/entity.ts +21 -5
- package/src/templates/github/ci.yml.ts +8 -9
- package/src/templates/guard-bare-error.ts +2 -2
- package/src/templates/guard-error-declared.ts +263 -0
- package/src/templates/guard-native-confirm.ts +132 -0
- package/src/templates/guard-raw-fetch.ts +151 -0
- package/src/templates/guard-raw-interval.ts +121 -0
- package/src/templates/guard-repo-raw-sql.ts +71 -2
- package/src/templates/job.ts +1 -1
- package/src/templates/naming.ts +5 -0
- package/src/templates/pinned/docker-compose.yml +6 -0
- package/src/templates/pinned-images.ts +8 -0
- package/src/templates/policy.ts +85 -2
- package/src/templates/query.ts +70 -13
- package/src/templates/resource-create.ts +3 -1
- package/src/templates/resource-form-island.ts +15 -4
- package/src/templates/resource-page-db.ts +83 -0
- package/src/templates/resource-page.ts +161 -52
- package/src/templates/route.ts +4 -6
- package/src/templates/scaffold-app.ts +3 -0
- package/src/templates/scaffold-auth.ts +2 -1
- package/src/templates/scaffold-bin.ts +5 -52
- package/src/templates/scaffold-claude-agents.ts +19 -19
- package/src/templates/scaffold-claude-commands.ts +28 -31
- package/src/templates/scaffold-claude.ts +26 -23
- package/src/templates/scaffold-container-compose.ts +6 -1
- package/src/templates/scaffold-container.ts +24 -4
- package/src/templates/scaffold-dashboard-bare.ts +3 -3
- package/src/templates/scaffold-dashboard-example-tests.ts +49 -9
- package/src/templates/scaffold-dashboard-example-view.ts +156 -0
- package/src/templates/scaffold-dashboard-example.ts +43 -131
- package/src/templates/scaffold-dashboard-shared.ts +3 -7
- package/src/templates/scaffold-db-client.ts +20 -6
- package/src/templates/scaffold-db-package.ts +5 -14
- package/src/templates/scaffold-docs.ts +73 -57
- package/src/templates/scaffold-domain-package.ts +18 -14
- package/src/templates/scaffold-entries.ts +7 -2
- package/src/templates/scaffold-exercise.ts +37 -0
- package/src/templates/scaffold-guards.ts +13 -1
- package/src/templates/scaffold-helm-alerts.ts +234 -0
- package/src/templates/scaffold-helm-templates.ts +7 -5
- package/src/templates/scaffold-helm.ts +46 -12
- package/src/templates/scaffold-i18n.ts +6 -2
- package/src/templates/scaffold-mcp-endpoint.ts +182 -0
- package/src/templates/scaffold-mcp-json.ts +31 -0
- package/src/templates/scaffold-mcp-package.ts +59 -56
- package/src/templates/scaffold-repo.ts +7 -2
- package/src/templates/scaffold-shell.ts +45 -79
- package/src/templates/scaffold-test-services.ts +13 -5
- package/src/templates/shapes/api-keys.ts +170 -0
- package/src/templates/shapes/api.ts +101 -0
- package/src/templates/shapes/blog-site.ts +340 -0
- package/src/templates/shapes/blog-slice.ts +265 -0
- package/src/templates/shapes/blog.ts +114 -0
- package/src/templates/shapes/dashboard-detail.ts +242 -0
- package/src/templates/shapes/dashboard.ts +53 -0
- package/src/templates/shapes/index.ts +17 -0
- package/src/templates/shapes/overlay.ts +99 -0
- package/src/templates/shapes/shared-edits.ts +46 -0
- package/src/templates/sign-in-action-tests.ts +237 -0
- package/src/templates/sign-in-authenticator.ts +112 -0
- package/src/templates/sign-in-oidc.ts +189 -0
- package/src/templates/sign-in-pages.ts +148 -0
- package/src/templates/sign-in-password.ts +216 -0
- package/src/templates/sign-in-shared.ts +268 -0
- package/src/templates/slice-foundation.ts +70 -15
- package/src/test-batches.ts +1 -1
- package/src/test-database.ts +1 -1
- package/src/test-shards.ts +7 -1
- package/src/test-workers.ts +18 -12
- package/src/tool-gate-names.ts +34 -0
- package/src/upgrade-agents-link.ts +49 -0
- package/src/upgrade-bun-floor.ts +225 -0
- package/src/upgrade-gate.ts +228 -0
- package/src/upgrade-pins.ts +63 -0
- package/src/upgrade-plan.ts +214 -0
- package/src/upgrade-registry.ts +53 -0
- package/src/upgrade-report.ts +139 -0
- package/src/upgrade-root.ts +74 -0
- package/src/verify-budgets-build.ts +118 -0
- package/src/verify-checks.ts +67 -24
- package/src/verify-coverage-run.ts +1 -1
- package/src/verify-e2e-lane.ts +41 -0
- package/src/verify-exercise.ts +64 -0
- package/src/verify-floor.ts +41 -0
- package/src/verify-merge.ts +19 -4
- package/src/verify-role-load.ts +2 -0
- package/src/verify-run.ts +28 -8
- package/src/verify-shard.ts +12 -2
- package/src/verify-skip-ceiling.ts +87 -0
- package/src/verify-step.ts +5 -0
- package/src/verify-tests.ts +25 -5
- package/src/view-drift.ts +55 -0
- package/src/worker-bundle.ts +3 -2
- package/src/workspace-graph.ts +38 -19
- package/src/write-generated.ts +43 -0
- package/src/zone-boot.ts +20 -0
- package/wiki/Actions.md +342 -0
- package/wiki/Admin-Dashboard.md +432 -0
- package/wiki/Agents.md +958 -0
- package/wiki/Auth.md +195 -0
- package/wiki/Bare-VM.md +111 -0
- package/wiki/Batching-And-Preloading.md +136 -0
- package/wiki/Building-Your-Own-Base.md +203 -0
- package/wiki/CI-Parallel-Gate.md +220 -0
- package/wiki/CLI-Reference.md +1894 -0
- package/wiki/Caching-And-Invalidation.md +255 -0
- package/wiki/Client-Data.md +194 -0
- package/wiki/Client-Navigation.md +513 -0
- package/wiki/Compared.md +93 -0
- package/wiki/Configuration.md +868 -0
- package/wiki/Contributing.md +12 -0
- package/wiki/Deploy-Recipes.md +122 -0
- package/wiki/Deployment.md +508 -0
- package/wiki/Entities-And-Migrations.md +1116 -0
- package/wiki/FAQ.md +175 -0
- package/wiki/Feature-Flags.md +65 -0
- package/wiki/Getting-Started.md +221 -0
- package/wiki/Home.md +160 -0
- package/wiki/I18n.md +239 -0
- package/wiki/Installation.md +206 -0
- package/wiki/Interface-Rules.md +339 -0
- package/wiki/Jobs-And-Workflows.md +783 -0
- package/wiki/Known-Gaps.md +185 -0
- package/wiki/MCP-And-AI.md +753 -0
- package/wiki/Mail.md +138 -0
- package/wiki/Migrating-An-Existing-App.md +594 -0
- package/wiki/Migrating-From-Django.md +54 -0
- package/wiki/Migrating-From-Laravel.md +53 -0
- package/wiki/Migrating-From-Node-Drizzle.md +53 -0
- package/wiki/Migrating-From-Rails.md +53 -0
- package/wiki/Migrations-And-Backfills.md +552 -0
- package/wiki/Money.md +338 -0
- package/wiki/N-Plus-One-Detection.md +197 -0
- package/wiki/Notify.md +208 -0
- package/wiki/Observability.md +245 -0
- package/wiki/Outbound-HTTP.md +109 -0
- package/wiki/PWA-And-Offline.md +343 -0
- package/wiki/Policies-And-Authz.md +251 -0
- package/wiki/Project-Layout.md +150 -0
- package/wiki/Queries-And-Live-Queries.md +429 -0
- package/wiki/Realtime.md +734 -0
- package/wiki/Resource-Management.md +121 -0
- package/wiki/Routes-And-Render-Modes.md +521 -0
- package/wiki/SEO.md +288 -0
- package/wiki/Scheduled-Tasks.md +175 -0
- package/wiki/Scraping.md +493 -0
- package/wiki/Shapes.md +63 -0
- package/wiki/Stability.md +97 -0
- package/wiki/Static-Assets.md +166 -0
- package/wiki/Storage-And-Uploads.md +404 -0
- package/wiki/Testing.md +1313 -0
- package/wiki/The-Eight-Primitives.md +247 -0
- package/wiki/Theming.md +362 -0
- package/wiki/Timezones-And-Dates.md +194 -0
- package/wiki/Troubleshooting.md +163 -0
- package/wiki/Tutorial-01-First-App.md +289 -0
- package/wiki/Tutorial-02-First-Feature.md +339 -0
- package/wiki/Tutorial-03-Auth-And-Admin.md +379 -0
- package/wiki/Tutorial-04-Jobs-And-Realtime.md +263 -0
- package/wiki/Tutorial-05-Deploy-Free.md +231 -0
- package/wiki/Tutorial-06-Growing-Up.md +158 -0
- package/wiki/UI-Components.md +156 -0
- package/wiki/Upgrading.md +3667 -0
- package/src/stored-object-headers.ts +0 -43
package/wiki/I18n.md
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
# I18n
|
|
2
|
+
|
|
3
|
+
Flat key catalog, one JSON file per locale. Loud misses. `Intl.*` for anything that is not a string.
|
|
4
|
+
|
|
5
|
+
Zero hardcoded user-facing strings — every string an end user can read goes through `t()`. A literal in JSX or in an error message is a lint failure and fails `x verify`.
|
|
6
|
+
|
|
7
|
+
## Catalogs
|
|
8
|
+
|
|
9
|
+
One file per locale, flat dot-keys after load, nested for authoring.
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
packages/i18n/src/catalogs/en.json # framework strings — ships inside @ultimat3/i18n
|
|
13
|
+
packages/i18n/catalogs/en.json # the app's own catalogs, same shape
|
|
14
|
+
packages/i18n/catalogs/es.json
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"nav": { "home": "Home", "billing": "Billing" },
|
|
20
|
+
"post": {
|
|
21
|
+
"published": "Published {title}",
|
|
22
|
+
"count_one": "{count} post",
|
|
23
|
+
"count_other": "{count} posts"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
| Rule | Detail |
|
|
29
|
+
|---|---|
|
|
30
|
+
| Authoring | nested objects; `flattenCatalog` produces `nav.home`, `post.published` |
|
|
31
|
+
| Key segments | `/^[A-Za-z0-9_-]+$/` — anything else is `X_CATALOG_INVALID` |
|
|
32
|
+
| Leaves | strings only. An array or a number leaf is `X_CATALOG_INVALID` |
|
|
33
|
+
| Collisions | a nested branch and a dotted key that flatten to the same path throw |
|
|
34
|
+
| Merge order | framework catalog first, app catalog last — apps override framework keys. **Under `en` only**: the framework catalog is English, so `defineCatalogs` seats it under core's `DEFAULT_LOCALE` (`en`) and nowhere else. An app shipping only `es` renders `⟦errors.notFound.title⟧` for a framework key it has not translated, which is the golden rule working — it used to be served English with `isMiss` reading `false`, a fallback locale chain wearing registration as a disguise |
|
|
35
|
+
| `en` | the reference locale. Its key set is what every other locale is diffed against |
|
|
36
|
+
|
|
37
|
+
## `t()`
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import { t } from '@ultimat3/i18n';
|
|
41
|
+
|
|
42
|
+
export function billingHeading(count: number): string {
|
|
43
|
+
return t('post.count', { count });
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
| Aspect | Behavior |
|
|
48
|
+
|---|---|
|
|
49
|
+
| Signature | `t(key: string, vars?: TranslateVars): string` — `TranslateVars` is `Record<string, string \| number \| boolean>` plus optional `count` |
|
|
50
|
+
| Locale | ambient, read from the ALS request context. `t()` never takes a locale argument |
|
|
51
|
+
| Miss | returns `⟦key⟧`. Never throws, never falls back to another locale, never returns the key bare |
|
|
52
|
+
| Typed keys | keys are generated into a union from the `en` catalog; an unknown key is a type error |
|
|
53
|
+
| Introspection | `t.has(key)`, `t.raw(key)`, `t.keys()`; `isMiss(rendered)` for assertions in tests |
|
|
54
|
+
| No concatenation | `t('a') + ' ' + name` is banned — put the slot in the string: `t('a', { name })` |
|
|
55
|
+
|
|
56
|
+
Word order differs per language, so a concatenated sentence is untranslatable by construction. One key = one complete sentence or label.
|
|
57
|
+
|
|
58
|
+
## Interpolation
|
|
59
|
+
|
|
60
|
+
| Form | Renders |
|
|
61
|
+
|---|---|
|
|
62
|
+
| `{name}` | `String(vars.name)` |
|
|
63
|
+
| `{{` / `}}` | a literal `{` / `}` |
|
|
64
|
+
| `{name}` with no `vars.name` | `⟦name⟧` — loud, same reason a missing key is loud |
|
|
65
|
+
|
|
66
|
+
`t()` interpolates on **every** call, with `vars` or without: `t('greeting')` on `Hello {name}` is `Hello ⟦name⟧`, exactly as `t('greeting', {})` is, and `{{` is unescaped either way. The template itself — to hand a client island its plural forms, say — is `t.raw(key)`, never `t(key)`.
|
|
67
|
+
|
|
68
|
+
## Pluralization
|
|
69
|
+
|
|
70
|
+
CLDR categories via `Intl.PluralRules`, never an English `n === 1` branch.
|
|
71
|
+
|
|
72
|
+
| Locale | Categories it needs |
|
|
73
|
+
|---|---|
|
|
74
|
+
| `en`, `de`, `es` | `one`, `other` |
|
|
75
|
+
| `pl`, `ru`, `uk` | `one`, `few`, `many`, `other` |
|
|
76
|
+
| `ar` | `zero`, `one`, `two`, `few`, `many`, `other` |
|
|
77
|
+
| `ja`, `zh`, `ko` | `other` only |
|
|
78
|
+
|
|
79
|
+
Pass `count`; the translator picks the key. Candidates, most specific first: `key_<category>` → `key_plural` → `key_other` → `key`. For `count === 1`: `key_one` → `key`. `_plural` is the two-form authoring shortcut; the `_<category>` suffixes are what a 3+ form locale needs.
|
|
80
|
+
|
|
81
|
+
## Ordinals and `select`
|
|
82
|
+
|
|
83
|
+
`As of 2026-11-06`. **The suffix mechanism, two more families; no ICU syntax.** One selection
|
|
84
|
+
(`selectMessageKey`) serves `t()` and a mail's render alike.
|
|
85
|
+
|
|
86
|
+
| Call | Reads, in order |
|
|
87
|
+
|---|---|
|
|
88
|
+
| `t('place', { ordinal: 2 })` | `place_ordinal_<category>` by CLDR's **ordinal** rules (`en`: 1 one, 2 two, 3 few, 4 and 11 other), then `place_ordinal_other` |
|
|
89
|
+
| `t('invite', { select: 'female' })` | `invite_select_female`, then `invite_select_other` |
|
|
90
|
+
| `t('invite', { select: 'female', count: 2 })` | each of those stems with `count`'s plural chain: `invite_select_female_other`, … |
|
|
91
|
+
|
|
92
|
+
A family the catalog lacks falls through to the key's own plural, as before. A `select` value is a
|
|
93
|
+
key segment (`[A-Za-z0-9-]`, ≤ 32) or it selects nothing. The typed key admits the stem (`invite`),
|
|
94
|
+
as it does a plural stem. `x i18n check` counts every member used through its stem, a locale owes
|
|
95
|
+
the stem and not each value (a language without gender defines `invite_select_other` alone), and a
|
|
96
|
+
family with no `_other` is `X_I18N_FAMILY_INCOMPLETE`, red in the `i18n` step.
|
|
97
|
+
|
|
98
|
+
## Loud misses
|
|
99
|
+
|
|
100
|
+
| Surface | Behavior on a missing key |
|
|
101
|
+
|---|---|
|
|
102
|
+
| dev render | `⟦post.published⟧` in the DOM — visible in the page and in review screenshots |
|
|
103
|
+
| `x verify` | check 8 (SEO + i18n) fails with `X_CATALOG_MISSING_KEYS` |
|
|
104
|
+
| CI | same failure, `--json` shaped |
|
|
105
|
+
| Tests | `isMiss(t('k'))` is the assertion — never assert on `''` |
|
|
106
|
+
|
|
107
|
+
Silently falling back to English is what makes a half-translated app ship. The framework does not do it.
|
|
108
|
+
|
|
109
|
+
## Strings in an island
|
|
110
|
+
|
|
111
|
+
`t()`'s catalog does not cross into an island, and a translator is not a prop. An island's own copy
|
|
112
|
+
arrives already translated, as text. When it needs keys — `t(key, { count })` after the page
|
|
113
|
+
rendered, or the design system's `ui.*` strings — the page sends a slice and the island builds a
|
|
114
|
+
translator over it (`As of 2026-10`):
|
|
115
|
+
|
|
116
|
+
| Side | Call |
|
|
117
|
+
|---|---|
|
|
118
|
+
| page (server) | `catalogSubset(t, ['app.post.likes'])` from `@ultimat3/i18n`; `uiCatalog(t)` from `@ultimat3/ui` for every `ui.*` key |
|
|
119
|
+
| island | `subsetTranslator(props.strings)` from `@ultimat3/i18n/subset`; `<UiProvider t={subsetTranslator(props.ui)}>` |
|
|
120
|
+
|
|
121
|
+
The slice carries every plural variant of each key, and a key it lacks renders `⟦key⟧` in the
|
|
122
|
+
island exactly as on the server.
|
|
123
|
+
|
|
124
|
+
**A form's validation messages key on the issue's `rule`**, never on its English `message` — the
|
|
125
|
+
framework's diagnostic text changes without a semver signal, the rule names do not
|
|
126
|
+
(`SCHEMA_RULES`, `@ultimat3/schema`). Ship one key per rule in the slice and pass `params`:
|
|
127
|
+
``messageFor: (issue) => t(`form.${issue.rule}`, issue.params)`` — `form.string.min` → `At least
|
|
128
|
+
{min} characters`. A third-party schema names no rule; fall back to a generic key for those.
|
|
129
|
+
|
|
130
|
+
## Commands
|
|
131
|
+
|
|
132
|
+
| Command | Does |
|
|
133
|
+
|---|---|
|
|
134
|
+
| `x i18n add <locale>` | create `catalogs/<locale>.json` seeded with every default-locale key, each value copied inside `⟦…⟧` |
|
|
135
|
+
| `x i18n sync <locale>` | add keys the locale is missing; a key it already has is never overwritten, translated or not; never deletes |
|
|
136
|
+
| `x i18n check --json` | extract keys from source, diff against every catalog and against `en`, report missing / unused / dynamic / slot mismatch / untranslated |
|
|
137
|
+
|
|
138
|
+
Every command supports `--json`. `add` and `sync` copy the source value inside the marker, so the text sits where the translation goes and `x i18n check` counts the key as missing until a person replaces it. Source means every shape below; a key only a mail or a push names is a key.
|
|
139
|
+
|
|
140
|
+
| Shape in source | Key |
|
|
141
|
+
|---|---|
|
|
142
|
+
| `t('k')`, `$t('k')`, `translate('k')` | first argument |
|
|
143
|
+
| `blocks.heading`, `paragraph`, `button`, `callout`, `detail` | first argument |
|
|
144
|
+
| `titleKey:`, `bodyKey:` | a literal value |
|
|
145
|
+
| `subject:` | a literal value, in a file that names `defineMail` |
|
|
146
|
+
|
|
147
|
+
Any other value of these is listed `dynamic`.
|
|
148
|
+
|
|
149
|
+
`check` reports five separate things, and three of them fail the command:
|
|
150
|
+
|
|
151
|
+
| Reported | Meaning | Exit |
|
|
152
|
+
|---|---|---|
|
|
153
|
+
| `missing` | a key source names, or a key `en` defines (compared by plural stem, so `pl`'s `_few` and `ja`'s lone `_other` answer it), that the locale does not define | non-zero, one `X_CATALOG_MISSING_KEYS` finding per locale |
|
|
154
|
+
| `unused` | a key the locale defines that no shape above names | 0 — informational |
|
|
155
|
+
| `dynamic` | `t(`plans.${plan}.name`)`, a key the static extractor cannot resolve | 0 — informational, with file, line and column |
|
|
156
|
+
| `slot mismatch` | two locales define one key with different interpolation slots — the slot sets are compared by name, so the same slots in another order are not a mismatch | non-zero, one `X_CATALOG_SLOT_MISMATCH` finding per direction |
|
|
157
|
+
| `untranslated` | a locale still carries the reference locale's value — byte-identical to it and longer than 24 characters | non-zero, one `X_CATALOG_UNTRANSLATED` finding per key |
|
|
158
|
+
|
|
159
|
+
The last two are the parity rules, and both compare a locale with the reference locale `en` (`As of 2026-10`). A key `en` defines and another locale lacks is a missing key, not a parity finding, and that holds for a key only reached dynamically (`t(`plans.${plan}.name`)`). A key only another locale defines is `unused` there. A `⟦key⟧` placeholder is a missing key too — never a copy. Every finding names the locale, the catalog key and the file `packages/i18n/catalogs/<locale>.json`; `x verify`'s `i18n` step and `x i18n check` report them from one implementation, so the command and the gate answer about an app with one voice.
|
|
160
|
+
|
|
161
|
+
Before any of the five, `check` resolves `@ultimat3/i18n` from the app root, from every workspace and from the CLI itself. Two real directories is `X_PACKAGE_DUPLICATED`, and every registration gap is then reported as the install's fault with the install's fix (pin one version, `bun install`) — not as a misplaced `defineCatalogs()`: each copy keeps its own registry, so the app registers into one and the check reads the other.
|
|
162
|
+
|
|
163
|
+
A dynamic call contributes its static head (`plans.`) as a runtime-key prefix, so a key only ever reached that way is never listed `unused` — otherwise the list reads as "safe to delete" over live keys. An expression with no static head (a ternary over literals, a bare variable) contributes nothing: a guessed prefix would suppress real gaps.
|
|
164
|
+
|
|
165
|
+
## Locale routing
|
|
166
|
+
|
|
167
|
+
`As of 22.3.0`. The routed locales are `routedLocales()`: the ones `defineCatalogs()` declared, default first. An app that declared none routes its fallback alone — no prefix, no per-locale files.
|
|
168
|
+
|
|
169
|
+
| Path (default `es-co`, also `en`) | Locale | Answer |
|
|
170
|
+
|---|---|---|
|
|
171
|
+
| `/precios` | `es-co` | the `/precios` route |
|
|
172
|
+
| `/en/precios` | `en` | the `/precios` route — the prefix is stripped before the route table is matched. An `isr` page keeps the prefix in its store key and is revalidated by that route's `ttl` and `tags` ([Routes](Routes-And-Render-Modes)) |
|
|
173
|
+
| `/es-co/precios` | — | `301` to `/precios` (`308` for a non-GET); the default is never prefixed |
|
|
174
|
+
| `/fr/precios` | — | `404`: `fr` is not routed, so `/fr/precios` is an ordinary path |
|
|
175
|
+
| `/en/nothing` | `en` | `404`, its error page in English |
|
|
176
|
+
|
|
177
|
+
| Rule | Detail |
|
|
178
|
+
|---|---|
|
|
179
|
+
| Prefix | authoritative on every surface: it beats `?locale=`, the cookie, the user and `Accept-Language`. Stripped in `@ultimat3/http`'s `context` stage, never by middleware — middleware wraps matched routes only |
|
|
180
|
+
| `site/` | the URL alone (`RouteMeta.localeSource: 'path'`). Unprefixed is **always** `defaultLocale` — no `Accept-Language` negotiation, because a CDN serves `/` as one file — and `Vary` drops `accept-language` |
|
|
181
|
+
| `app/` / `api/` | unprefixed keeps `query → cookie → user → header`, configurable via `configureLocales({ order })`. The `user` rung is the actor's saved `locale`, which your `authenticate` hook sets (`userActor({ id, locale, tz })`); `@ultimat3/http` re-resolves once `auth` has run. An explicit choice outranks the browser: `?locale=` is how an email preview link pins a locale, the cookie is what the language switcher writes, and `Accept-Language` is only the fallback when nobody has chosen. An `order` without `'header'` never reads it, and the response drops `accept-language` from `Vary` (`As of 22.10`) — a CDN no longer stores one copy per browser language of a page that cannot differ by it |
|
|
182
|
+
| Static export | `x build --target static` renders each `site/` page once per routed locale, in that locale's context: `index.html` (`lang="es-co"`), `en/index.html` (`lang="en"`), `en/precios/index.html`. Every static host serves `/en/precios` from that layout with no rewrite |
|
|
183
|
+
| Cookie | `x_locale`, written only by an explicit language switcher |
|
|
184
|
+
| Unsupported tag | skipped, not thrown — a stale cookie must never 500 a page. `assertSupportedLocale` throws `X_LOCALE_UNSUPPORTED` where an unknown tag is a caller bug |
|
|
185
|
+
| Direction | `currentDirection()` returns `'rtl'` for `ar`, `he`, `fa`, `ur`, … and is written to `<html dir>` |
|
|
186
|
+
|
|
187
|
+
| API | From | Answers |
|
|
188
|
+
|---|---|---|
|
|
189
|
+
| `routedLocales()` | `@ultimat3/i18n` | the locales a URL may name, default first |
|
|
190
|
+
| `localizedPath(path, locale)` | `@ultimat3/i18n` | `/precios` + `en` → `/en/precios`; the default → `/precios`; `/` + `en` → `/en/`; an existing prefix is re-spelled, never doubled |
|
|
191
|
+
| `splitLocalePrefix(pathname)` / `unlocalizedPath(pathname)` | `@ultimat3/i18n` | the prefix split off, or the bare path |
|
|
192
|
+
| `localizePath(path, locale, locales, defaultLocale)` | `@ultimat3/core` | the same arithmetic over an explicit list — for browser code, which must not import the i18n barrel |
|
|
193
|
+
| `<LocaleSwitcher locales={routedLocales()} path={pathname} />` | `@ultimat3/ui` | links to this page in each locale; `hrefFor` overrides, `defaultLocale` defaults to `locales[0]` |
|
|
194
|
+
|
|
195
|
+
`meta` receives `locale`, `localizedPath(locale)` and `alternates` (`{ locale, path }` per routed locale). The head then carries, with no route code:
|
|
196
|
+
|
|
197
|
+
| Tag | Value |
|
|
198
|
+
|---|---|
|
|
199
|
+
| `<link rel="canonical">`, `og:url` | absolute, against `APP_URL` / `SITE_ORIGIN` / `site.origin` ([Configuration](Configuration)) |
|
|
200
|
+
| `<link rel="alternate" hreflang>` | one per routed locale in BCP 47 region form (`es-co` → `es-CO`), plus `x-default` → the default locale's page. A route's own `alternates` replace the set whole |
|
|
201
|
+
| `og:locale` / `og:locale:alternate` | `es_CO` form — language and region only, so `zh-hant-tw` is `zh_TW`; the document's locale, then every other one |
|
|
202
|
+
|
|
203
|
+
`sitemap.xml` lists every page once per locale, each with the same `xhtml:link` cluster. A single-locale app emits none of the cluster.
|
|
204
|
+
|
|
205
|
+
## Numbers, dates, money
|
|
206
|
+
|
|
207
|
+
Never hand-rolled, never string-formatted in a component.
|
|
208
|
+
|
|
209
|
+
| Value | Route |
|
|
210
|
+
|---|---|
|
|
211
|
+
| Number / percent | `Intl.NumberFormat` with the request locale |
|
|
212
|
+
| Date / time | `Intl.DateTimeFormat` with the request locale **and an explicit IANA `timeZone`** → [Timezones and dates](Timezones-And-Dates) |
|
|
213
|
+
| Currency | `Money = { minor, currency }` formatted at the edge → [Money](Money) |
|
|
214
|
+
|
|
215
|
+
A colour is a token, a string is a key, a date needs a zone, an amount is minor units. Same four rules everywhere.
|
|
216
|
+
|
|
217
|
+
## Errors
|
|
218
|
+
|
|
219
|
+
| Code | Cause | Fix |
|
|
220
|
+
|---|---|---|
|
|
221
|
+
| `X_LOCALE_UNSUPPORTED` | tag is not in the supported set | `x i18n add <locale>` |
|
|
222
|
+
| `X_CATALOG_MISSING_KEYS` | a locale file is missing keys source names or `en` defines (names up to 12 of them) | `x i18n sync <locale>` |
|
|
223
|
+
| `X_CATALOG_INVALID` | non-string leaf, bad key segment, or duplicate flat key | `x i18n check --json` |
|
|
224
|
+
| `X_CATALOG_SLOT_MISMATCH` | two locales define one key with different interpolation slots | `x i18n check --json` — the finding names the locale, the key and the slots that differ |
|
|
225
|
+
| `X_CATALOG_UNTRANSLATED` | a locale still carries the reference locale's value | `x i18n check --json` — the finding names the locale and the key still carrying the reference value |
|
|
226
|
+
| `X_PACKAGE_DUPLICATED` | two installed copies of `@ultimat3/i18n`, each with its own registry | pin one version in the `package.json` the finding names, then `bun install` |
|
|
227
|
+
|
|
228
|
+
Full list: [Error codes](Error-Codes).
|
|
229
|
+
|
|
230
|
+
## Rules
|
|
231
|
+
|
|
232
|
+
- Zero hardcoded user-facing strings. Everything through `t()`.
|
|
233
|
+
- One key per complete sentence. Never concatenate translated fragments.
|
|
234
|
+
- `en` is the reference key set; other locales are diffed against it, never the reverse.
|
|
235
|
+
- A miss is `⟦key⟧` and a red build, never English.
|
|
236
|
+
- `x i18n sync` adds; deletion of a key is a deliberate edit in a PR.
|
|
237
|
+
- Supported locales live in `app.config.ts`; `SUPPORTED_LOCALES` is the framework's own set, which apps narrow or extend.
|
|
238
|
+
|
|
239
|
+
Source: [`packages/i18n/src`](https://github.com/developerz-ai/ultimate/blob/main/packages/i18n/src)
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# Installation
|
|
2
|
+
|
|
3
|
+
## Prerequisites
|
|
4
|
+
|
|
5
|
+
| Requirement | Version | Needed for | Notes |
|
|
6
|
+
|---|---|---|---|
|
|
7
|
+
| Bun | **>= 1.4.4** (`engines.bun`; target 2.0) | everything | the only runtime. No Node, Deno, or workerd support — ever |
|
|
8
|
+
| Postgres | 16+ | prod, parity checks | **optional in dev** — `x dev` embeds one in `.x/pg` |
|
|
9
|
+
| Docker | any | parity checks, prod images | never required for `x dev`; `x build --target docker` needs it |
|
|
10
|
+
| Redis | 7+ | cache tier 3 in prod | dev uses an in-process map behind the same interface |
|
|
11
|
+
| NATS | 2.10+ | fanout in prod | dev runs it in-process |
|
|
12
|
+
| Native toolchain | — | **nothing** | no native addons in the dependency graph; no `node-gyp`, no `sharp`, no `libvips` |
|
|
13
|
+
|
|
14
|
+
Bun below the floor fails before anything else runs:
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
X_BUN_VERSION: bun version is below the framework floor
|
|
18
|
+
cause: Bun 1.4.3 is older than the required 1.4.4
|
|
19
|
+
fix: bun upgrade
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
`x doctor --json` checks the runtime, the embedded Postgres, ports, and the resolved env. Outside an app, `x` is on no `PATH` and `bunx x` fetches an **unrelated** npm package named `x`: spell it `bunx @ultimat3/cli doctor --json` there, and start an app with `bunx create-ultimate myapp`.
|
|
23
|
+
|
|
24
|
+
| Platform | Status | Proved by |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| Linux x64 | supported | every CI job; `scaffold-smoke` gates a fresh scaffold twice per shape |
|
|
27
|
+
| macOS | supported | the `macos` CI job: a fresh scaffold's `bun run setup` and its gate on `macos-latest` |
|
|
28
|
+
| Windows, native (PowerShell, cmd) | supported | the `windows` CI job: lint, typecheck, a path-heavy unit subset and a scaffold's gate, in pwsh |
|
|
29
|
+
|
|
30
|
+
| Without Docker | Works |
|
|
31
|
+
|---|---|
|
|
32
|
+
| `bun run dev`, `bun run setup`, `bun run verify` (the gate) | yes — the database is embedded and no service is dialled ([Bare VM](Bare-VM)) |
|
|
33
|
+
| a deploy | takes an image (`x build --target docker` needs Docker, or a host that builds the Dockerfile — [Deploy recipes](Deploy-Recipes)), or `x build --target binary` with no container runtime |
|
|
34
|
+
|
|
35
|
+
## Create an app
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
bunx create-ultimate myapp && cd myapp && bun run setup && bun run dev
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`create-ultimate` is a thin front for `x new`. Inside an existing workspace, use `x new` directly.
|
|
42
|
+
|
|
43
|
+
**PowerShell and cmd run the same commands.** `bun run setup` runs the app's `bin/setup.ts` —
|
|
44
|
+
TypeScript that Bun executes, no bash and no WSL — and `bun run dev` and `bun run verify` are
|
|
45
|
+
`x dev` and `x verify`. Windows PowerShell 5.1 has no `&&`: type the commands one per line there
|
|
46
|
+
(PowerShell 7 and cmd take the line as written).
|
|
47
|
+
|
|
48
|
+
**`bun run setup` is not optional.** `x new` writes files and installs nothing, so `cd myapp && x dev`
|
|
49
|
+
stops on `X_BUILD_FAILED` naming `bun install` (measured `As of 2026-08-23`; this page said
|
|
50
|
+
otherwise until then). `bun run setup` is six steps, idempotent and safe to re-run:
|
|
51
|
+
|
|
52
|
+
| # | Step | Why it is in the script |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| 1 | `bun install` | `x new` writes files and installs nothing |
|
|
55
|
+
| 2 | touch `.env.development.local` | per-box secrets, gitignored, wins over the committed `.env.development` |
|
|
56
|
+
| 3 | `x db gen "initial"` | skipped when `packages/db/migrations` already holds a `.sql` — `x db gen` is that directory's only writer |
|
|
57
|
+
| 4 | `x db migrate` | applies them, then diffs the live schema against the ledger |
|
|
58
|
+
| 5 | `x db seed` | run through the CLI, never `bun run`: the CLI owns the connection, so it reaches the same embedded database the migration just wrote to |
|
|
59
|
+
| 6 | `x manifest` | `x.manifest.json` is a projection of the loaded app, so `x new` cannot write it. Nothing else ever ran the command, and `x verify`'s `manifest` step now refuses its absence with `X_MANIFEST_MISSING` |
|
|
60
|
+
|
|
61
|
+
Every flag, from the registry — `bun run x -- help new`, or `x help new` in an app. There are six,
|
|
62
|
+
each with a default, so **`x new` asks nothing**: an agent cannot answer a prompt and is never given
|
|
63
|
+
one.
|
|
64
|
+
|
|
65
|
+
| Flag | Default | Effect |
|
|
66
|
+
|---|---|---|
|
|
67
|
+
| `--dir <path>` | cwd | parent directory the app is written into |
|
|
68
|
+
| `--example` / `--no-example` | `--example` | include the example feature slice; `--no-example` gives the same shape with an empty `app/` |
|
|
69
|
+
| `--shape <shape>` | none | start from `api`, `blog` or `dashboard` instead of the dashboard and example slice — what each writes and leaves out: [Shapes](Shapes) |
|
|
70
|
+
| `--git` / `--no-git` | `--git` | `git init` and commit the scaffold |
|
|
71
|
+
| `--dry-run` | off | print the file list, write nothing |
|
|
72
|
+
| `--force` | off | write into a directory that already exists |
|
|
73
|
+
| `--json` | off | machine-readable result: the directory, every path written, the git outcome |
|
|
74
|
+
|
|
75
|
+
There is **no** `--admin`, `--locale`, `--tz`, `--currency`, `--no-install` or `--yes` — this table
|
|
76
|
+
listed all six until 2026-08-23 and the parser refuses each with `X_CLI_BAD_FLAG`. `apps/admin/` is
|
|
77
|
+
always written; locale, timezone and currency are `app.config.ts` fields you edit after scaffolding
|
|
78
|
+
([Configuration](Configuration)).
|
|
79
|
+
|
|
80
|
+
What `x new` writes, and who owns it afterwards:
|
|
81
|
+
|
|
82
|
+
| Artifact | Author | Rule |
|
|
83
|
+
|---|---|---|
|
|
84
|
+
| `.env.development` | generated, **complete and valid**, committed | non-secret defaults; per-box secrets go in `.env.development.local`, which wins. Dev secrets carry a loud `dev-only-` prefix. The scaffold writes no `.env` — Bun still loads `.env`, `.env.<mode>` and `.env.local` when they exist ([`packages/core/src/env-example.ts:31`](https://github.com/developerz-ai/ultimate/blob/main/packages/core/src/env-example.ts)), and `.env` is the one you never commit |
|
|
85
|
+
| `.env.example` | generated from `envSchema`, plus the framework's deploy-required secrets | never hand-edited — it is a projection of the declaration, and drift fails `x verify`. Regenerate with `x env example` |
|
|
86
|
+
| `app.config.ts` | yours | the one config file; validated at boot ([Configuration](Configuration)) |
|
|
87
|
+
| `x.manifest.json` | generated every build | routes, entities, actions, jobs, policies, tags, MCP tools, budgets. Never hand-edited; drift fails `x verify` |
|
|
88
|
+
| `openapi.json` | generated | HTTP surface from `action` / `query` declarations |
|
|
89
|
+
| `CLAUDE.md`, and `AGENTS.md` a symlink to it | **human-authored stub**, one file | short, terse; a copy only where Windows refuses the link. Ultimate never generates prose docs at runtime |
|
|
90
|
+
| `.github/workflows/ci.yml` | generated | `bun run setup` then `bun run verify`, on push and pull request — yours to edit, and the same two commands the app's `README.md` opens with |
|
|
91
|
+
| `docker/` | generated | dev compose + per-role prod compose + Dockerfile ([Deployment](Deployment)) |
|
|
92
|
+
| `packages/db/migrations/` | **not written at all** | `x db gen` is its only writer, `As of 2026-08`. First commands in a new app: `x db gen "initial"`, then `x db migrate` — `bun run setup` runs both. Until the first one has, `x verify`'s `drift` step is red and names it, for any app declaring an entity; `--no-example` declares none, and zero against zero is agreement |
|
|
93
|
+
|
|
94
|
+
## Typed env, validated at boot
|
|
95
|
+
|
|
96
|
+
Declared at module scope in `app.config.ts`, next to `defineConfig`. There is no `env.ts` — `app.config.ts` is the one file the CLI and the runtime both load, so it is the one place the env gate can run before anything binds.
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
// app.config.ts
|
|
100
|
+
import { defineConfig, defineEnv } from '@ultimat3/core';
|
|
101
|
+
|
|
102
|
+
export const env = defineEnv({
|
|
103
|
+
DATABASE_URL: { type: 'url' },
|
|
104
|
+
NATS_URL: { type: 'url', required: false },
|
|
105
|
+
S3_BUCKET: { type: 'string' },
|
|
106
|
+
VAPID: { type: 'string', required: false },
|
|
107
|
+
STRIPE_KEY: { type: 'string', pattern: /^sk_/, secret: true },
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
export const config = defineConfig({ name: 'myapp' /* … */ });
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Boot parses this before any listener binds. Failure costs ~40ms and exit 1 — not a 3am `undefined` in a payment handler.
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
X_ENV_MISSING: required environment variables are missing or invalid
|
|
117
|
+
cause: STRIPE_KEY missing; NATS_URL is not a URL ("nats:4222")
|
|
118
|
+
fix: add STRIPE_KEY, NATS_URL to .env (copy .env.example), then run: x env check
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
The `fix:` is per-key and built from your own declarations ([`packages/core/src/env.ts:237`](https://github.com/developerz-ai/ultimate/blob/main/packages/core/src/env.ts)); `defineEnv({ KEY: { fix } })` overrides it for one key.
|
|
122
|
+
|
|
123
|
+
| Property | Behavior |
|
|
124
|
+
|---|---|
|
|
125
|
+
| Access | `env.STRIPE_KEY` is typed. A `process.env` read outside the schema is a lint error |
|
|
126
|
+
| Per-role | a role requires only the keys it uses — `ROLE=worker` does not fail on a missing `VAPID` |
|
|
127
|
+
| Repair | `x env example` regenerates `.env.example` from the schema; copy the keys it names into `.env.development.local` in dev, or into your platform's secret store in production. There is **no** `x env --fix` — `x env` declares `check` and `example` and no flags, so `x env check --fix` dies at the parser with `X_CLI_BAD_FLAG` |
|
|
128
|
+
| CI | `x verify`'s `manifest` step holds `.env.example` to the schema's projection (`X_ENV_EXAMPLE_DRIFT`); `x env check` is not a gate step — run it against the deploy's own environment (`ULTIMATE_ENV=production x env check`), where it also reports the framework's deploy-required secrets |
|
|
129
|
+
| Provenance | `/_x` → **Env** shows every resolved key and which source it came from |
|
|
130
|
+
|
|
131
|
+
## What Bun natives replace
|
|
132
|
+
|
|
133
|
+
Each row is a dependency subtree that never enters the lockfile. Target: **under 40 direct dependencies for the whole framework**.
|
|
134
|
+
|
|
135
|
+
| Bun primitive | Replaces | Deps killed (approx) |
|
|
136
|
+
|---|---|---|
|
|
137
|
+
| `Bun.sql` | `pg`, `pg-pool`, `pg-connection-string`, `postgres`, pool wrappers | ~8 |
|
|
138
|
+
| `Bun.redis` | `ioredis` / `redis` + command/parser packages | ~5 |
|
|
139
|
+
| `Bun.s3` | `@aws-sdk/client-s3`, `@aws-sdk/s3-request-presigner` + AWS SDK core | ~25 |
|
|
140
|
+
| `Bun.serve` WebSockets | `ws`, `socket.io`, `engine.io`, `uWebSockets.js` | ~10 |
|
|
141
|
+
| `bun test` | `vitest` / `jest`, `@types/jest`, coverage + mock + snapshot plugins | ~30 |
|
|
142
|
+
| `Bun.build` | `esbuild` / `rollup` / `vite` + framework plugin + postcss chain | ~40 |
|
|
143
|
+
| `Bun.Transpiler` / macros | `ts-node`, `tsx`, `swc`, babel presets | ~15 |
|
|
144
|
+
| `Bun.password` | `bcrypt` / `argon2` native addons | ~4 |
|
|
145
|
+
| `Bun.file` / `Bun.write` | `fs-extra`, `graceful-fs`, `globby` | ~6 |
|
|
146
|
+
| `bun --hot` | `nodemon`, `concurrently`, HMR middleware | ~5 |
|
|
147
|
+
|
|
148
|
+
One more subtree dies with no Bun native behind it — a pure-TypeScript framework pipeline, written because native addons are blocked:
|
|
149
|
+
|
|
150
|
+
| Framework primitive | Replaces | Deps killed (approx) |
|
|
151
|
+
|---|---|---|
|
|
152
|
+
| `@ultimat3/core` image | `sharp` + libvips + `imagemin` plugins | ~12 |
|
|
153
|
+
|
|
154
|
+
Stated cost: no native-addon packages, and Bun's long-running-process maturity is less proven than Node's.
|
|
155
|
+
|
|
156
|
+
## Editor and agent setup
|
|
157
|
+
|
|
158
|
+
Two MCP servers, and an app ships both. The **dev tools** are `x mcp serve` — `x dev` does not
|
|
159
|
+
start them, and nothing listens on 9229 until this command runs. A new app's `.mcp.json` already
|
|
160
|
+
names the stdio form, as `bunx @ultimat3/cli mcp serve --transport stdio`, so a client that reads
|
|
161
|
+
that file starts it with no setup; [MCP and AI](MCP-And-AI#client-config) says what to give one that does not.
|
|
162
|
+
The **app's own tools** are `POST /mcp` on the app (`apps/web/mcp.ts`), mounted by `x dev` and the
|
|
163
|
+
web role, refusing any request without a bearer token. The HTTP form below prints its token:
|
|
164
|
+
|
|
165
|
+
```
|
|
166
|
+
bunx x mcp serve --transport stdio # a client that spawns the server
|
|
167
|
+
bunx x mcp serve --transport http --port 9229 # POST /mcp; the bearer token is printed at boot
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
`stdio` and `http` are the two transports (`x help mcp`) — there is no `ws` transport, and this page
|
|
171
|
+
told readers to configure one until 2026-08-23. Register the URL and the printed token with whatever
|
|
172
|
+
MCP client you use; a request without the token is answered 401 with no catalog.
|
|
173
|
+
|
|
174
|
+
The catalog is `bunx x mcp tools --json`: every tool's exact name and description, read off the
|
|
175
|
+
server itself rather than counted here. The core of it:
|
|
176
|
+
|
|
177
|
+
| Tool | Replaces the agent's usual guess |
|
|
178
|
+
|---|---|
|
|
179
|
+
| `routes.list` | grepping a router directory |
|
|
180
|
+
| `schema.describe` | reading migration files in order |
|
|
181
|
+
| `policies.list` | "is this endpoint protected?" |
|
|
182
|
+
| `actions.describe` | reading `api/` by hand — actions and queries in one call |
|
|
183
|
+
| `jobs.inspect` | reading `jobs.ts` for retry policy and step names |
|
|
184
|
+
| `queue.depth` | guessing whether the worker is keeping up |
|
|
185
|
+
| `manifest.read` | ten separate reads |
|
|
186
|
+
| `errors.explain` | a web search for an error string |
|
|
187
|
+
| `db.query` | inventing a query and hoping (read-only; 100-row default, 1000-row maximum) |
|
|
188
|
+
| `db.migrate` | mutating the dev DB — writes land in a **branch DB only** |
|
|
189
|
+
| `tests.run` | parsing terminal output |
|
|
190
|
+
| `verify.run` | guessing whether the work is shippable |
|
|
191
|
+
| `logs.tail` | scrollback archaeology |
|
|
192
|
+
|
|
193
|
+
Read tools are unrestricted in dev; write tools are scoped to branch environments. The dev server is never exposed under `ROLE=web`. `db.query` refuses a batch, a write keyword anywhere at statement level (a data-modifying CTE included), a locking clause, `EXPLAIN ANALYZE`, and the banned function families matched by prefix (`pg_read_*`, `pg_ls_*`, `lo_*`, `pg_advisory_*`, `pg_sleep*`, `set_config`, …) — `X_MCP_QUERY_REJECTED`, before the host sees the string. Use `x mcp` for a standalone server (CI, remote agents). Editor config: Biome is the only formatter/linter — one binary, one config, no ESLint or Prettier.
|
|
194
|
+
|
|
195
|
+
## Upgrading and removal
|
|
196
|
+
|
|
197
|
+
| Task | Command |
|
|
198
|
+
|---|---|
|
|
199
|
+
| Framework version bump | `x upgrade --json`, one major per run ([CLI reference](CLI-Reference#x-upgrade)). An app below 30.0.0 has no such command yet: `bunx @ultimat3/cli@latest upgrade --json` |
|
|
200
|
+
| Check what a bump would change | `x upgrade --dry-run --json`: the pins, the Bun floor edits and the section to read. Writes nothing, runs no gate |
|
|
201
|
+
| Remove dev state (embedded PG, storage, caches) | `rm -rf .x` |
|
|
202
|
+
| Remove the CLI | it ships with the app; deleting the repo is the uninstall |
|
|
203
|
+
|
|
204
|
+
Framework ranges: an app declares every `@ultimat3/*` package at `^<version>`, as `x new` writes it, and `bun.lock` pins the exact build. Keep them on one version: the packages move in lockstep, and `x upgrade` moves them together. A third-party wrapper declares them as `peerDependencies` at `^<major>` ([Publish it](Building-Your-Own-Base#publish-it)).
|
|
205
|
+
|
|
206
|
+
Version pinning: `As of 2026-11`, the Bun floor is the one stated at the top of this page and 2.0 the target; SolidJS 2 is in beta and is pinned exactly, and its upgrade is framework work, not app work. There is no ArkType or Drizzle pin to carry — `@ultimat3/schema` ships its own dependency-free validators and `@ultimat3/entity` its own `postgresDriver()`. Details and codemod inventory in [Upgrading](Upgrading). Boot failures and port conflicts in [Troubleshooting](Troubleshooting).
|