@intentius/chant 0.124.28 → 0.124.30
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/dist/build-params.d.ts +34 -1
- package/dist/build-params.d.ts.map +1 -1
- package/dist/build-params.js +31 -1
- package/dist/change-set-graph.d.ts +8 -1
- package/dist/change-set-graph.d.ts.map +1 -1
- package/dist/change-set-graph.js +4 -2
- package/dist/change-set.d.ts +10 -0
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +4 -3
- package/dist/cli/build-params-cli.d.ts +7 -1
- package/dist/cli/build-params-cli.d.ts.map +1 -1
- package/dist/cli/build-params-cli.js +17 -8
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/build.js +6 -3
- package/dist/cli/handlers/approve.d.ts.map +1 -1
- package/dist/cli/handlers/approve.js +45 -0
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/build.js +18 -4
- package/dist/cli/handlers/fan-out.d.ts +9 -0
- package/dist/cli/handlers/fan-out.d.ts.map +1 -1
- package/dist/cli/handlers/fan-out.js +67 -8
- package/dist/cli/handlers/lifecycle-gc.d.ts +19 -0
- package/dist/cli/handlers/lifecycle-gc.d.ts.map +1 -0
- package/dist/cli/handlers/lifecycle-gc.js +143 -0
- package/dist/cli/handlers/pr.d.ts +11 -3
- package/dist/cli/handlers/pr.d.ts.map +1 -1
- package/dist/cli/handlers/pr.js +134 -26
- package/dist/cli/handlers/promote.d.ts +7 -0
- package/dist/cli/handlers/promote.d.ts.map +1 -1
- package/dist/cli/handlers/promote.js +210 -33
- package/dist/cli/handlers/release.d.ts +62 -5
- package/dist/cli/handlers/release.d.ts.map +1 -1
- package/dist/cli/handlers/release.js +414 -28
- package/dist/cli/handlers/run.d.ts +11 -1
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/handlers/run.js +67 -6
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +91 -10
- package/dist/cli/parse-args.d.ts.map +1 -1
- package/dist/cli/parse-args.js +73 -2
- package/dist/cli/registry.d.ts +32 -4
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/cli/run-events.d.ts +3 -2
- package/dist/cli/run-events.d.ts.map +1 -1
- package/dist/cli/run-events.js +5 -3
- package/dist/components/auto-release.d.ts +2 -0
- package/dist/components/auto-release.d.ts.map +1 -1
- package/dist/components/auto-release.js +1 -0
- package/dist/components/capability.d.ts +9 -0
- package/dist/components/capability.d.ts.map +1 -1
- package/dist/components/driver.d.ts +8 -0
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/driver.js +35 -0
- package/dist/components/fan-out-inputs.d.ts +69 -0
- package/dist/components/fan-out-inputs.d.ts.map +1 -0
- package/dist/components/fan-out-inputs.js +169 -0
- package/dist/components/fan-out-record.d.ts +13 -0
- package/dist/components/fan-out-record.d.ts.map +1 -1
- package/dist/components/fan-out-record.js +16 -0
- package/dist/components/fan-out-run.d.ts.map +1 -1
- package/dist/components/fan-out-run.js +50 -0
- package/dist/components/fan-out.d.ts +4 -0
- package/dist/components/fan-out.d.ts.map +1 -1
- package/dist/components/pr-pipeline.d.ts +49 -0
- package/dist/components/pr-pipeline.d.ts.map +1 -1
- package/dist/components/pr-pipeline.js +38 -4
- package/dist/components/pr-run.d.ts +20 -2
- package/dist/components/pr-run.d.ts.map +1 -1
- package/dist/components/pr-run.js +26 -13
- package/dist/components/promote.d.ts +23 -0
- package/dist/components/promote.d.ts.map +1 -1
- package/dist/components/promote.js +60 -2
- package/dist/components/release-compile.d.ts +9 -0
- package/dist/components/release-compile.d.ts.map +1 -1
- package/dist/components/release-compile.js +9 -3
- package/dist/components/release-gate.d.ts +11 -1
- package/dist/components/release-gate.d.ts.map +1 -1
- package/dist/components/release-gate.js +10 -4
- package/dist/components/wave-plan.d.ts +16 -0
- package/dist/components/wave-plan.d.ts.map +1 -1
- package/dist/components/wave-plan.js +36 -2
- package/dist/config.d.ts +38 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -0
- package/dist/flag-params.d.ts +54 -0
- package/dist/flag-params.d.ts.map +1 -0
- package/dist/flag-params.js +130 -0
- package/dist/lifecycle/__fixtures__/publish-fakes.d.ts +120 -0
- package/dist/lifecycle/__fixtures__/publish-fakes.d.ts.map +1 -0
- package/dist/lifecycle/__fixtures__/publish-fakes.js +304 -0
- package/dist/lifecycle/change-set-store.d.ts +2 -1
- package/dist/lifecycle/change-set-store.d.ts.map +1 -1
- package/dist/lifecycle/change-set-store.js +2 -1
- package/dist/lifecycle/delivery-metrics.d.ts +139 -0
- package/dist/lifecycle/delivery-metrics.d.ts.map +1 -0
- package/dist/lifecycle/delivery-metrics.js +145 -0
- package/dist/lifecycle/deploy-outcome.d.ts +81 -0
- package/dist/lifecycle/deploy-outcome.d.ts.map +1 -0
- package/dist/lifecycle/deploy-outcome.js +101 -0
- package/dist/lifecycle/gate-change-set.d.ts +10 -1
- package/dist/lifecycle/gate-change-set.d.ts.map +1 -1
- package/dist/lifecycle/gate-change-set.js +35 -3
- package/dist/lifecycle/gate-ledger.d.ts +2 -3
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
- package/dist/lifecycle/git.d.ts +30 -1
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lifecycle/git.js +31 -5
- package/dist/lifecycle/index.d.ts +4 -0
- package/dist/lifecycle/index.d.ts.map +1 -1
- package/dist/lifecycle/index.js +4 -0
- package/dist/lifecycle/mixed-environment.d.ts +95 -0
- package/dist/lifecycle/mixed-environment.d.ts.map +1 -0
- package/dist/lifecycle/mixed-environment.js +154 -0
- package/dist/lifecycle/publish-attachments.d.ts +79 -0
- package/dist/lifecycle/publish-attachments.d.ts.map +1 -0
- package/dist/lifecycle/publish-attachments.js +179 -0
- package/dist/lifecycle/publish-credentials.d.ts +37 -0
- package/dist/lifecycle/publish-credentials.d.ts.map +1 -0
- package/dist/lifecycle/publish-credentials.js +69 -0
- package/dist/lifecycle/publish-declaration.d.ts +97 -0
- package/dist/lifecycle/publish-declaration.d.ts.map +1 -0
- package/dist/lifecycle/publish-declaration.js +83 -0
- package/dist/lifecycle/publish-forge.d.ts +53 -0
- package/dist/lifecycle/publish-forge.d.ts.map +1 -0
- package/dist/lifecycle/publish-forge.js +298 -0
- package/dist/lifecycle/publish-npm.d.ts +61 -0
- package/dist/lifecycle/publish-npm.d.ts.map +1 -0
- package/dist/lifecycle/publish-npm.js +242 -0
- package/dist/lifecycle/publish-oci.d.ts +64 -0
- package/dist/lifecycle/publish-oci.d.ts.map +1 -0
- package/dist/lifecycle/publish-oci.js +383 -0
- package/dist/lifecycle/publish-record.d.ts +66 -0
- package/dist/lifecycle/publish-record.d.ts.map +1 -0
- package/dist/lifecycle/publish-record.js +84 -0
- package/dist/lifecycle/publish-targets.d.ts +45 -0
- package/dist/lifecycle/publish-targets.d.ts.map +1 -0
- package/dist/lifecycle/publish-targets.js +85 -0
- package/dist/lifecycle/release-cut.d.ts +48 -1
- package/dist/lifecycle/release-cut.d.ts.map +1 -1
- package/dist/lifecycle/release-cut.js +116 -23
- package/dist/lifecycle/release-join.d.ts +95 -0
- package/dist/lifecycle/release-join.d.ts.map +1 -0
- package/dist/lifecycle/release-join.js +139 -0
- package/dist/lifecycle/release-ledger.d.ts +8 -3
- package/dist/lifecycle/release-ledger.d.ts.map +1 -1
- package/dist/lifecycle/release-ledger.js +2 -0
- package/dist/lifecycle/release-publish.d.ts +312 -0
- package/dist/lifecycle/release-publish.d.ts.map +1 -0
- package/dist/lifecycle/release-publish.js +335 -0
- package/dist/lifecycle/release-revocation.d.ts +80 -0
- package/dist/lifecycle/release-revocation.d.ts.map +1 -0
- package/dist/lifecycle/release-revocation.js +123 -0
- package/dist/lifecycle/release-store.d.ts +12 -2
- package/dist/lifecycle/release-store.d.ts.map +1 -1
- package/dist/lifecycle/release-store.js +4 -2
- package/dist/lifecycle/release-verify.d.ts +61 -0
- package/dist/lifecycle/release-verify.d.ts.map +1 -0
- package/dist/lifecycle/release-verify.js +92 -0
- package/dist/lifecycle/release-workstation.d.ts +91 -0
- package/dist/lifecycle/release-workstation.d.ts.map +1 -0
- package/dist/lifecycle/release-workstation.js +180 -0
- package/dist/lifecycle/retention-policy.d.ts +45 -0
- package/dist/lifecycle/retention-policy.d.ts.map +1 -0
- package/dist/lifecycle/retention-policy.js +43 -0
- package/dist/lifecycle/retention.d.ts +185 -0
- package/dist/lifecycle/retention.d.ts.map +1 -0
- package/dist/lifecycle/retention.js +400 -0
- package/dist/lifecycle/run-ledger.d.ts +1 -1
- package/dist/lifecycle/run-ledger.d.ts.map +1 -1
- package/dist/lifecycle/run-ledger.js +9 -0
- package/dist/op/activities/activity-contracts.d.ts +1 -0
- package/dist/op/activities/activity-contracts.d.ts.map +1 -1
- package/dist/op/activities/activity-contracts.js +1 -0
- package/dist/op/activities/shell.d.ts +6 -0
- package/dist/op/activities/shell.d.ts.map +1 -1
- package/dist/op/activities/shell.js +1 -1
- package/dist/op/local-executor.d.ts +23 -1
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/local-executor.js +9 -3
- package/dist/op/runtime.d.ts +27 -0
- package/dist/op/runtime.d.ts.map +1 -1
- package/dist/op/runtime.js +20 -0
- package/dist/plan-summary.d.ts.map +1 -1
- package/dist/plan-summary.js +8 -2
- package/dist/pr-forge.d.ts +23 -0
- package/dist/pr-forge.d.ts.map +1 -1
- package/dist/pr-forge.js +44 -0
- package/dist/pr-loop.d.ts +17 -0
- package/dist/pr-loop.d.ts.map +1 -1
- package/dist/pr-loop.js +30 -4
- package/dist/pr-report-url.d.ts +82 -0
- package/dist/pr-report-url.d.ts.map +1 -0
- package/dist/pr-report-url.js +174 -0
- package/dist/provenance.d.ts +2 -2
- package/dist/provenance.d.ts.map +1 -1
- package/dist/run-event-store.d.ts +29 -2
- package/dist/run-event-store.d.ts.map +1 -1
- package/dist/run-event-store.js +50 -3
- package/dist/run-event-stream.d.ts +4 -3
- package/dist/run-event-stream.d.ts.map +1 -1
- package/dist/run-event-stream.js +7 -3
- package/dist/run-event.d.ts +34 -1
- package/dist/run-event.d.ts.map +1 -1
- package/dist/run-event.js +4 -0
- package/dist/wave-runtime.mjs +260 -229
- package/dist/workspace/__fixtures__/flag-workspace.d.ts +31 -0
- package/dist/workspace/__fixtures__/flag-workspace.d.ts.map +1 -0
- package/dist/workspace/__fixtures__/flag-workspace.js +114 -0
- package/dist/workspace/change-set.schema.json +5 -1
- package/dist/workspace/check.schema.json +4 -2
- package/dist/workspace/checks/records.d.ts +9 -1
- package/dist/workspace/checks/records.d.ts.map +1 -1
- package/dist/workspace/checks/records.js +35 -1
- package/dist/workspace/checks.d.ts +1 -0
- package/dist/workspace/checks.d.ts.map +1 -1
- package/dist/workspace/checks.js +2 -1
- package/dist/workspace/ci-green-forge.d.ts +2 -0
- package/dist/workspace/ci-green-forge.d.ts.map +1 -1
- package/dist/workspace/ci-green.d.ts +10 -0
- package/dist/workspace/ci-green.d.ts.map +1 -1
- package/dist/workspace/ci-green.js +14 -5
- package/dist/workspace/ci-run.d.ts +222 -0
- package/dist/workspace/ci-run.d.ts.map +1 -0
- package/dist/workspace/ci-run.js +464 -0
- package/dist/workspace/ci-serve.d.ts +214 -0
- package/dist/workspace/ci-serve.d.ts.map +1 -0
- package/dist/workspace/ci-serve.js +412 -0
- package/dist/workspace/declaration.d.ts +16 -0
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.js +26 -0
- package/dist/workspace/declaration.schema.json +64 -0
- package/dist/workspace/declared-kinds.d.ts +16 -0
- package/dist/workspace/declared-kinds.d.ts.map +1 -1
- package/dist/workspace/declared-kinds.js +25 -0
- package/dist/workspace/delivery-metrics.d.ts +91 -0
- package/dist/workspace/delivery-metrics.d.ts.map +1 -0
- package/dist/workspace/delivery-metrics.js +217 -0
- package/dist/workspace/delivery-metrics.schema.json +519 -0
- package/dist/workspace/flags-read.d.ts +37 -0
- package/dist/workspace/flags-read.d.ts.map +1 -0
- package/dist/workspace/flags-read.js +103 -0
- package/dist/workspace/flags.d.ts +100 -0
- package/dist/workspace/flags.d.ts.map +1 -0
- package/dist/workspace/flags.js +98 -0
- package/dist/workspace/graph-cli.js +1 -1
- package/dist/workspace/intent-cli.d.ts +11 -2
- package/dist/workspace/intent-cli.d.ts.map +1 -1
- package/dist/workspace/intent-cli.js +54 -5
- package/dist/workspace/intent-trace.d.ts +219 -0
- package/dist/workspace/intent-trace.d.ts.map +1 -0
- package/dist/workspace/intent-trace.js +314 -0
- package/dist/workspace/intent.d.ts +30 -6
- package/dist/workspace/intent.d.ts.map +1 -1
- package/dist/workspace/intent.js +141 -21
- package/dist/workspace/intent.schema.json +702 -6
- package/dist/workspace/overlap.d.ts +146 -0
- package/dist/workspace/overlap.d.ts.map +1 -0
- package/dist/workspace/overlap.js +276 -0
- package/dist/workspace/pr-report.schema.json +5 -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 +3 -0
- package/dist/workspace/records.d.ts +15 -2
- package/dist/workspace/records.d.ts.map +1 -1
- package/dist/workspace/records.js +32 -0
- package/dist/workspace/records.schema.json +21 -2
- package/dist/workspace/reference-kinds/flag/flag.kind.mjs +32 -0
- package/dist/workspace/reference-kinds/flag/flag.schema.json +53 -0
- package/dist/workspace/releases.d.ts +9 -0
- package/dist/workspace/releases.d.ts.map +1 -1
- package/dist/workspace/releases.js +9 -0
- package/dist/workspace/releases.schema.json +123 -1
- package/dist/workspace/run-event.schema.json +24 -6
- package/dist/workspace/status-gates.d.ts +8 -0
- package/dist/workspace/status-gates.d.ts.map +1 -1
- package/dist/workspace/status-stewards.d.ts +2 -0
- package/dist/workspace/status-stewards.d.ts.map +1 -1
- package/dist/workspace/status-stewards.js +1 -0
- package/dist/workspace/status.d.ts +78 -0
- package/dist/workspace/status.d.ts.map +1 -1
- package/dist/workspace/status.js +170 -3
- package/dist/workspace/status.schema.json +210 -2
- package/dist/workspace/wip.d.ts.map +1 -1
- package/dist/workspace/wip.js +11 -3
- package/package.json +1 -1
- package/src/build-params.test.ts +46 -0
- package/src/build-params.ts +55 -1
- package/src/change-set-graph.ts +10 -2
- package/src/change-set.ts +14 -3
- package/src/cli/build-params-cli.ts +22 -9
- package/src/cli/commands/build.ts +6 -3
- package/src/cli/handlers/approve-release.test.ts +5 -1
- package/src/cli/handlers/approve-revoked.test.ts +155 -0
- package/src/cli/handlers/approve.ts +47 -0
- package/src/cli/handlers/build.test.ts +30 -0
- package/src/cli/handlers/build.ts +20 -4
- package/src/cli/handlers/fan-out.test.ts +262 -1
- package/src/cli/handlers/fan-out.ts +72 -8
- package/src/cli/handlers/intent-trace.e2e.test.ts +201 -0
- package/src/cli/handlers/lifecycle-gc.test.ts +126 -0
- package/src/cli/handlers/lifecycle-gc.ts +146 -0
- package/src/cli/handlers/pr.test.ts +148 -1
- package/src/cli/handlers/pr.ts +144 -28
- package/src/cli/handlers/promote.test.ts +280 -2
- package/src/cli/handlers/promote.ts +210 -34
- package/src/cli/handlers/release-implicit.e2e.test.ts +12 -4
- package/src/cli/handlers/release-join.e2e.test.ts +185 -0
- package/src/cli/handlers/release-publish-targets.e2e.test.ts +295 -0
- package/src/cli/handlers/release-verify.e2e.test.ts +117 -0
- package/src/cli/handlers/release-workstation-ci.e2e.test.ts +135 -0
- package/src/cli/handlers/release.e2e.test.ts +146 -32
- package/src/cli/handlers/release.ts +415 -28
- package/src/cli/handlers/run-auto-release.test.ts +9 -0
- package/src/cli/handlers/run-release.test.ts +55 -0
- package/src/cli/handlers/run.ts +69 -5
- package/src/cli/main.test.ts +1 -0
- package/src/cli/main.ts +91 -10
- package/src/cli/parse-args.ts +54 -2
- package/src/cli/registry.ts +32 -4
- package/src/cli/run-events.ts +5 -2
- package/src/components/auto-release.test.ts +9 -0
- package/src/components/auto-release.ts +3 -0
- package/src/components/capability.ts +9 -0
- package/src/components/driver.test.ts +24 -0
- package/src/components/driver.ts +36 -0
- package/src/components/fan-out-inputs.test.ts +85 -0
- package/src/components/fan-out-inputs.ts +192 -0
- package/src/components/fan-out-record.test.ts +10 -0
- package/src/components/fan-out-record.ts +19 -0
- package/src/components/fan-out-run.ts +48 -0
- package/src/components/fan-out.ts +4 -0
- package/src/components/pr-pipeline.ts +67 -4
- package/src/components/pr-run.test.ts +127 -0
- package/src/components/pr-run.ts +32 -12
- package/src/components/promote.ts +68 -2
- package/src/components/release-compile.ts +17 -3
- package/src/components/release-gate.ts +18 -4
- package/src/components/wave-plan.ts +45 -2
- package/src/config.ts +8 -0
- package/src/flag-params.test.ts +144 -0
- package/src/flag-params.ts +134 -0
- package/src/lifecycle/__fixtures__/publish-fakes.ts +351 -0
- package/src/lifecycle/change-set-store.ts +2 -1
- package/src/lifecycle/delivery-metrics.ts +263 -0
- package/src/lifecycle/deploy-outcome.ts +147 -0
- package/src/lifecycle/gate-change-set.ts +37 -6
- package/src/lifecycle/gate-ledger.ts +2 -2
- package/src/lifecycle/git.ts +61 -8
- package/src/lifecycle/index.ts +4 -0
- package/src/lifecycle/mixed-environment.test.ts +97 -0
- package/src/lifecycle/mixed-environment.ts +204 -0
- package/src/lifecycle/publish-attachments.ts +196 -0
- package/src/lifecycle/publish-credentials.ts +84 -0
- package/src/lifecycle/publish-declaration.ts +95 -0
- package/src/lifecycle/publish-forge.ts +325 -0
- package/src/lifecycle/publish-npm.ts +259 -0
- package/src/lifecycle/publish-oci.ts +396 -0
- package/src/lifecycle/publish-record.ts +113 -0
- package/src/lifecycle/publish-resume.test.ts +168 -0
- package/src/lifecycle/publish-targets.test.ts +494 -0
- package/src/lifecycle/publish-targets.ts +100 -0
- package/src/lifecycle/release-cut.test.ts +74 -2
- package/src/lifecycle/release-cut.ts +158 -22
- package/src/lifecycle/release-flag.test.ts +112 -0
- package/src/lifecycle/release-join.test.ts +68 -0
- package/src/lifecycle/release-join.ts +182 -0
- package/src/lifecycle/release-ledger.ts +9 -3
- package/src/lifecycle/release-publish.test.ts +225 -0
- package/src/lifecycle/release-publish.ts +568 -0
- package/src/lifecycle/release-revocation.ts +147 -0
- package/src/lifecycle/release-store.ts +15 -4
- package/src/lifecycle/release-verify.test.ts +110 -0
- package/src/lifecycle/release-verify.ts +120 -0
- package/src/lifecycle/release-workstation.test.ts +204 -0
- package/src/lifecycle/release-workstation.ts +216 -0
- package/src/lifecycle/retention-growth.e2e.test.ts +279 -0
- package/src/lifecycle/retention-policy.ts +63 -0
- package/src/lifecycle/retention.test.ts +363 -0
- package/src/lifecycle/retention.ts +508 -0
- package/src/lifecycle/run-ledger.ts +7 -1
- package/src/op/activities/activity-contracts.ts +1 -0
- package/src/op/activities/shell.ts +7 -1
- package/src/op/local-executor.ts +27 -4
- package/src/op/runtime.ts +42 -0
- package/src/plan-summary.test.ts +20 -0
- package/src/plan-summary.ts +8 -1
- package/src/pr-forge.test.ts +25 -0
- package/src/pr-forge.ts +62 -0
- package/src/pr-loop.test.ts +13 -1
- package/src/pr-loop.ts +41 -4
- package/src/pr-report-url.test.ts +131 -0
- package/src/pr-report-url.ts +209 -0
- package/src/provenance.ts +2 -2
- package/src/run-event-store.ts +63 -3
- package/src/run-event-stream.ts +8 -4
- package/src/run-event.ts +38 -1
- package/src/workspace/__fixtures__/flag-workspace.ts +132 -0
- package/src/workspace/__fixtures__/older-records/change-set-984534ea9.json +100 -0
- package/src/workspace/__fixtures__/older-records/older-records.json +326 -0
- package/src/workspace/change-set.schema.json +5 -1
- package/src/workspace/check.schema.json +4 -2
- package/src/workspace/checks/records.ts +35 -1
- package/src/workspace/checks.ts +2 -1
- package/src/workspace/ci-green-forge.ts +2 -0
- package/src/workspace/ci-green.ts +20 -5
- package/src/workspace/ci-run.test.ts +416 -0
- package/src/workspace/ci-run.ts +571 -0
- package/src/workspace/ci-serve.test.ts +268 -0
- package/src/workspace/ci-serve.ts +521 -0
- package/src/workspace/declaration.schema.json +64 -0
- package/src/workspace/declaration.test.ts +28 -0
- package/src/workspace/declaration.ts +38 -0
- package/src/workspace/declared-kinds.ts +40 -1
- package/src/workspace/delivery-metrics.schema.json +519 -0
- package/src/workspace/delivery-metrics.test.ts +236 -0
- package/src/workspace/delivery-metrics.ts +286 -0
- package/src/workspace/flags-read.ts +121 -0
- package/src/workspace/flags.test.ts +141 -0
- package/src/workspace/flags.ts +157 -0
- package/src/workspace/graph-cli.ts +1 -1
- package/src/workspace/intent-cli.ts +46 -5
- package/src/workspace/intent-trace.test.ts +173 -0
- package/src/workspace/intent-trace.ts +484 -0
- package/src/workspace/intent.schema.json +702 -6
- package/src/workspace/intent.ts +148 -25
- package/src/workspace/older-records.test.ts +412 -0
- package/src/workspace/overlap.test.ts +165 -0
- package/src/workspace/overlap.ts +340 -0
- package/src/workspace/pr-report.schema.json +5 -0
- package/src/workspace/read-contract.e2e.test.ts +2 -1
- package/src/workspace/reason-codes.test.ts +5 -0
- package/src/workspace/reason-codes.ts +3 -0
- package/src/workspace/record-assets.test.ts +2 -1
- package/src/workspace/records-contract.e2e.test.ts +2 -1
- package/src/workspace/records.schema.json +21 -2
- package/src/workspace/records.ts +36 -2
- package/src/workspace/reference-kinds/flag/flag.kind.mjs +32 -0
- package/src/workspace/reference-kinds/flag/flag.schema.json +53 -0
- package/src/workspace/releases.schema.json +123 -1
- package/src/workspace/releases.ts +11 -0
- package/src/workspace/run-event.schema.json +24 -6
- package/src/workspace/status-contract.test.ts +29 -0
- package/src/workspace/status-gates.ts +8 -0
- package/src/workspace/status-mixed.test.ts +152 -0
- package/src/workspace/status-revoked.test.ts +153 -0
- package/src/workspace/status-stewards.ts +3 -0
- package/src/workspace/status.schema.json +210 -2
- package/src/workspace/status.ts +254 -3
- package/src/workspace/wip.ts +11 -2
|
@@ -60,6 +60,8 @@ function world() {
|
|
|
60
60
|
planned: [] as Array<{ root: string; vars?: Record<string, unknown> }>,
|
|
61
61
|
outputsRead: [] as string[],
|
|
62
62
|
applied: [] as string[],
|
|
63
|
+
/** The vars each root applied with, after wiring resolved. */
|
|
64
|
+
ranWith: {} as Record<string, Record<string, unknown> | undefined>,
|
|
63
65
|
fromPlan: {} as Record<string, boolean>,
|
|
64
66
|
failing: new Set<string>(),
|
|
65
67
|
planFailing: new Set<string>(),
|
|
@@ -100,6 +102,7 @@ function world() {
|
|
|
100
102
|
},
|
|
101
103
|
async run(ctx: DeployContext, input: RootInput) {
|
|
102
104
|
w.fromPlan[input.root] = ctx.plans?.[input.root] !== undefined;
|
|
105
|
+
w.ranWith[input.root] = input.vars;
|
|
103
106
|
if (w.failing.has(input.root)) throw new Error(`${input.root} failed`);
|
|
104
107
|
w.applied.push(input.root);
|
|
105
108
|
if (input.root === "a") w.state.a = { id: `a-${w.versions.a ?? "v1"}` };
|
|
@@ -166,6 +169,130 @@ describe("planPrSet", () => {
|
|
|
166
169
|
});
|
|
167
170
|
});
|
|
168
171
|
|
|
172
|
+
describe("a dependency outside the selection (#4104)", () => {
|
|
173
|
+
/** Two members: `up`, and `down` reading its `id`. The pull request changes `down` alone. */
|
|
174
|
+
const PAIR: DriverComponent[] = [root("up"), root("down", ["up"], { upstream: { stackOutput: { stack: "up", name: "id" } } })];
|
|
175
|
+
|
|
176
|
+
function pairWorld() {
|
|
177
|
+
const w = world();
|
|
178
|
+
w.state = { up: { id: "up-live", arn: "arn:up" } };
|
|
179
|
+
w.versions.down = "v2";
|
|
180
|
+
return w;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
test("the dependent's plan resolves the upstream's outputs from its deployed outputs, and the report lists them", async () => {
|
|
184
|
+
const w = pairWorld();
|
|
185
|
+
const plan = planFanOut({ components: PAIR, changed: ["down"] });
|
|
186
|
+
expect(plan.order).toEqual(["down"]);
|
|
187
|
+
expect(plan.seeds).toEqual(["up"]);
|
|
188
|
+
|
|
189
|
+
const set = await planPrSet({ components: PAIR, plan, registry: w.registry, env: "prod" });
|
|
190
|
+
expect(w.outputsRead).toEqual(["up"]);
|
|
191
|
+
expect(w.planned).toEqual([{ root: "down", vars: { upstream: "up-live" } }]);
|
|
192
|
+
expect(set.outputs).toEqual({ up: { id: "up-live", arn: "arn:up" } });
|
|
193
|
+
expect(set.deployedOutputs).toEqual({ up: ["arn", "id"] });
|
|
194
|
+
|
|
195
|
+
const report = prReport({
|
|
196
|
+
stage: "plan",
|
|
197
|
+
pr: 12,
|
|
198
|
+
env: "prod",
|
|
199
|
+
base: "1".repeat(40),
|
|
200
|
+
head: "2".repeat(40),
|
|
201
|
+
op: "pr-12",
|
|
202
|
+
gate: "pr-apply",
|
|
203
|
+
plan,
|
|
204
|
+
signal: { changed: ["down"], indeterminate: [], unclaimed: [] },
|
|
205
|
+
set,
|
|
206
|
+
approval: { status: "pending" },
|
|
207
|
+
status: "planned",
|
|
208
|
+
});
|
|
209
|
+
expect(report.deployedOutputs).toEqual({ up: ["arn", "id"] });
|
|
210
|
+
expect(report.selection).toMatchObject({ changed: ["down"], dependents: [] });
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test("the apply's plan reads the same outputs, gets the approved digest, and applies against them", async () => {
|
|
214
|
+
const w = pairWorld();
|
|
215
|
+
const plan = planFanOut({ components: PAIR, changed: ["down"] });
|
|
216
|
+
const approved = await planPrSet({ components: PAIR, plan, registry: w.registry, env: "prod" });
|
|
217
|
+
|
|
218
|
+
// pr-apply plans the members again before anything applies.
|
|
219
|
+
w.outputsRead.length = 0;
|
|
220
|
+
const set = await planPrSet({ components: PAIR, plan, registry: w.registry, env: "prod" });
|
|
221
|
+
expect(w.outputsRead).toEqual(["up"]);
|
|
222
|
+
expect(set.doc.digest).toBe(approved.doc.digest);
|
|
223
|
+
const check = await decidePrGate(memoryGateLedgerPort({ resolutions: [approval(approved.doc.digest)] }), {
|
|
224
|
+
op: "pr-12",
|
|
225
|
+
gate: "pr-apply",
|
|
226
|
+
digest: set.doc.digest,
|
|
227
|
+
now: NOW,
|
|
228
|
+
});
|
|
229
|
+
expect(check.satisfied).toBe(true);
|
|
230
|
+
|
|
231
|
+
const { members, status } = await applyPrSet({ components: PAIR, plan, registry: w.registry, env: "prod", set });
|
|
232
|
+
expect(status).toBe("applied");
|
|
233
|
+
expect(w.applied).toEqual(["down"]);
|
|
234
|
+
expect(w.ranWith.down).toEqual({ upstream: "up-live" });
|
|
235
|
+
expect(members.map((m) => [m.member, m.status, m.inputsMoved])).toEqual([["down", "applied", undefined]]);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test("an upstream whose deployed output moved after approval moves the apply's digest", async () => {
|
|
239
|
+
const w = pairWorld();
|
|
240
|
+
const plan = planFanOut({ components: PAIR, changed: ["down"] });
|
|
241
|
+
const approved = await planPrSet({ components: PAIR, plan, registry: w.registry, env: "prod" });
|
|
242
|
+
w.state.up = { id: "up-moved", arn: "arn:up" };
|
|
243
|
+
const set = await planPrSet({ components: PAIR, plan, registry: w.registry, env: "prod" });
|
|
244
|
+
expect(set.doc.digest).not.toBe(approved.doc.digest);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test("--seed-outputs wins: a seeded upstream is not read and not listed", async () => {
|
|
248
|
+
const w = pairWorld();
|
|
249
|
+
const plan = planFanOut({ components: PAIR, changed: ["down"] });
|
|
250
|
+
const set = await planPrSet({ components: PAIR, plan, registry: w.registry, env: "prod", seededOutputs: { up: { id: "up-seeded" } } });
|
|
251
|
+
expect(w.outputsRead).toEqual([]);
|
|
252
|
+
expect(w.planned).toEqual([{ root: "down", vars: { upstream: "up-seeded" } }]);
|
|
253
|
+
expect(set.deployedOutputs).toBeUndefined();
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test("an upstream whose steps cannot say leaves the reference unresolved", async () => {
|
|
257
|
+
const w = pairWorld();
|
|
258
|
+
const silent: DriverComponent[] = [
|
|
259
|
+
{ name: "up", dependsOn: [], deploy: [{ phase: "Apply", steps: [{ kind: "no-outputs" }] }] },
|
|
260
|
+
PAIR[1]!,
|
|
261
|
+
];
|
|
262
|
+
w.registry.register({ kind: "no-outputs", async run() { return {}; } } as never);
|
|
263
|
+
const plan = planFanOut({ components: silent, changed: ["down"] });
|
|
264
|
+
const set = await planPrSet({ components: silent, plan, registry: w.registry, env: "prod" });
|
|
265
|
+
expect(w.outputsRead).toEqual([]);
|
|
266
|
+
expect(set.outputs).toEqual({});
|
|
267
|
+
expect(set.deployedOutputs).toBeUndefined();
|
|
268
|
+
expect(w.planned).toHaveLength(1);
|
|
269
|
+
expect(w.planned[0]!.vars?.upstream).not.toBe("up-live");
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test("the selection in place of the whole graph is refused, naming the dependency", async () => {
|
|
273
|
+
const w = pairWorld();
|
|
274
|
+
const plan = planFanOut({ components: PAIR, changed: ["down"] });
|
|
275
|
+
await expect(planPrSet({ components: [PAIR[1]!], plan, registry: w.registry, env: "prod" })).rejects.toThrow(/up, a dependency of a planned member/);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
test("a resume plans against the record's outputs and reads no dependency they cover", async () => {
|
|
279
|
+
const w = world();
|
|
280
|
+
w.versions.a = "v2";
|
|
281
|
+
const plan = planFanOut({ components: ESTATE, changed: ["a"] });
|
|
282
|
+
const { set, first, record } = await firstAttempt(w, plan, ["app"]);
|
|
283
|
+
expect(first.run.completed).toEqual(["a"]);
|
|
284
|
+
expect(set.deployedOutputs).toEqual({ a: ["id"], net: ["cidr"] });
|
|
285
|
+
|
|
286
|
+
w.outputsRead.length = 0;
|
|
287
|
+
w.state.net = { cidr: "10.9.0.0/16" };
|
|
288
|
+
const resumed = await resumePrSet({ components: ESTATE, plan, registry: w.registry, env: "prod", record, completed: ["a"] });
|
|
289
|
+
if (!resumed.ok) throw new Error(resumed.reasons.join("; "));
|
|
290
|
+
expect(w.outputsRead).toEqual([]);
|
|
291
|
+
expect(resumed.set.doc.digest).toBe(set.doc.digest);
|
|
292
|
+
expect(resumed.set.outputs).toBe(record.planOutputs);
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
|
|
169
296
|
describe("the gate", () => {
|
|
170
297
|
test("pending with no approval, approved for the digest, changed for another one", async () => {
|
|
171
298
|
const w = world();
|
package/src/components/pr-run.ts
CHANGED
|
@@ -19,6 +19,16 @@
|
|
|
19
19
|
* its dependents in one rollout is what gated waves are for (#3049): each
|
|
20
20
|
* wave is planned after the one before it applied.
|
|
21
21
|
*
|
|
22
|
+
* A dependency the change did not select is read the same way (#4104), so
|
|
23
|
+
* a change to one cluster plans it against the network it sits on. That is
|
|
24
|
+
* why `components` is every component in the project and not the selection.
|
|
25
|
+
* `--seed-outputs` wins over a read, and a dependency whose steps cannot say
|
|
26
|
+
* leaves its dependents' references unresolved. The apply stage's plan reads
|
|
27
|
+
* the same dependencies, so on the same estate it gets the digest the plan
|
|
28
|
+
* stage showed, and it applies against the outputs its plans read
|
|
29
|
+
* (`set.outputs`). A resume plans against the outputs the approved plans
|
|
30
|
+
* read, from the record (`planOutputs`), and reads nothing those cover.
|
|
31
|
+
*
|
|
22
32
|
* ## Finishing an apply that failed partway
|
|
23
33
|
*
|
|
24
34
|
* {@link resumePrSet} (#3464) is fan-out's resume (#2417, #3049) for a pull
|
|
@@ -53,10 +63,10 @@ import type { PrApplyRecord } from "./fan-out-record";
|
|
|
53
63
|
import type { CapabilityRegistry } from "./capability";
|
|
54
64
|
import type { RunProgressEvent } from "./run-progress";
|
|
55
65
|
import type { RunEventSink } from "../run-event-stream";
|
|
56
|
-
import { planWaveComponent,
|
|
66
|
+
import { planWaveComponent, readDeployedOutputs } from "./wave-plan";
|
|
57
67
|
|
|
58
68
|
export interface PrPlanSetOptions {
|
|
59
|
-
/** Every component in the project. */
|
|
69
|
+
/** Every component in the project, not the selection: a planned member's dependency outside it is read too (#4104). */
|
|
60
70
|
components: DriverComponent[];
|
|
61
71
|
/** The fan-out the change derives (`planFanOut`). Its `order` is what is planned. */
|
|
62
72
|
plan: FanOutPlan;
|
|
@@ -85,6 +95,12 @@ export interface PrPlanSet {
|
|
|
85
95
|
plans: Record<string, Record<string, unknown>>;
|
|
86
96
|
/** The outputs the plans read: the seeds, and each dependency's outputs as they stood. */
|
|
87
97
|
outputs: Record<string, Record<string, unknown>>;
|
|
98
|
+
/**
|
|
99
|
+
* Per dependency read from the deployed estate (#4104), the names of the
|
|
100
|
+
* outputs the read gave, sorted. The seeds are not here. Absent when
|
|
101
|
+
* nothing was read. The report's `deployedOutputs`.
|
|
102
|
+
*/
|
|
103
|
+
deployedOutputs?: Record<string, string[]>;
|
|
88
104
|
/** Whether any component failed to plan. */
|
|
89
105
|
failed: boolean;
|
|
90
106
|
}
|
|
@@ -101,12 +117,15 @@ function failedParts(component: DriverComponent, error: string): ChangeSetPart[]
|
|
|
101
117
|
/**
|
|
102
118
|
* Plan every component in `plan.order`, a wave at a time, each against the
|
|
103
119
|
* outputs its dependencies have now. Never throws for a component that fails
|
|
104
|
-
* to plan: it becomes a failed member, and `failed` is set.
|
|
120
|
+
* to plan: it becomes a failed member, and `failed` is set. Throws when a
|
|
121
|
+
* planned member's dependency is not among `components`, which means the
|
|
122
|
+
* caller passed the selection where the whole graph belongs.
|
|
105
123
|
*/
|
|
106
124
|
export async function planPrSet(options: PrPlanSetOptions): Promise<PrPlanSet> {
|
|
107
125
|
const byName = new Map(options.components.map((c) => [c.name, c]));
|
|
108
126
|
const outputs: Record<string, Record<string, unknown>> = { ...(options.seededOutputs ?? {}) };
|
|
109
127
|
const read = new Set(Object.keys(outputs));
|
|
128
|
+
const deployed: Record<string, string[]> = {};
|
|
110
129
|
const parts: ChangeSetPart[] = [];
|
|
111
130
|
const componentOf = new Map<string, string>();
|
|
112
131
|
const plans: Record<string, Record<string, unknown>> = {};
|
|
@@ -117,15 +136,14 @@ export async function planPrSet(options: PrPlanSetOptions): Promise<PrPlanSet> {
|
|
|
117
136
|
for (const wave of options.plan.waves) {
|
|
118
137
|
// The dependencies' outputs first, each read once. A dependency that
|
|
119
138
|
// cannot say leaves its references unresolved, which its dependent's
|
|
120
|
-
// plan reports if it needs them.
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if (found) outputs[dep] = found;
|
|
127
|
-
}
|
|
139
|
+
// plan reports if it needs them. A dependency outside the selection is
|
|
140
|
+
// read the same way (#4104).
|
|
141
|
+
const deps = [...new Set(wave.flatMap((name) => byName.get(name)?.dependsOn ?? []))].filter((dep) => !read.has(dep));
|
|
142
|
+
for (const dep of deps) {
|
|
143
|
+
read.add(dep);
|
|
144
|
+
if (!byName.has(dep)) throw new Error(`planPrSet: ${dep}, a dependency of a planned member, is not among the components; pass every component in the project`);
|
|
128
145
|
}
|
|
146
|
+
Object.assign(deployed, await readDeployedOutputs(deps, byName, options.registry, (dep) => deployContext(options, dep), outputs));
|
|
129
147
|
const planned = await Promise.all(
|
|
130
148
|
wave.map(async (name) => {
|
|
131
149
|
const at = { component: name, phase: PR_PLAN_PHASE, step: "plan" };
|
|
@@ -170,7 +188,8 @@ export async function planPrSet(options: PrPlanSetOptions): Promise<PrPlanSet> {
|
|
|
170
188
|
counts: memberCounts(doc, m.member),
|
|
171
189
|
...(m.error ? { error: m.error } : {}),
|
|
172
190
|
}));
|
|
173
|
-
|
|
191
|
+
const deployedOutputs = Object.fromEntries(Object.entries(deployed).sort(([a], [b]) => a.localeCompare(b)));
|
|
192
|
+
return { doc, members, plans, outputs, failed, ...(Object.keys(deployedOutputs).length > 0 ? { deployedOutputs } : {}) };
|
|
174
193
|
}
|
|
175
194
|
|
|
176
195
|
/**
|
|
@@ -488,6 +507,7 @@ export function prReport(input: PrReportInput): PrReport {
|
|
|
488
507
|
...(input.refusal ? { refusal: input.refusal } : {}),
|
|
489
508
|
...(input.message ? { message: input.message } : {}),
|
|
490
509
|
...(input.resumed ? { resumed: { applied: [...input.resumed].sort() } } : {}),
|
|
510
|
+
...(input.set.deployedOutputs ? { deployedOutputs: input.set.deployedOutputs } : {}),
|
|
491
511
|
approval: input.approval,
|
|
492
512
|
selection: {
|
|
493
513
|
changed: input.plan.order.filter((c) => changed.has(c)),
|
|
@@ -53,6 +53,7 @@ import {
|
|
|
53
53
|
type DriverStep,
|
|
54
54
|
} from "./driver";
|
|
55
55
|
import type { GateLedgerPort } from "../op/gate";
|
|
56
|
+
import type { Release } from "../lifecycle/release-store";
|
|
56
57
|
import type { RunProgressEvent } from "./run-progress";
|
|
57
58
|
import type { RunEventSink } from "../run-event-stream";
|
|
58
59
|
|
|
@@ -249,6 +250,66 @@ export function planRollback(input: {
|
|
|
249
250
|
return { from: env, to: env, items: [{ component, digest: target.digest, source: target }], notPromoted: [] };
|
|
250
251
|
}
|
|
251
252
|
|
|
253
|
+
/**
|
|
254
|
+
* Decide what a rollback to a release restores (#4035): every component of
|
|
255
|
+
* `release`, each at the newest record in `env`'s ledger that names the
|
|
256
|
+
* release, so the digest restored is the one the environment ran from it. A
|
|
257
|
+
* component of the release this checkout does not declare is reported and
|
|
258
|
+
* left alone. One the environment never ran from the release has no recorded
|
|
259
|
+
* digest, and is refused. So is a release the environment already runs,
|
|
260
|
+
* unless one of its components is `unsettled` (a run left it failed or gated):
|
|
261
|
+
* then the rollback is what puts that component back too.
|
|
262
|
+
*/
|
|
263
|
+
export function planReleaseRollback(input: {
|
|
264
|
+
env: string;
|
|
265
|
+
records: ReleaseRecord[];
|
|
266
|
+
declared: string[];
|
|
267
|
+
release: Pick<Release, "releaseDigest" | "artifactDigests" | "targets">;
|
|
268
|
+
/** The release's label, or null; used in messages. */
|
|
269
|
+
label: string | null;
|
|
270
|
+
/** Components a run left failed or gated in `env` (../lifecycle/mixed-environment.ts). */
|
|
271
|
+
unsettled?: ReadonlySet<string>;
|
|
272
|
+
}): PromotionPlan | { error: string } {
|
|
273
|
+
const { env, records, declared, release } = input;
|
|
274
|
+
const name = input.label ?? release.releaseDigest;
|
|
275
|
+
const target = release.targets.find((t) => t.env === env);
|
|
276
|
+
const inRelease = new Set<string>();
|
|
277
|
+
for (const t of target?.templates ?? []) if (t.component !== null) inRelease.add(t.component);
|
|
278
|
+
for (const a of release.artifactDigests) inRelease.add(a.component);
|
|
279
|
+
const items: PromotionItem[] = [];
|
|
280
|
+
const notPromoted: PromotionPlan["notPromoted"] = [];
|
|
281
|
+
const neverRan: string[] = [];
|
|
282
|
+
for (const component of [...inRelease].sort()) {
|
|
283
|
+
if (!declared.includes(component)) {
|
|
284
|
+
notPromoted.push({ component, reason: `in release ${name} but not declared in this checkout` });
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
const ran = records.filter((r) => r.component === component && r.releaseDigest === release.releaseDigest);
|
|
288
|
+
if (ran.length === 0) {
|
|
289
|
+
neverRan.push(component);
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
const record = latestPerComponent(ran).get(component)!;
|
|
293
|
+
items.push({ component, digest: record.digest, source: record });
|
|
294
|
+
}
|
|
295
|
+
if (neverRan.length > 0) {
|
|
296
|
+
return {
|
|
297
|
+
error: `release ${name} never deployed ${neverRan.map((c) => `"${c}"`).join(", ")} to "${env}", so no recorded digest can be restored; ` +
|
|
298
|
+
`promote it instead: chant components promote --to ${env} --release ${name}`,
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
if (items.length === 0) return { error: `release ${name} has no component declared in this checkout` };
|
|
302
|
+
const current = latestPerComponent(records);
|
|
303
|
+
const already = items.every((i) => {
|
|
304
|
+
const now = current.get(i.component);
|
|
305
|
+
return now !== undefined && now.releaseDigest === release.releaseDigest && now.digest === i.digest && !input.unsettled?.has(i.component);
|
|
306
|
+
});
|
|
307
|
+
if (already) {
|
|
308
|
+
return { error: `"${env}" already runs release ${name}; nothing to roll back. After a deploy that failed partway, chant components redeploy puts a component's release back` };
|
|
309
|
+
}
|
|
310
|
+
return { from: env, to: env, items, notPromoted };
|
|
311
|
+
}
|
|
312
|
+
|
|
252
313
|
/**
|
|
253
314
|
* Decide what a redeploy deploys: the release of `component` the ledger in
|
|
254
315
|
* `env` records as current. A deploy that fails partway records nothing, so
|
|
@@ -641,6 +702,11 @@ function refTo(record: ReleaseRecord) {
|
|
|
641
702
|
return { env: record.env, runId: record.runId, timestamp: record.timestamp };
|
|
642
703
|
}
|
|
643
704
|
|
|
705
|
+
/** A rollback and a redeploy put an earlier deploy back, so they name the release it came from (#4043). */
|
|
706
|
+
function releaseOf(source: ReleaseRecord): { releaseDigest?: string } {
|
|
707
|
+
return source.releaseDigest !== undefined ? { releaseDigest: source.releaseDigest } : {};
|
|
708
|
+
}
|
|
709
|
+
|
|
644
710
|
/**
|
|
645
711
|
* The release record a successful promotion appends to the target ledger.
|
|
646
712
|
* The digest, git sha and archive identities are the source release's own,
|
|
@@ -657,7 +723,7 @@ export function promotionRecord(item: PromotionItem, to: string, run: DeployRunI
|
|
|
657
723
|
* release.
|
|
658
724
|
*/
|
|
659
725
|
export function rollbackRecord(item: PromotionItem, env: string, run: DeployRunInfo): ReleaseRecordInput {
|
|
660
|
-
return { ...sharedRecord(item, env, run), restores: refTo(item.source) };
|
|
726
|
+
return { ...sharedRecord(item, env, run), restores: refTo(item.source), ...releaseOf(item.source) };
|
|
661
727
|
}
|
|
662
728
|
|
|
663
729
|
/**
|
|
@@ -667,5 +733,5 @@ export function rollbackRecord(item: PromotionItem, env: string, run: DeployRunI
|
|
|
667
733
|
* change, and the record says when it was last put back.
|
|
668
734
|
*/
|
|
669
735
|
export function redeployRecord(item: PromotionItem, env: string, run: DeployRunInfo): ReleaseRecordInput {
|
|
670
|
-
return { ...sharedRecord(item, env, run), redeploys: refTo(item.source) };
|
|
736
|
+
return { ...sharedRecord(item, env, run), redeploys: refTo(item.source), ...releaseOf(item.source) };
|
|
671
737
|
}
|
|
@@ -28,7 +28,7 @@ import { spawn, spawnSync } from "node:child_process";
|
|
|
28
28
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
29
29
|
import { isAbsolute, relative, resolve } from "node:path";
|
|
30
30
|
import { loadChantConfig, type ChantConfig } from "../config";
|
|
31
|
-
import {
|
|
31
|
+
import { resolveFlaggedBuildParams } from "../flag-params";
|
|
32
32
|
import { discoverComponents } from "./discover";
|
|
33
33
|
import { withoutDisabled } from "./cli-support";
|
|
34
34
|
import { applyConfigDefaults } from "./config-defaults";
|
|
@@ -72,6 +72,13 @@ export interface CompiledTarget {
|
|
|
72
72
|
components: string[];
|
|
73
73
|
/** Each component's build-time steps, as declared for this environment, in composition order. */
|
|
74
74
|
buildSteps: Record<string, unknown[]>;
|
|
75
|
+
/**
|
|
76
|
+
* Each component's publish steps (`PUBLISH_STEP_KINDS`), as declared for
|
|
77
|
+
* this environment, in composition order (#4027). The cut's dry run shows
|
|
78
|
+
* them as what each environment's deploy publishes. Absent from a compiler
|
|
79
|
+
* that does not report them.
|
|
80
|
+
*/
|
|
81
|
+
publishSteps?: Record<string, unknown[]>;
|
|
75
82
|
/** For each component, the files its build steps read, relative to the member's directory. Used to tell whether an artifact's source changed. */
|
|
76
83
|
buildInputs: Record<string, string[]>;
|
|
77
84
|
/** For each component, the file that declares it, relative to the member's directory. */
|
|
@@ -132,6 +139,11 @@ export function buildStepsOf(component: DriverComponent): DriverStep[] {
|
|
|
132
139
|
return stepsOf(component.deploy).map((s) => s.step).filter((s) => BUILD_STEP_KINDS.includes(s.kind));
|
|
133
140
|
}
|
|
134
141
|
|
|
142
|
+
/** A component's publish steps ({@link PUBLISH_STEP_KINDS}), each as declared, in composition order. */
|
|
143
|
+
export function publishStepsOf(component: DriverComponent): DriverStep[] {
|
|
144
|
+
return stepsOf(component.deploy).map((s) => s.step).filter((s) => PUBLISH_STEP_KINDS.includes(s.kind));
|
|
145
|
+
}
|
|
146
|
+
|
|
135
147
|
/** The literal paths a component's archive steps read: `docker-build`'s `context` and `dockerfile`, `zip-package`'s `source`, `jvm-build`'s `path`. */
|
|
136
148
|
export function buildInputPaths(component: DriverComponent): string[] {
|
|
137
149
|
const out: string[] = [];
|
|
@@ -201,10 +213,11 @@ async function loadComponents(
|
|
|
201
213
|
env: string,
|
|
202
214
|
): Promise<{ config: ChantConfig; params: ReleaseParam[]; declared: number; components: Array<{ component: DriverComponent; file: string }> }> {
|
|
203
215
|
const { config } = await loadChantConfig(inputs.projectDir).catch(() => ({ config: {} as ChantConfig }));
|
|
204
|
-
|
|
216
|
+
// #4024: a workspace member's flag records back their parameters, so a flag's value is a param with source flag:<name>.
|
|
217
|
+
const resolved = await resolveFlaggedBuildParams(inputs.projectDir, config.buildParams, {
|
|
205
218
|
...(inputs.params ? { cli: inputs.params } : {}),
|
|
206
219
|
...(inputs.paramsFile ? { paramsFile: inputs.paramsFile.replaceAll("{env}", env) } : {}),
|
|
207
|
-
});
|
|
220
|
+
}, env);
|
|
208
221
|
if (!resolved.success) throw new ReleaseCompileError(`build parameters for "${env}" did not resolve:\n${resolved.errors.join("\n")}`);
|
|
209
222
|
const discovered = await discoverComponents(inputs.projectDir, { buildParams: resolved.provenance });
|
|
210
223
|
if (discovered.errors.length > 0) throw new ReleaseCompileError(discovered.errors.map((e) => e.message).join("\n"));
|
|
@@ -267,6 +280,7 @@ export async function compileEnvironment(inputs: CompileInputs, env: string): Pr
|
|
|
267
280
|
params,
|
|
268
281
|
components: components.map((c) => c.component.name),
|
|
269
282
|
buildSteps: Object.fromEntries(components.map(({ component }) => [component.name, buildStepsOf(component)])),
|
|
283
|
+
publishSteps: Object.fromEntries(components.map(({ component }) => [component.name, publishStepsOf(component)])),
|
|
270
284
|
buildInputs: Object.fromEntries(components.map(({ component }) => [component.name, buildInputPaths(component)])),
|
|
271
285
|
componentFiles: Object.fromEntries(components.map(({ component, file }) => [component.name, rel(file)])),
|
|
272
286
|
templates,
|
|
@@ -47,14 +47,16 @@
|
|
|
47
47
|
* environment has no deploy of the component). When they are all equal the
|
|
48
48
|
* member has no entries. When the environment's deploy names no release, or
|
|
49
49
|
* the release it names can't be read, the member has a hole: nothing says
|
|
50
|
-
* what the environment runs, so the change can't be shown empty.
|
|
50
|
+
* what the environment runs, so the change can't be shown empty. So does a
|
|
51
|
+
* component a promote, rollback or redeploy left failed or gated with nothing
|
|
52
|
+
* recorded since (#4035): its record names what ran before that run.
|
|
51
53
|
*/
|
|
52
54
|
|
|
53
55
|
import type { CapabilityRegistry } from "./capability";
|
|
54
56
|
import { deployContext, resolveComponentGraph, resolveStepInput, type DriverComponent, type DriverGate, type DriverPhase, type DriverStep } from "./driver";
|
|
55
57
|
import { componentPlanDigest } from "./gate-plan";
|
|
56
58
|
import { gateName, RELEASE_GATE_OP } from "../op/gate-name";
|
|
57
|
-
import { capabilitySteps, readComponentOutputs } from "./wave-plan";
|
|
59
|
+
import { capabilitySteps, planContext, readComponentOutputs } from "./wave-plan";
|
|
58
60
|
import {
|
|
59
61
|
composeChangeSet,
|
|
60
62
|
isEmptyChangeSet,
|
|
@@ -111,6 +113,12 @@ export interface ReleaseChangeSetInput {
|
|
|
111
113
|
registry: CapabilityRegistry;
|
|
112
114
|
/** Per component, what `env` runs now. A component missing here has never been deployed to it. */
|
|
113
115
|
current: ReadonlyMap<string, CurrentDeploy>;
|
|
116
|
+
/**
|
|
117
|
+
* Components a promote, rollback or redeploy left failed or gated in `env`
|
|
118
|
+
* with nothing recorded since (#4035), each with why. What they run is not
|
|
119
|
+
* known, so each is a hole whatever its ledger record says.
|
|
120
|
+
*/
|
|
121
|
+
unsettled?: ReadonlyMap<string, string>;
|
|
114
122
|
/** Outputs supplied by the caller (`--seed-outputs`). */
|
|
115
123
|
seededOutputs?: Record<string, Record<string, unknown>>;
|
|
116
124
|
/** The chant version to record in the document. */
|
|
@@ -151,6 +159,8 @@ export function chantComponentPart(input: {
|
|
|
151
159
|
env: string;
|
|
152
160
|
digest: string;
|
|
153
161
|
current: CurrentDeploy | undefined;
|
|
162
|
+
/** Why the component is unsettled in `env` (#4035), when it is. */
|
|
163
|
+
unsettled?: string;
|
|
154
164
|
}): ChangeSetPart {
|
|
155
165
|
const { component, release, env, digest, current } = input;
|
|
156
166
|
const name = component.name;
|
|
@@ -184,6 +194,10 @@ export function chantComponentPart(input: {
|
|
|
184
194
|
},
|
|
185
195
|
],
|
|
186
196
|
});
|
|
197
|
+
if (input.unsettled !== undefined) {
|
|
198
|
+
member.holes.push({ address: name, type: RELEASE_COMPONENT_ENTRY_TYPE, reason: input.unsettled });
|
|
199
|
+
return { member, entries: [] };
|
|
200
|
+
}
|
|
187
201
|
if (!current) {
|
|
188
202
|
return entry("create", [
|
|
189
203
|
{ path: "artifact", after: digest },
|
|
@@ -238,7 +252,7 @@ export async function planReleaseChangeSet(input: ReleaseChangeSetInput): Promis
|
|
|
238
252
|
const digest = input.digests.get(name) ?? "";
|
|
239
253
|
const plannable = capabilitySteps(component.deploy).filter((s) => registry.has(s.kind) && registry.resolve(s.kind).plan !== undefined);
|
|
240
254
|
if (plannable.length === 0) {
|
|
241
|
-
parts.push(chantComponentPart({ component, release, env, digest, current: input.current.get(name) }));
|
|
255
|
+
parts.push(chantComponentPart({ component, release, env, digest, current: input.current.get(name), unsettled: input.unsettled?.get(name) }));
|
|
242
256
|
continue;
|
|
243
257
|
}
|
|
244
258
|
const ctx = deployContext({ env }, name);
|
|
@@ -251,7 +265,7 @@ export async function planReleaseChangeSet(input: ReleaseChangeSetInput): Promis
|
|
|
251
265
|
}
|
|
252
266
|
for (const step of plannable) {
|
|
253
267
|
const { kind, ...rest } = step;
|
|
254
|
-
const planned = await registry.resolve(kind).plan!(ctx, resolveStepInput(rest, {}, outputs) as never);
|
|
268
|
+
const planned = await registry.resolve(kind).plan!(planContext(ctx, rest, outputs), resolveStepInput(rest, {}, outputs) as never);
|
|
255
269
|
parts.push(
|
|
256
270
|
planned.changeSet ?? {
|
|
257
271
|
member: {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* `@<component>.publish.*`) resolves.
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
-
import { resolveStepInput, type DriverComponent, type DriverPhase, type DriverStep } from "./driver";
|
|
22
|
+
import { resolveStepInput, unresolvedWiringSources, type DriverComponent, type DriverPhase, type DriverStep } from "./driver";
|
|
23
23
|
import { componentPlanDigest } from "./gate-plan";
|
|
24
24
|
import type { CapabilityRegistry, DeployContext } from "./capability";
|
|
25
25
|
import type { WaveMember } from "../gated-waves";
|
|
@@ -60,6 +60,19 @@ export function capabilitySteps(phases: readonly DriverPhase[]): DriverStep[] {
|
|
|
60
60
|
return out;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
/**
|
|
64
|
+
* The context a step is planned with: `ctx`, plus the components whose
|
|
65
|
+
* outputs the step reads and could not read yet (`unresolvedInputs`, #4084).
|
|
66
|
+
*/
|
|
67
|
+
export function planContext(
|
|
68
|
+
ctx: DeployContext,
|
|
69
|
+
stepInput: Record<string, unknown>,
|
|
70
|
+
componentOutputs: Record<string, Record<string, unknown>>,
|
|
71
|
+
): DeployContext {
|
|
72
|
+
const unresolved = unresolvedWiringSources(stepInput, componentOutputs);
|
|
73
|
+
return unresolved.length > 0 ? { ...ctx, unresolvedInputs: unresolved } : ctx;
|
|
74
|
+
}
|
|
75
|
+
|
|
63
76
|
/** Plan one component against the outputs earlier waves produced. Never throws: a failure comes back as `error`. */
|
|
64
77
|
export async function planWaveComponent(
|
|
65
78
|
component: DriverComponent,
|
|
@@ -76,7 +89,7 @@ export async function planWaveComponent(
|
|
|
76
89
|
if (!capability.plan) continue;
|
|
77
90
|
const { kind: _kind, ...rest } = step;
|
|
78
91
|
const input = resolveStepInput(rest, {}, componentOutputs);
|
|
79
|
-
const planned = await capability.plan(ctx, input as never);
|
|
92
|
+
const planned = await capability.plan(planContext(ctx, rest, componentOutputs), input as never);
|
|
80
93
|
const member = { member: planned.member, planDigest: planned.planDigest };
|
|
81
94
|
members.push(member);
|
|
82
95
|
parts.push(planned.changeSet ?? emptyPart(member));
|
|
@@ -122,3 +135,33 @@ export async function readComponentOutputs(
|
|
|
122
135
|
}
|
|
123
136
|
return found;
|
|
124
137
|
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Read the deployed outputs of each of `names` that `outputs` holds nothing
|
|
141
|
+
* for yet (#4104), through {@link readComponentOutputs}, and add them to
|
|
142
|
+
* `outputs`. A name that is not among `components`, or whose steps cannot
|
|
143
|
+
* say, is left out, so its dependents' references stay unresolved. Returns,
|
|
144
|
+
* per component read, the names of the outputs the read gave, sorted.
|
|
145
|
+
*
|
|
146
|
+
* `components` must be the whole graph, not the selection: the dependency a
|
|
147
|
+
* selected member reads is often one the change did not select.
|
|
148
|
+
*/
|
|
149
|
+
export async function readDeployedOutputs(
|
|
150
|
+
names: readonly string[],
|
|
151
|
+
components: ReadonlyMap<string, DriverComponent>,
|
|
152
|
+
registry: CapabilityRegistry,
|
|
153
|
+
ctxOf: (component: string) => DeployContext,
|
|
154
|
+
outputs: Record<string, Record<string, unknown>>,
|
|
155
|
+
): Promise<Record<string, string[]>> {
|
|
156
|
+
const read: Record<string, string[]> = {};
|
|
157
|
+
for (const name of names) {
|
|
158
|
+
if (name in outputs || name in read) continue;
|
|
159
|
+
const component = components.get(name);
|
|
160
|
+
if (!component) continue;
|
|
161
|
+
const found = await readComponentOutputs(component, registry, ctxOf(name), outputs);
|
|
162
|
+
if (!found) continue;
|
|
163
|
+
outputs[name] = found;
|
|
164
|
+
read[name] = Object.keys(found).sort();
|
|
165
|
+
}
|
|
166
|
+
return read;
|
|
167
|
+
}
|
package/src/config.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { findProjectConfig } from "./project-root";
|
|
|
12
12
|
import { evaluateProjectConfig } from "./config-sandbox";
|
|
13
13
|
import { lexiconNames, pathLexiconRoot, registerLexiconDeclarations, type LexiconDeclaration } from "./lexicon-module";
|
|
14
14
|
import { decideConfigSchema, type DecideConfig } from "./op/decide-config";
|
|
15
|
+
import { PublishDeclarationSchema, type PublishDeclaration } from "./lifecycle/publish-declaration";
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* One project-declared environment (chant #1166). Historically always a bare
|
|
@@ -169,6 +170,7 @@ export const ChantConfigSchema = z.object({
|
|
|
169
170
|
).optional(),
|
|
170
171
|
release: z.object({
|
|
171
172
|
autoRecord: z.boolean().optional(),
|
|
173
|
+
publish: z.array(PublishDeclarationSchema).optional(),
|
|
172
174
|
}).optional(),
|
|
173
175
|
// Strict: a misspelt key here would leave Ops on the local runtime with
|
|
174
176
|
// nothing said (#2523).
|
|
@@ -382,6 +384,12 @@ export interface ChantConfig {
|
|
|
382
384
|
release?: {
|
|
383
385
|
/** Set false to disable auto-emitting a release record after a successful component deploy project-wide. The `--no-release-record` CLI flag overrides this per-invocation. */
|
|
384
386
|
autoRecord?: boolean;
|
|
387
|
+
/**
|
|
388
|
+
* Where `chant release publish` publishes a cut release (#4047): npm
|
|
389
|
+
* packages, an image in an OCI registry, release assets on a forge. Run in
|
|
390
|
+
* the order given. See ./lifecycle/publish-declaration.ts.
|
|
391
|
+
*/
|
|
392
|
+
publish?: PublishDeclaration[];
|
|
385
393
|
};
|
|
386
394
|
|
|
387
395
|
/** Defaults for `chant run` and its subcommands (#2523). */
|