@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
package/src/cmd-jobs-spec.ts
CHANGED
|
@@ -14,6 +14,7 @@ export const JOBS_SUBCOMMANDS = [
|
|
|
14
14
|
'cancel',
|
|
15
15
|
'delete',
|
|
16
16
|
'promote',
|
|
17
|
+
'resolve-step',
|
|
17
18
|
'pause',
|
|
18
19
|
'resume',
|
|
19
20
|
'drain',
|
|
@@ -24,9 +25,9 @@ export const jobsSpec: CommandSpec = {
|
|
|
24
25
|
// Never ENDING in "(planned)": that suffix is how `x help` and the planned-table tests tell a
|
|
25
26
|
// planned COMMAND from a shipped one, and `x jobs` ships — only its `drain` does not.
|
|
26
27
|
summary:
|
|
27
|
-
'list, show, retry, cancel, delete and promote jobs; pause and resume a queue (drain is planned)',
|
|
28
|
+
'list, show, retry, cancel, delete and promote jobs; resolve a step; pause and resume a queue (drain is planned)',
|
|
28
29
|
usage:
|
|
29
|
-
'x jobs [list|show <id>|retry <id>|cancel <id>|delete <id>|promote <id>|pause <queue>|resume <queue>|drain] [--queue q] [--state s] [--name n] [--limit n] [--after cursor] [--from-step name] [--reason text] [--json]',
|
|
30
|
+
'x jobs [list|show <id>|retry <id>|cancel <id>|delete <id>|promote <id>|resolve-step <id> <step>|pause <queue>|resume <queue>|drain] [--queue q] [--state s] [--name n] [--limit n] [--after cursor] [--from-step name] [--reason text] [--ran|--not-ran] [--output json] [--json]',
|
|
30
31
|
requiresApp: true,
|
|
31
32
|
subcommands: JOBS_SUBCOMMANDS,
|
|
32
33
|
// The bare `x jobs` lists; it never retries, cancels or drains anything.
|
|
@@ -57,5 +58,23 @@ export const jobsSpec: CommandSpec = {
|
|
|
57
58
|
summary: 'cancel: why, recorded on the job',
|
|
58
59
|
subcommands: ['cancel'],
|
|
59
60
|
},
|
|
61
|
+
{
|
|
62
|
+
name: 'ran',
|
|
63
|
+
type: 'boolean',
|
|
64
|
+
summary: 'resolve-step: the step happened — record --output as its answer',
|
|
65
|
+
subcommands: ['resolve-step'],
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: 'not-ran',
|
|
69
|
+
type: 'boolean',
|
|
70
|
+
summary: 'resolve-step: the step did not happen — the next run calls it again',
|
|
71
|
+
subcommands: ['resolve-step'],
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: 'output',
|
|
75
|
+
type: 'string',
|
|
76
|
+
summary: 'resolve-step: with --ran, the JSON a replay of the step returns (default null)',
|
|
77
|
+
subcommands: ['resolve-step'],
|
|
78
|
+
},
|
|
60
79
|
],
|
|
61
80
|
};
|
package/src/cmd-jobs.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
// `x jobs list|show|retry|cancel|delete|promote|pause|resume` — introspect and
|
|
2
|
-
// bound to `@ultimat3/jobs`'s own introspection so the CLI, `/_x` and MCP
|
|
3
|
-
// `drain` is planned (`cmd-planned.ts`): Postgres is the one durable driver.
|
|
4
|
-
// the driver-injected logic is `jobs-report.ts`, the `--json` shapes
|
|
5
|
-
// `jobs-
|
|
1
|
+
// `x jobs list|show|retry|cancel|delete|promote|resolve-step|pause|resume` — introspect and
|
|
2
|
+
// recover the job queue, bound to `@ultimat3/jobs`'s own introspection so the CLI, `/_x` and MCP
|
|
3
|
+
// report identically; `drain` is planned (`cmd-planned.ts`): Postgres is the one durable driver.
|
|
4
|
+
// CLI wiring only: the driver-injected logic is `jobs-report.ts`, the `--json` shapes
|
|
5
|
+
// `jobs-json.ts`, the table `jobs-table.ts`, the queue `jobs-driver.ts` — shared with `x db`.
|
|
6
6
|
|
|
7
7
|
import { renderFixShellArg } from '@ultimat3/core';
|
|
8
8
|
import type { JobDriver } from '@ultimat3/jobs';
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
} from '@ultimat3/jobs';
|
|
17
17
|
import { loadApp } from './app-load';
|
|
18
18
|
import { requireAppRoot } from './app-root';
|
|
19
|
+
import { runResolveStep } from './cmd-jobs-resolve';
|
|
19
20
|
import { jobsSpec } from './cmd-jobs-spec';
|
|
20
21
|
import { plannedSubcommand } from './cmd-planned';
|
|
21
22
|
import type { CliCommand, CommandContext } from './command';
|
|
@@ -250,7 +251,7 @@ async function runPause(
|
|
|
250
251
|
}
|
|
251
252
|
|
|
252
253
|
/** The subcommands that answer a `JobTrace`. `list`, `delete`, `promote` and the rest read rows only. */
|
|
253
|
-
const TRACE_SUBCOMMANDS: ReadonlySet<string> = new Set(['show', 'retry', 'cancel']);
|
|
254
|
+
const TRACE_SUBCOMMANDS: ReadonlySet<string> = new Set(['show', 'retry', 'cancel', 'resolve-step']);
|
|
254
255
|
|
|
255
256
|
export const jobsCommand: CliCommand = {
|
|
256
257
|
spec: jobsSpec,
|
|
@@ -274,6 +275,9 @@ export const jobsCommand: CliCommand = {
|
|
|
274
275
|
if (sub === 'cancel') return runCancel(driver, ctx);
|
|
275
276
|
if (sub === 'delete') return runDelete(driver, ctx);
|
|
276
277
|
if (sub === 'promote') return runPromote(driver, ctx);
|
|
278
|
+
if (sub === 'resolve-step') {
|
|
279
|
+
return runResolveStep(driver, ctx, requireIdPositional(ctx, sub, driver));
|
|
280
|
+
}
|
|
277
281
|
if (sub === 'pause' || sub === 'resume') return runPause(driver, ctx, sub);
|
|
278
282
|
return runList(driver, ctx);
|
|
279
283
|
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// `x mail`'s declaration, apart from its body: the parser, `x help` and the `errors` step read it
|
|
2
|
+
// without loading `cmd-mail.ts`, which `registry.ts` imports only when the command runs.
|
|
3
|
+
|
|
4
|
+
import type { CommandSpec } from './parse';
|
|
5
|
+
|
|
6
|
+
export const MAIL_SUBCOMMANDS = ['preview'] as const;
|
|
7
|
+
|
|
8
|
+
export const mailSpec: CommandSpec = {
|
|
9
|
+
name: 'mail',
|
|
10
|
+
summary: "render a mail's declared preview in every locale, nothing sent",
|
|
11
|
+
usage: 'x mail preview <id> [--locale <tag>] [--tz <zone>] [--out <dir>] [--json]',
|
|
12
|
+
requiresApp: true,
|
|
13
|
+
subcommands: MAIL_SUBCOMMANDS,
|
|
14
|
+
defaultSubcommand: 'preview',
|
|
15
|
+
flags: [
|
|
16
|
+
{ name: 'locale', type: 'string', summary: 'one locale; default every locale the app routes' },
|
|
17
|
+
{
|
|
18
|
+
name: 'tz',
|
|
19
|
+
type: 'string',
|
|
20
|
+
summary: 'IANA zone every date in the body renders in; default UTC, as the /_x panel',
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: 'out',
|
|
24
|
+
type: 'string',
|
|
25
|
+
summary: 'write <id>.<locale>.html and .txt into this directory',
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
};
|
package/src/cmd-mail.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// `x mail preview <id>` — a template checked in every locale without triggering the flow that
|
|
2
|
+
// sends it: `@ultimat3/mail`'s `previewMail` over the mail's declared `preview` data. A key a
|
|
3
|
+
// locale cannot resolve is named in `--json` and fails the command, as `send` would refuse it.
|
|
4
|
+
|
|
5
|
+
// why: Bun exposes no path-join primitive; `--out` is a directory the files are joined under.
|
|
6
|
+
import { join, resolve } from 'node:path';
|
|
7
|
+
import { appLocaleSet } from '@ultimat3/i18n/app-catalogs';
|
|
8
|
+
import { previewMail } from '@ultimat3/mail';
|
|
9
|
+
import { assertTimeZone } from '@ultimat3/time';
|
|
10
|
+
import { loadApp } from './app-load';
|
|
11
|
+
import { requireAppRoot } from './app-root';
|
|
12
|
+
import { mailSpec } from './cmd-mail-spec';
|
|
13
|
+
import type { CliCommand, CommandContext } from './command';
|
|
14
|
+
import { MissingPositionalError } from './errors';
|
|
15
|
+
import { msg } from './messages';
|
|
16
|
+
import type { CommandResult, JsonValue } from './output';
|
|
17
|
+
import { flagString } from './parse';
|
|
18
|
+
|
|
19
|
+
/** A sample has no reader, so no reader's zone: UTC unless `--tz` names one — never the ambient. */
|
|
20
|
+
const PREVIEW_ZONE = 'UTC';
|
|
21
|
+
|
|
22
|
+
export const mailCommand: CliCommand = {
|
|
23
|
+
spec: mailSpec,
|
|
24
|
+
async run(ctx: CommandContext): Promise<CommandResult> {
|
|
25
|
+
const root = requireAppRoot('mail', ctx.cwd).dir;
|
|
26
|
+
const id = ctx.args.positionals[0];
|
|
27
|
+
if (id === undefined) {
|
|
28
|
+
throw new MissingPositionalError({
|
|
29
|
+
command: 'mail preview',
|
|
30
|
+
positional: 'id',
|
|
31
|
+
example: 'x mail preview welcome --locale es',
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
await loadApp(root);
|
|
35
|
+
const asked = flagString(ctx.args, 'locale');
|
|
36
|
+
const locales = asked === undefined ? (await appLocaleSet(root)).locales : [asked];
|
|
37
|
+
const tz = assertTimeZone(flagString(ctx.args, 'tz') ?? PREVIEW_ZONE);
|
|
38
|
+
const out = flagString(ctx.args, 'out');
|
|
39
|
+
const previews: Record<string, JsonValue>[] = [];
|
|
40
|
+
for (const locale of locales) {
|
|
41
|
+
const preview = previewMail(id, { locale, tz });
|
|
42
|
+
const files: string[] = [];
|
|
43
|
+
if (out !== undefined) {
|
|
44
|
+
const base = join(resolve(ctx.cwd, out), `${id}.${preview.locale}`);
|
|
45
|
+
await Bun.write(`${base}.html`, preview.html);
|
|
46
|
+
await Bun.write(`${base}.txt`, preview.text);
|
|
47
|
+
files.push(`${base}.html`, `${base}.txt`);
|
|
48
|
+
}
|
|
49
|
+
previews.push({
|
|
50
|
+
locale: preview.locale,
|
|
51
|
+
subject: preview.subject,
|
|
52
|
+
missing: preview.misses.map((miss) => miss.key),
|
|
53
|
+
files,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
const missing = previews.filter(
|
|
57
|
+
(each) => Array.isArray(each['missing']) && each['missing'].length > 0,
|
|
58
|
+
);
|
|
59
|
+
return {
|
|
60
|
+
ok: missing.length === 0,
|
|
61
|
+
command: 'mail',
|
|
62
|
+
summary: msg(missing.length === 0 ? 'cli.mail.preview.ok' : 'cli.mail.preview.missing', {
|
|
63
|
+
id,
|
|
64
|
+
missing: missing.length,
|
|
65
|
+
locales: previews.length,
|
|
66
|
+
}),
|
|
67
|
+
data: { id, previews, tz },
|
|
68
|
+
};
|
|
69
|
+
},
|
|
70
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// `x mcp lock <name>` — fetch a remote MCP server's tools named by the app's `mcpTools({ name,
|
|
2
|
+
// allow })` and write what a reviewer accepts into `tools.lock.json`. `--check` writes nothing and
|
|
3
|
+
// fails on a drift (`X_MCP_REMOTE_TOOL_CHANGED`): the convention's build error, for `verify`.
|
|
4
|
+
|
|
5
|
+
// why: Bun exposes no path-join primitive; the lock sits at the app root.
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { renderFixShellArg } from '@ultimat3/core';
|
|
8
|
+
import {
|
|
9
|
+
assertLocked,
|
|
10
|
+
declaredMcpServers,
|
|
11
|
+
McpRemoteToolChangedError,
|
|
12
|
+
readToolsLock,
|
|
13
|
+
TOOLS_LOCK_FILE,
|
|
14
|
+
writeToolsLock,
|
|
15
|
+
} from '@ultimat3/mcp';
|
|
16
|
+
import { loadApp } from './app-load';
|
|
17
|
+
import type { CommandContext } from './command';
|
|
18
|
+
import { MissingPositionalError } from './errors';
|
|
19
|
+
import { msg } from './messages';
|
|
20
|
+
import type { CommandResult } from './output';
|
|
21
|
+
import { flagBool } from './parse';
|
|
22
|
+
|
|
23
|
+
export async function mcpLock(ctx: CommandContext, root: string): Promise<CommandResult> {
|
|
24
|
+
const name = ctx.args.positionals[0];
|
|
25
|
+
if (name === undefined) {
|
|
26
|
+
throw new MissingPositionalError({
|
|
27
|
+
command: 'mcp lock',
|
|
28
|
+
positional: 'name',
|
|
29
|
+
example: 'x mcp lock <name> --json # the name given to mcpTools({ name })',
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
// The app's own modules evaluate every `mcpTools()`; a missing lock is a load finding, not a stop.
|
|
33
|
+
await loadApp(root);
|
|
34
|
+
const server = declaredMcpServers().get(name);
|
|
35
|
+
if (server === undefined) {
|
|
36
|
+
throw new McpRemoteToolChangedError({
|
|
37
|
+
server: name,
|
|
38
|
+
changes: [
|
|
39
|
+
`no mcpTools({ name: ${renderFixShellArg(name, '<name>')} }) is declared (declared: ${[...declaredMcpServers().keys()].join(', ') || 'none'})`,
|
|
40
|
+
],
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
const file = join(root, TOOLS_LOCK_FILE);
|
|
44
|
+
const handle = Bun.file(file);
|
|
45
|
+
const previous: unknown = (await handle.exists())
|
|
46
|
+
? ((await handle.json()) as unknown)
|
|
47
|
+
: undefined;
|
|
48
|
+
const live = await server.client.listTools();
|
|
49
|
+
if (flagBool(ctx.args, 'check')) {
|
|
50
|
+
assertLocked(name, readToolsLock(previous).get(name), server.url, server.allow, live);
|
|
51
|
+
return {
|
|
52
|
+
ok: true,
|
|
53
|
+
command: 'mcp',
|
|
54
|
+
summary: msg('cli.mcp.lock_current', { server: name, file: TOOLS_LOCK_FILE }),
|
|
55
|
+
data: { server: name, file: TOOLS_LOCK_FILE, changed: false },
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
await Bun.write(file, writeToolsLock(previous, name, server.url, server.allow, live));
|
|
59
|
+
return {
|
|
60
|
+
ok: true,
|
|
61
|
+
command: 'mcp',
|
|
62
|
+
summary: msg('cli.mcp.locked', {
|
|
63
|
+
server: name,
|
|
64
|
+
tools: server.allow.length,
|
|
65
|
+
file: TOOLS_LOCK_FILE,
|
|
66
|
+
}),
|
|
67
|
+
data: { server: name, file: TOOLS_LOCK_FILE, tools: [...server.allow] },
|
|
68
|
+
};
|
|
69
|
+
}
|
package/src/cmd-mcp-spec.ts
CHANGED
|
@@ -8,14 +8,33 @@ export const DEFAULT_PORT = 9229;
|
|
|
8
8
|
export const mcpSpec: CommandSpec = {
|
|
9
9
|
name: 'mcp',
|
|
10
10
|
summary: 'serve the dev tools: routes, schema, policies, db, queues, logs, tests, verify',
|
|
11
|
-
usage:
|
|
11
|
+
usage:
|
|
12
|
+
'x mcp tools | x mcp serve [--transport stdio|http] [--port 9229] | x mcp lock <name> [--check] [--json]',
|
|
12
13
|
requiresApp: true,
|
|
13
|
-
subcommands: ['serve', 'tools'],
|
|
14
|
+
subcommands: ['serve', 'tools', 'lock'],
|
|
14
15
|
// No default, deliberately: `wiki/CLI-Reference.md` already says to write `x mcp serve` rather
|
|
15
16
|
// than a bare `x mcp`, and the parser's old first-element guess made the bare form START A
|
|
16
17
|
// SERVER — the one thing a word typed by mistake must not do. Refusing enforces the guidance.
|
|
17
18
|
flags: [
|
|
18
|
-
{
|
|
19
|
-
|
|
19
|
+
{
|
|
20
|
+
name: 'transport',
|
|
21
|
+
type: 'string',
|
|
22
|
+
summary: 'stdio | http',
|
|
23
|
+
default: 'stdio',
|
|
24
|
+
subcommands: ['serve', 'tools'],
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: 'port',
|
|
28
|
+
type: 'string',
|
|
29
|
+
summary: 'HTTP port',
|
|
30
|
+
default: String(DEFAULT_PORT),
|
|
31
|
+
subcommands: ['serve', 'tools'],
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: 'check',
|
|
35
|
+
type: 'boolean',
|
|
36
|
+
summary: 'compare the remote server to tools.lock.json, write nothing',
|
|
37
|
+
subcommands: ['lock'],
|
|
38
|
+
},
|
|
20
39
|
],
|
|
21
40
|
};
|
package/src/cmd-mcp.ts
CHANGED
|
@@ -10,13 +10,16 @@ import {
|
|
|
10
10
|
timingSafeEqual,
|
|
11
11
|
UltimateError,
|
|
12
12
|
} from '@ultimat3/core';
|
|
13
|
-
import {
|
|
13
|
+
import { jsonResponse, methodNotAllowed, toProblem } from '@ultimat3/http';
|
|
14
|
+
import { mcpHostRefused, mcpHttpRoute, type StdioTransportInput, serveStdio } from '@ultimat3/mcp';
|
|
14
15
|
import { requireAppRoot } from './app-root';
|
|
16
|
+
import { mcpLock } from './cmd-mcp-lock';
|
|
15
17
|
import { DEFAULT_PORT, mcpSpec } from './cmd-mcp-spec';
|
|
16
18
|
import type { CliCommand, CommandContext } from './command';
|
|
17
19
|
import { BadFlagError } from './errors';
|
|
18
20
|
import { intFlagOr, neighbouringPort, PORT_RANGE } from './flag-number';
|
|
19
21
|
import { holdUntilShutdown } from './hold';
|
|
22
|
+
import { isLoopbackHostname } from './loopback-host';
|
|
20
23
|
import type { CliMcpServer } from './mcp-host';
|
|
21
24
|
import { createDevMcpServer, DEV_TOOL_SCOPES } from './mcp-host';
|
|
22
25
|
import { msg } from './messages';
|
|
@@ -119,8 +122,18 @@ export async function startMcpHttp(host: CliMcpServer, port: number): Promise<Mc
|
|
|
119
122
|
idleTimeout: 0,
|
|
120
123
|
fetch: (request: Request, server): Response | Promise<Response> => {
|
|
121
124
|
const url = new URL(request.url);
|
|
122
|
-
|
|
123
|
-
|
|
125
|
+
// Where this server IS reached — every refusal's fix names it, never the Host a request sent.
|
|
126
|
+
const endpoint = `${server.url.origin}${route.path}`;
|
|
127
|
+
// Before anything else, as `/_x` does: this server serves this machine, and a rebound name
|
|
128
|
+
// reaches it with a Host and an Origin that agree, which no Origin rule can tell apart.
|
|
129
|
+
if (!isLoopbackHostname(url.hostname)) return mcpHostRefused(endpoint);
|
|
130
|
+
if (url.pathname !== route.path) return notFound(route.path);
|
|
131
|
+
// `@ultimat3/http`'s own 405, the one the pipeline answers on an app's mounted `/mcp`.
|
|
132
|
+
if (request.method !== route.method) {
|
|
133
|
+
return jsonResponse(toProblem(methodNotAllowed(request.method, route.path, ['POST'])), {
|
|
134
|
+
status: 405,
|
|
135
|
+
headers: { allow: 'POST' },
|
|
136
|
+
});
|
|
124
137
|
}
|
|
125
138
|
// The socket's peer: failed tokens are metered per address, and only the server knows it.
|
|
126
139
|
return route.handle(request, { address: server.requestIP(request)?.address });
|
|
@@ -211,6 +224,7 @@ export const mcpCommand: CliCommand = {
|
|
|
211
224
|
spec: mcpSpec,
|
|
212
225
|
async run(ctx: CommandContext): Promise<CommandResult> {
|
|
213
226
|
const root = requireAppRoot('mcp', ctx.cwd).dir;
|
|
227
|
+
if (ctx.args.subcommand === 'lock') return mcpLock(ctx, root);
|
|
214
228
|
const transport = flagString(ctx.args, 'transport') ?? 'stdio';
|
|
215
229
|
// Validated before the app is loaded: a typo must not cost a boot to report.
|
|
216
230
|
if (!isTransport(transport)) {
|
package/src/cmd-new-spec.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// `x new`'s declaration, apart from its body: the parser, `x help` and the `errors` step
|
|
2
2
|
// read it without loading `cmd-new.ts`, which `registry.ts` imports only when the command runs.
|
|
3
3
|
|
|
4
|
+
import { NEW_SHAPES } from './new-shapes';
|
|
4
5
|
import type { CommandSpec } from './parse';
|
|
5
6
|
|
|
6
7
|
export const newSpec: CommandSpec = {
|
|
@@ -9,7 +10,7 @@ export const newSpec: CommandSpec = {
|
|
|
9
10
|
// Every flag the table below declares, in the spelling that turns it off where the default is
|
|
10
11
|
// on: the usage line offered `--no-example` while the table listed `--example`, and a reader
|
|
11
12
|
// had to reconcile the two to answer "which one do I get if I type neither".
|
|
12
|
-
usage:
|
|
13
|
+
usage: `x new <name> [--dir path] [--shape ${NEW_SHAPES.join('|')}] [--no-example] [--no-git] [--dry-run] [--force] [--json]`,
|
|
13
14
|
flags: [
|
|
14
15
|
{ name: 'dir', type: 'string', summary: 'parent directory (default: cwd)' },
|
|
15
16
|
{
|
|
@@ -21,6 +22,12 @@ export const newSpec: CommandSpec = {
|
|
|
21
22
|
summary: 'include the example feature slice (default: on; --no-example for an empty app/)',
|
|
22
23
|
default: true,
|
|
23
24
|
},
|
|
25
|
+
{
|
|
26
|
+
// The list is `NEW_SHAPES`, so `x help new` cannot name a shape `x new` refuses.
|
|
27
|
+
name: 'shape',
|
|
28
|
+
type: 'string',
|
|
29
|
+
summary: `start from an app shape: ${NEW_SHAPES.join(', ')} (default: none — the dashboard and example slice; never with --no-example)`,
|
|
30
|
+
},
|
|
24
31
|
{
|
|
25
32
|
name: 'git',
|
|
26
33
|
type: 'boolean',
|
package/src/cmd-new.ts
CHANGED
|
@@ -14,17 +14,23 @@ import { AppNameEmptyError, AppNameIsPathError, MissingPositionalError } from '.
|
|
|
14
14
|
import type { Runner } from './exec';
|
|
15
15
|
import { reproducedFlags } from './invocation-flags';
|
|
16
16
|
import { msg } from './messages';
|
|
17
|
+
import { type NewShape, readShape } from './new-shapes';
|
|
18
|
+
import { runShapeGenerators } from './new-shapes-generate';
|
|
17
19
|
import type { CommandResult } from './output';
|
|
18
20
|
import { flagBool, flagString } from './parse';
|
|
19
21
|
import { quoteArg } from './shell-quote';
|
|
20
22
|
import type { GeneratedFile } from './templates';
|
|
21
23
|
import { appFiles, names, repoFiles, resourceFiles } from './templates';
|
|
24
|
+
import { applyOverlay, SHAPE_OVERLAYS } from './templates/shapes';
|
|
22
25
|
import { loadVersion } from './version-loader';
|
|
26
|
+
import { writeGenerated } from './write-generated';
|
|
23
27
|
|
|
24
28
|
export interface NewAppOptions {
|
|
25
29
|
readonly name: string;
|
|
26
30
|
/** The example feature slice. `--no-example` gives the same shape with an empty app/. */
|
|
27
31
|
readonly example: boolean;
|
|
32
|
+
/** `--shape`: an overlay over the scaffold below (`templates/shapes/`); absent is the default. */
|
|
33
|
+
readonly shape?: NewShape | undefined;
|
|
28
34
|
}
|
|
29
35
|
|
|
30
36
|
/** `--no-git`'s `problem`, matched rather than re-spelled where the report decides on a line. */
|
|
@@ -102,6 +108,21 @@ export async function initRepository(runner: Runner, dir: string): Promise<Repos
|
|
|
102
108
|
return { initialized: true, committed: true, problem: null };
|
|
103
109
|
}
|
|
104
110
|
|
|
111
|
+
/** git's refusal of a commit on a box with no `user.name` / `user.email` configured. */
|
|
112
|
+
const NO_IDENTITY = /Author identity unknown|Please tell me who you are|empty ident/i;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* The commands that finish what `initRepository` could not: no `git init` once `.git` exists, and
|
|
116
|
+
* the identity first when that is what refused, or the pasted commit fails the same way.
|
|
117
|
+
*/
|
|
118
|
+
export function repositoryFix(git: RepositoryInit): string {
|
|
119
|
+
const commit = "git add -A && git commit -m 'x new'";
|
|
120
|
+
if (!git.initialized) return `git init && ${commit}`;
|
|
121
|
+
return NO_IDENTITY.test(git.problem ?? '')
|
|
122
|
+
? `git config user.name '<your name>' && git config user.email '<you@example.com>' && ${commit}`
|
|
123
|
+
: commit;
|
|
124
|
+
}
|
|
125
|
+
|
|
105
126
|
/** `path` relative to `cwd` when it lies under it, else absolute — never a `../../` walk. */
|
|
106
127
|
const displayPath = (cwd: string, path: string): string => {
|
|
107
128
|
const rel = relative(cwd, path);
|
|
@@ -111,8 +132,18 @@ const displayPath = (cwd: string, path: string): string => {
|
|
|
111
132
|
/** The `cd` operand of the next-steps line: the target as typed from `cwd`, pasteable verbatim. */
|
|
112
133
|
const cdTarget = (cwd: string, target: string): string => quoteArg(displayPath(cwd, target));
|
|
113
134
|
|
|
135
|
+
/** The `x g` lines a shape runs once its files are written, as a reader would type them. */
|
|
136
|
+
const shapeGenerateLines = (shape: NewShape): readonly string[] =>
|
|
137
|
+
(SHAPE_OVERLAYS[shape].generate ?? []).map((argv) => ['x', 'g', ...argv].join(' '));
|
|
138
|
+
|
|
114
139
|
/** Pure: the complete file list for a new app, so `--dry-run` and the test see the same thing. */
|
|
115
140
|
export function planNewApp(options: NewAppOptions): readonly GeneratedFile[] {
|
|
141
|
+
if (options.shape !== undefined) {
|
|
142
|
+
const overlay = SHAPE_OVERLAYS[options.shape];
|
|
143
|
+
const base = planNewApp({ name: options.name, example: overlay.example });
|
|
144
|
+
// Merge-aware, as below: a shape's catalog keys land in the one catalog the base wrote.
|
|
145
|
+
return dedupe(applyOverlay(base, overlay, names(options.name)));
|
|
146
|
+
}
|
|
116
147
|
const app = names(options.name);
|
|
117
148
|
const files: GeneratedFile[] = [
|
|
118
149
|
...repoFiles(app, loadVersion(), options.example),
|
|
@@ -159,7 +190,8 @@ export interface WrittenApp {
|
|
|
159
190
|
*/
|
|
160
191
|
export async function writeNewApp(target: string, options: NewAppOptions): Promise<WrittenApp> {
|
|
161
192
|
const files = planNewApp(options);
|
|
162
|
-
|
|
193
|
+
// In order, so a link's target (`CLAUDE.md` before `AGENTS.md`) is on disk when it lands.
|
|
194
|
+
for (const file of files) await writeGenerated(target, file);
|
|
163
195
|
return { dir: target, files: files.map((file) => file.path) };
|
|
164
196
|
}
|
|
165
197
|
|
|
@@ -231,7 +263,13 @@ export const newCommand: CliCommand = {
|
|
|
231
263
|
});
|
|
232
264
|
}
|
|
233
265
|
const target = resolve(parentDir(ctx.cwd, flagString(ctx.args, 'dir')), app.kebab);
|
|
234
|
-
|
|
266
|
+
// Before the target is touched: a refused shape writes nothing, `--dry-run` included.
|
|
267
|
+
const shape = readShape(ctx.args, invocationOf(ctx, 'new'), app.kebab);
|
|
268
|
+
const options: NewAppOptions = {
|
|
269
|
+
name: raw,
|
|
270
|
+
example: ctx.args.flags.get('example') !== false,
|
|
271
|
+
shape,
|
|
272
|
+
};
|
|
235
273
|
|
|
236
274
|
if (flagBool(ctx.args, 'dry-run')) {
|
|
237
275
|
const files = planNewApp(options);
|
|
@@ -243,7 +281,13 @@ export const newCommand: CliCommand = {
|
|
|
243
281
|
dir: target,
|
|
244
282
|
cd: cdTarget(ctx.cwd, target),
|
|
245
283
|
}),
|
|
246
|
-
data: {
|
|
284
|
+
data: {
|
|
285
|
+
dir: target,
|
|
286
|
+
files: files.map((file) => file.path),
|
|
287
|
+
// What a real run then runs in the app, so a dry run names the files it cannot plan.
|
|
288
|
+
...(shape === undefined ? {} : { generate: shapeGenerateLines(shape) }),
|
|
289
|
+
dryRun: true,
|
|
290
|
+
},
|
|
247
291
|
// Where each file WOULD land, as the caller would reach it: relative to the cwd, `--dir`
|
|
248
292
|
// included — `${app.kebab}/…` named the wrong place whenever `--dir` was set.
|
|
249
293
|
lines: files.map((file) =>
|
|
@@ -273,26 +317,34 @@ export const newCommand: CliCommand = {
|
|
|
273
317
|
// `git add -A && git commit` them — they are the user's, and `x new` did not write them.
|
|
274
318
|
const existed = existsSync(target);
|
|
275
319
|
const written = await writeNewApp(target, options);
|
|
320
|
+
// Before the first commit, so the repository starts with what the shape's generators wrote.
|
|
321
|
+
const generated =
|
|
322
|
+
shape === undefined
|
|
323
|
+
? []
|
|
324
|
+
: await runShapeGenerators(ctx, shape, target, SHAPE_OVERLAYS[shape].generate ?? []);
|
|
276
325
|
const git =
|
|
277
326
|
ctx.args.flags.get('git') === false
|
|
278
327
|
? NO_GIT
|
|
279
328
|
: existed
|
|
280
329
|
? PRE_EXISTING
|
|
281
330
|
: await initRepository(ctx.runner, target);
|
|
282
|
-
const
|
|
331
|
+
const files = [...new Set([...written.files, ...generated])];
|
|
332
|
+
const lines = [msg('cli.new.wrote', { count: files.length, dir: target })];
|
|
283
333
|
if (git.problem !== null && git.problem !== SKIPPED && git !== PRE_EXISTING) {
|
|
284
|
-
lines.push(msg('cli.new.noRepository', { problem: git.problem }));
|
|
285
|
-
// Raw, unlike the two prose lines around it: this one is an instruction to run verbatim, and
|
|
286
|
-
// a translated command is a broken one (`packages/cli/CLAUDE.md`).
|
|
287
334
|
lines.push(
|
|
288
|
-
|
|
335
|
+
msg(git.initialized ? 'cli.new.uncommitted' : 'cli.new.noRepository', {
|
|
336
|
+
problem: git.problem,
|
|
337
|
+
}),
|
|
289
338
|
);
|
|
339
|
+
// Raw, unlike the two prose lines around it: this one is an instruction to run verbatim, and
|
|
340
|
+
// a translated command is a broken one (`packages/cli/CLAUDE.md`).
|
|
341
|
+
lines.push(` run: cd ${quoteArg(target)} && ${repositoryFix(git)}`);
|
|
290
342
|
}
|
|
291
343
|
return {
|
|
292
344
|
ok: true,
|
|
293
345
|
command: 'new',
|
|
294
346
|
summary: msg('cli.new.done', { name: app.kebab, cd: cdTarget(ctx.cwd, target) }),
|
|
295
|
-
data: { dir: written.dir, files
|
|
347
|
+
data: { dir: written.dir, files, git },
|
|
296
348
|
lines,
|
|
297
349
|
};
|
|
298
350
|
},
|
package/src/cmd-planned.ts
CHANGED
|
@@ -42,12 +42,6 @@ export const PLANNED_COMMANDS: readonly PlannedCommand[] = [
|
|
|
42
42
|
usage: 'x status [--json]',
|
|
43
43
|
fix: 'x doctor --json',
|
|
44
44
|
},
|
|
45
|
-
{
|
|
46
|
-
name: 'upgrade',
|
|
47
|
-
summary: 'move every @ultimat3/* in lockstep, with codemods',
|
|
48
|
-
usage: 'x upgrade [--dry-run] [--json]',
|
|
49
|
-
fix: 'bun update --latest && x verify',
|
|
50
|
-
},
|
|
51
45
|
{
|
|
52
46
|
name: 'logs',
|
|
53
47
|
summary: 'structured logs and OTel spans, filterable',
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// `x repro`'s declaration, apart from its body: the parser, `x help` and the `errors` step read it
|
|
2
|
+
// without loading `cmd-repro.ts`, which `registry.ts` imports only when the command runs.
|
|
3
|
+
|
|
4
|
+
import type { CommandSpec } from './parse';
|
|
5
|
+
|
|
6
|
+
export const reproSpec: CommandSpec = {
|
|
7
|
+
name: 'repro',
|
|
8
|
+
summary: 'a dead job or a captured 5xx, as a failing test: x repro <id> --write --verify',
|
|
9
|
+
usage:
|
|
10
|
+
'x repro <jobId|requestId|traceId|fingerprint> [--write] [--verify] | x repro --list [--code X_…] | x repro --from <file> [--write] [--verify] [--json]',
|
|
11
|
+
requiresApp: true,
|
|
12
|
+
flags: [
|
|
13
|
+
{
|
|
14
|
+
name: 'write',
|
|
15
|
+
type: 'boolean',
|
|
16
|
+
summary: 'write the test to repro/repro-<id>.<kind>.test.ts',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
name: 'verify',
|
|
20
|
+
type: 'boolean',
|
|
21
|
+
summary: 'run the written test and answer { reproduced, code, fingerprint }',
|
|
22
|
+
},
|
|
23
|
+
{ name: 'list', type: 'boolean', summary: 'the captured bundles, newest first' },
|
|
24
|
+
{ name: 'code', type: 'string', summary: 'list: only bundles of this X_* code' },
|
|
25
|
+
{
|
|
26
|
+
name: 'from',
|
|
27
|
+
type: 'string',
|
|
28
|
+
summary: 'a bundle file (x repro <id> --json) instead of the database',
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
};
|