@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/src/style-routes.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// image.
|
|
5
5
|
|
|
6
6
|
import type { Route, UltimateRequest } from '@ultimat3/http';
|
|
7
|
-
import { applyCacheHeaders, jsonResponse, NO_STORE } from '@ultimat3/http';
|
|
7
|
+
import { applyCacheHeaders, jsonResponse, NO_STORE, textResponse } from '@ultimat3/http';
|
|
8
8
|
import type { StyleBundle } from './style-bundle';
|
|
9
9
|
import { STYLE_BASE_PATH } from './style-bundle';
|
|
10
10
|
|
|
@@ -49,7 +49,8 @@ export function styleRoutes(source: StyleSource): readonly Route[] {
|
|
|
49
49
|
return applyCacheHeaders(missing, NO_STORE);
|
|
50
50
|
}
|
|
51
51
|
return applyCacheHeaders(
|
|
52
|
-
|
|
52
|
+
// `textResponse` for the length it declares: `compress` reads nothing undeclared.
|
|
53
|
+
textResponse(chunk.css, { headers: { 'content-type': 'text/css; charset=utf-8' } }),
|
|
53
54
|
{ mode: 'immutable' },
|
|
54
55
|
);
|
|
55
56
|
},
|
package/src/sw-precache-plan.ts
CHANGED
|
@@ -49,7 +49,7 @@ const spell = (path: string, locale: string, locales: RoutedLocales): string =>
|
|
|
49
49
|
* The route table, as the service worker sees it — once per routed locale. The server strips a
|
|
50
50
|
* `/en/` prefix before it matches (`@ultimat3/http`'s `locale-prefix.ts`), and the worker matches
|
|
51
51
|
* pathnames: with one spelling per route, no `/en/…` URL had a rule, so the English site was never
|
|
52
|
-
* cached and never offline (
|
|
52
|
+
* cached and never offline (one adopter, 22.3.2). Only the fields a browser can act on cross,
|
|
53
53
|
* plus — for a spelling this build rendered — the content hash and byte count of its document.
|
|
54
54
|
*/
|
|
55
55
|
export function pwaRoutes(
|
|
@@ -106,7 +106,7 @@ export interface PrecacheAssetInput {
|
|
|
106
106
|
/**
|
|
107
107
|
* The island chunks a PRECACHED page boots, every surface stylesheet and every framework script.
|
|
108
108
|
* Every island in the app was precached, so a first anonymous visit to a marketing page downloaded
|
|
109
|
-
* the admin, KYC and payment islands too — 43 chunks, ~1 MB on
|
|
109
|
+
* the admin, KYC and payment islands too — 43 chunks, ~1 MB on one adopter (22.3.2). The rest are
|
|
110
110
|
* cached the first time a page asks for one (`ISLAND_BASE_PATH` is a runtime asset prefix).
|
|
111
111
|
*
|
|
112
112
|
* Two readers of "which islands", because only one of them exists per build: the static export
|
package/src/sw-routes.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// both artifacts as files and mounts nothing, and it must not have to import a route table to do it.
|
|
4
4
|
|
|
5
5
|
import type { CacheHint, Route, UltimateRequest } from '@ultimat3/http';
|
|
6
|
-
import { applyCacheHeaders } from '@ultimat3/http';
|
|
6
|
+
import { applyCacheHeaders, textResponse } from '@ultimat3/http';
|
|
7
7
|
import { revalidatedResponse } from './revalidated-response';
|
|
8
8
|
import type { ServiceWorkerArtifacts } from './sw-artifacts';
|
|
9
9
|
import { SERVICE_WORKER_PATH, SW_REGISTER_PATH, SW_SCOPE } from './sw-artifacts';
|
|
@@ -24,7 +24,7 @@ export const serviceWorkerRoutes = (artifacts: ServiceWorkerArtifacts): readonly
|
|
|
24
24
|
meta: { name: 'pwa.serviceWorker', auth: 'public' },
|
|
25
25
|
handler: () =>
|
|
26
26
|
applyCacheHeaders(
|
|
27
|
-
|
|
27
|
+
textResponse(artifacts.source, {
|
|
28
28
|
headers: {
|
|
29
29
|
'content-type': 'text/javascript; charset=utf-8',
|
|
30
30
|
// Without it the browser refuses to let a worker served from `/` control `/`, which
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
// The tests `x g action` and `x g mutator` write beside the declaration: a `unit` file for its shape
|
|
2
|
+
// and handler, a `contract` file for its projections. Split from `action.ts` because the gate types
|
|
3
|
+
// a test by its filename, and the two together passed that file's ceiling.
|
|
4
|
+
|
|
5
|
+
import { sortedImports } from './imports';
|
|
6
|
+
import type { NameSet } from './naming';
|
|
7
|
+
|
|
8
|
+
/** The code the slice's `errors.ts` declares for a missing row. */
|
|
9
|
+
const notFoundCode = (feature: NameSet): string =>
|
|
10
|
+
`X_${feature.kebab.toUpperCase().split('-').join('_')}_NOT_FOUND`;
|
|
11
|
+
|
|
12
|
+
const ID = '00000000-0000-4000-8000-000000000001';
|
|
13
|
+
const ORG = '00000000-0000-4000-8000-000000000002';
|
|
14
|
+
const OTHER_ORG = '00000000-0000-4000-8000-000000000009';
|
|
15
|
+
|
|
16
|
+
/** The declaration-shape assertions that differ between the two primitives. */
|
|
17
|
+
const shapeTest = (name: NameSet, feature: NameSet, isMutator: boolean): string =>
|
|
18
|
+
isMutator
|
|
19
|
+
? `unitTest('${name.camel} projects both halves and a conflict strategy', () => {
|
|
20
|
+
// The projected names mirror the declaration: local() optimistic, server() authoritative.
|
|
21
|
+
// server() routes through the same invoke() core as every other surface, so it cannot skip
|
|
22
|
+
// the input parse, the policy or the output parse.
|
|
23
|
+
expect(target.describeMutator().kind).toBe('mutator');
|
|
24
|
+
expect(target.conflict).toBe('server-wins');
|
|
25
|
+
expect(typeof target.local).toBe('function');
|
|
26
|
+
expect(typeof target.server).toBe('function');
|
|
27
|
+
});`
|
|
28
|
+
: `unitTest('${name.camel} is a declared action that invalidates its slice', () => {
|
|
29
|
+
expect(target.kind).toBe('action');
|
|
30
|
+
expect(target.describe().name).toBe('${name.camel}');
|
|
31
|
+
// Without \`cache: { invalidates }\` every cached read of this slice serves the row as it was.
|
|
32
|
+
expect(target.describe().invalidates).toContain(${feature.camel}Tag.entity);
|
|
33
|
+
});`;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The contract test's preamble. Everything but the target rides only where it is USED: the
|
|
37
|
+
* scaffolded app lints with `noUnusedVariables: error`, so an actor or an input the file never
|
|
38
|
+
* reads would be a lint failure in code nobody typed — which is what a hand-written policy, whose
|
|
39
|
+
* tenancy this generator asserts nothing about, was handed.
|
|
40
|
+
*/
|
|
41
|
+
const preamble = (
|
|
42
|
+
name: NameSet,
|
|
43
|
+
feature: NameSet,
|
|
44
|
+
isMutator: boolean,
|
|
45
|
+
wrappers: string,
|
|
46
|
+
outsider: boolean,
|
|
47
|
+
/** `testActor` is imported for the outsider, or for a contract suite's exercise actor. */
|
|
48
|
+
importsActor = outsider,
|
|
49
|
+
): string => `${importsActor ? "import { testActor } from '@ultimat3/policy';\n" : ''}import { ${wrappers} } from '@ultimat3/testing';
|
|
50
|
+
import { ${name.camel} } from './${name.kebab}';
|
|
51
|
+
${
|
|
52
|
+
outsider
|
|
53
|
+
? `
|
|
54
|
+
const id = '${ID}';
|
|
55
|
+
const orgId = '${ORG}';
|
|
56
|
+
const input = { id, orgId${isMutator ? ", title: 'a title'" : ''} };
|
|
57
|
+
`
|
|
58
|
+
: ''
|
|
59
|
+
}
|
|
60
|
+
// Named here because every projection needs a stable name and this file does not boot the app.
|
|
61
|
+
// At boot \`registerActions(await import('./actions'))\` stamps the same name onto the same
|
|
62
|
+
// object, so each projection of it works there with nothing to remember.
|
|
63
|
+
const target = ${name.camel}.named('${name.camel}');
|
|
64
|
+
${
|
|
65
|
+
outsider
|
|
66
|
+
? `
|
|
67
|
+
// Holds the grant, wrong org. That is the interesting actor: a denial here is the predicate
|
|
68
|
+
// deciding, not the permission check, so this test fails if the tenancy rule is ever dropped.
|
|
69
|
+
const outsider = testActor('outsider', {
|
|
70
|
+
orgId: '${OTHER_ORG}',
|
|
71
|
+
permissions: ['${feature.kebab}:write'],
|
|
72
|
+
}).actor;
|
|
73
|
+
`
|
|
74
|
+
: ''
|
|
75
|
+
}`;
|
|
76
|
+
|
|
77
|
+
/** What the unit test may do with the slice the declaration lives in. */
|
|
78
|
+
export interface HandlerShape {
|
|
79
|
+
readonly isMutator: boolean;
|
|
80
|
+
/** The handler reads a row by id and throws `<Feature>NotFoundError` when there is none. */
|
|
81
|
+
readonly lookup: boolean;
|
|
82
|
+
/** The slice still takes the row `x g entity` scaffolds, so the test may store one. */
|
|
83
|
+
readonly storesRow: boolean;
|
|
84
|
+
readonly dbModule: string;
|
|
85
|
+
/**
|
|
86
|
+
* The permission the writer is granted — `<feature>:write` for the policy this generator wrote,
|
|
87
|
+
* the one literal `can('<perm>')` of a handwritten one, or `undefined` when that policy's grant
|
|
88
|
+
* cannot be read, and then no assertion depends on it.
|
|
89
|
+
*/
|
|
90
|
+
readonly grant: string | undefined;
|
|
91
|
+
/** The input and the writer name an org: the slice is tenant-scoped, or its policy reads one. */
|
|
92
|
+
readonly org: boolean;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* The optimistic half, run against the client store `@ultimat3/testing` ships. A twin is replayed
|
|
97
|
+
* on every rebase, so the two facts worth failing on are the row one application leaves and that a
|
|
98
|
+
* second leaves the same one — and that a row the store never held is not invented.
|
|
99
|
+
*/
|
|
100
|
+
const localTwinTests = (): string => `
|
|
101
|
+
unitTest('the local twin marks the row pending, and a replay lands in the same place', () => {
|
|
102
|
+
const store = memoryLocalTx({ [TABLE]: { [id]: { id, title: 'before', pending: false } } });
|
|
103
|
+
target.local(store.tx, input);
|
|
104
|
+
const once = store.rows(TABLE);
|
|
105
|
+
expect(once).toEqual({ [id]: { id, title: 'a title', pending: true } });
|
|
106
|
+
// A rebase replays the queued write: twice through must equal once through.
|
|
107
|
+
target.local(store.tx, input);
|
|
108
|
+
expect(store.rows(TABLE)).toEqual(once);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
unitTest('the local twin invents no row the store never held', () => {
|
|
112
|
+
const store = memoryLocalTx();
|
|
113
|
+
target.local(store.tx, input);
|
|
114
|
+
expect(store.rows(TABLE)).toEqual({});
|
|
115
|
+
});
|
|
116
|
+
`;
|
|
117
|
+
|
|
118
|
+
/** The authoritative half's cases, refusal first, in the shape the slice earns. */
|
|
119
|
+
const handlerTests = (feature: NameSet, shape: HandlerShape): string => {
|
|
120
|
+
if (shape.grant === undefined) {
|
|
121
|
+
return `
|
|
122
|
+
// No handler assertion: ../policy.ts is hand-written and its grant is not one literal can('…'),
|
|
123
|
+
// so which actor reaches the handler is the author's to state — add that test here.
|
|
124
|
+
`;
|
|
125
|
+
}
|
|
126
|
+
const answer = shape.isMutator ? "{ id, title: 'a title' }" : '{ id }';
|
|
127
|
+
if (!shape.lookup) {
|
|
128
|
+
return `
|
|
129
|
+
unitTest('an actor without the grant is refused before the handler runs', async () => {
|
|
130
|
+
const stranger = testActor('stranger'${shape.org ? ', { orgId }' : ''}).actor;
|
|
131
|
+
const refused = await target.as(stranger, input).catch((error: unknown) => error);
|
|
132
|
+
expect(refused).toBeUltimateError('X_FORBIDDEN');
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
unitTest('the handler answers a writer${shape.org ? ' in the org' : ''}', async () => {
|
|
136
|
+
expect(await target.as(writer, input)).toEqual(${answer});
|
|
137
|
+
});
|
|
138
|
+
`;
|
|
139
|
+
}
|
|
140
|
+
const found = shape.isMutator
|
|
141
|
+
? "{ id: row.id, title: 'a title' }"
|
|
142
|
+
: "{ id: row.id, title: 'kept' }";
|
|
143
|
+
const call = shape.isMutator
|
|
144
|
+
? "{ id: row.id, orgId, title: 'a title' }"
|
|
145
|
+
: '{ id: row.id, orgId }';
|
|
146
|
+
return `
|
|
147
|
+
unitTest('the handler refuses an id nothing holds', async () => {
|
|
148
|
+
const refused = await target.as(writer, input).catch((error: unknown) => error);
|
|
149
|
+
expect(refused).toBeUltimateError('${notFoundCode(feature)}');
|
|
150
|
+
});
|
|
151
|
+
${
|
|
152
|
+
shape.storesRow
|
|
153
|
+
? `
|
|
154
|
+
unitTest('the handler answers the row it found', async () => {
|
|
155
|
+
const draft = { orgId, title: 'kept', price: { minor: 0, currency: 'USD' } };
|
|
156
|
+
const row = await runWithContext(ctxOf({ actor: writer }), () => repo.insert(draft));
|
|
157
|
+
const answer = await target.as(writer, ${call});
|
|
158
|
+
expect(answer).toEqual(${found});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
unitTest('the handler reads its row in one statement', async ({ statements }) => {
|
|
162
|
+
const draft = { orgId, title: 'kept', price: { minor: 0, currency: 'USD' } };
|
|
163
|
+
const row = await runWithContext(ctxOf({ actor: writer }), () => repo.insert(draft));
|
|
164
|
+
const before = statements.count();
|
|
165
|
+
await target.as(writer, ${call});
|
|
166
|
+
// Its own lookup and nothing else, on this driver as on Postgres: a read added per row is red.
|
|
167
|
+
expect(statements.count() - before).toBe(1);
|
|
168
|
+
});
|
|
169
|
+
`
|
|
170
|
+
: ''
|
|
171
|
+
}`;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* The unit test: the declaration's shape, the input it refuses, and the HANDLER — run through
|
|
176
|
+
* `.as()`, the one execution path, against the in-memory driver. A mutator's local twin runs
|
|
177
|
+
* against `memoryLocalTx`. The contract projections are next door, in their own suite.
|
|
178
|
+
*
|
|
179
|
+
* A row is stored only in a slice whose row this generator can spell (`sliceTakesScaffoldRow`): a
|
|
180
|
+
* slice an author reshaped has its own columns, and a test that inserted `{ title, price }` into
|
|
181
|
+
* it would be a guess about someone else's table.
|
|
182
|
+
*/
|
|
183
|
+
export const actionUnitTest = (name: NameSet, feature: NameSet, shape: HandlerShape): string => {
|
|
184
|
+
const known = shape.grant !== undefined;
|
|
185
|
+
const stores = known && shape.lookup && shape.storesRow;
|
|
186
|
+
const testing = [
|
|
187
|
+
...(stores ? ['afterEach'] : []),
|
|
188
|
+
'expect',
|
|
189
|
+
...(shape.isMutator ? ['memoryLocalTx'] : []),
|
|
190
|
+
'unitTest',
|
|
191
|
+
].join(', ');
|
|
192
|
+
return `// ${name.camel}: its declared shape, the input it refuses, and what its handler answers — in
|
|
193
|
+
// process, so all three belong to the \`unit\` step. The contract projections are next door.
|
|
194
|
+
${sortedImports([
|
|
195
|
+
...(stores ? ["import { ctxOf, runWithContext } from '@ultimat3/core';"] : []),
|
|
196
|
+
...(known ? ["import { testActor } from '@ultimat3/policy';"] : []),
|
|
197
|
+
`import { ${testing} } from '@ultimat3/testing';`,
|
|
198
|
+
...(stores ? [`import { driver } from '${shape.dbModule}';`] : []),
|
|
199
|
+
])}${shape.isMutator ? '' : `\nimport { ${feature.camel}Tag } from '../policy';`}${stores ? "\nimport * as repo from '../repo';" : ''}
|
|
200
|
+
import { ${name.camel} } from './${name.kebab}';
|
|
201
|
+
|
|
202
|
+
const id = '${ID}';
|
|
203
|
+
${shape.org ? `const orgId = '${ORG}';\n` : ''}const input = { id${shape.org ? ', orgId' : ''}${shape.isMutator ? ", title: 'a title'" : ''} };
|
|
204
|
+
${shape.isMutator ? `\n// The client store's table: the entity's own, so the twin and the server row share one key.\nconst TABLE = '${feature.table}';\n` : ''}
|
|
205
|
+
// Named here because every projection needs a stable name and this file does not boot the app.
|
|
206
|
+
// At boot \`registerActions(await import('./actions'))\` stamps the same name onto the same
|
|
207
|
+
// object, so each projection of it works there with nothing to remember.
|
|
208
|
+
const target = ${name.camel}.named('${name.camel}');
|
|
209
|
+
${
|
|
210
|
+
known
|
|
211
|
+
? `
|
|
212
|
+
// Holds the grant${shape.org ? ' in the org the input names' : ''}: past the policy, so what answers is the handler.
|
|
213
|
+
const grant = ['${shape.grant}'];
|
|
214
|
+
const writer = testActor('writer', { ${shape.org ? 'orgId, ' : ''}permissions: grant }).actor;
|
|
215
|
+
`
|
|
216
|
+
: ''
|
|
217
|
+
}${
|
|
218
|
+
stores
|
|
219
|
+
? `
|
|
220
|
+
// One store per process: without this, one test's rows are the next test's fixtures.
|
|
221
|
+
afterEach(() => {
|
|
222
|
+
driver.reset?.();
|
|
223
|
+
});
|
|
224
|
+
`
|
|
225
|
+
: ''
|
|
226
|
+
}
|
|
227
|
+
${shapeTest(name, feature, shape.isMutator)}
|
|
228
|
+
|
|
229
|
+
unitTest('${name.camel} rejects input that is not a uuid', async () => {
|
|
230
|
+
await expect(target.input).toRejectInput({ ...input, id: 'not-a-uuid' });
|
|
231
|
+
await expect(target.input).toAcceptInput(input);
|
|
232
|
+
});
|
|
233
|
+
${shape.isMutator ? localTwinTests() : ''}${handlerTests(feature, shape)}`;
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Who the generated-input runs act as: an actor holding the slice's grant — `<feature>:write` for
|
|
238
|
+
* the policy this generator wrote, the one literal `can('<perm>')` of a handwritten one — in the
|
|
239
|
+
* input's org when the rule compares orgs. A grant that cannot be read is the author's to name,
|
|
240
|
+
* so those runs are skipped out loud instead.
|
|
241
|
+
*/
|
|
242
|
+
const exerciseActor = (grant: string | undefined, orgRule: boolean): string =>
|
|
243
|
+
grant === undefined
|
|
244
|
+
? ''
|
|
245
|
+
: `
|
|
246
|
+
// Holds the grant${orgRule ? ', in the org every generated input is bound to' : ''}: the actor the handler runs as when \`.contract()\`
|
|
247
|
+
// calls it with generated legal input.
|
|
248
|
+
const member = testActor('member', {${orgRule ? '\n orgId,' : ''}
|
|
249
|
+
permissions: ['${grant}'],
|
|
250
|
+
}).actor;
|
|
251
|
+
`;
|
|
252
|
+
|
|
253
|
+
/** The `.contract()` argument: the actor and, under an org rule, the org every draw is bound to. */
|
|
254
|
+
const exerciseOption = (grant: string | undefined, orgRule: boolean): string => {
|
|
255
|
+
if (grant === undefined) {
|
|
256
|
+
return "{\n exercise: { off: '../policy.ts names no one grant: pass { actor } holding what it checks' },\n }";
|
|
257
|
+
}
|
|
258
|
+
return orgRule
|
|
259
|
+
? '{\n exercise: { actor: member, input: (drawn) => ({ ...(drawn as object), orgId }) },\n }'
|
|
260
|
+
: '{ exercise: { actor: member } }';
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
export const actionContractTest = (
|
|
264
|
+
name: NameSet,
|
|
265
|
+
feature: NameSet,
|
|
266
|
+
isMutator: boolean,
|
|
267
|
+
/** The slice's policy is the one this generator wrote, so its tenancy rule is known. */
|
|
268
|
+
scaffoldPolicy = true,
|
|
269
|
+
/** That policy holds an org rule: false for a slice whose entity names no tenant. */
|
|
270
|
+
orgRule = scaffoldPolicy,
|
|
271
|
+
/** The permission the exercise actor holds; `undefined` when the policy's grant cannot be read. */
|
|
272
|
+
grant: string | undefined = scaffoldPolicy ? `${feature.kebab}:write` : undefined,
|
|
273
|
+
): string => `// ${name.camel}: the contract every action owes, and the foreign-org actor it denies before the
|
|
274
|
+
// handler runs. One declaration, every surface.
|
|
275
|
+
${preamble(name, feature, isMutator, 'contractTest, expect', orgRule, orgRule || grant !== undefined)}${exerciseActor(grant, orgRule)}
|
|
276
|
+
contractTest('${name.camel} passes the action contract', async () => {
|
|
277
|
+
// Four assertions the framework makes for any action, without knowing what this one does:
|
|
278
|
+
// garbage input is rejected, an anonymous actor is denied, the operation reaches the OpenAPI
|
|
279
|
+
// document, and the handler survives 16 generated legal inputs, each rolled back.
|
|
280
|
+
for (const contract of target.contract(${exerciseOption(grant, orgRule)})) {
|
|
281
|
+
await contract.run();
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
${
|
|
286
|
+
orgRule
|
|
287
|
+
? `contractTest('${name.camel} denies a foreign org', async () => {
|
|
288
|
+
// \`.as()\` is the one execution path with the actor swapped, so this denial is the same one
|
|
289
|
+
// HTTP, MCP and the job surface would produce — and no repo call happened to produce it.
|
|
290
|
+
const denied = await target.as(outsider, input).catch((error: unknown) => error);
|
|
291
|
+
expect(denied).toBeUltimateError('X_FORBIDDEN');
|
|
292
|
+
});
|
|
293
|
+
`
|
|
294
|
+
: scaffoldPolicy
|
|
295
|
+
? `// No foreign-org assertion: this slice's entity names no tenant, so there is no other org to deny.
|
|
296
|
+
`
|
|
297
|
+
: `// No foreign-org assertion: ../policy.ts is hand-written, and whether it checks the org is the
|
|
298
|
+
// author's rule to state — add that test here.
|
|
299
|
+
`
|
|
300
|
+
}
|
|
301
|
+
// No MCP tool until it is described: a placeholder description misleads the agent reading it.
|
|
302
|
+
contractTest('${name.camel} projects one operation and no tool', () => {
|
|
303
|
+
expect(target.openapi().operationId).toBe('${name.camel}');
|
|
304
|
+
expect(target.mcp?.expose ?? false).toBe(false);
|
|
305
|
+
});
|
|
306
|
+
`;
|