@intentius/chant 0.124.27 → 0.124.28
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/README.md +1 -1
- package/dist/__golden__/wave-loop/fan-out-gated.json +8 -0
- package/dist/__golden__/wave-loop/fan-out-no-members.json +4 -0
- package/dist/__golden__/wave-loop/fan-out-only.json +6 -0
- package/dist/__golden__/wave-loop/op-by-apply-hold.json +2 -0
- package/dist/__golden__/wave-loop/op-cost-member.json +3 -0
- package/dist/__golden__/wave-loop/op-held-on-before-apply.json +1 -0
- package/dist/__golden__/wave-loop/op-held-on.json +2 -0
- package/dist/__golden__/wave-loop/op-hooks-held.json +2 -0
- package/dist/__golden__/wave-loop/op-hooks-hold-before-apply.json +2 -0
- package/dist/__golden__/wave-loop/op-hooks-refused-at-base.json +3 -0
- package/dist/__golden__/wave-loop/op-hooks-share.json +3 -0
- package/dist/__golden__/wave-loop/op-per-run.json +7 -0
- package/dist/__golden__/wave-loop/op-pr-review.json +7 -0
- package/dist/__golden__/wave-loop/op-sealed.json +3 -0
- package/dist/__golden__/wave-loop/op-spend.json +3 -0
- package/dist/__golden__/wave-loop/op-wave-commands.json +7 -0
- package/dist/__golden__/wave-loop/op-wide-waiting.json +4 -0
- package/dist/change-set-graph.d.ts +56 -0
- package/dist/change-set-graph.d.ts.map +1 -0
- package/dist/change-set-graph.js +128 -0
- package/dist/change-set.d.ts +52 -3
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +35 -4
- package/dist/chant-version.d.ts +9 -0
- package/dist/chant-version.d.ts.map +1 -0
- package/dist/chant-version.js +16 -0
- package/dist/cli/commands/build.d.ts +8 -0
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/build.js +9 -8
- package/dist/cli/commands/check-lexicon-docs.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon-docs.js +30 -2
- package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon.js +13 -2
- package/dist/cli/commands/init-lexicon/templates/docs.d.ts +4 -1
- package/dist/cli/commands/init-lexicon/templates/docs.d.ts.map +1 -1
- package/dist/cli/commands/init-lexicon/templates/docs.js +6 -6
- package/dist/cli/commands/init-lexicon.d.ts.map +1 -1
- package/dist/cli/commands/init-lexicon.js +17 -8
- package/dist/cli/handlers/approve.d.ts.map +1 -1
- package/dist/cli/handlers/approve.js +19 -4
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/build.js +28 -0
- package/dist/cli/handlers/change-set.d.ts +10 -0
- package/dist/cli/handlers/change-set.d.ts.map +1 -1
- package/dist/cli/handlers/change-set.js +51 -1
- package/dist/cli/handlers/fan-out.d.ts +1 -1
- package/dist/cli/handlers/fan-out.d.ts.map +1 -1
- package/dist/cli/handlers/fan-out.js +14 -3
- package/dist/cli/handlers/pr.d.ts +8 -2
- package/dist/cli/handlers/pr.d.ts.map +1 -1
- package/dist/cli/handlers/pr.js +148 -24
- package/dist/cli/handlers/promote.d.ts.map +1 -1
- package/dist/cli/handlers/promote.js +256 -21
- package/dist/cli/handlers/release.d.ts +34 -0
- package/dist/cli/handlers/release.d.ts.map +1 -0
- package/dist/cli/handlers/release.js +203 -0
- package/dist/cli/handlers/run.d.ts +28 -2
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/handlers/run.js +170 -13
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +88 -13
- package/dist/cli/mcp/workspace-tools.js +1 -1
- package/dist/cli/parse-args.d.ts.map +1 -1
- package/dist/cli/parse-args.js +49 -3
- package/dist/cli/registry.d.ts +29 -3
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/cli/run-events.d.ts +29 -0
- package/dist/cli/run-events.d.ts.map +1 -0
- package/dist/cli/run-events.js +47 -0
- package/dist/cli/version.d.ts +3 -5
- package/dist/cli/version.d.ts.map +1 -1
- package/dist/cli/version.js +3 -13
- package/dist/codegen/docs-in-site.d.ts +71 -0
- package/dist/codegen/docs-in-site.d.ts.map +1 -0
- package/dist/codegen/docs-in-site.js +177 -0
- package/dist/codegen/docs-types.d.ts +14 -1
- package/dist/codegen/docs-types.d.ts.map +1 -1
- package/dist/codegen/docs.d.ts +18 -1
- package/dist/codegen/docs.d.ts.map +1 -1
- package/dist/codegen/docs.js +40 -7
- package/dist/codegen/rehype-base-url.mjs +15 -9
- package/dist/components/auto-release.d.ts +4 -1
- package/dist/components/auto-release.d.ts.map +1 -1
- package/dist/components/auto-release.js +2 -0
- package/dist/components/cli-support.d.ts +6 -0
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/cli-support.js +10 -9
- package/dist/components/driver.d.ts +42 -9
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/driver.js +81 -54
- package/dist/components/fan-out-run.d.ts +7 -0
- package/dist/components/fan-out-run.d.ts.map +1 -1
- package/dist/components/fan-out-run.js +35 -26
- package/dist/components/pr-run.d.ts +11 -0
- package/dist/components/pr-run.d.ts.map +1 -1
- package/dist/components/pr-run.js +20 -1
- package/dist/components/promote-order.d.ts +38 -0
- package/dist/components/promote-order.d.ts.map +1 -0
- package/dist/components/promote-order.js +62 -0
- package/dist/components/promote.d.ts +15 -2
- package/dist/components/promote.d.ts.map +1 -1
- package/dist/components/promote.js +17 -0
- package/dist/components/release-compile.d.ts +133 -0
- package/dist/components/release-compile.d.ts.map +1 -0
- package/dist/components/release-compile.js +376 -0
- package/dist/components/release-gate.d.ts +125 -0
- package/dist/components/release-gate.d.ts.map +1 -0
- package/dist/components/release-gate.js +282 -0
- package/dist/components/release-promote.d.ts +96 -0
- package/dist/components/release-promote.d.ts.map +1 -0
- package/dist/components/release-promote.js +204 -0
- package/dist/components/run-progress.d.ts +7 -0
- package/dist/components/run-progress.d.ts.map +1 -1
- package/dist/components/wave-plan.d.ts +3 -1
- package/dist/components/wave-plan.d.ts.map +1 -1
- package/dist/components/wave-plan.js +1 -1
- package/dist/config.d.ts +7 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/lifecycle/change-set-store.d.ts +67 -0
- package/dist/lifecycle/change-set-store.d.ts.map +1 -0
- package/dist/lifecycle/change-set-store.js +96 -0
- package/dist/lifecycle/commit-sha.d.ts +13 -0
- package/dist/lifecycle/commit-sha.d.ts.map +1 -0
- package/dist/lifecycle/commit-sha.js +21 -0
- package/dist/lifecycle/gate-change-set.d.ts +69 -0
- package/dist/lifecycle/gate-change-set.d.ts.map +1 -0
- package/dist/lifecycle/gate-change-set.js +106 -0
- package/dist/lifecycle/gate-ledger.d.ts +49 -1
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
- package/dist/lifecycle/gate-ledger.js +18 -0
- package/dist/lifecycle/release-cut.d.ts +107 -0
- package/dist/lifecycle/release-cut.d.ts.map +1 -0
- package/dist/lifecycle/release-cut.js +273 -0
- package/dist/lifecycle/release-ledger.d.ts +54 -0
- package/dist/lifecycle/release-ledger.d.ts.map +1 -1
- package/dist/lifecycle/release-ledger.js +53 -20
- package/dist/lifecycle/release-store.d.ts +190 -0
- package/dist/lifecycle/release-store.d.ts.map +1 -0
- package/dist/lifecycle/release-store.js +318 -0
- package/dist/lifecycle/run-ledger.d.ts +4 -0
- package/dist/lifecycle/run-ledger.d.ts.map +1 -1
- package/dist/lifecycle/run-ledger.js +2 -0
- package/dist/op/activities/change-coverage.d.ts.map +1 -1
- package/dist/op/activities/change-coverage.js +4 -2
- package/dist/op/activities/source-release.d.ts.map +1 -1
- package/dist/op/activities/source-release.js +7 -3
- package/dist/op/activities/source-rollback.d.ts.map +1 -1
- package/dist/op/activities/source-rollback.js +7 -3
- package/dist/op/gate-name.d.ts +11 -2
- package/dist/op/gate-name.d.ts.map +1 -1
- package/dist/op/gate-name.js +12 -3
- package/dist/op/gate-run-locator.d.ts +22 -0
- package/dist/op/gate-run-locator.d.ts.map +1 -1
- package/dist/op/gate-run-locator.js +31 -0
- package/dist/op/gate.d.ts +27 -3
- package/dist/op/gate.d.ts.map +1 -1
- package/dist/op/gate.js +57 -3
- package/dist/op/local-executor.d.ts +36 -1
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/local-executor.js +158 -52
- package/dist/op/op-waves-run.d.ts.map +1 -1
- package/dist/op/op-waves-run.js +4 -0
- package/dist/op/run-live.d.ts +2 -0
- package/dist/op/run-live.d.ts.map +1 -1
- package/dist/op/run-live.js +1 -1
- package/dist/op/runtime.d.ts +46 -0
- package/dist/op/runtime.d.ts.map +1 -1
- package/dist/op/runtimes/local.d.ts.map +1 -1
- package/dist/op/runtimes/local.js +8 -0
- package/dist/point-decision.d.ts +2 -0
- package/dist/point-decision.d.ts.map +1 -1
- package/dist/point-decision.js +9 -2
- package/dist/run-event-store.d.ts +77 -0
- package/dist/run-event-store.d.ts.map +1 -0
- package/dist/run-event-store.js +204 -0
- package/dist/run-event-stream.d.ts +132 -0
- package/dist/run-event-stream.d.ts.map +1 -0
- package/dist/run-event-stream.js +238 -0
- package/dist/run-event.d.ts +267 -0
- package/dist/run-event.d.ts.map +1 -0
- package/dist/run-event.js +207 -0
- package/dist/wave-runtime.mjs +204 -199
- package/dist/workspace/__fixtures__/run-event-check.d.ts +19 -0
- package/dist/workspace/__fixtures__/run-event-check.d.ts.map +1 -0
- package/dist/workspace/__fixtures__/run-event-check.js +59 -0
- package/dist/workspace/change-set-show.d.ts +56 -0
- package/dist/workspace/change-set-show.d.ts.map +1 -0
- package/dist/workspace/change-set-show.js +54 -0
- package/dist/workspace/change-set-show.schema.json +102 -0
- package/dist/workspace/change-set.schema.json +28 -1
- package/dist/workspace/changes-cli.d.ts.map +1 -1
- package/dist/workspace/changes-cli.js +10 -1
- package/dist/workspace/changes.d.ts +37 -0
- package/dist/workspace/changes.d.ts.map +1 -1
- package/dist/workspace/changes.js +49 -2
- package/dist/workspace/changes.schema.json +135 -0
- package/dist/workspace/ci-green-cli.d.ts +19 -4
- package/dist/workspace/ci-green-cli.d.ts.map +1 -1
- package/dist/workspace/ci-green-cli.js +75 -12
- package/dist/workspace/ci-green-forge.d.ts +20 -3
- package/dist/workspace/ci-green-forge.d.ts.map +1 -1
- package/dist/workspace/ci-green-forge.js +6 -4
- package/dist/workspace/ci-green-workflow.d.ts +11 -0
- package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
- package/dist/workspace/ci-green-workflow.js +6 -0
- package/dist/workspace/ci-green.d.ts +273 -13
- package/dist/workspace/ci-green.d.ts.map +1 -1
- package/dist/workspace/ci-green.js +449 -38
- package/dist/workspace/ci-last-green.schema.json +32 -2
- package/dist/workspace/ci-list.schema.json +525 -0
- package/dist/workspace/commit-links.d.ts +85 -0
- package/dist/workspace/commit-links.d.ts.map +1 -0
- package/dist/workspace/commit-links.js +163 -0
- package/dist/workspace/declaration.d.ts +19 -0
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.js +1 -1
- package/dist/workspace/declaration.schema.json +12 -0
- package/dist/workspace/identity-gate-rule.d.ts +37 -0
- package/dist/workspace/identity-gate-rule.d.ts.map +1 -1
- package/dist/workspace/identity-gate-rule.js +72 -7
- package/dist/workspace/identity.d.ts +12 -0
- package/dist/workspace/identity.d.ts.map +1 -1
- package/dist/workspace/identity.js +13 -1
- package/dist/workspace/intent.d.ts +6 -1
- package/dist/workspace/intent.d.ts.map +1 -1
- package/dist/workspace/intent.js +51 -1
- package/dist/workspace/ls.d.ts +4 -0
- package/dist/workspace/ls.d.ts.map +1 -1
- package/dist/workspace/ls.js +2 -0
- package/dist/workspace/ls.schema.json +3 -1
- package/dist/workspace/member-pipeline.d.ts +6 -0
- package/dist/workspace/member-pipeline.d.ts.map +1 -1
- package/dist/workspace/member-pipeline.js +6 -0
- package/dist/workspace/promotion-order.d.ts +41 -0
- package/dist/workspace/promotion-order.d.ts.map +1 -0
- package/dist/workspace/promotion-order.js +95 -0
- package/dist/workspace/reason-codes.d.ts +2 -0
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/reason-codes.js +4 -1
- package/dist/workspace/releases.d.ts +72 -0
- package/dist/workspace/releases.d.ts.map +1 -0
- package/dist/workspace/releases.js +94 -0
- package/dist/workspace/releases.schema.json +391 -0
- package/dist/workspace/run-event.schema.json +288 -0
- package/dist/workspace/status-gates.d.ts +26 -1
- package/dist/workspace/status-gates.d.ts.map +1 -1
- package/dist/workspace/status-gates.js +31 -2
- package/dist/workspace/status-stewards.d.ts +4 -0
- package/dist/workspace/status-stewards.d.ts.map +1 -1
- package/dist/workspace/status-stewards.js +2 -0
- package/dist/workspace/status.d.ts +39 -0
- package/dist/workspace/status.d.ts.map +1 -1
- package/dist/workspace/status.js +48 -1
- package/dist/workspace/status.schema.json +70 -1
- package/dist/workspace/trust/ci-tag-statement.d.ts +102 -0
- package/dist/workspace/trust/ci-tag-statement.d.ts.map +1 -0
- package/dist/workspace/trust/ci-tag-statement.js +123 -0
- package/dist/workspace/trust/op-run-statement.d.ts +91 -0
- package/dist/workspace/trust/op-run-statement.d.ts.map +1 -0
- package/dist/workspace/trust/op-run-statement.js +143 -0
- package/dist/workspace/trust/rotation.d.ts +18 -2
- package/dist/workspace/trust/rotation.d.ts.map +1 -1
- package/dist/workspace/trust/rotation.js +65 -16
- package/dist/workspace/trust/seal.d.ts +11 -0
- package/dist/workspace/trust/seal.d.ts.map +1 -1
- package/dist/workspace/trust/seal.js +9 -0
- package/dist/workspace/trust/ssh-commit.d.ts.map +1 -1
- package/dist/workspace/trust/ssh-commit.js +10 -10
- package/package.json +1 -1
- package/src/__golden__/wave-loop/fan-out-gated.json +8 -0
- package/src/__golden__/wave-loop/fan-out-no-members.json +4 -0
- package/src/__golden__/wave-loop/fan-out-only.json +6 -0
- package/src/__golden__/wave-loop/op-by-apply-hold.json +2 -0
- package/src/__golden__/wave-loop/op-cost-member.json +3 -0
- package/src/__golden__/wave-loop/op-held-on-before-apply.json +1 -0
- package/src/__golden__/wave-loop/op-held-on.json +2 -0
- package/src/__golden__/wave-loop/op-hooks-held.json +2 -0
- package/src/__golden__/wave-loop/op-hooks-hold-before-apply.json +2 -0
- package/src/__golden__/wave-loop/op-hooks-refused-at-base.json +3 -0
- package/src/__golden__/wave-loop/op-hooks-share.json +3 -0
- package/src/__golden__/wave-loop/op-per-run.json +7 -0
- package/src/__golden__/wave-loop/op-pr-review.json +7 -0
- package/src/__golden__/wave-loop/op-sealed.json +3 -0
- package/src/__golden__/wave-loop/op-spend.json +3 -0
- package/src/__golden__/wave-loop/op-wave-commands.json +7 -0
- package/src/__golden__/wave-loop/op-wide-waiting.json +4 -0
- package/src/change-set-graph.test.ts +72 -0
- package/src/change-set-graph.ts +133 -0
- package/src/change-set.ts +76 -6
- package/src/chant-version.ts +16 -0
- package/src/cli/commands/build.ts +19 -8
- package/src/cli/commands/check-lexicon-docs.test.ts +51 -0
- package/src/cli/commands/check-lexicon-docs.ts +39 -2
- package/src/cli/commands/check-lexicon.ts +14 -2
- package/src/cli/commands/init-lexicon/templates/docs.ts +7 -6
- package/src/cli/commands/init-lexicon.test.ts +25 -0
- package/src/cli/commands/init-lexicon.ts +18 -8
- package/src/cli/handlers/approve-release.test.ts +80 -0
- package/src/cli/handlers/approve.ts +18 -2
- package/src/cli/handlers/build.test.ts +52 -0
- package/src/cli/handlers/build.ts +29 -0
- package/src/cli/handlers/change-set.ts +49 -1
- package/src/cli/handlers/fan-out.test.ts +48 -0
- package/src/cli/handlers/fan-out.ts +15 -3
- package/src/cli/handlers/operator.test.ts +37 -0
- package/src/cli/handlers/pr.test.ts +194 -0
- package/src/cli/handlers/pr.ts +168 -34
- package/src/cli/handlers/promote.test.ts +389 -5
- package/src/cli/handlers/promote.ts +279 -20
- package/src/cli/handlers/release-implicit.e2e.test.ts +153 -0
- package/src/cli/handlers/release.e2e.test.ts +85 -0
- package/src/cli/handlers/release.ts +206 -0
- package/src/cli/handlers/run-auto-release.test.ts +71 -0
- package/src/cli/handlers/run-events-cmd.test.ts +92 -0
- package/src/cli/handlers/run.test.ts +30 -0
- package/src/cli/handlers/run.ts +176 -15
- package/src/cli/main.test.ts +7 -0
- package/src/cli/main.ts +88 -13
- package/src/cli/mcp/never-over-mcp.test.ts +2 -0
- package/src/cli/mcp/workspace-tools.ts +1 -1
- package/src/cli/parse-args.ts +37 -3
- package/src/cli/registry.ts +29 -3
- package/src/cli/run-events.ts +54 -0
- package/src/cli/version.ts +3 -13
- package/src/codegen/docs-in-site.test.ts +166 -0
- package/src/codegen/docs-in-site.ts +182 -0
- package/src/codegen/docs-site/AgentPrompt.astro +26 -1
- package/src/codegen/docs-types.ts +14 -1
- package/src/codegen/docs.ts +60 -7
- package/src/codegen/rehype-base-url.d.mts +3 -1
- package/src/codegen/rehype-base-url.mjs +15 -9
- package/src/codegen/rehype-base-url.test.ts +34 -0
- package/src/components/auto-release.test.ts +21 -0
- package/src/components/auto-release.ts +6 -1
- package/src/components/cli-support.test.ts +82 -0
- package/src/components/cli-support.ts +16 -9
- package/src/components/driver.ts +129 -56
- package/src/components/fan-out-run.ts +56 -35
- package/src/components/pr-run.ts +30 -1
- package/src/components/promote-order.test.ts +90 -0
- package/src/components/promote-order.ts +75 -0
- package/src/components/promote.test.ts +10 -1
- package/src/components/promote.ts +26 -1
- package/src/components/release-compile.test.ts +147 -0
- package/src/components/release-compile.ts +445 -0
- package/src/components/release-gate.test.ts +215 -0
- package/src/components/release-gate.ts +341 -0
- package/src/components/release-promote.test.ts +159 -0
- package/src/components/release-promote.ts +232 -0
- package/src/components/run-progress.ts +7 -0
- package/src/components/wave-plan.ts +1 -1
- package/src/config.ts +7 -1
- package/src/index.ts +2 -0
- package/src/lifecycle/change-set-store.test.ts +217 -0
- package/src/lifecycle/change-set-store.ts +112 -0
- package/src/lifecycle/commit-sha.ts +23 -0
- package/src/lifecycle/gate-change-set.e2e.test.ts +122 -0
- package/src/lifecycle/gate-change-set.ts +133 -0
- package/src/lifecycle/gate-ledger.test.ts +33 -0
- package/src/lifecycle/gate-ledger.ts +66 -1
- package/src/lifecycle/release-cut-signed.test.ts +180 -0
- package/src/lifecycle/release-cut.test.ts +411 -0
- package/src/lifecycle/release-cut.ts +367 -0
- package/src/lifecycle/release-ledger.test.ts +14 -0
- package/src/lifecycle/release-ledger.ts +85 -17
- package/src/lifecycle/release-store.ts +423 -0
- package/src/lifecycle/run-ledger.test.ts +10 -0
- package/src/lifecycle/run-ledger.ts +6 -0
- package/src/op/activities/change-coverage.ts +4 -1
- package/src/op/activities/source-release.ts +8 -3
- package/src/op/activities/source-rollback.ts +8 -4
- package/src/op/commit-run-gate-deploy-join.test.ts +117 -0
- package/src/op/gate-name.ts +13 -3
- package/src/op/gate-release.test.ts +126 -0
- package/src/op/gate-run-locator.test.ts +110 -0
- package/src/op/gate-run-locator.ts +35 -0
- package/src/op/gate.ts +88 -6
- package/src/op/local-executor.ts +207 -52
- package/src/op/op-waves-run.ts +4 -0
- package/src/op/run-events.test.ts +166 -0
- package/src/op/run-live.ts +1 -1
- package/src/op/runtime.ts +39 -0
- package/src/op/runtimes/local.test.ts +16 -0
- package/src/op/runtimes/local.ts +9 -0
- package/src/point-decision.ts +12 -2
- package/src/run-event-store.test.ts +144 -0
- package/src/run-event-store.ts +213 -0
- package/src/run-event-stream.test.ts +175 -0
- package/src/run-event-stream.ts +306 -0
- package/src/run-event.test.ts +277 -0
- package/src/run-event.ts +451 -0
- package/src/wave-loop-golden.test.ts +9 -0
- package/src/workspace/__fixtures__/run-event-check.ts +58 -0
- package/src/workspace/ancestry-index.test.ts +93 -0
- package/src/workspace/change-set-show.schema.json +102 -0
- package/src/workspace/change-set-show.ts +72 -0
- package/src/workspace/change-set.schema.json +28 -1
- package/src/workspace/changes-cli.ts +10 -1
- package/src/workspace/changes.schema.json +135 -0
- package/src/workspace/changes.test.ts +1 -1
- package/src/workspace/changes.ts +84 -2
- package/src/workspace/ci-green-cli.ts +75 -13
- package/src/workspace/ci-green-forge.ts +24 -4
- package/src/workspace/ci-green-runs.test.ts +242 -0
- package/src/workspace/ci-green-signed.test.ts +302 -0
- package/src/workspace/ci-green-workflow.test.ts +16 -0
- package/src/workspace/ci-green-workflow.ts +12 -0
- package/src/workspace/ci-green.test.ts +131 -2
- package/src/workspace/ci-green.ts +624 -42
- package/src/workspace/ci-last-green.schema.json +32 -2
- package/src/workspace/ci-list.schema.json +525 -0
- package/src/workspace/commit-links.test.ts +279 -0
- package/src/workspace/commit-links.ts +183 -0
- package/src/workspace/declaration.schema.json +12 -0
- package/src/workspace/declaration.ts +22 -2
- package/src/workspace/identity-gate-rule.ts +91 -7
- package/src/workspace/identity.ts +14 -1
- package/src/workspace/intent.ts +48 -1
- package/src/workspace/ls.schema.json +3 -1
- package/src/workspace/ls.ts +6 -0
- package/src/workspace/member-pipeline.test.ts +17 -0
- package/src/workspace/member-pipeline.ts +9 -0
- package/src/workspace/promotion-order.ts +97 -0
- package/src/workspace/read-contract.e2e.test.ts +4 -1
- package/src/workspace/reason-codes.test.ts +2 -1
- package/src/workspace/reason-codes.ts +4 -1
- package/src/workspace/releases.schema.json +391 -0
- package/src/workspace/releases.test.ts +108 -0
- package/src/workspace/releases.ts +122 -0
- package/src/workspace/run-event.schema.json +288 -0
- package/src/workspace/status-contract.test.ts +52 -1
- package/src/workspace/status-gates.ts +56 -1
- package/src/workspace/status-stewards.ts +6 -0
- package/src/workspace/status.schema.json +70 -1
- package/src/workspace/status.ts +80 -1
- package/src/workspace/trust/ci-tag-statement.ts +151 -0
- package/src/workspace/trust/op-run-statement.ts +148 -0
- package/src/workspace/trust/rotation.ts +61 -14
- package/src/workspace/trust/seal.ts +10 -0
- package/src/workspace/trust/ssh-commit.ts +10 -9
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/astro.config.mjs +0 -14
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/package.json +0 -16
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content/docs/index.mdx +0 -8
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content.config.ts +0 -15
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/tsconfig.json +0 -10
|
@@ -16,20 +16,23 @@
|
|
|
16
16
|
import { resolve } from "node:path";
|
|
17
17
|
import { readFileSync } from "node:fs";
|
|
18
18
|
import { loadChantConfig } from "../../config.js";
|
|
19
|
+
import { promotionOrder } from "../../workspace/promotion-order.js";
|
|
20
|
+
import { resolvePromotionSource } from "../../components/promote-order.js";
|
|
19
21
|
import { fetchLifecycle, getHeadCommit, pushLifecycle } from "../../lifecycle/git.js";
|
|
20
|
-
import { appendReleaseRecord, readReleaseLedger, resolveRunId, } from "../../lifecycle/release-ledger.js";
|
|
22
|
+
import { appendReleaseRecord, latestPerComponent, readReleaseLedger, resolveRunId, } from "../../lifecycle/release-ledger.js";
|
|
21
23
|
import { isComponentList, resolveComponentTargets } from "../../components/cli-support.js";
|
|
22
24
|
import { applyConfigDefaults } from "../../components/config-defaults.js";
|
|
23
25
|
import { fanOutRegistry } from "../../components/fan-out-support.js";
|
|
24
26
|
import { renderDriverHuman } from "../../components/driver-output.js";
|
|
25
27
|
import { ndjsonProgressSink } from "../../components/run-progress.js";
|
|
26
|
-
import {
|
|
28
|
+
import { openCliRunEvents, refusesBothStreams } from "../run-events.js";
|
|
29
|
+
import { parseDigestPins, planPromotion, planRollback, planRedeploy, withoutBuildSteps, runPromotion, promotionRecord, rollbackRecord, redeployRecord, gateApprover, gateAdmission, } from "../../components/promote.js";
|
|
27
30
|
import { summaryLedgerPrefix, writeGatedRunSummary } from "../../op/gate-summary.js";
|
|
28
|
-
import { approveCommand, describeGateMismatch, gateIsSealed } from "../../op/gate.js";
|
|
31
|
+
import { approveCommand, describeGateMismatch, gateIsSealed, gitGateLedgerPort } from "../../op/gate.js";
|
|
29
32
|
import { resolveCliBuildParams, parseParamFlags } from "../build-params-cli.js";
|
|
30
33
|
import { formatError, formatWarning, formatSuccess, formatBold, formatInfo } from "../format.js";
|
|
31
34
|
import { GATED_EXIT_CODE } from "./run.js";
|
|
32
|
-
const PROMOTE_USAGE = "chant components promote --
|
|
35
|
+
const PROMOTE_USAGE = "chant components promote --to <env> [--from <env>] [--allow-skip] [--release <label|digest>] [--component <name> [--digest <sha256:...>] | --digest <component>=<sha256:...> ...] [--dry-run] [--json]";
|
|
33
36
|
const ROLLBACK_USAGE = "chant components rollback <env> --component <name> [--digest <sha256:...>] [--dry-run] [--json]";
|
|
34
37
|
const REDEPLOY_USAGE = "chant components redeploy <env> --component <name> [--digest <sha256:...>] [--dry-run] [--json]";
|
|
35
38
|
function renderPlan(verb, plan, removed) {
|
|
@@ -96,13 +99,110 @@ async function readLedger(env) {
|
|
|
96
99
|
}
|
|
97
100
|
return ledger.records;
|
|
98
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* Plan, store and bind the release gate for a promote under a release
|
|
104
|
+
* (#4045, ws-104 questions 2 and 6). Undefined when no promoted composition
|
|
105
|
+
* declares a gate, so there is nothing to bind. A number is the exit code of
|
|
106
|
+
* a refusal already printed: a provisional member, or a member that failed
|
|
107
|
+
* to plan. On an empty change set, each gate gets an `empty: true`
|
|
108
|
+
* resolution the run then counts once it reads the stored document back.
|
|
109
|
+
*/
|
|
110
|
+
async function bindReleaseGate(input) {
|
|
111
|
+
const { plan, under, port } = input;
|
|
112
|
+
const gate = await import("../../components/release-gate.js");
|
|
113
|
+
const gates = gate.gateNamesOf(input.prepared);
|
|
114
|
+
if (gates.length === 0)
|
|
115
|
+
return undefined;
|
|
116
|
+
const env = plan.to;
|
|
117
|
+
const releaseDigest = under.release.releaseDigest;
|
|
118
|
+
// What the environment runs now, to compare each component with.
|
|
119
|
+
const { readRelease } = await import("../../lifecycle/release-store.js");
|
|
120
|
+
const latest = latestPerComponent((await readLedger(env)).filter((r) => r.env === env));
|
|
121
|
+
const releases = new Map();
|
|
122
|
+
const current = new Map();
|
|
123
|
+
for (const item of plan.items) {
|
|
124
|
+
const record = latest.get(item.component);
|
|
125
|
+
if (!record)
|
|
126
|
+
continue;
|
|
127
|
+
const named = record.releaseDigest;
|
|
128
|
+
if (named !== undefined && !releases.has(named))
|
|
129
|
+
releases.set(named, await readRelease(named).catch(() => null));
|
|
130
|
+
current.set(item.component, { record, release: named !== undefined ? (releases.get(named) ?? null) : null });
|
|
131
|
+
}
|
|
132
|
+
const doc = await gate.planReleaseChangeSet({
|
|
133
|
+
release: under.release,
|
|
134
|
+
env,
|
|
135
|
+
components: input.prepared,
|
|
136
|
+
digests: new Map(plan.items.map((i) => [i.component, i.digest])),
|
|
137
|
+
registry: input.registry,
|
|
138
|
+
current,
|
|
139
|
+
seededOutputs: input.seededOutputs,
|
|
140
|
+
});
|
|
141
|
+
const provisional = gate.provisionalMembers(doc);
|
|
142
|
+
if (provisional.length > 0) {
|
|
143
|
+
console.error(formatError({
|
|
144
|
+
message: `the change set of release ${under.label ?? releaseDigest} for "${env}" has provisional members (${provisional.join(", ")}), which no approval covers; nothing was deployed`,
|
|
145
|
+
hint: "Deploy what they read first, so they plan against real values.",
|
|
146
|
+
}));
|
|
147
|
+
return 1;
|
|
148
|
+
}
|
|
149
|
+
const failed = doc.members.filter((m) => m.status === "failed");
|
|
150
|
+
if (failed.length > 0) {
|
|
151
|
+
for (const m of failed)
|
|
152
|
+
console.error(formatError({ message: `${m.member} failed to plan for "${env}"${m.error ? `: ${m.error}` : ""}` }));
|
|
153
|
+
console.error(formatError({ message: "nothing was deployed" }));
|
|
154
|
+
return 1;
|
|
155
|
+
}
|
|
156
|
+
// The document is stored before the gate is decided, so the plan a pending
|
|
157
|
+
// gate waits on can be read, and an empty pass can be checked.
|
|
158
|
+
const { storeGateChangeSet } = await import("../../lifecycle/gate-change-set.js");
|
|
159
|
+
try {
|
|
160
|
+
const stored = await storeGateChangeSet({ doc, cwd: input.projectPath, at: input.head ?? null });
|
|
161
|
+
if (stored.warning)
|
|
162
|
+
console.error(formatWarning({ message: stored.warning }));
|
|
163
|
+
}
|
|
164
|
+
catch (err) {
|
|
165
|
+
console.error(formatWarning({ message: `could not store the change set ${doc.digest}: ${err instanceof Error ? err.message : String(err)}` }));
|
|
166
|
+
}
|
|
167
|
+
const { isEmptyChangeSet } = await import("../../change-set.js");
|
|
168
|
+
const empty = isEmptyChangeSet(doc);
|
|
169
|
+
console.error(formatInfo(`release gate: change set ${doc.digest} for "${env}"` +
|
|
170
|
+
(empty ? ", empty: its gates pass without an approval" : `, ${doc.summary.entries} entr${doc.summary.entries === 1 ? "y" : "ies"}, ${doc.summary.holes} hole(s)`) +
|
|
171
|
+
` (chant change-set show ${doc.digest})`));
|
|
172
|
+
if (empty) {
|
|
173
|
+
if (!port.appendResolution)
|
|
174
|
+
throw new Error("the gate ledger can't record a resolution, so an empty change set can't pass its gates");
|
|
175
|
+
const { resolutions } = await port.read(gate.RELEASE_GATE_OP);
|
|
176
|
+
const timestamp = new Date().toISOString();
|
|
177
|
+
for (const name of gates) {
|
|
178
|
+
const standing = resolutions.some((r) => r.empty === true && r.gate === name && r.environment === env && r.releaseDigest === releaseDigest && r.planDigest === doc.digest);
|
|
179
|
+
if (standing)
|
|
180
|
+
continue;
|
|
181
|
+
// Pushed with the resolution; the deploy records the promote writes push the same branch.
|
|
182
|
+
await port.appendResolution(gate.emptyResolution({ gate: name, doc, actor: input.actor, timestamp, ...(input.head ? { gitSha: input.head } : {}) }));
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const { readStoredChangeSet } = await import("../../lifecycle/change-set-store.js");
|
|
186
|
+
return {
|
|
187
|
+
doc,
|
|
188
|
+
empty,
|
|
189
|
+
binding: {
|
|
190
|
+
op: gate.RELEASE_GATE_OP,
|
|
191
|
+
planDigest: doc.digest,
|
|
192
|
+
releaseDigest,
|
|
193
|
+
verifyEmpty: gate.emptyVerifier((digest) => readStoredChangeSet(digest, { cwd: input.projectPath }), { releaseDigest, environment: env }),
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
}
|
|
99
197
|
/**
|
|
100
198
|
* Prepare, run and record a plan. Shared by promote, rollback and redeploy,
|
|
101
199
|
* which differ only in how the plan was chosen, whether the publish step runs,
|
|
102
200
|
* and which field names the earlier release on the new record.
|
|
103
201
|
*/
|
|
104
|
-
async function deployPlan(ctx, verb, plan, project, actor, record) {
|
|
202
|
+
async function deployPlan(ctx, verb, plan, project, actor, record, under) {
|
|
105
203
|
const { args } = ctx;
|
|
204
|
+
if (refusesBothStreams(args))
|
|
205
|
+
return 1;
|
|
106
206
|
const byName = new Map(project.targets.map((c) => [c.name, applyConfigDefaults(c, project.config)]));
|
|
107
207
|
const prepared = [];
|
|
108
208
|
const removed = new Map();
|
|
@@ -124,6 +224,34 @@ async function deployPlan(ctx, verb, plan, project, actor, record) {
|
|
|
124
224
|
console.error(formatError({ message: `nothing was deployed` }));
|
|
125
225
|
return 1;
|
|
126
226
|
}
|
|
227
|
+
// #4026: under a release, the deploy reads the templates the release
|
|
228
|
+
// recorded for this environment, checked against their digests.
|
|
229
|
+
let pinnedFiles = [];
|
|
230
|
+
if (under) {
|
|
231
|
+
const { pinReleaseTemplates } = await import("../../components/release-promote.js");
|
|
232
|
+
const { readReleaseTemplate } = await import("../../lifecycle/release-store.js");
|
|
233
|
+
const pinned = await pinReleaseTemplates(prepared, under.release, under.target, (digest) => readReleaseTemplate(digest));
|
|
234
|
+
if ("errors" in pinned) {
|
|
235
|
+
for (const message of pinned.errors)
|
|
236
|
+
console.error(formatError({ message }));
|
|
237
|
+
console.error(formatError({ message: `nothing was deployed` }));
|
|
238
|
+
return 1;
|
|
239
|
+
}
|
|
240
|
+
prepared.splice(0, prepared.length, ...pinned.components);
|
|
241
|
+
pinnedFiles = pinned.files;
|
|
242
|
+
for (const item of plan.items) {
|
|
243
|
+
const named = item.source.releaseDigest;
|
|
244
|
+
if (named !== undefined && named !== under.release.releaseDigest) {
|
|
245
|
+
console.error(formatWarning({ message: `${item.component}: the ${plan.from} deploy being promoted came from release ${named}, not ${under.release.releaseDigest}` }));
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
// #4045: the artifact deployed against the artifacts the release built.
|
|
249
|
+
const { releaseArtifactMismatches } = await import("../../components/release-promote.js");
|
|
250
|
+
const { readBuildManifest } = await import("../../lifecycle/build-ledger-store.js");
|
|
251
|
+
const mismatches = await releaseArtifactMismatches(under.release, plan.items.map((i) => ({ component: i.component, source: i.source })), (digest) => readBuildManifest(digest).catch(() => null));
|
|
252
|
+
for (const message of mismatches)
|
|
253
|
+
console.error(formatWarning({ message }));
|
|
254
|
+
}
|
|
127
255
|
// The composition deployed is this checkout's. Say so when it is not the
|
|
128
256
|
// commit the artifact was built from.
|
|
129
257
|
const head = await getHeadCommit().catch(() => undefined);
|
|
@@ -135,15 +263,29 @@ async function deployPlan(ctx, verb, plan, project, actor, record) {
|
|
|
135
263
|
}));
|
|
136
264
|
}
|
|
137
265
|
}
|
|
266
|
+
const releaseJson = under
|
|
267
|
+
? { release: { releaseDigest: under.release.releaseDigest, label: under.label, templates: under.target.templates } }
|
|
268
|
+
: {};
|
|
138
269
|
if (args.dryRun) {
|
|
139
270
|
if (args.json)
|
|
140
|
-
console.log(JSON.stringify(planJson(plan, removed), null, 2));
|
|
141
|
-
else
|
|
271
|
+
console.log(JSON.stringify({ ...planJson(plan, removed), ...releaseJson }, null, 2));
|
|
272
|
+
else {
|
|
142
273
|
renderPlan(verb, plan, removed);
|
|
274
|
+
if (under)
|
|
275
|
+
console.error(` under release ${under.label ?? under.release.releaseDigest}: ${under.target.templates.length} template(s) from its "${plan.to}" target`);
|
|
276
|
+
}
|
|
143
277
|
return 0;
|
|
144
278
|
}
|
|
145
279
|
if (!args.json)
|
|
146
280
|
renderPlan(verb, plan, removed);
|
|
281
|
+
// #4045: the pinned templates are written for this run and removed after it.
|
|
282
|
+
const { writePinnedTemplates, removePinnedTemplates } = await import("../../components/release-promote.js");
|
|
283
|
+
const tidy = () => {
|
|
284
|
+
if (pinnedFiles.length > 0)
|
|
285
|
+
removePinnedTemplates(project.projectPath, pinnedFiles);
|
|
286
|
+
};
|
|
287
|
+
if (pinnedFiles.length > 0)
|
|
288
|
+
writePinnedTemplates(project.projectPath, pinnedFiles);
|
|
147
289
|
const seededOutputs = {};
|
|
148
290
|
for (const file of args.seedOutputs ?? []) {
|
|
149
291
|
try {
|
|
@@ -151,32 +293,80 @@ async function deployPlan(ctx, verb, plan, project, actor, record) {
|
|
|
151
293
|
}
|
|
152
294
|
catch (err) {
|
|
153
295
|
console.error(formatError({ message: `--seed-outputs: could not read "${file}": ${err instanceof Error ? err.message : String(err)}` }));
|
|
296
|
+
tidy();
|
|
154
297
|
return 1;
|
|
155
298
|
}
|
|
156
299
|
}
|
|
157
300
|
const registry = await fanOutRegistry(project.projectPath, project.config);
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
301
|
+
const port = gitGateLedgerPort();
|
|
302
|
+
// #4045: under a release, every gate binds the release's change set for this environment.
|
|
303
|
+
let releaseGate;
|
|
304
|
+
let changeSetJson;
|
|
305
|
+
if (under) {
|
|
306
|
+
try {
|
|
307
|
+
const bound = await bindReleaseGate({ plan, prepared, under, registry, port, seededOutputs, actor: actor, projectPath: project.projectPath, head });
|
|
308
|
+
if (typeof bound === "number") {
|
|
309
|
+
tidy();
|
|
310
|
+
return bound;
|
|
311
|
+
}
|
|
312
|
+
if (bound) {
|
|
313
|
+
releaseGate = bound.binding;
|
|
314
|
+
changeSetJson = { changeSet: bound.doc.digest, empty: bound.empty };
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
catch (err) {
|
|
318
|
+
tidy();
|
|
319
|
+
throw err;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
// The run id the release records carry, chosen before the run so that
|
|
323
|
+
// `--events-json` (#4041) names the run by it too.
|
|
324
|
+
const { runId, runOrigin } = resolveRunId(args.runId);
|
|
325
|
+
const events = await openCliRunEvents(args, {
|
|
326
|
+
kind: "component",
|
|
327
|
+
name: `${verb} ${plan.items.map((i) => i.component).join(",")} in ${plan.to}`,
|
|
328
|
+
runId,
|
|
329
|
+
cwd: project.projectPath,
|
|
164
330
|
});
|
|
331
|
+
let run;
|
|
332
|
+
try {
|
|
333
|
+
run = await runPromotion({
|
|
334
|
+
plan,
|
|
335
|
+
components: prepared,
|
|
336
|
+
registry,
|
|
337
|
+
componentOutputs: seededOutputs,
|
|
338
|
+
gates: port,
|
|
339
|
+
...(releaseGate ? { releaseGate } : {}),
|
|
340
|
+
...(args.progressJson ? { onProgress: ndjsonProgressSink() } : {}),
|
|
341
|
+
...(events ? { onRunEvent: events.emit } : {}),
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
catch (err) {
|
|
345
|
+
events?.settle("failed", err instanceof Error ? err.message : String(err));
|
|
346
|
+
throw err;
|
|
347
|
+
}
|
|
348
|
+
finally {
|
|
349
|
+
tidy();
|
|
350
|
+
}
|
|
351
|
+
// The driver settles the stream; this only ends one it left open.
|
|
352
|
+
events?.settle(run.status === "fail" ? "failed" : run.status);
|
|
165
353
|
if (!args.json)
|
|
166
354
|
renderDriverHuman(run);
|
|
167
355
|
// Record every component that deployed, whatever happened to the rest: a
|
|
168
356
|
// component that reached the environment and is missing from its ledger
|
|
169
357
|
// would be exactly the unrecorded deploy the ledger exists to rule out.
|
|
170
358
|
const deployed = plan.items.filter((item) => run.results.some((r) => r.component === item.component && r.status === "ok"));
|
|
171
|
-
const { runId, runOrigin } = resolveRunId(args.runId);
|
|
172
359
|
const timestamp = new Date().toISOString();
|
|
173
360
|
const recorded = [];
|
|
174
361
|
let recordError;
|
|
175
362
|
if (deployed.length > 0) {
|
|
176
363
|
try {
|
|
177
364
|
for (const item of deployed) {
|
|
178
|
-
const
|
|
179
|
-
const
|
|
365
|
+
const result = run.results.find((r) => r.component === item.component);
|
|
366
|
+
const approver = gateApprover(result);
|
|
367
|
+
const admission = gateAdmission(result);
|
|
368
|
+
const input = record(item, plan.to, { runId, ...(runOrigin ? { runOrigin } : {}), actor: actor, timestamp, ...(approver ? { approver } : {}), ...(admission ? { admission } : {}) });
|
|
369
|
+
const { record: written } = await appendReleaseRecord(under ? { ...input, releaseDigest: under.release.releaseDigest } : input);
|
|
180
370
|
recorded.push(written);
|
|
181
371
|
}
|
|
182
372
|
await pushLifecycle();
|
|
@@ -186,7 +376,8 @@ async function deployPlan(ctx, verb, plan, project, actor, record) {
|
|
|
186
376
|
}
|
|
187
377
|
}
|
|
188
378
|
if (args.json) {
|
|
189
|
-
|
|
379
|
+
const withChangeSet = under && changeSetJson ? { release: { ...releaseJson.release, ...changeSetJson } } : releaseJson;
|
|
380
|
+
console.log(JSON.stringify({ ...planJson(plan, removed), ...withChangeSet, run, recorded }, null, 2));
|
|
190
381
|
}
|
|
191
382
|
else {
|
|
192
383
|
for (const r of recorded) {
|
|
@@ -247,10 +438,9 @@ function actorOrRefuse(ctx) {
|
|
|
247
438
|
}
|
|
248
439
|
export async function runComponentsPromote(ctx) {
|
|
249
440
|
const { args } = ctx;
|
|
250
|
-
const from = args.migrateFrom;
|
|
251
441
|
const to = args.migrateTo;
|
|
252
|
-
if (!
|
|
253
|
-
console.error(formatError({ message: "--
|
|
442
|
+
if (!to) {
|
|
443
|
+
console.error(formatError({ message: "--to <env> is required", hint: PROMOTE_USAGE }));
|
|
254
444
|
return 1;
|
|
255
445
|
}
|
|
256
446
|
const who = actorOrRefuse(ctx);
|
|
@@ -259,6 +449,24 @@ export async function runComponentsPromote(ctx) {
|
|
|
259
449
|
const project = await loadProject(ctx, args.component ?? "all");
|
|
260
450
|
if (typeof project === "number")
|
|
261
451
|
return project;
|
|
452
|
+
// The member's `environments` array is its promotion order (#4030). It
|
|
453
|
+
// defaults --from and refuses a skipped step; a member that declares none
|
|
454
|
+
// has no order, so --from is required and nothing is refused.
|
|
455
|
+
const resolved = resolvePromotionSource({
|
|
456
|
+
order: promotionOrder(project.config.environments),
|
|
457
|
+
to,
|
|
458
|
+
...(args.migrateFrom ? { from: args.migrateFrom } : {}),
|
|
459
|
+
...(args.allowSkip ? { allowSkip: true } : {}),
|
|
460
|
+
});
|
|
461
|
+
if ("error" in resolved) {
|
|
462
|
+
console.error(formatError({ message: `${resolved.error.message} [${resolved.error.code}]`, hint: resolved.error.fix }));
|
|
463
|
+
return 1;
|
|
464
|
+
}
|
|
465
|
+
const from = resolved.from;
|
|
466
|
+
if (!from) {
|
|
467
|
+
console.error(formatError({ message: "--from <env> is required: this member declares no environments, so there is no promotion order to default it from", hint: PROMOTE_USAGE }));
|
|
468
|
+
return 1;
|
|
469
|
+
}
|
|
262
470
|
// Without --component, each --digest is <component>=<digest> (#2602): a
|
|
263
471
|
// generated promote job pins every component to the release its own
|
|
264
472
|
// pipeline run recorded, rather than whatever is latest in --from.
|
|
@@ -277,6 +485,33 @@ export async function runComponentsPromote(ctx) {
|
|
|
277
485
|
return 1;
|
|
278
486
|
}
|
|
279
487
|
await fetchLifecycle().catch(() => false);
|
|
488
|
+
// #4026: --release names the release this promote deploys, by label or digest.
|
|
489
|
+
let under;
|
|
490
|
+
if (args.release) {
|
|
491
|
+
const { resolveRelease } = await import("../../lifecycle/release-store.js");
|
|
492
|
+
const { targetFor, isImplicitRelease } = await import("../../components/release-promote.js");
|
|
493
|
+
const found = await resolveRelease(args.release);
|
|
494
|
+
if (!found) {
|
|
495
|
+
console.error(formatError({ message: `no release is labelled or digested "${args.release}" in this member`, hint: "List them with: chant release list" }));
|
|
496
|
+
return 1;
|
|
497
|
+
}
|
|
498
|
+
const target = targetFor(found.release, to);
|
|
499
|
+
if ("error" in target) {
|
|
500
|
+
console.error(formatError({ message: target.error }));
|
|
501
|
+
return 1;
|
|
502
|
+
}
|
|
503
|
+
// #4046: a release of a project with no components records build output
|
|
504
|
+
// and no component, so there is no deploy step here to point at it.
|
|
505
|
+
if (isImplicitRelease(found.release)) {
|
|
506
|
+
const name = found.label ?? found.release.releaseDigest;
|
|
507
|
+
console.error(formatError({
|
|
508
|
+
message: `release ${name} is of a project with no components, so promote has no component to deploy`,
|
|
509
|
+
hint: `Write its "${to}" build output with \`chant release export ${name} --env ${to}\`, then run the project's own deploy against those files.`,
|
|
510
|
+
}));
|
|
511
|
+
return 1;
|
|
512
|
+
}
|
|
513
|
+
under = { release: found.release, label: found.label, target: target.target };
|
|
514
|
+
}
|
|
280
515
|
const plan = planPromotion({
|
|
281
516
|
from,
|
|
282
517
|
to,
|
|
@@ -290,7 +525,7 @@ export async function runComponentsPromote(ctx) {
|
|
|
290
525
|
console.error(formatError({ message: plan.error, hint: PROMOTE_USAGE }));
|
|
291
526
|
return 1;
|
|
292
527
|
}
|
|
293
|
-
return deployPlan(ctx, "promote", plan, project, who.actor, promotionRecord);
|
|
528
|
+
return deployPlan(ctx, "promote", plan, project, who.actor, promotionRecord, under);
|
|
294
529
|
}
|
|
295
530
|
export async function runComponentsRollback(ctx) {
|
|
296
531
|
const { args } = ctx;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `chant release` (#4026, ws-104): the release is what graduates.
|
|
3
|
+
*
|
|
4
|
+
* release cut [<label>] [--build <command>] [--param k=v]... [--params-file <file>] [--src <dir>] [-o <file>] [--lexicon <name>] [--actor <name>] [--sign [<key file>]] [--json]
|
|
5
|
+
* cut a release of this member from the checkout's HEAD, which must be green;
|
|
6
|
+
* --sign signs the cut with the actor's ssh key (#4031); `--src`, `-o` and
|
|
7
|
+
* `--lexicon` shape the build of a project with no components (#4046)
|
|
8
|
+
* release list [--json]
|
|
9
|
+
* every release of this member with its targets, its cuts and where it runs;
|
|
10
|
+
* `--json` prints the read contract's `releases` document
|
|
11
|
+
* release export <label|digest> --env <env> [-o <dir>] [--json]
|
|
12
|
+
* write the files the release recorded for <env>, checked against their digests (#4046)
|
|
13
|
+
* release compile --env <env> [--build <command>] [--artifacts --component <a,b>] --json
|
|
14
|
+
* internal: what the cut runs in a child process per environment
|
|
15
|
+
*
|
|
16
|
+
* The workstation UX for `cut` grows in #4027. A declaration whose
|
|
17
|
+
* `identity.gates` names a `release` gate requires a signed cut by an actor
|
|
18
|
+
* in its class (#4031).
|
|
19
|
+
*/
|
|
20
|
+
import type { CommandContext } from "../registry.js";
|
|
21
|
+
export declare function runReleaseCut(ctx: CommandContext): Promise<number>;
|
|
22
|
+
export declare function runReleaseList(ctx: CommandContext): Promise<number>;
|
|
23
|
+
/**
|
|
24
|
+
* `release export <label|digest> --env <env> [-o <dir>]` (#4046): write each
|
|
25
|
+
* file the release's target for <env> records, at the path it was compiled to,
|
|
26
|
+
* under <dir> (the member's directory by default), after checking each one's
|
|
27
|
+
* bytes against its digest. This is how a project with no components deploys
|
|
28
|
+
* a release: its own deploy reads these files instead of building again.
|
|
29
|
+
*/
|
|
30
|
+
export declare function runReleaseExport(ctx: CommandContext): Promise<number>;
|
|
31
|
+
/** Internal: compile one environment, or build artifacts, in this process, and print the JSON the cut reads. */
|
|
32
|
+
export declare function runReleaseCompile(ctx: CommandContext): Promise<number>;
|
|
33
|
+
export declare function runReleaseUnknown(ctx: CommandContext): Promise<number>;
|
|
34
|
+
//# sourceMappingURL=release.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"release.d.ts","sourceRoot":"","sources":["../../../src/cli/handlers/release.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAKH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAuBlD,wBAAsB,aAAa,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAoDxE;AAED,wBAAsB,cAAc,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAyBzE;AAED;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAyC3E;AAED,gHAAgH;AAChH,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAsB5E;AAED,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAG5E"}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `chant release` (#4026, ws-104): the release is what graduates.
|
|
3
|
+
*
|
|
4
|
+
* release cut [<label>] [--build <command>] [--param k=v]... [--params-file <file>] [--src <dir>] [-o <file>] [--lexicon <name>] [--actor <name>] [--sign [<key file>]] [--json]
|
|
5
|
+
* cut a release of this member from the checkout's HEAD, which must be green;
|
|
6
|
+
* --sign signs the cut with the actor's ssh key (#4031); `--src`, `-o` and
|
|
7
|
+
* `--lexicon` shape the build of a project with no components (#4046)
|
|
8
|
+
* release list [--json]
|
|
9
|
+
* every release of this member with its targets, its cuts and where it runs;
|
|
10
|
+
* `--json` prints the read contract's `releases` document
|
|
11
|
+
* release export <label|digest> --env <env> [-o <dir>] [--json]
|
|
12
|
+
* write the files the release recorded for <env>, checked against their digests (#4046)
|
|
13
|
+
* release compile --env <env> [--build <command>] [--artifacts --component <a,b>] --json
|
|
14
|
+
* internal: what the cut runs in a child process per environment
|
|
15
|
+
*
|
|
16
|
+
* The workstation UX for `cut` grows in #4027. A declaration whose
|
|
17
|
+
* `identity.gates` names a `release` gate requires a signed cut by an actor
|
|
18
|
+
* in its class (#4031).
|
|
19
|
+
*/
|
|
20
|
+
import { resolve } from "node:path";
|
|
21
|
+
import { formatBold, formatError, formatSuccess, formatWarning } from "../format.js";
|
|
22
|
+
import { parseParamFlags } from "../build-params-cli.js";
|
|
23
|
+
const USAGE = "chant release cut [<label>] [--build <command>] [--param <k=v>] [--params-file <file>] [--src <dir>] [-o <file>] [--lexicon <name>] [--actor <name>] [--sign [<key file>]] [--json] | release list [--json] | release export <label|digest> --env <env> [-o <dir>] [--json]";
|
|
24
|
+
/** The build inputs `release cut` and `release compile` share. */
|
|
25
|
+
function compileInputs(ctx, params) {
|
|
26
|
+
const { args } = ctx;
|
|
27
|
+
return {
|
|
28
|
+
projectDir: resolve("."),
|
|
29
|
+
...(args.buildCommand ? { buildCommand: args.buildCommand } : {}),
|
|
30
|
+
...(params ? { params } : {}),
|
|
31
|
+
...(args.paramsFile ? { paramsFile: args.paramsFile } : {}),
|
|
32
|
+
...(args.src ? { src: args.src } : {}),
|
|
33
|
+
...(args.output ? { output: args.output } : {}),
|
|
34
|
+
...(args.lexicon ? { lexicon: args.lexicon } : {}),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function resolveActor(ctx) {
|
|
38
|
+
return ctx.args.actor ?? process.env.GITHUB_ACTOR ?? process.env.GITLAB_USER_LOGIN ?? process.env.USER;
|
|
39
|
+
}
|
|
40
|
+
export async function runReleaseCut(ctx) {
|
|
41
|
+
const { args } = ctx;
|
|
42
|
+
const positional = args.extraPositional;
|
|
43
|
+
if (positional && args.label && positional !== args.label) {
|
|
44
|
+
console.error(formatError({ message: `the label is given twice, as "${positional}" and --label "${args.label}"`, hint: USAGE }));
|
|
45
|
+
return 1;
|
|
46
|
+
}
|
|
47
|
+
const label = positional ?? args.label ?? null;
|
|
48
|
+
const actor = resolveActor(ctx);
|
|
49
|
+
if (!actor) {
|
|
50
|
+
console.error(formatError({ message: "Could not resolve --actor from the environment", hint: "Pass --actor explicitly, or set GITHUB_ACTOR / GITLAB_USER_LOGIN / USER." }));
|
|
51
|
+
return 1;
|
|
52
|
+
}
|
|
53
|
+
const cwd = resolve(".");
|
|
54
|
+
const { spawnTargetCompiler, ReleaseCompileError } = await import("../../components/release-compile.js");
|
|
55
|
+
const { cutRelease, ReleaseCutError } = await import("../../lifecycle/release-cut.js");
|
|
56
|
+
const { ReleaseLabelError } = await import("../../lifecycle/release-store.js");
|
|
57
|
+
const { pushLifecycleReport } = await import("../../lifecycle/git.js");
|
|
58
|
+
const params = parseParamFlags(args.param);
|
|
59
|
+
const compiler = spawnTargetCompiler(compileInputs(ctx, params));
|
|
60
|
+
try {
|
|
61
|
+
const result = await cutRelease({ cwd, compiler, label, actor, ...(args.sign !== undefined ? { sign: args.sign } : {}) });
|
|
62
|
+
const push = await pushLifecycleReport({ cwd }).catch(() => ({ pushed: false, pushWarning: undefined }));
|
|
63
|
+
if (args.json) {
|
|
64
|
+
console.log(JSON.stringify({ release: result.release, label: result.label, cut: result.cut, stored: result.stored, reused: result.reused }, null, 2));
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
const name = result.label ? formatBold(result.label) : "unlabelled release";
|
|
68
|
+
if (result.cut)
|
|
69
|
+
console.error(formatSuccess(`Cut ${name}: ${result.release.releaseDigest}`));
|
|
70
|
+
else
|
|
71
|
+
console.error(formatSuccess(`Already cut${result.label ? ` as ${formatBold(result.label)}` : ""}: ${result.release.releaseDigest}`));
|
|
72
|
+
console.error(` commit ${result.release.commit}`);
|
|
73
|
+
if (result.cut)
|
|
74
|
+
console.error(result.cut.signature ? ` signed by ${result.cut.actor}` : " not signed");
|
|
75
|
+
console.error(` artifacts ${result.release.artifactDigests.length}, targets ${result.release.targets.map((t) => `${t.env} (${t.templates.length} template${t.templates.length === 1 ? "" : "s"})`).join(", ")}`);
|
|
76
|
+
for (const r of result.reused)
|
|
77
|
+
console.error(` ${r.component}: artifacts unchanged, carried from ${r.from}`);
|
|
78
|
+
console.log(result.release.releaseDigest);
|
|
79
|
+
}
|
|
80
|
+
if (push.pushWarning)
|
|
81
|
+
console.error(formatWarning({ message: push.pushWarning }));
|
|
82
|
+
return 0;
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
if (err instanceof ReleaseCutError) {
|
|
86
|
+
console.error(formatError({ message: `${err.message} [${err.code}]`, ...(err.fix ? { hint: err.fix } : {}) }));
|
|
87
|
+
return 1;
|
|
88
|
+
}
|
|
89
|
+
if (err instanceof ReleaseLabelError) {
|
|
90
|
+
console.error(formatError({ message: `${err.message} [${err.code}]` }));
|
|
91
|
+
return 1;
|
|
92
|
+
}
|
|
93
|
+
if (err instanceof ReleaseCompileError) {
|
|
94
|
+
console.error(formatError({ message: `no release was cut: ${err.message}` }));
|
|
95
|
+
return 1;
|
|
96
|
+
}
|
|
97
|
+
throw err;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
export async function runReleaseList(ctx) {
|
|
101
|
+
const { releasesDocument } = await import("../../workspace/releases.js");
|
|
102
|
+
const doc = await releasesDocument({ cwd: resolve(".") });
|
|
103
|
+
if (ctx.args.json) {
|
|
104
|
+
console.log(JSON.stringify(doc, null, 2));
|
|
105
|
+
return "error" in doc ? 1 : 0;
|
|
106
|
+
}
|
|
107
|
+
if ("error" in doc) {
|
|
108
|
+
console.error(formatError({ message: doc.error.message, hint: USAGE }));
|
|
109
|
+
return 1;
|
|
110
|
+
}
|
|
111
|
+
if (doc.reason)
|
|
112
|
+
console.error(formatWarning({ message: doc.reason.message }));
|
|
113
|
+
if (doc.releases.length === 0) {
|
|
114
|
+
console.error("No releases. Cut one from a green commit: chant release cut <label>");
|
|
115
|
+
return 0;
|
|
116
|
+
}
|
|
117
|
+
for (const r of doc.releases) {
|
|
118
|
+
const where = r.environments.map((e) => e.env).join(", ") || "not deployed from the release";
|
|
119
|
+
// #4031: the newest cut's signature, as the policy at base judges it.
|
|
120
|
+
const newest = r.cuts.at(-1);
|
|
121
|
+
const signature = newest ? (newest.signatureCheck.status === "verified" ? `signed by ${newest.actor}` : newest.signatureCheck.status) : "-";
|
|
122
|
+
console.log(`${(r.label ?? "-").padEnd(16)} ${r.releaseDigest} ${r.commit.slice(0, 12)} ${signature} ${where}`);
|
|
123
|
+
if (newest?.signatureCheck.status === "unverified")
|
|
124
|
+
console.error(` ${r.label ?? r.releaseDigest}: ${newest.signatureCheck.reason}`);
|
|
125
|
+
}
|
|
126
|
+
return 0;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* `release export <label|digest> --env <env> [-o <dir>]` (#4046): write each
|
|
130
|
+
* file the release's target for <env> records, at the path it was compiled to,
|
|
131
|
+
* under <dir> (the member's directory by default), after checking each one's
|
|
132
|
+
* bytes against its digest. This is how a project with no components deploys
|
|
133
|
+
* a release: its own deploy reads these files instead of building again.
|
|
134
|
+
*/
|
|
135
|
+
export async function runReleaseExport(ctx) {
|
|
136
|
+
const { args } = ctx;
|
|
137
|
+
const ref = args.extraPositional;
|
|
138
|
+
if (!ref || !args.env) {
|
|
139
|
+
console.error(formatError({ message: "release export needs a release and an environment", hint: USAGE }));
|
|
140
|
+
return 1;
|
|
141
|
+
}
|
|
142
|
+
const { fetchLifecycle } = await import("../../lifecycle/git.js");
|
|
143
|
+
const { resolveRelease, readReleaseTemplate } = await import("../../lifecycle/release-store.js");
|
|
144
|
+
const { targetFor, targetFiles, writePinnedTemplates } = await import("../../components/release-promote.js");
|
|
145
|
+
await fetchLifecycle().catch(() => false);
|
|
146
|
+
const found = await resolveRelease(ref);
|
|
147
|
+
if (!found) {
|
|
148
|
+
console.error(formatError({ message: `no release is labelled or digested "${ref}" in this member`, hint: "List them with: chant release list" }));
|
|
149
|
+
return 1;
|
|
150
|
+
}
|
|
151
|
+
const target = targetFor(found.release, args.env);
|
|
152
|
+
if ("error" in target) {
|
|
153
|
+
console.error(formatError({ message: target.error }));
|
|
154
|
+
return 1;
|
|
155
|
+
}
|
|
156
|
+
const read = await targetFiles(found.release, target.target, (digest) => readReleaseTemplate(digest));
|
|
157
|
+
if ("errors" in read) {
|
|
158
|
+
for (const e of read.errors)
|
|
159
|
+
console.error(formatError({ message: e }));
|
|
160
|
+
return 1;
|
|
161
|
+
}
|
|
162
|
+
const dir = resolve(args.output ?? ".");
|
|
163
|
+
writePinnedTemplates(dir, read.files);
|
|
164
|
+
if (args.json) {
|
|
165
|
+
console.log(JSON.stringify({ releaseDigest: found.release.releaseDigest, label: found.label, env: args.env, dir, templates: target.target.templates, params: target.target.params }, null, 2));
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
console.error(formatSuccess(`Wrote ${read.files.length} file(s) of ${found.label ? formatBold(found.label) : found.release.releaseDigest} for "${args.env}"`));
|
|
169
|
+
for (const f of read.files)
|
|
170
|
+
console.log(f.path);
|
|
171
|
+
}
|
|
172
|
+
return 0;
|
|
173
|
+
}
|
|
174
|
+
/** Internal: compile one environment, or build artifacts, in this process, and print the JSON the cut reads. */
|
|
175
|
+
export async function runReleaseCompile(ctx) {
|
|
176
|
+
const { args } = ctx;
|
|
177
|
+
const env = args.env;
|
|
178
|
+
if (!env) {
|
|
179
|
+
console.error(formatError({ message: "release compile needs --env <env>" }));
|
|
180
|
+
return 1;
|
|
181
|
+
}
|
|
182
|
+
const { compileEnvironment, buildReleaseArtifacts } = await import("../../components/release-compile.js");
|
|
183
|
+
const params = parseParamFlags(args.param);
|
|
184
|
+
const inputs = compileInputs(ctx, params);
|
|
185
|
+
try {
|
|
186
|
+
if (args.artifacts) {
|
|
187
|
+
const names = (args.component ?? "").split(",").map((n) => n.trim()).filter(Boolean);
|
|
188
|
+
console.log(JSON.stringify({ artifacts: await buildReleaseArtifacts(inputs, env, names) }));
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
console.log(JSON.stringify(await compileEnvironment(inputs, env)));
|
|
192
|
+
}
|
|
193
|
+
return 0;
|
|
194
|
+
}
|
|
195
|
+
catch (err) {
|
|
196
|
+
console.error(formatError({ message: err instanceof Error ? err.message : String(err) }));
|
|
197
|
+
return 1;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
export async function runReleaseUnknown(ctx) {
|
|
201
|
+
console.error(formatError({ message: `Unknown release subcommand: ${ctx.args.path}`, hint: USAGE }));
|
|
202
|
+
return 1;
|
|
203
|
+
}
|