@ultimat3/cli 29.3.2 → 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 +13 -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 +28 -8
- 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 +42 -138
- 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 +17 -27
- 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
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// The X_* codes of `x shot` and the dev MCP server's `ui.*` tools — the picture-taking surface —
|
|
2
|
+
// split out of `error-codes.ts` at its 500-line ceiling. `error-codes.ts` folds these into the
|
|
3
|
+
// CLI's one owned union and registers them in its one call; nothing else imports this file.
|
|
4
|
+
|
|
5
|
+
/** Codes `x shot` and `ui.shot` / `ui.interact` / `ui.diff` declare and own. */
|
|
6
|
+
export const CLI_SHOT_ERROR_CODES = [
|
|
7
|
+
'X_SHOT_BROWSER_MISSING',
|
|
8
|
+
// `ui.shot` (the dev MCP server): a route it will not photograph, and why.
|
|
9
|
+
'X_UI_SHOT_ROUTE_UNKNOWN',
|
|
10
|
+
'X_UI_SHOT_ROUTE_UNBUDGETED',
|
|
11
|
+
// `ui.interact`: the four ways a step list is refused. All four REFUSE rather than trim or skip
|
|
12
|
+
// — a step dropped, a keystroke swallowed or a navigation followed changes what the picture is of.
|
|
13
|
+
'X_UI_INTERACT_STEPS_INVALID',
|
|
14
|
+
'X_UI_INTERACT_SECRET_FIELD',
|
|
15
|
+
'X_UI_INTERACT_LEFT_APP',
|
|
16
|
+
'X_UI_INTERACT_STEP_FAILED',
|
|
17
|
+
// `ui.diff`: the three ways two captures fail to become a comparison. The path gate is what
|
|
18
|
+
// lets a file-reading tool sit under `dev:read` — it reads `.x/shot/` and nothing else.
|
|
19
|
+
'X_UI_DIFF_PATH_OUTSIDE',
|
|
20
|
+
'X_UI_DIFF_FILE_MISSING',
|
|
21
|
+
'X_UI_DIFF_SIZE_MISMATCH',
|
|
22
|
+
// `x shot --island` — one code per way a component's named state fails to become a picture.
|
|
23
|
+
// The last of the four is the one that gates: it is checked against the expansion computed
|
|
24
|
+
// before a browser existed, so a capture loop that swallowed a failure cannot exit 0.
|
|
25
|
+
'X_SHOT_ISLAND_STATES_EMPTY',
|
|
26
|
+
'X_SHOT_ISLAND_UNPHOTOGRAPHABLE',
|
|
27
|
+
'X_SHOT_ISLAND_UNSTUBBED_REQUEST',
|
|
28
|
+
'X_SHOT_ISLAND_MISSING',
|
|
29
|
+
// The raw-CDP shot page's own refusals (`cdp-shot-errors.ts` holds the classes): which host it
|
|
30
|
+
// may open, which element it was pointed at, and which key it was asked to press.
|
|
31
|
+
'X_SHOT_HOST_REFUSED',
|
|
32
|
+
'X_SHOT_ELEMENT_MISSING',
|
|
33
|
+
'X_SHOT_ELEMENT_UNREADY',
|
|
34
|
+
'X_SHOT_KEY_INVALID',
|
|
35
|
+
] as const;
|
|
36
|
+
|
|
37
|
+
export type CliShotErrorCode = (typeof CLI_SHOT_ERROR_CODES)[number];
|
|
38
|
+
|
|
39
|
+
/** Their titles, typed over the list above so a code without one is a build error. */
|
|
40
|
+
export const CLI_SHOT_ERROR_TITLES: Readonly<Record<CliShotErrorCode, string>> = {
|
|
41
|
+
X_SHOT_BROWSER_MISSING: '`x shot` has no browser to launch',
|
|
42
|
+
X_UI_SHOT_ROUTE_UNKNOWN: 'a ui.* tool was asked for a path no route answers',
|
|
43
|
+
X_UI_SHOT_ROUTE_UNBUDGETED: 'a ui.* tool refused a route that declares no budget.js',
|
|
44
|
+
X_UI_INTERACT_STEPS_INVALID: 'a ui.interact step list is over its bounds or malformed',
|
|
45
|
+
X_UI_INTERACT_SECRET_FIELD: 'ui.interact refused to type into a password field',
|
|
46
|
+
X_UI_INTERACT_LEFT_APP: 'a ui.interact step navigated off the dev server origin',
|
|
47
|
+
X_UI_INTERACT_STEP_FAILED: 'a ui.interact step raised a scraping error',
|
|
48
|
+
X_UI_DIFF_PATH_OUTSIDE: 'ui.diff was handed a path that does not resolve inside .x/shot/',
|
|
49
|
+
X_UI_DIFF_FILE_MISSING: 'ui.diff was handed a capture that is not on disk',
|
|
50
|
+
X_UI_DIFF_SIZE_MISMATCH: 'ui.diff was handed two captures of different sizes',
|
|
51
|
+
X_SHOT_ISLAND_STATES_EMPTY: 'an island states file declares no manifest',
|
|
52
|
+
X_SHOT_ISLAND_UNPHOTOGRAPHABLE: 'the island never reached a state worth photographing',
|
|
53
|
+
X_SHOT_ISLAND_UNSTUBBED_REQUEST: 'the island requested something no state stub answers',
|
|
54
|
+
X_SHOT_ISLAND_MISSING: 'a declared island picture is not on disk',
|
|
55
|
+
X_SHOT_HOST_REFUSED: "x shot was asked to open a host outside the run's allow list",
|
|
56
|
+
X_SHOT_ELEMENT_MISSING: 'no element matched the selector before the deadline',
|
|
57
|
+
X_SHOT_ELEMENT_UNREADY: 'the element matched and never became ready to act on',
|
|
58
|
+
X_SHOT_KEY_INVALID: 'a key chord the browser cannot press',
|
|
59
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// The X_* codes of the app tooling commands — `x client`, `x graph` / `x impact` and `x repro` —
|
|
2
|
+
// split out of `error-codes.ts` at its 500-line ceiling, as `error-codes-shot.ts` was. That file
|
|
3
|
+
// folds these into the CLI's one owned union and registers them in its one call.
|
|
4
|
+
|
|
5
|
+
/** Codes `x client`, `x graph`, `x impact` and `x repro` declare and own. */
|
|
6
|
+
export const CLI_TOOLING_ERROR_CODES = [
|
|
7
|
+
'X_CLIENT_SPEC_UNSUPPORTED',
|
|
8
|
+
'X_CLIENT_STALE',
|
|
9
|
+
'X_CLIENT_EDITED',
|
|
10
|
+
'X_GRAPH_NODE_UNKNOWN',
|
|
11
|
+
'X_CLIENT_SPEC_INVALID',
|
|
12
|
+
'X_REPRO_UNKNOWN',
|
|
13
|
+
'X_REPRO_NOT_REPRODUCED',
|
|
14
|
+
'X_REPRO_CAPTURE_OFF',
|
|
15
|
+
] as const;
|
|
16
|
+
|
|
17
|
+
export type CliToolingErrorCode = (typeof CLI_TOOLING_ERROR_CODES)[number];
|
|
18
|
+
|
|
19
|
+
/** Their titles, typed over the list above so a code without one is a build error. */
|
|
20
|
+
export const CLI_TOOLING_ERROR_TITLES: Readonly<Record<CliToolingErrorCode, string>> = {
|
|
21
|
+
X_CLIENT_SPEC_UNSUPPORTED: 'x client met an OpenAPI construct its compiler refuses',
|
|
22
|
+
X_CLIENT_STALE: 'a generated client is older than the document it was compiled from',
|
|
23
|
+
X_CLIENT_EDITED: 'a generated client file was changed by hand',
|
|
24
|
+
X_GRAPH_NODE_UNKNOWN: 'x graph or x impact was asked about a node the app graph does not hold',
|
|
25
|
+
X_CLIENT_SPEC_INVALID: 'x client was handed a document that is not OpenAPI 3.0 or 3.1',
|
|
26
|
+
X_REPRO_UNKNOWN: 'x repro was given an id nothing holds',
|
|
27
|
+
X_REPRO_NOT_REPRODUCED: 'a written repro test did not fail the way the original did',
|
|
28
|
+
X_REPRO_CAPTURE_OFF: 'x repro was given a request id while failure capture is off',
|
|
29
|
+
};
|
package/src/error-codes.ts
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
// the CLI is the surface an agent reads first — a failure here has to be actionable without a doc
|
|
4
4
|
// lookup or a second round-trip. The classes that throw these codes live in `./errors`.
|
|
5
5
|
import { registerErrorCodes } from '@ultimat3/core';
|
|
6
|
+
import type { CliShotErrorCode } from './error-codes-shot';
|
|
7
|
+
import { CLI_SHOT_ERROR_CODES, CLI_SHOT_ERROR_TITLES } from './error-codes-shot';
|
|
8
|
+
import type { CliToolingErrorCode } from './error-codes-tooling';
|
|
9
|
+
import { CLI_TOOLING_ERROR_CODES, CLI_TOOLING_ERROR_TITLES } from './error-codes-tooling';
|
|
6
10
|
|
|
7
11
|
/** Codes this package declares and owns. */
|
|
8
12
|
export const CLI_OWNED_ERROR_CODES = [
|
|
@@ -161,34 +165,6 @@ export const CLI_OWNED_ERROR_CODES = [
|
|
|
161
165
|
// source-level fix, and an agent following it here edits a file that is already right — the
|
|
162
166
|
// repair is an install, and only `duplicate-packages.ts` can see that.
|
|
163
167
|
'X_PACKAGE_DUPLICATED',
|
|
164
|
-
'X_SHOT_BROWSER_MISSING',
|
|
165
|
-
// `ui.shot` (the dev MCP server): a route it will not photograph, and why.
|
|
166
|
-
'X_UI_SHOT_ROUTE_UNKNOWN',
|
|
167
|
-
'X_UI_SHOT_ROUTE_UNBUDGETED',
|
|
168
|
-
// `ui.interact`: the four ways a step list is refused. All four REFUSE rather than trim or skip
|
|
169
|
-
// — a step dropped, a keystroke swallowed or a navigation followed changes what the picture is of.
|
|
170
|
-
'X_UI_INTERACT_STEPS_INVALID',
|
|
171
|
-
'X_UI_INTERACT_SECRET_FIELD',
|
|
172
|
-
'X_UI_INTERACT_LEFT_APP',
|
|
173
|
-
'X_UI_INTERACT_STEP_FAILED',
|
|
174
|
-
// `ui.diff`: the three ways two captures fail to become a comparison. The path gate is what
|
|
175
|
-
// lets a file-reading tool sit under `dev:read` — it reads `.x/shot/` and nothing else.
|
|
176
|
-
'X_UI_DIFF_PATH_OUTSIDE',
|
|
177
|
-
'X_UI_DIFF_FILE_MISSING',
|
|
178
|
-
'X_UI_DIFF_SIZE_MISMATCH',
|
|
179
|
-
// `x shot --island` — one code per way a component's named state fails to become a picture.
|
|
180
|
-
// The last of the four is the one that gates: it is checked against the expansion computed
|
|
181
|
-
// before a browser existed, so a capture loop that swallowed a failure cannot exit 0.
|
|
182
|
-
'X_SHOT_ISLAND_STATES_EMPTY',
|
|
183
|
-
'X_SHOT_ISLAND_UNPHOTOGRAPHABLE',
|
|
184
|
-
'X_SHOT_ISLAND_UNSTUBBED_REQUEST',
|
|
185
|
-
'X_SHOT_ISLAND_MISSING',
|
|
186
|
-
// The raw-CDP shot page's own refusals (`cdp-shot-errors.ts` holds the classes): which host it
|
|
187
|
-
// may open, which element it was pointed at, and which key it was asked to press.
|
|
188
|
-
'X_SHOT_HOST_REFUSED',
|
|
189
|
-
'X_SHOT_ELEMENT_MISSING',
|
|
190
|
-
'X_SHOT_ELEMENT_UNREADY',
|
|
191
|
-
'X_SHOT_KEY_INVALID',
|
|
192
168
|
'X_GH_UNAVAILABLE',
|
|
193
169
|
'X_GH_NOT_AUTHENTICATED',
|
|
194
170
|
'X_GH_COMMAND_FAILED',
|
|
@@ -237,6 +213,43 @@ export const CLI_OWNED_ERROR_CODES = [
|
|
|
237
213
|
'X_CATALOG_SLOT_MISMATCH',
|
|
238
214
|
'X_CATALOG_UNTRANSLATED',
|
|
239
215
|
'X_WORKER_QUEUE_UNKNOWN',
|
|
216
|
+
'X_UPGRADE_DIRTY_TREE',
|
|
217
|
+
'X_UPGRADE_MIXED_PINS',
|
|
218
|
+
'X_UPGRADE_HOP_TOO_WIDE',
|
|
219
|
+
'X_UPGRADE_REGISTRY_UNREACHABLE',
|
|
220
|
+
'X_UPGRADE_PIN_UNREADABLE',
|
|
221
|
+
'X_UPGRADE_VERSION_UNPUBLISHED',
|
|
222
|
+
'X_UPGRADE_INSTALL_FAILED',
|
|
223
|
+
'X_UPGRADE_GATE_UNREADABLE',
|
|
224
|
+
'X_DEPLOY_BUILD_UNSTAMPED',
|
|
225
|
+
'X_DEPLOY_UNSTABLE',
|
|
226
|
+
'X_DEPLOY_QUEUE_UNSERVED',
|
|
227
|
+
'X_DEPLOY_BUILD_MISMATCH',
|
|
228
|
+
'X_JOB_OUTLASTS_DRAIN',
|
|
229
|
+
'X_FEATURE_REQUIRED',
|
|
230
|
+
'X_BOUNDARY_PACKAGE_TO_APP',
|
|
231
|
+
'X_APP_IMPORT_UNRESOLVED',
|
|
232
|
+
'X_STREAM_ALREADY_FLUSHED',
|
|
233
|
+
'X_QUERY_SLICE_ENTITY',
|
|
234
|
+
'X_DEPLOY_ENV_MISSING',
|
|
235
|
+
'X_ENV_FILE_EXISTS',
|
|
236
|
+
'X_DEV_STOP_TIMEOUT',
|
|
237
|
+
'X_DEV_STOP_DENIED',
|
|
238
|
+
'X_ASSET_RETAIN_FAILED',
|
|
239
|
+
'X_APP_MODULE_LOAD_FAILED',
|
|
240
|
+
'X_CHART_MISSING',
|
|
241
|
+
'X_ENTITY_VIEW_DRIFT',
|
|
242
|
+
'X_ENTITY_RETAIN_UNSWEPT',
|
|
243
|
+
'X_NEW_SHAPE_UNKNOWN',
|
|
244
|
+
'X_NEW_SHAPE_CONFLICT',
|
|
245
|
+
'X_SEO_CARD_ROUTE_UNKNOWN',
|
|
246
|
+
'X_SEO_CARD_BROWSER_MISSING',
|
|
247
|
+
'X_IMAGE_SOURCE_UNREADABLE',
|
|
248
|
+
'X_VERIFY_SKIPS_OVER_CEILING',
|
|
249
|
+
'X_ASSET_VARIANT_UNDECLARED',
|
|
250
|
+
'X_NEW_SHAPE_GENERATE_FAILED',
|
|
251
|
+
'X_ISLAND_DISPOSER_MISSING',
|
|
252
|
+
'X_SOCKET_HAND_ROLLED',
|
|
240
253
|
] as const;
|
|
241
254
|
|
|
242
255
|
/**
|
|
@@ -260,9 +273,8 @@ export const CLI_BORROWED_ERROR_CODES = [
|
|
|
260
273
|
// nearest declared name all come from `permissionUnknown`. A CLI-owned twin would be a second
|
|
261
274
|
// wording for the condition `can()` already refuses at run time, and the two would drift.
|
|
262
275
|
'X_PERMISSION_UNKNOWN',
|
|
263
|
-
// `@ultimat3/db`'s, reported by `x doctor`'s database probe with
|
|
264
|
-
//
|
|
265
|
-
// both db's, and a CLI twin would be one unreachable database with two names.
|
|
276
|
+
// `@ultimat3/db`'s, reported by `x doctor`'s database probe with db's own fix: the CLI only asks
|
|
277
|
+
// before a pool opens, and a CLI twin would be one unreachable database with two names.
|
|
266
278
|
'X_DB_UNAVAILABLE',
|
|
267
279
|
'X_CONFIG_INVALID',
|
|
268
280
|
'X_ENV_MISSING',
|
|
@@ -271,17 +283,24 @@ export const CLI_BORROWED_ERROR_CODES = [
|
|
|
271
283
|
] as const;
|
|
272
284
|
|
|
273
285
|
/** Every code the CLI can throw: the ones it owns plus the one it borrows. */
|
|
274
|
-
export const CLI_ERROR_CODES = [
|
|
286
|
+
export const CLI_ERROR_CODES = [
|
|
287
|
+
...CLI_OWNED_ERROR_CODES,
|
|
288
|
+
...CLI_SHOT_ERROR_CODES,
|
|
289
|
+
...CLI_TOOLING_ERROR_CODES,
|
|
290
|
+
...CLI_BORROWED_ERROR_CODES,
|
|
291
|
+
] as const;
|
|
275
292
|
|
|
276
|
-
|
|
293
|
+
/** The codes listed in THIS file — the ones `CLI_ERROR_TITLES` below must title. */
|
|
294
|
+
type CliListedErrorCode = (typeof CLI_OWNED_ERROR_CODES)[number];
|
|
295
|
+
export type CliOwnedErrorCode = CliListedErrorCode | CliShotErrorCode | CliToolingErrorCode;
|
|
277
296
|
export type CliErrorCode = (typeof CLI_ERROR_CODES)[number];
|
|
278
297
|
|
|
279
298
|
/**
|
|
280
299
|
* Registered titles, so `x errors list` enumerates the CLI's codes alongside every other
|
|
281
300
|
* package's instead of leaving a hole an agent has to read source to fill. Typed over
|
|
282
|
-
* `
|
|
301
|
+
* `CliListedErrorCode`, so adding a code without a title is a build error.
|
|
283
302
|
*/
|
|
284
|
-
export const CLI_ERROR_TITLES: Readonly<Record<
|
|
303
|
+
export const CLI_ERROR_TITLES: Readonly<Record<CliListedErrorCode, string>> = {
|
|
285
304
|
X_CLI_UNKNOWN_COMMAND: 'not a command in the registry',
|
|
286
305
|
X_CLI_BAD_FLAG: 'unknown flag, missing value, or a value the command refuses',
|
|
287
306
|
X_VERIFY_FAILED: 'at least one x verify step failed',
|
|
@@ -355,24 +374,6 @@ export const CLI_ERROR_TITLES: Readonly<Record<CliOwnedErrorCode, string>> = {
|
|
|
355
374
|
X_SECRETS_EDIT_FAILED: 'the editor exited non-zero, so nothing was resealed',
|
|
356
375
|
X_WORKSPACE_DEP_UNDECLARED: 'a workspace imports another workspace it does not declare',
|
|
357
376
|
X_PACKAGE_DUPLICATED: 'two copies of one registry-holding framework package are installed',
|
|
358
|
-
X_SHOT_BROWSER_MISSING: '`x shot` has no browser to launch',
|
|
359
|
-
X_UI_SHOT_ROUTE_UNKNOWN: 'a ui.* tool was asked for a path no route answers',
|
|
360
|
-
X_UI_SHOT_ROUTE_UNBUDGETED: 'a ui.* tool refused a route that declares no budget.js',
|
|
361
|
-
X_UI_INTERACT_STEPS_INVALID: 'a ui.interact step list is over its bounds or malformed',
|
|
362
|
-
X_UI_INTERACT_SECRET_FIELD: 'ui.interact refused to type into a password field',
|
|
363
|
-
X_UI_INTERACT_LEFT_APP: 'a ui.interact step navigated off the dev server origin',
|
|
364
|
-
X_UI_INTERACT_STEP_FAILED: 'a ui.interact step raised a scraping error',
|
|
365
|
-
X_UI_DIFF_PATH_OUTSIDE: 'ui.diff was handed a path that does not resolve inside .x/shot/',
|
|
366
|
-
X_UI_DIFF_FILE_MISSING: 'ui.diff was handed a capture that is not on disk',
|
|
367
|
-
X_UI_DIFF_SIZE_MISMATCH: 'ui.diff was handed two captures of different sizes',
|
|
368
|
-
X_SHOT_ISLAND_STATES_EMPTY: 'an island states file declares no manifest',
|
|
369
|
-
X_SHOT_ISLAND_UNPHOTOGRAPHABLE: 'the island never reached a state worth photographing',
|
|
370
|
-
X_SHOT_ISLAND_UNSTUBBED_REQUEST: 'the island requested something no state stub answers',
|
|
371
|
-
X_SHOT_ISLAND_MISSING: 'a declared island picture is not on disk',
|
|
372
|
-
X_SHOT_HOST_REFUSED: "x shot was asked to open a host outside the run's allow list",
|
|
373
|
-
X_SHOT_ELEMENT_MISSING: 'no element matched the selector before the deadline',
|
|
374
|
-
X_SHOT_ELEMENT_UNREADY: 'the element matched and never became ready to act on',
|
|
375
|
-
X_SHOT_KEY_INVALID: 'a key chord the browser cannot press',
|
|
376
377
|
X_GH_UNAVAILABLE: 'the GitHub CLI is not runnable from here',
|
|
377
378
|
X_GH_NOT_AUTHENTICATED: 'gh holds no credentials for this host',
|
|
378
379
|
X_GH_COMMAND_FAILED: 'a gh invocation exited non-zero',
|
|
@@ -430,12 +431,57 @@ export const CLI_ERROR_TITLES: Readonly<Record<CliOwnedErrorCode, string>> = {
|
|
|
430
431
|
X_CATALOG_SLOT_MISMATCH: 'two locales define one key with different interpolation slots',
|
|
431
432
|
X_CATALOG_UNTRANSLATED: "a locale still carries the reference locale's value",
|
|
432
433
|
X_WORKER_QUEUE_UNKNOWN: 'WORKER_QUEUES names a queue no job and no jobs.queues entry declares',
|
|
434
|
+
X_UPGRADE_DIRTY_TREE: 'x upgrade refused a tree with uncommitted changes',
|
|
435
|
+
X_UPGRADE_MIXED_PINS: 'x upgrade found two @ultimat3/* versions across the workspaces',
|
|
436
|
+
X_UPGRADE_HOP_TOO_WIDE: 'x upgrade --to is outside the one hop a run may make',
|
|
437
|
+
X_UPGRADE_REGISTRY_UNREACHABLE: 'x upgrade could not read the published versions from npm',
|
|
438
|
+
X_UPGRADE_PIN_UNREADABLE: 'x upgrade found an @ultimat3/* dependency that names no version',
|
|
439
|
+
X_UPGRADE_VERSION_UNPUBLISHED: 'x upgrade --to names a version npm does not have',
|
|
440
|
+
X_UPGRADE_INSTALL_FAILED: 'bun install failed after x upgrade wrote the pins',
|
|
441
|
+
X_UPGRADE_GATE_UNREADABLE: 'the installed x verify answered with no JSON report',
|
|
442
|
+
X_DEPLOY_BUILD_UNSTAMPED: 'the deployment answers with no build header',
|
|
443
|
+
X_DEPLOY_UNSTABLE: 'the build answered but did not hold',
|
|
444
|
+
X_DEPLOY_QUEUE_UNSERVED: 'a registered queue has no serving worker of this build',
|
|
445
|
+
X_DEPLOY_BUILD_MISMATCH: 'production never served this build',
|
|
446
|
+
X_JOB_OUTLASTS_DRAIN: "a job's declared timeout outlasts the worker's budget",
|
|
447
|
+
X_FEATURE_REQUIRED: 'x g query or x g backfill named no slice to read',
|
|
448
|
+
X_BOUNDARY_PACKAGE_TO_APP: 'packages/ imported apps/ at runtime',
|
|
449
|
+
X_APP_IMPORT_UNRESOLVED: 'an app module imports what does not resolve',
|
|
450
|
+
X_STREAM_ALREADY_FLUSHED: 'a response header was written after the stream flushed',
|
|
451
|
+
X_QUERY_SLICE_ENTITY: 'x g query into a slice whose entity is not the one it scaffolds',
|
|
452
|
+
X_DEPLOY_ENV_MISSING: 'x deploy --method compose found no complete .env.production',
|
|
453
|
+
X_ENV_FILE_EXISTS: 'x env init --production found .env.production already written',
|
|
454
|
+
X_DEV_STOP_TIMEOUT: 'x stop sent SIGTERM and x dev did not exit in time',
|
|
455
|
+
X_DEV_STOP_DENIED: 'x stop may not signal the x dev the lock names',
|
|
456
|
+
X_ASSET_RETAIN_FAILED: 'Build assets were not retained',
|
|
457
|
+
X_APP_MODULE_LOAD_FAILED:
|
|
458
|
+
'an app module failed to load, so the role refused to serve a partial app',
|
|
459
|
+
X_CHART_MISSING: 'x g chart-alerts found no Helm chart to write into',
|
|
460
|
+
X_ENTITY_VIEW_DRIFT: "a view entity's columns disagree with the view its migration created",
|
|
461
|
+
X_ENTITY_RETAIN_UNSWEPT: 'an entity declares retain and no purge() sweeps it',
|
|
462
|
+
X_NEW_SHAPE_UNKNOWN: 'x new --shape names no registered shape',
|
|
463
|
+
X_NEW_SHAPE_CONFLICT: 'x new --shape with --no-example',
|
|
464
|
+
X_SEO_CARD_ROUTE_UNKNOWN: 'og.capture names a path no site/ route answers',
|
|
465
|
+
X_SEO_CARD_BROWSER_MISSING:
|
|
466
|
+
'a static build with social cards found no browser to photograph them',
|
|
467
|
+
X_IMAGE_SOURCE_UNREADABLE: 'a responsiveImage src is not a file the static export holds',
|
|
468
|
+
X_VERIFY_SKIPS_OVER_CEILING: 'a step skipped more tests than its skipCeiling allows',
|
|
469
|
+
X_ASSET_VARIANT_UNDECLARED: 'an /assets image variant responsiveImage() never mints',
|
|
470
|
+
X_NEW_SHAPE_GENERATE_FAILED: 'a shape generator failed in the new app',
|
|
471
|
+
X_ISLAND_DISPOSER_MISSING: "an island's mount returns no disposer",
|
|
472
|
+
X_SOCKET_HAND_ROLLED: 'an app upgrades a websocket itself',
|
|
433
473
|
};
|
|
434
474
|
|
|
435
475
|
// One unconditional call, so a second package claiming one of the CLI's codes throws
|
|
436
476
|
// X_ERROR_CODE_DUPLICATE instead of losing silently to whichever module imported first.
|
|
437
477
|
registerErrorCodes(
|
|
438
|
-
Object.fromEntries(
|
|
478
|
+
Object.fromEntries(
|
|
479
|
+
Object.entries({
|
|
480
|
+
...CLI_ERROR_TITLES,
|
|
481
|
+
...CLI_SHOT_ERROR_TITLES,
|
|
482
|
+
...CLI_TOOLING_ERROR_TITLES,
|
|
483
|
+
}).map(([code, title]) => [code, { title }]),
|
|
484
|
+
),
|
|
439
485
|
);
|
|
440
486
|
|
|
441
487
|
// This file exports NO `docsFor(code)`, and adding one back is the defect. A CLI error passes no
|
package/src/error-contract.ts
CHANGED
|
@@ -42,8 +42,8 @@ export const COMMAND_TOKENS: readonly RegExp[] = [
|
|
|
42
42
|
// Built from `fix-path.ts`'s extension list, because the token that makes a fix count as an
|
|
43
43
|
// instruction is exactly the token `citedPathProblem` then has to resolve.
|
|
44
44
|
new RegExp(FILE_TOKEN_PATTERN),
|
|
45
|
-
// A repo script, run as written: `bin/check --full`, `./bin/probe`. An app's
|
|
46
|
-
// so without this
|
|
45
|
+
// A repo script, run as written: `bin/check --full`, `./bin/probe`. An app's own wrapper is often
|
|
46
|
+
// `bin/check` (an older scaffold's gate), so without this its fixes read as the banned word "check".
|
|
47
47
|
// Anchored like `x` above — `/usr/bin/env` and `robin/check` are not the repo's scripts.
|
|
48
48
|
// (`scripts/<name>.ts` is already a file token.)
|
|
49
49
|
/(?:^|[\s;|&("'`])(?:\.\/)?bin\/[\w.-]+/,
|
package/src/errors.ts
CHANGED
|
@@ -123,11 +123,30 @@ export class VerifyFailedError extends UltimateError {
|
|
|
123
123
|
|
|
124
124
|
/** The command needs an app root (a directory containing `app.config.ts`) and found none. */
|
|
125
125
|
export class NotInAppError extends UltimateError {
|
|
126
|
-
constructor(input: {
|
|
126
|
+
constructor(input: {
|
|
127
|
+
command: string;
|
|
128
|
+
from: string;
|
|
129
|
+
/** `app.config.ts` directories under `from`: an app below the cwd is one `cd` away. */
|
|
130
|
+
below?: readonly string[];
|
|
131
|
+
/** `from` already depends on the framework, so `x new` would be a second project, not a fix. */
|
|
132
|
+
adopter?: boolean;
|
|
133
|
+
}) {
|
|
134
|
+
const [first] = input.below ?? [];
|
|
135
|
+
const found = (input.below ?? []).join(', ');
|
|
127
136
|
super({
|
|
128
137
|
code: 'X_NOT_IN_APP',
|
|
129
|
-
cause:
|
|
130
|
-
|
|
138
|
+
cause:
|
|
139
|
+
first !== undefined
|
|
140
|
+
? `"x ${input.command}" runs from the directory holding app.config.ts, and that is below ${input.from}: ${found}`
|
|
141
|
+
: input.adopter === true
|
|
142
|
+
? `"x ${input.command}" must run inside an Ultimate app; ${input.from} depends on @ultimat3/* but no app.config.ts exists at or above it, or within three directories below`
|
|
143
|
+
: `"x ${input.command}" must run inside an Ultimate app; no app.config.ts at or above ${input.from}`,
|
|
144
|
+
fix:
|
|
145
|
+
first !== undefined
|
|
146
|
+
? `cd ${quoteArg(first)} && x ${quoteArg(input.command)} --json`
|
|
147
|
+
: input.adopter === true
|
|
148
|
+
? `x new scratch-app --dry-run --json # its file list carries app.config.ts; write one at the app root, then re-run: x ${quoteArg(input.command)} --json`
|
|
149
|
+
: 'bunx create-ultimate myapp && cd myapp',
|
|
131
150
|
});
|
|
132
151
|
}
|
|
133
152
|
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// Where `x exercise` runs: a test process, pinned before the app loads, on a scratch migrated
|
|
2
|
+
// database installed as the ambient `db()` and thrown away after — never the developer's own (the
|
|
3
|
+
// pass COMMITS its rows), on the engine the schema dump replays on (`db-replay-engine.ts`), so the
|
|
4
|
+
// gate needs nothing listening, exactly as the `drift` step does not.
|
|
5
|
+
|
|
6
|
+
import type { Environment } from '@ultimat3/core';
|
|
7
|
+
import { ENVIRONMENT_KEY, resolveEnvironment, UltimateError } from '@ultimat3/core';
|
|
8
|
+
import type { DbClient } from '@ultimat3/db';
|
|
9
|
+
import { migrate, raw, setDbClient } from '@ultimat3/db';
|
|
10
|
+
import { testSealKeyEnv } from '@ultimat3/testing';
|
|
11
|
+
import { chooseReplayEngine, withScratchDatabase } from './db-replay-engine';
|
|
12
|
+
import { applyFrameworkSchema } from './framework-schema';
|
|
13
|
+
import { readMigrations } from './migrations';
|
|
14
|
+
import { resolveServices } from './runtime-bindings';
|
|
15
|
+
|
|
16
|
+
type Env = Readonly<Record<string, string | undefined>>;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* A deployed environment asked for the pass. `X_CONTRACT_EXERCISE_ENVIRONMENT`, core's code for
|
|
20
|
+
* the same refusal one level down — with this command's fix, never `bun test`'s.
|
|
21
|
+
*/
|
|
22
|
+
export class ExerciseDeployRefusedError extends UltimateError {
|
|
23
|
+
constructor(environment: Environment) {
|
|
24
|
+
super({
|
|
25
|
+
code: 'X_CONTRACT_EXERCISE_ENVIRONMENT',
|
|
26
|
+
cause: `x exercise runs every handler on generated input, and ${ENVIRONMENT_KEY} resolved ${environment}: the pass runs in development or test only`,
|
|
27
|
+
fix: `${ENVIRONMENT_KEY}=development x exercise --json # from a checkout, never against a deploy`,
|
|
28
|
+
meta: { environment },
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The environment the pass runs as: always `test`, once a deploy is refused. The pass is a test run
|
|
35
|
+
* on a scratch database thrown away after it, and an app picks its store from this answer at module
|
|
36
|
+
* load: a `development` app on its embedded memory store would read none of the rows seeded here.
|
|
37
|
+
*/
|
|
38
|
+
export function exerciseEnvironment(env: Env): Environment {
|
|
39
|
+
const environment = resolveEnvironment({ env });
|
|
40
|
+
if (environment !== 'development' && environment !== 'test') {
|
|
41
|
+
throw new ExerciseDeployRefusedError(environment);
|
|
42
|
+
}
|
|
43
|
+
return 'test';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const PINNED = [ENVIRONMENT_KEY, 'NODE_ENV'] as const;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* `work` — the app's load and the pass — in a test process: the environment pinned BEFORE any app
|
|
50
|
+
* module reads it, and the throwaway seal key `bun test` gets when the app supplied none (CI names
|
|
51
|
+
* no `ULTIMATE_SECRETS_KEY`, and every row sealed here dies with the scratch database). Every
|
|
52
|
+
* variable is put back as found: a command that exits must not leave the next one a test process.
|
|
53
|
+
*/
|
|
54
|
+
export async function withExerciseProcess<T>(
|
|
55
|
+
root: string,
|
|
56
|
+
env: Env,
|
|
57
|
+
work: () => Promise<T>,
|
|
58
|
+
): Promise<T> {
|
|
59
|
+
const environment = exerciseEnvironment(env);
|
|
60
|
+
const pinned: Record<string, string> = Object.fromEntries(
|
|
61
|
+
PINNED.map((key) => [key, environment]),
|
|
62
|
+
);
|
|
63
|
+
const added = { ...pinned, ...testSealKeyEnv({ root, env: { ...env, ...pinned } }) };
|
|
64
|
+
const previous = Object.keys(added).map((key) => [key, process.env[key]] as const);
|
|
65
|
+
Object.assign(process.env, added);
|
|
66
|
+
try {
|
|
67
|
+
return await work();
|
|
68
|
+
} finally {
|
|
69
|
+
for (const [key, value] of previous) {
|
|
70
|
+
if (value === undefined) delete process.env[key];
|
|
71
|
+
else process.env[key] = value;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Framework tables, then every migration — the order every real boot runs them in — then `work`
|
|
78
|
+
* with the client installed. Released either way: a command that exits holding an installed
|
|
79
|
+
* client would hand the next caller in this process a closed pool.
|
|
80
|
+
*/
|
|
81
|
+
export async function withExerciseDatabase<T>(
|
|
82
|
+
root: string,
|
|
83
|
+
env: Env,
|
|
84
|
+
work: (client: DbClient) => Promise<T>,
|
|
85
|
+
): Promise<T> {
|
|
86
|
+
const migrations = await readMigrations(root);
|
|
87
|
+
const engine = await chooseReplayEngine(migrations, env);
|
|
88
|
+
const { stateDir } = resolveServices(root, env);
|
|
89
|
+
return withScratchDatabase(engine, stateDir, async (client) => {
|
|
90
|
+
await applyFrameworkSchema((statement) => client.execute(raw(statement)));
|
|
91
|
+
await migrate({ migrations, client });
|
|
92
|
+
setDbClient(client);
|
|
93
|
+
try {
|
|
94
|
+
return await work(client);
|
|
95
|
+
} finally {
|
|
96
|
+
setDbClient(undefined);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// What `x exercise` makes of a pass's records: one finding per `unreached` primitive, carrying the
|
|
2
|
+
// override to paste as its fix, and per crossed tenant and diverged crash proof; the counts; the
|
|
3
|
+
// table; and the one report document both `--json` and `.x/exercise.json` hold. Pure — records in, plain values out — so the gate step and the
|
|
4
|
+
// command cannot disagree about what is red.
|
|
5
|
+
|
|
6
|
+
// why: Bun ships no path joiner.
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import {
|
|
9
|
+
ERROR_DOCS_URL,
|
|
10
|
+
renderCauseValue,
|
|
11
|
+
renderFixLiteral,
|
|
12
|
+
renderFixShellArg,
|
|
13
|
+
} from '@ultimat3/core';
|
|
14
|
+
import type { ExerciseRecord } from '@ultimat3/testing';
|
|
15
|
+
import { planSweepFindings, tenantProofFindings } from '@ultimat3/testing';
|
|
16
|
+
import type { Finding } from './output';
|
|
17
|
+
import { renderTable } from './table';
|
|
18
|
+
|
|
19
|
+
/** Where the pass's report is written, app-root-relative: state, gitignored with the rest of `.x/`. */
|
|
20
|
+
export const EXERCISE_REPORT_FILE = join('.x', 'exercise.json');
|
|
21
|
+
|
|
22
|
+
/** The file an override is pasted into — `defineExercise()`'s one home. */
|
|
23
|
+
export const EXERCISE_FILE = 'apps/web/exercise.ts';
|
|
24
|
+
|
|
25
|
+
type Outcome = ExerciseRecord['outcome'];
|
|
26
|
+
|
|
27
|
+
export interface ExerciseCounts {
|
|
28
|
+
readonly total: number;
|
|
29
|
+
readonly reached: number;
|
|
30
|
+
readonly refused: number;
|
|
31
|
+
readonly unreached: number;
|
|
32
|
+
readonly off: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const exerciseCounts = (records: readonly ExerciseRecord[]): ExerciseCounts => {
|
|
36
|
+
const count = (outcome: Outcome): number =>
|
|
37
|
+
records.filter((one) => one.outcome === outcome).length;
|
|
38
|
+
return {
|
|
39
|
+
total: records.length,
|
|
40
|
+
reached: count('reached'),
|
|
41
|
+
refused: count('refused'),
|
|
42
|
+
unreached: count('unreached'),
|
|
43
|
+
off: count('off'),
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* `X_CONTRACT_EXERCISE_UNREACHED`, `@ultimat3/action`'s code for the same silence one level up: a
|
|
49
|
+
* primitive no run got to as an allowed actor. The fix is the override the pass printed, or the
|
|
50
|
+
* input shape to fill when it printed none — then the one-primitive rerun that proves it.
|
|
51
|
+
*/
|
|
52
|
+
const unreachedFinding = (record: ExerciseRecord): Finding => {
|
|
53
|
+
const name = renderFixShellArg(record.name, '<name>');
|
|
54
|
+
const override = record.override ?? `${record.name}: { input: { /* a value it accepts */ } }`;
|
|
55
|
+
const stopped = record.code === undefined ? '' : ` — stopped on ${record.code}`;
|
|
56
|
+
const why = record.reason === undefined ? '' : ` (${renderCauseValue(record.reason)})`;
|
|
57
|
+
return {
|
|
58
|
+
code: 'X_CONTRACT_EXERCISE_UNREACHED',
|
|
59
|
+
cause: `${record.kind} ${renderCauseValue(record.name)} never reached its handler as an allowed actor in the exercise pass${stopped}${why}`,
|
|
60
|
+
fix: `defineExercise({ ${override} }) # paste the entry into ${EXERCISE_FILE}, or { off: '<why>' }; then: x exercise ${name} --json`,
|
|
61
|
+
docs: ERROR_DOCS_URL,
|
|
62
|
+
at: EXERCISE_FILE,
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
type CrashFinding = Extract<
|
|
67
|
+
NonNullable<ExerciseRecord['crash']>,
|
|
68
|
+
{ readonly verdict: 'diverged' }
|
|
69
|
+
>['findings'][number];
|
|
70
|
+
|
|
71
|
+
const crashFindingsOf = (record: ExerciseRecord): readonly CrashFinding[] =>
|
|
72
|
+
record.crash?.verdict === 'diverged' ? record.crash.findings : [];
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* `X_JOB_CRASH_DIVERGED`, testing's code: a reached job, killed at a step boundary, made an effect
|
|
76
|
+
* more often than a clean run — it sits outside every `step.run`, so a retry makes it again. One
|
|
77
|
+
* finding per effect and the step it follows, every boundary that showed it named in the cause.
|
|
78
|
+
*/
|
|
79
|
+
const crashFindings = (record: ExerciseRecord): readonly Finding[] => {
|
|
80
|
+
const groups = new Map<string, CrashFinding[]>();
|
|
81
|
+
for (const one of crashFindingsOf(record)) {
|
|
82
|
+
const key = `${one.effect}\u0000${one.after ?? ''}`;
|
|
83
|
+
groups.set(key, [...(groups.get(key) ?? []), one]);
|
|
84
|
+
}
|
|
85
|
+
const job = renderFixShellArg(record.name, '<job>');
|
|
86
|
+
return [...groups.values()].map((group): Finding => {
|
|
87
|
+
const [first] = group as [CrashFinding, ...CrashFinding[]];
|
|
88
|
+
const where =
|
|
89
|
+
first.after === undefined
|
|
90
|
+
? 'before the first step'
|
|
91
|
+
: `after step ${JSON.stringify(first.after)}`;
|
|
92
|
+
const at = group.map((one) => JSON.stringify(one.boundary)).join(', ');
|
|
93
|
+
return {
|
|
94
|
+
code: 'X_JOB_CRASH_DIVERGED',
|
|
95
|
+
cause: `job ${renderCauseValue(record.name)} killed at ${at} made ${renderCauseValue(first.effect)} ${first.killed}× against ${first.clean}× in a clean run: it runs outside every step, ${where}, so the retry after a crash makes it again`,
|
|
96
|
+
fix: `x exercise ${job} --json # in job(${renderFixLiteral(record.name, '<job>')}): wrap the ${renderFixLiteral(first.effect, '<effect>')} made ${where} in step.run('<name>', async () => …)`,
|
|
97
|
+
docs: ERROR_DOCS_URL,
|
|
98
|
+
at: `${record.kind} ${record.name}`,
|
|
99
|
+
};
|
|
100
|
+
});
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/** One line per killed boundary that diverged: `job chargeOrder · killed at mail · X_… effect 2×`. */
|
|
104
|
+
export const crashFindingLines = (records: readonly ExerciseRecord[]): readonly string[] =>
|
|
105
|
+
records.flatMap((record) =>
|
|
106
|
+
crashFindingsOf(record).map(
|
|
107
|
+
(one) =>
|
|
108
|
+
`${record.kind} ${record.name} · killed at ${one.boundary} · X_JOB_CRASH_DIVERGED ${one.effect} ${one.killed}× (clean ${one.clean}×)`,
|
|
109
|
+
),
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Unreached primitives, then the tenant proof's crossed ones: tenant B handed A's data or changed
|
|
114
|
+
* A's row; then the crash proof's diverged jobs. `unproven`, either proof's, is reported on the
|
|
115
|
+
* record and never red — the pass could not ask, which is not an answer.
|
|
116
|
+
*/
|
|
117
|
+
export const exerciseFindings = (records: readonly ExerciseRecord[]): readonly Finding[] => [
|
|
118
|
+
...records.filter((one) => one.outcome === 'unreached').map(unreachedFinding),
|
|
119
|
+
...tenantProofFindings(records).map((one) => ({ ...one, docs: ERROR_DOCS_URL })),
|
|
120
|
+
...records.flatMap(crashFindings),
|
|
121
|
+
// The plan and scale sweeps: a statement no index serves, a shape that grows with the rows.
|
|
122
|
+
...planSweepFindings(records).map((one) => ({ ...one, docs: ERROR_DOCS_URL })),
|
|
123
|
+
];
|
|
124
|
+
|
|
125
|
+
export interface ExerciseReport {
|
|
126
|
+
readonly seed: number | null;
|
|
127
|
+
readonly counts: ExerciseCounts;
|
|
128
|
+
readonly records: readonly ExerciseRecord[];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export const exerciseReport = (
|
|
132
|
+
records: readonly ExerciseRecord[],
|
|
133
|
+
seed: number | undefined,
|
|
134
|
+
): ExerciseReport => ({ seed: seed ?? null, counts: exerciseCounts(records), records });
|
|
135
|
+
|
|
136
|
+
/** Unreached first — the rows that need a hand — then refused, off, reached; by name within. */
|
|
137
|
+
const RANK: readonly Outcome[] = ['unreached', 'refused', 'off', 'reached'];
|
|
138
|
+
|
|
139
|
+
export const renderExerciseTable = (records: readonly ExerciseRecord[]): readonly string[] =>
|
|
140
|
+
renderTable(
|
|
141
|
+
['primitive', 'kind', 'outcome', 'tenant', 'crash', 'code', 'statements', 'effects', 'ms'],
|
|
142
|
+
records
|
|
143
|
+
.toSorted(
|
|
144
|
+
(left, right) =>
|
|
145
|
+
RANK.indexOf(left.outcome) - RANK.indexOf(right.outcome) ||
|
|
146
|
+
left.name.localeCompare(right.name),
|
|
147
|
+
)
|
|
148
|
+
.map((one) => [
|
|
149
|
+
one.name,
|
|
150
|
+
one.kind,
|
|
151
|
+
one.outcome,
|
|
152
|
+
one.tenant?.verdict ?? '-',
|
|
153
|
+
one.crash?.verdict ?? '-',
|
|
154
|
+
one.code ?? '-',
|
|
155
|
+
String(one.statements),
|
|
156
|
+
String(one.effects.length),
|
|
157
|
+
String(one.ms),
|
|
158
|
+
]),
|
|
159
|
+
);
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// `apps/web/exercise.ts`, read: the one `defineExercise()` export an app may declare, or none. A
|
|
2
|
+
// file with no such export, or with two, is refused — an override nobody reads is a silent skip,
|
|
3
|
+
// and two are two answers to one question.
|
|
4
|
+
|
|
5
|
+
// why: Bun ships no path joiner.
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { UltimateError } from '@ultimat3/core';
|
|
8
|
+
import type { ExerciseOverrides } from '@ultimat3/testing';
|
|
9
|
+
import { EXERCISE_FILE } from './exercise-findings';
|
|
10
|
+
|
|
11
|
+
const isOverrides = (value: unknown): value is ExerciseOverrides =>
|
|
12
|
+
typeof value === 'object' &&
|
|
13
|
+
value !== null &&
|
|
14
|
+
(value as { readonly kind?: unknown }).kind === 'exercise-overrides';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The file exists and declares no single `defineExercise()` export. `X_CONTRACT_EXERCISE_UNDECLARED`
|
|
18
|
+
* — `@ultimat3/action`'s code for an exercise option that says nothing — because that is what a
|
|
19
|
+
* file of overrides the pass cannot find is.
|
|
20
|
+
*/
|
|
21
|
+
export class ExerciseOverridesUnreadableError extends UltimateError {
|
|
22
|
+
constructor(found: number) {
|
|
23
|
+
super({
|
|
24
|
+
code: 'X_CONTRACT_EXERCISE_UNDECLARED',
|
|
25
|
+
cause: `${EXERCISE_FILE} exports ${found === 0 ? 'no' : String(found)} defineExercise() value${found === 1 ? '' : 's'}, so x exercise cannot tell which overrides to apply`,
|
|
26
|
+
fix: `export const exercise = defineExercise({}); # one export in ${EXERCISE_FILE}, imported from @ultimat3/testing`,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The overrides, or `undefined` when the app has no `apps/web/exercise.ts`. */
|
|
32
|
+
export async function readExerciseOverrides(root: string): Promise<ExerciseOverrides | undefined> {
|
|
33
|
+
const path = join(root, EXERCISE_FILE);
|
|
34
|
+
if (!(await Bun.file(path).exists())) return undefined;
|
|
35
|
+
const module = (await import(path)) as Record<string, unknown>;
|
|
36
|
+
const found = Object.values(module).filter(isOverrides);
|
|
37
|
+
const [only] = found;
|
|
38
|
+
if (found.length !== 1 || only === undefined) {
|
|
39
|
+
throw new ExerciseOverridesUnreadableError(found.length);
|
|
40
|
+
}
|
|
41
|
+
return only;
|
|
42
|
+
}
|