@intentius/chant 0.124.27 → 0.124.29
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/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 +63 -0
- package/dist/change-set-graph.d.ts.map +1 -0
- package/dist/change-set-graph.js +130 -0
- package/dist/change-set.d.ts +62 -3
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +36 -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/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 +8 -0
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/build.js +15 -11
- 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 +64 -4
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/build.js +43 -4
- 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 +10 -1
- package/dist/cli/handlers/fan-out.d.ts.map +1 -1
- package/dist/cli/handlers/fan-out.js +54 -6
- 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 +16 -2
- package/dist/cli/handlers/pr.d.ts.map +1 -1
- package/dist/cli/handlers/pr.js +269 -37
- 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 +440 -28
- package/dist/cli/handlers/release.d.ts +91 -0
- package/dist/cli/handlers/release.d.ts.map +1 -0
- package/dist/cli/handlers/release.js +589 -0
- package/dist/cli/handlers/run.d.ts +38 -2
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/handlers/run.js +234 -16
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +143 -17
- 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 +73 -5
- package/dist/cli/registry.d.ts +43 -5
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/cli/run-events.d.ts +30 -0
- package/dist/cli/run-events.d.ts.map +1 -0
- package/dist/cli/run-events.js +49 -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 +6 -1
- package/dist/components/auto-release.d.ts.map +1 -1
- package/dist/components/auto-release.js +3 -0
- package/dist/components/capability.d.ts +9 -0
- package/dist/components/capability.d.ts.map +1 -1
- 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 +50 -9
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/driver.js +116 -54
- 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 +7 -0
- package/dist/components/fan-out-run.d.ts.map +1 -1
- package/dist/components/fan-out-run.js +85 -26
- 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 +25 -0
- package/dist/components/pr-pipeline.d.ts.map +1 -1
- package/dist/components/pr-pipeline.js +22 -4
- 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 +38 -2
- package/dist/components/promote.d.ts.map +1 -1
- package/dist/components/promote.js +77 -2
- package/dist/components/release-compile.d.ts +142 -0
- package/dist/components/release-compile.d.ts.map +1 -0
- package/dist/components/release-compile.js +382 -0
- package/dist/components/release-gate.d.ts +135 -0
- package/dist/components/release-gate.d.ts.map +1 -0
- package/dist/components/release-gate.js +288 -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 +8 -1
- package/dist/components/wave-plan.d.ts.map +1 -1
- package/dist/components/wave-plan.js +11 -3
- package/dist/config.d.ts +45 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -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/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -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 +68 -0
- package/dist/lifecycle/change-set-store.d.ts.map +1 -0
- package/dist/lifecycle/change-set-store.js +97 -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/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 +78 -0
- package/dist/lifecycle/gate-change-set.d.ts.map +1 -0
- package/dist/lifecycle/gate-change-set.js +138 -0
- package/dist/lifecycle/gate-ledger.d.ts +51 -4
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
- package/dist/lifecycle/gate-ledger.js +18 -0
- 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 +154 -0
- package/dist/lifecycle/release-cut.d.ts.map +1 -0
- package/dist/lifecycle/release-cut.js +366 -0
- 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 +54 -0
- package/dist/lifecycle/release-ledger.d.ts.map +1 -1
- package/dist/lifecycle/release-ledger.js +53 -20
- 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 +200 -0
- package/dist/lifecycle/release-store.d.ts.map +1 -0
- package/dist/lifecycle/release-store.js +320 -0
- 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 +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/plan-summary.d.ts.map +1 -1
- package/dist/plan-summary.js +8 -2
- 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/pr-loop.d.ts +7 -0
- package/dist/pr-loop.d.ts.map +1 -1
- package/dist/pr-loop.js +23 -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 +104 -0
- package/dist/run-event-store.d.ts.map +1 -0
- package/dist/run-event-store.js +251 -0
- package/dist/run-event-stream.d.ts +133 -0
- package/dist/run-event-stream.d.ts.map +1 -0
- package/dist/run-event-stream.js +242 -0
- package/dist/run-event.d.ts +285 -0
- package/dist/run-event.d.ts.map +1 -0
- package/dist/run-event.js +209 -0
- package/dist/wave-runtime.mjs +262 -226
- 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/__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 +32 -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/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-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 +281 -13
- package/dist/workspace/ci-green.d.ts.map +1 -1
- package/dist/workspace/ci-green.js +456 -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 +35 -0
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.js +27 -1
- package/dist/workspace/declaration.schema.json +76 -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/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-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 +36 -7
- package/dist/workspace/intent.d.ts.map +1 -1
- package/dist/workspace/intent.js +192 -22
- package/dist/workspace/intent.schema.json +702 -6
- 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/overlap.d.ts +146 -0
- package/dist/workspace/overlap.d.ts.map +1 -0
- package/dist/workspace/overlap.js +276 -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 +4 -0
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/reason-codes.js +7 -1
- 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 +81 -0
- package/dist/workspace/releases.d.ts.map +1 -0
- package/dist/workspace/releases.js +103 -0
- package/dist/workspace/releases.schema.json +513 -0
- package/dist/workspace/run-event.schema.json +295 -0
- package/dist/workspace/status-gates.d.ts +34 -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 +117 -0
- package/dist/workspace/status.d.ts.map +1 -1
- package/dist/workspace/status.js +218 -4
- package/dist/workspace/status.schema.json +268 -2
- 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/dist/workspace/wip.d.ts.map +1 -1
- package/dist/workspace/wip.js +11 -3
- 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/build-params.test.ts +46 -0
- package/src/build-params.ts +55 -1
- package/src/change-set-graph.test.ts +72 -0
- package/src/change-set-graph.ts +141 -0
- package/src/change-set.ts +87 -6
- package/src/chant-version.ts +16 -0
- package/src/cli/build-params-cli.ts +22 -9
- package/src/cli/commands/build.ts +25 -11
- 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 +84 -0
- package/src/cli/handlers/approve-revoked.test.ts +155 -0
- package/src/cli/handlers/approve.ts +65 -2
- package/src/cli/handlers/build.test.ts +67 -0
- package/src/cli/handlers/build.ts +46 -4
- package/src/cli/handlers/change-set.ts +49 -1
- package/src/cli/handlers/fan-out.test.ts +228 -1
- package/src/cli/handlers/fan-out.ts +53 -6
- 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/operator.test.ts +37 -0
- package/src/cli/handlers/pr.test.ts +341 -0
- package/src/cli/handlers/pr.ts +292 -47
- package/src/cli/handlers/promote.test.ts +669 -7
- package/src/cli/handlers/promote.ts +464 -29
- package/src/cli/handlers/release-implicit.e2e.test.ts +161 -0
- 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 +199 -0
- package/src/cli/handlers/release.ts +593 -0
- package/src/cli/handlers/run-auto-release.test.ts +80 -0
- package/src/cli/handlers/run-events-cmd.test.ts +92 -0
- package/src/cli/handlers/run-release.test.ts +55 -0
- package/src/cli/handlers/run.test.ts +30 -0
- package/src/cli/handlers/run.ts +243 -18
- package/src/cli/main.test.ts +8 -0
- package/src/cli/main.ts +143 -17
- 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 +56 -5
- package/src/cli/registry.ts +43 -5
- package/src/cli/run-events.ts +57 -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 +30 -0
- package/src/components/auto-release.ts +9 -1
- package/src/components/capability.ts +9 -0
- package/src/components/cli-support.test.ts +82 -0
- package/src/components/cli-support.ts +16 -9
- package/src/components/driver.test.ts +24 -0
- package/src/components/driver.ts +165 -56
- 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 +104 -35
- package/src/components/fan-out.ts +4 -0
- package/src/components/pr-pipeline.ts +35 -4
- 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 +94 -3
- package/src/components/release-compile.test.ts +147 -0
- package/src/components/release-compile.ts +459 -0
- package/src/components/release-gate.test.ts +215 -0
- package/src/components/release-gate.ts +355 -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 +16 -3
- package/src/config.ts +15 -1
- package/src/flag-params.test.ts +144 -0
- package/src/flag-params.ts +134 -0
- package/src/index.ts +2 -0
- package/src/lifecycle/__fixtures__/publish-fakes.ts +351 -0
- package/src/lifecycle/change-set-store.test.ts +217 -0
- package/src/lifecycle/change-set-store.ts +113 -0
- package/src/lifecycle/commit-sha.ts +23 -0
- package/src/lifecycle/delivery-metrics.ts +263 -0
- package/src/lifecycle/deploy-outcome.ts +147 -0
- package/src/lifecycle/gate-change-set.e2e.test.ts +122 -0
- package/src/lifecycle/gate-change-set.ts +164 -0
- package/src/lifecycle/gate-ledger.test.ts +33 -0
- package/src/lifecycle/gate-ledger.ts +68 -3
- 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-signed.test.ts +180 -0
- package/src/lifecycle/release-cut.test.ts +483 -0
- package/src/lifecycle/release-cut.ts +503 -0
- 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.test.ts +14 -0
- package/src/lifecycle/release-ledger.ts +85 -17
- 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 +434 -0
- 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.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/plan-summary.test.ts +20 -0
- package/src/plan-summary.ts +8 -1
- package/src/point-decision.ts +12 -2
- package/src/pr-loop.ts +25 -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.test.ts +144 -0
- package/src/run-event-store.ts +273 -0
- package/src/run-event-stream.test.ts +175 -0
- package/src/run-event-stream.ts +309 -0
- package/src/run-event.test.ts +277 -0
- package/src/run-event.ts +471 -0
- package/src/wave-loop-golden.test.ts +9 -0
- 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/__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 +32 -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/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-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 +635 -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 +76 -0
- package/src/workspace/declaration.test.ts +28 -0
- package/src/workspace/declaration.ts +60 -2
- 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/identity-gate-rule.ts +91 -7
- package/src/workspace/identity.ts +14 -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 +196 -26
- 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/older-records.test.ts +412 -0
- package/src/workspace/overlap.test.ts +165 -0
- package/src/workspace/overlap.ts +340 -0
- package/src/workspace/promotion-order.ts +97 -0
- package/src/workspace/read-contract.e2e.test.ts +5 -1
- package/src/workspace/reason-codes.test.ts +7 -1
- package/src/workspace/reason-codes.ts +7 -1
- 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 +513 -0
- package/src/workspace/releases.test.ts +108 -0
- package/src/workspace/releases.ts +133 -0
- package/src/workspace/run-event.schema.json +295 -0
- package/src/workspace/status-contract.test.ts +81 -1
- package/src/workspace/status-gates.ts +64 -1
- package/src/workspace/status-mixed.test.ts +152 -0
- package/src/workspace/status-revoked.test.ts +153 -0
- package/src/workspace/status-stewards.ts +6 -0
- package/src/workspace/status.schema.json +268 -2
- package/src/workspace/status.ts +334 -4
- 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/workspace/wip.ts +11 -2
- 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
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which releases are revoked (#4033, ws-105 question 2).
|
|
3
|
+
*
|
|
4
|
+
* A release traces to one commit (ws-104), and a commit is revoked when it
|
|
5
|
+
* carries a `ci/revoked/<sha>` tag that counts (ws-103). So a release is
|
|
6
|
+
* revoked exactly when its commit is, and this module derives that at read
|
|
7
|
+
* time from the tags. Nothing is written when a commit is revoked: the tag is
|
|
8
|
+
* the record, `chant ci tick` makes it once, and every reader below judges it
|
|
9
|
+
* the same way.
|
|
10
|
+
*
|
|
11
|
+
* The tags are judged as `release cut` judges them (#4031): with
|
|
12
|
+
* `ci.green.signedSince` declared, a revoked tag without a valid runner
|
|
13
|
+
* statement is ignored and revokes nothing. The declaration and the runner
|
|
14
|
+
* keys are read at base, the green branch's tip on `origin` (or the local
|
|
15
|
+
* branch, or `HEAD`). Only local tags are read; nothing is fetched.
|
|
16
|
+
*
|
|
17
|
+
* The readers:
|
|
18
|
+
*
|
|
19
|
+
* - `chant approve` refuses a release gate whose release is revoked
|
|
20
|
+
* (`release-revoked`), and `chant components promote --release` refuses to
|
|
21
|
+
* deploy one, so a pending gate for it can never pass.
|
|
22
|
+
* - `chant workspace status --json` reads such a gate as `superseded` and
|
|
23
|
+
* flags a component whose environment runs a revoked release, with the
|
|
24
|
+
* rollback line.
|
|
25
|
+
* - `chant release list` marks a revoked release.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { execFileSync } from "node:child_process";
|
|
29
|
+
import type { CiGreen } from "../workspace/declaration";
|
|
30
|
+
import type { CiTags, RevokedTagView } from "../workspace/ci-green";
|
|
31
|
+
|
|
32
|
+
/** A revoked release: the commit it was cut from and the revoked tag on it, as `ci list` shows the tag. */
|
|
33
|
+
export interface ReleaseRevocation extends RevokedTagView {
|
|
34
|
+
/** The release's commit, which the tag names. */
|
|
35
|
+
commit: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** The stable code `approve` and `promote --release` refuse a revoked release with. */
|
|
39
|
+
export const RELEASE_REVOKED_CODE = "release-revoked";
|
|
40
|
+
|
|
41
|
+
function tryGit(top: string, args: string[]): string | null {
|
|
42
|
+
try {
|
|
43
|
+
return execFileSync("git", args, { cwd: top, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
44
|
+
} catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The ci tags at `top`, judged against `green` at base: the green branch's
|
|
51
|
+
* tip on `origin`, else the local branch, else `HEAD`. With no `ci.green`
|
|
52
|
+
* every tag counts. Throws `WorkspaceReadError` (`declaration-invalid`) when
|
|
53
|
+
* `ci.green.signedSince` names no commit here.
|
|
54
|
+
*/
|
|
55
|
+
export async function judgedCiTags(top: string, green: CiGreen | null): Promise<CiTags> {
|
|
56
|
+
const { readCiTags } = await import("../workspace/ci-green");
|
|
57
|
+
const base = green
|
|
58
|
+
? (tryGit(top, ["rev-parse", "--verify", "--quiet", `refs/remotes/origin/${green.branch}^{commit}`]) ??
|
|
59
|
+
tryGit(top, ["rev-parse", "--verify", "--quiet", `refs/heads/${green.branch}^{commit}`]) ??
|
|
60
|
+
tryGit(top, ["rev-parse", "HEAD"]))
|
|
61
|
+
: null;
|
|
62
|
+
return readCiTags(top, green, base);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Every revoked commit in the repository holding `cwd`, keyed by its full
|
|
67
|
+
* sha, with the declaration's `ci.green` read at base as `release cut` reads
|
|
68
|
+
* it. Empty outside a git repository. Throws `WorkspaceReadError`
|
|
69
|
+
* (`declaration-invalid`) when the tags can't be judged.
|
|
70
|
+
*/
|
|
71
|
+
export async function readRevokedCommits(cwd: string): Promise<Map<string, ReleaseRevocation>> {
|
|
72
|
+
const top = tryGit(cwd, ["rev-parse", "--show-toplevel"]);
|
|
73
|
+
if (!top) return new Map();
|
|
74
|
+
const { scopeSource } = await import("../workspace/write-scope");
|
|
75
|
+
return revokedCommitsAt(top, scopeSource(cwd).declaration?.ci?.green ?? null);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** {@link readRevokedCommits} for a caller that has the git top and the declaration's `ci.green` at base already. */
|
|
79
|
+
export async function revokedCommitsAt(top: string, green: CiGreen | null): Promise<Map<string, ReleaseRevocation>> {
|
|
80
|
+
const { revokedTagView } = await import("../workspace/ci-green");
|
|
81
|
+
const out = new Map<string, ReleaseRevocation>();
|
|
82
|
+
for (const [sha, counted] of (await judgedCiTags(top, green)).revoked) out.set(sha, { commit: sha, ...revokedTagView(sha, counted) });
|
|
83
|
+
return out;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** What a reader prints for a release it found revoked: the revocation, the release and its label. */
|
|
87
|
+
export interface RevokedRelease extends ReleaseRevocation {
|
|
88
|
+
releaseDigest: string;
|
|
89
|
+
/** The release's label, or null when its cuts gave it none. */
|
|
90
|
+
label: string | null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* A memoised lookup from a release digest to its revocation, for the member
|
|
95
|
+
* whose ledger is read from `memberDir`: null when the release is not
|
|
96
|
+
* revoked, or its file can't be read. `revoked` is called on the first
|
|
97
|
+
* lookup only, so a reader that meets no release reads no tags, and no
|
|
98
|
+
* release file is read while no commit is revoked.
|
|
99
|
+
*/
|
|
100
|
+
export function revokedReleaseLookup(
|
|
101
|
+
memberDir: string,
|
|
102
|
+
revoked: () => Promise<ReadonlyMap<string, ReleaseRevocation>>,
|
|
103
|
+
): (releaseDigest: string) => Promise<RevokedRelease | null> {
|
|
104
|
+
const seen = new Map<string, Promise<RevokedRelease | null>>();
|
|
105
|
+
let labels: Promise<Map<string, string>> | undefined;
|
|
106
|
+
return (releaseDigest) => {
|
|
107
|
+
let found = seen.get(releaseDigest);
|
|
108
|
+
if (!found) {
|
|
109
|
+
found = (async () => {
|
|
110
|
+
const commits = await revoked();
|
|
111
|
+
if (commits.size === 0) return null;
|
|
112
|
+
const { readRelease, readCuts, labelsByDigest } = await import("./release-store");
|
|
113
|
+
const loc = { cwd: memberDir };
|
|
114
|
+
const release = await readRelease(releaseDigest, loc).catch(() => null);
|
|
115
|
+
const r = release ? revocationOf(release, commits) : null;
|
|
116
|
+
if (!r) return null;
|
|
117
|
+
labels ??= readCuts(loc).then((c) => labelsByDigest(c.cuts), () => new Map<string, string>());
|
|
118
|
+
return { ...r, releaseDigest, label: (await labels).get(releaseDigest) ?? null };
|
|
119
|
+
})();
|
|
120
|
+
seen.set(releaseDigest, found);
|
|
121
|
+
}
|
|
122
|
+
return found;
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Whether `release` is revoked: its commit's revoked tag, or null. */
|
|
127
|
+
export function revocationOf(release: { commit: string }, revoked: ReadonlyMap<string, ReleaseRevocation>): ReleaseRevocation | null {
|
|
128
|
+
return revoked.get(release.commit) ?? null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** What failed, in words: `phase e2e failed in deploy-smoke (Op run r-12 in staging)`. */
|
|
132
|
+
export function describeRevocation(r: ReleaseRevocation): string {
|
|
133
|
+
const run = r.run
|
|
134
|
+
? ` in ${r.run.name}${r.run.opRun ? ` (Op run ${r.run.opRun.id} in ${r.run.opRun.env})` : r.run.id !== null ? ` (run ${r.run.id})` : ""}`
|
|
135
|
+
: "";
|
|
136
|
+
return r.phase !== null ? `phase ${r.phase} failed${run}` : `a required phase failed${run}`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** The refusal `approve` and `promote --release` print for a revoked release, and the line that fixes it. */
|
|
140
|
+
export function revokedRefusal(release: { releaseDigest: string; label: string | null }, r: ReleaseRevocation, fix: string): { message: string; hint: string } {
|
|
141
|
+
return {
|
|
142
|
+
message:
|
|
143
|
+
`release ${release.label ?? release.releaseDigest} was cut from commit ${r.commit.slice(0, 12)}, which ${r.tag} revokes ` +
|
|
144
|
+
`(${describeRevocation(r)}), so it is not deployed and no approval of it counts [${RELEASE_REVOKED_CODE}]`,
|
|
145
|
+
hint: fix,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The release (#4026, ws-104): one green commit of one member, compiled for
|
|
3
|
+
* every environment the member declares, stored on `chant/lifecycle`.
|
|
4
|
+
*
|
|
5
|
+
* A release is a different object from a deploy record. A deploy record is
|
|
6
|
+
* one line of `<env>/releases.jsonl` (./release-ledger.ts) saying a component
|
|
7
|
+
* was deployed. A release is what is cut once from a green commit and then
|
|
8
|
+
* moves through the environments: the commit, the artifacts built once from
|
|
9
|
+
* it, and one compile target per environment, each with the template digests
|
|
10
|
+
* and the build parameters that environment was compiled with.
|
|
11
|
+
*
|
|
12
|
+
* Storage, per ws-104 question 3:
|
|
13
|
+
*
|
|
14
|
+
* _releases/<releaseDigest>.json the release, written once and never rewritten
|
|
15
|
+
* _releases/cuts.jsonl one line per cut, with the release's label
|
|
16
|
+
* _releases/templates/<digest>.txt each template's bytes, by content digest
|
|
17
|
+
*
|
|
18
|
+
* `:` is written as `_` in file names, as `_plans/` and `_builds/` do. A
|
|
19
|
+
* workspace member's copy sits under `_members/<member>/` through the same
|
|
20
|
+
* `ledgerDir` prefix every other store uses.
|
|
21
|
+
*
|
|
22
|
+
* The template bytes are kept so that a promote under a release deploys the
|
|
23
|
+
* templates the cut recorded, not whatever the promoting checkout holds
|
|
24
|
+
* (ws-104 question 5). They are named by content digest, so a template that
|
|
25
|
+
* two environments or two releases share is stored once.
|
|
26
|
+
*
|
|
27
|
+
* `releaseDigest` is `computePlanDigest("release", subject)` over the member,
|
|
28
|
+
* the commit, the artifact digests and each target's env, templates and the
|
|
29
|
+
* name and value of each param. A param's `source`, the label, the actor, the
|
|
30
|
+
* time and any signature stay outside it, so two cuts of one commit give the
|
|
31
|
+
* same digest.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
import { sortedJsonReplacer } from "../utils";
|
|
35
|
+
import { contentDigest } from "../content-digest";
|
|
36
|
+
import { computePlanDigest } from "./plan-digest";
|
|
37
|
+
import {
|
|
38
|
+
ledgerDir,
|
|
39
|
+
listFilesInDir,
|
|
40
|
+
readBlobBySha,
|
|
41
|
+
readBlobFromPath,
|
|
42
|
+
readPathSha,
|
|
43
|
+
RefCASConflictError,
|
|
44
|
+
writeLedgerFiles,
|
|
45
|
+
type LedgerFileWrite,
|
|
46
|
+
type LedgerLocation,
|
|
47
|
+
} from "./git";
|
|
48
|
+
import type { RunOrigin } from "./release-ledger";
|
|
49
|
+
|
|
50
|
+
/** The directory on `chant/lifecycle` that holds releases, cuts and template bytes. */
|
|
51
|
+
export const RELEASES_DIR = "_releases";
|
|
52
|
+
/** The cut log inside {@link RELEASES_DIR}. */
|
|
53
|
+
export const CUTS_FILE = "cuts.jsonl";
|
|
54
|
+
/** The directory inside {@link RELEASES_DIR} that holds template bytes by content digest. */
|
|
55
|
+
export const TEMPLATES_SUBDIR = "templates";
|
|
56
|
+
|
|
57
|
+
/** The `kind` a release digest is computed under, so it can never equal a plan digest of another kind. */
|
|
58
|
+
export const RELEASE_DIGEST_KIND = "release";
|
|
59
|
+
|
|
60
|
+
/** An artifact built once from the release's commit: an `image` or `asset` archive entry. */
|
|
61
|
+
export interface ReleaseArtifact {
|
|
62
|
+
component: string;
|
|
63
|
+
kind: "image" | "asset";
|
|
64
|
+
/** The archive path the build step wrote. */
|
|
65
|
+
path: string;
|
|
66
|
+
digest: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** One template of a target: a file a component's deploy reads, compiled for the target's environment. */
|
|
70
|
+
export interface ReleaseTemplate {
|
|
71
|
+
/** The component whose deploy reads it, or null for a project with no components. */
|
|
72
|
+
component: string | null;
|
|
73
|
+
/** The path the deploy step names, relative to the member's directory. */
|
|
74
|
+
path: string;
|
|
75
|
+
/** `sha256:<hex>` of the file's bytes. */
|
|
76
|
+
digest: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** One resolved build parameter of a target. */
|
|
80
|
+
export interface ReleaseParam {
|
|
81
|
+
name: string;
|
|
82
|
+
value: string | number | boolean;
|
|
83
|
+
/** Where the value came from: `cli`, `params-file`, `env` or `default`. Outside the digest. */
|
|
84
|
+
source: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** What one environment compiles to. */
|
|
88
|
+
export interface ReleaseTarget {
|
|
89
|
+
env: string;
|
|
90
|
+
templates: ReleaseTemplate[];
|
|
91
|
+
params: ReleaseParam[];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** The release file, `_releases/<releaseDigest>.json`. */
|
|
95
|
+
export interface Release {
|
|
96
|
+
version: 1;
|
|
97
|
+
releaseDigest: string;
|
|
98
|
+
/** The workspace member, its path of names joined with `/` when nested, or null outside a workspace. */
|
|
99
|
+
member: string | null;
|
|
100
|
+
/** The full sha of the green commit. */
|
|
101
|
+
commit: string;
|
|
102
|
+
artifactDigests: ReleaseArtifact[];
|
|
103
|
+
targets: ReleaseTarget[];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** One line of `_releases/cuts.jsonl`. */
|
|
107
|
+
export interface ReleaseCut {
|
|
108
|
+
version: 1;
|
|
109
|
+
releaseDigest: string;
|
|
110
|
+
/** The human name of the release, such as `v1.4.0`, or null for a cut with none. */
|
|
111
|
+
label: string | null;
|
|
112
|
+
actor: string;
|
|
113
|
+
timestamp: string;
|
|
114
|
+
/**
|
|
115
|
+
* The run the cut ran in (#4027): the CI run's id in CI, or an id minted on
|
|
116
|
+
* the machine that cut (`local-<epoch ms>`). Null on every cut written
|
|
117
|
+
* before 0.124.29.
|
|
118
|
+
*/
|
|
119
|
+
runId: string | null;
|
|
120
|
+
/**
|
|
121
|
+
* Where that run ran: the CI run's origin, or `{ forge: "local" }` for a
|
|
122
|
+
* minted id (#4027). A cut written before 0.124.29 holds null outside CI.
|
|
123
|
+
*/
|
|
124
|
+
runOrigin: RunOrigin | null;
|
|
125
|
+
/**
|
|
126
|
+
* The cut's signature (#4031): an armored ssh signature by the actor's key
|
|
127
|
+
* in the `chant-release` namespace over the release digest, the label, the
|
|
128
|
+
* actor and the time, one per line (`releaseSealPayload`). Null for an
|
|
129
|
+
* unsigned cut, and on every cut written before 0.124.28.
|
|
130
|
+
*/
|
|
131
|
+
signature: string | null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** The parts of a release a digest is computed from. */
|
|
135
|
+
export type ReleaseSubject = Pick<Release, "member" | "commit" | "artifactDigests" | "targets">;
|
|
136
|
+
|
|
137
|
+
const byString = (a: string, b: string): number => (a < b ? -1 : a > b ? 1 : 0);
|
|
138
|
+
|
|
139
|
+
/** The same release with every list in the order ws-104 sets: artifacts by component then path, targets by env, templates by component then path, params by name. */
|
|
140
|
+
export function normalizeRelease<T extends ReleaseSubject>(release: T): T {
|
|
141
|
+
return {
|
|
142
|
+
...release,
|
|
143
|
+
artifactDigests: [...release.artifactDigests]
|
|
144
|
+
.map(({ component, kind, path, digest }) => ({ component, kind, path, digest }))
|
|
145
|
+
.sort((a, b) => byString(a.component, b.component) || byString(a.path, b.path)),
|
|
146
|
+
targets: [...release.targets]
|
|
147
|
+
.map((t) => ({
|
|
148
|
+
env: t.env,
|
|
149
|
+
templates: [...t.templates]
|
|
150
|
+
.map(({ component, path, digest }) => ({ component, path, digest }))
|
|
151
|
+
.sort((a, b) => byString(a.component ?? "", b.component ?? "") || byString(a.path, b.path)),
|
|
152
|
+
params: [...t.params].map(({ name, value, source }) => ({ name, value, source })).sort((a, b) => byString(a.name, b.name)),
|
|
153
|
+
}))
|
|
154
|
+
.sort((a, b) => byString(a.env, b.env)),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** The hash input of ws-104 question 3: a param's `source` is left out. */
|
|
159
|
+
export function releaseHashInput(release: ReleaseSubject): unknown {
|
|
160
|
+
const n = normalizeRelease(release);
|
|
161
|
+
return {
|
|
162
|
+
member: n.member,
|
|
163
|
+
commit: n.commit,
|
|
164
|
+
artifactDigests: n.artifactDigests,
|
|
165
|
+
targets: n.targets.map((t) => ({ env: t.env, templates: t.templates, params: t.params.map(({ name, value }) => ({ name, value })) })),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** `computePlanDigest("release", input)` over {@link releaseHashInput}. */
|
|
170
|
+
export function computeReleaseDigest(release: ReleaseSubject): string {
|
|
171
|
+
return computePlanDigest(RELEASE_DIGEST_KIND, releaseHashInput(release));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** A release with its digest computed and its lists in order. */
|
|
175
|
+
export function makeRelease(subject: ReleaseSubject): Release {
|
|
176
|
+
const normalized = normalizeRelease(subject);
|
|
177
|
+
return { version: 1, releaseDigest: computeReleaseDigest(normalized), ...normalized };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Whether a release's own `releaseDigest` is the digest of what it holds. */
|
|
181
|
+
export function verifyReleaseDigest(release: Release): boolean {
|
|
182
|
+
return release.releaseDigest === computeReleaseDigest(release);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const stem = (digest: string): string => digest.replace(/:/g, "_");
|
|
186
|
+
const releaseFile = (digest: string): string => `${stem(digest)}.json`;
|
|
187
|
+
const templateFile = (digest: string): string => `${stem(digest)}.txt`;
|
|
188
|
+
|
|
189
|
+
/** A label names a release in commands such as `chant release cut <label>`, so it is one plain word. */
|
|
190
|
+
const LABEL_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$/;
|
|
191
|
+
|
|
192
|
+
/** Why a label can't be used, or undefined when it can. */
|
|
193
|
+
export function invalidLabel(label: string): string | undefined {
|
|
194
|
+
if (LABEL_PATTERN.test(label)) return undefined;
|
|
195
|
+
return `"${label}" is not a release label: use letters, digits and . _ + -, starting with a letter or digit, at most 128 characters`;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Thrown when a cut would give a label two digests, or a digest two labels. */
|
|
199
|
+
export class ReleaseLabelError extends Error {
|
|
200
|
+
constructor(
|
|
201
|
+
readonly code: "release-label-taken" | "release-already-labelled" | "release-label-invalid",
|
|
202
|
+
message: string,
|
|
203
|
+
) {
|
|
204
|
+
super(message);
|
|
205
|
+
this.name = "ReleaseLabelError";
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** A release's template bytes, as the cut read them. */
|
|
210
|
+
export interface TemplateContent {
|
|
211
|
+
digest: string;
|
|
212
|
+
content: string;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Store a release and its template bytes in one commit. A release already
|
|
217
|
+
* stored under its digest is never rewritten, and a template already stored
|
|
218
|
+
* is not written again. Does not push.
|
|
219
|
+
*/
|
|
220
|
+
export async function persistRelease(
|
|
221
|
+
release: Release,
|
|
222
|
+
templates: readonly TemplateContent[],
|
|
223
|
+
opts?: { cwd?: string },
|
|
224
|
+
): Promise<{ commit: string | null; written: boolean }> {
|
|
225
|
+
if (!verifyReleaseDigest(release)) {
|
|
226
|
+
throw new Error(`release ${release.releaseDigest} does not hash to its own digest; refusing to store it`);
|
|
227
|
+
}
|
|
228
|
+
for (const t of templates) {
|
|
229
|
+
if (contentDigest(t.content) !== t.digest) throw new Error(`template bytes do not hash to ${t.digest}; refusing to store them`);
|
|
230
|
+
}
|
|
231
|
+
const dir = await ledgerDir(RELEASES_DIR, opts);
|
|
232
|
+
const files: LedgerFileWrite[] = [];
|
|
233
|
+
const releasePath = `${dir}/${releaseFile(release.releaseDigest)}`;
|
|
234
|
+
const exists = (await readPathSha(RELEASES_DIR, releaseFile(release.releaseDigest), opts)) !== null;
|
|
235
|
+
if (!exists) files.push({ path: releasePath, content: `${JSON.stringify(release, sortedJsonReplacer)}\n`, expectPriorSha: null });
|
|
236
|
+
const seen = new Set<string>();
|
|
237
|
+
for (const t of templates) {
|
|
238
|
+
if (seen.has(t.digest)) continue;
|
|
239
|
+
seen.add(t.digest);
|
|
240
|
+
if ((await readPathSha(`${RELEASES_DIR}/${TEMPLATES_SUBDIR}`, templateFile(t.digest), opts)) !== null) continue;
|
|
241
|
+
files.push({ path: `${dir}/${TEMPLATES_SUBDIR}/${templateFile(t.digest)}`, content: t.content, expectPriorSha: null });
|
|
242
|
+
}
|
|
243
|
+
if (files.length === 0) return { commit: null, written: false };
|
|
244
|
+
try {
|
|
245
|
+
const commit = await writeLedgerFiles(files, `Release ${release.releaseDigest}`, opts);
|
|
246
|
+
return { commit, written: !exists };
|
|
247
|
+
} catch (err) {
|
|
248
|
+
// Another writer stored the same content-addressed files first.
|
|
249
|
+
if (err instanceof RefCASConflictError) return { commit: null, written: false };
|
|
250
|
+
throw err;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** Parse a stored release file, or null when it is not a release. */
|
|
255
|
+
export function parseRelease(text: string): Release | null {
|
|
256
|
+
let parsed: unknown;
|
|
257
|
+
try {
|
|
258
|
+
parsed = JSON.parse(text);
|
|
259
|
+
} catch {
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
263
|
+
const r = parsed as Partial<Release>;
|
|
264
|
+
const ok =
|
|
265
|
+
r.version === 1 &&
|
|
266
|
+
typeof r.releaseDigest === "string" &&
|
|
267
|
+
(r.member === null || typeof r.member === "string") &&
|
|
268
|
+
typeof r.commit === "string" &&
|
|
269
|
+
Array.isArray(r.artifactDigests) &&
|
|
270
|
+
r.artifactDigests.every((a) => a && typeof a.component === "string" && (a.kind === "image" || a.kind === "asset") && typeof a.path === "string" && typeof a.digest === "string") &&
|
|
271
|
+
Array.isArray(r.targets) &&
|
|
272
|
+
r.targets.every(
|
|
273
|
+
(t) =>
|
|
274
|
+
t &&
|
|
275
|
+
typeof t.env === "string" &&
|
|
276
|
+
Array.isArray(t.templates) &&
|
|
277
|
+
t.templates.every((x) => x && (x.component === null || typeof x.component === "string") && typeof x.path === "string" && typeof x.digest === "string") &&
|
|
278
|
+
Array.isArray(t.params) &&
|
|
279
|
+
t.params.every((p) => p && typeof p.name === "string" && ["string", "number", "boolean"].includes(typeof p.value) && typeof p.source === "string"),
|
|
280
|
+
);
|
|
281
|
+
return ok ? (r as Release) : null;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/** Read one release by digest. Null when none is stored, or when the file is not a release whose content hashes to its digest. */
|
|
285
|
+
export async function readRelease(digest: string, opts?: LedgerLocation): Promise<Release | null> {
|
|
286
|
+
const text = await readBlobFromPath(RELEASES_DIR, releaseFile(digest), opts);
|
|
287
|
+
if (text === null) return null;
|
|
288
|
+
const release = parseRelease(text);
|
|
289
|
+
if (!release || release.releaseDigest !== digest || !verifyReleaseDigest(release)) return null;
|
|
290
|
+
return release;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/** Every stored release, and how many release files could not be read as one. */
|
|
294
|
+
export async function readAllReleases(opts?: LedgerLocation): Promise<{ releases: Release[]; malformed: number }> {
|
|
295
|
+
const releases: Release[] = [];
|
|
296
|
+
let malformed = 0;
|
|
297
|
+
for (const name of await listFilesInDir(RELEASES_DIR, opts)) {
|
|
298
|
+
if (!name.endsWith(".json")) continue;
|
|
299
|
+
const digest = name.slice(0, -".json".length).replace(/_/g, ":");
|
|
300
|
+
const release = await readRelease(digest, opts);
|
|
301
|
+
if (release) releases.push(release);
|
|
302
|
+
else malformed++;
|
|
303
|
+
}
|
|
304
|
+
return { releases, malformed };
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/** Parse the lines of `cuts.jsonl`. A line that is not a cut is counted and skipped. */
|
|
308
|
+
export function parseCuts(text: string | null): { cuts: ReleaseCut[]; malformed: number } {
|
|
309
|
+
const cuts: ReleaseCut[] = [];
|
|
310
|
+
let malformed = 0;
|
|
311
|
+
for (const line of (text ?? "").split("\n").map((l) => l.trim()).filter(Boolean)) {
|
|
312
|
+
try {
|
|
313
|
+
const c = JSON.parse(line) as Partial<ReleaseCut>;
|
|
314
|
+
const ok =
|
|
315
|
+
c &&
|
|
316
|
+
c.version === 1 &&
|
|
317
|
+
typeof c.releaseDigest === "string" &&
|
|
318
|
+
(c.label === null || typeof c.label === "string") &&
|
|
319
|
+
typeof c.actor === "string" &&
|
|
320
|
+
typeof c.timestamp === "string" &&
|
|
321
|
+
(c.runOrigin === null || (typeof c.runOrigin === "object" && typeof c.runOrigin?.forge === "string")) &&
|
|
322
|
+
(c.signature === null || c.signature === undefined || typeof c.signature === "string") &&
|
|
323
|
+
(c.runId === null || c.runId === undefined || typeof c.runId === "string");
|
|
324
|
+
if (!ok) {
|
|
325
|
+
malformed++;
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
cuts.push({ ...(c as ReleaseCut), runId: c.runId ?? null, signature: c.signature ?? null });
|
|
329
|
+
} catch {
|
|
330
|
+
malformed++;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return { cuts, malformed };
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/** Every cut, oldest first. */
|
|
337
|
+
export async function readCuts(opts?: LedgerLocation): Promise<{ cuts: ReleaseCut[]; malformed: number }> {
|
|
338
|
+
return parseCuts(await readBlobFromPath(RELEASES_DIR, CUTS_FILE, opts));
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/** The label each digest carries, from its labelled cuts. */
|
|
342
|
+
export function labelsByDigest(cuts: readonly ReleaseCut[]): Map<string, string> {
|
|
343
|
+
const out = new Map<string, string>();
|
|
344
|
+
for (const c of cuts) if (c.label !== null && !out.has(c.releaseDigest)) out.set(c.releaseDigest, c.label);
|
|
345
|
+
return out;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/** What {@link appendCut} did. */
|
|
349
|
+
export type CutOutcome =
|
|
350
|
+
| { appended: true; cut: ReleaseCut }
|
|
351
|
+
/** The digest was already cut; nothing was written. `label` is the label it carries, or null. */
|
|
352
|
+
| { appended: false; label: string | null };
|
|
353
|
+
|
|
354
|
+
const CUT_RETRY_ATTEMPTS = 5;
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Append a cut for `releaseDigest`. A label names one digest and a digest
|
|
358
|
+
* takes at most one label (ws-104 question 3): cutting a digest again is a
|
|
359
|
+
* no-op that returns its label, giving an unlabelled digest a label appends a
|
|
360
|
+
* cut, and cutting a digest under a label other than its own, or under a
|
|
361
|
+
* label another digest has, throws {@link ReleaseLabelError}. Does not push.
|
|
362
|
+
*/
|
|
363
|
+
export async function appendCut(
|
|
364
|
+
input: Omit<ReleaseCut, "version" | "signature" | "runId"> & { signature?: string | null; runId?: string | null },
|
|
365
|
+
opts?: { cwd?: string },
|
|
366
|
+
): Promise<CutOutcome> {
|
|
367
|
+
if (input.label !== null) {
|
|
368
|
+
const why = invalidLabel(input.label);
|
|
369
|
+
if (why) throw new ReleaseLabelError("release-label-invalid", why);
|
|
370
|
+
}
|
|
371
|
+
let lastErr: unknown;
|
|
372
|
+
for (let attempt = 1; attempt <= CUT_RETRY_ATTEMPTS; attempt++) {
|
|
373
|
+
const priorSha = await readPathSha(RELEASES_DIR, CUTS_FILE, opts);
|
|
374
|
+
const existing = priorSha ? await readBlobBySha(priorSha, opts) : null;
|
|
375
|
+
const { cuts } = parseCuts(existing);
|
|
376
|
+
const labels = labelsByDigest(cuts);
|
|
377
|
+
const own = labels.get(input.releaseDigest);
|
|
378
|
+
const alreadyCut = cuts.some((c) => c.releaseDigest === input.releaseDigest);
|
|
379
|
+
if (own !== undefined) {
|
|
380
|
+
if (input.label === null || input.label === own) return { appended: false, label: own };
|
|
381
|
+
throw new ReleaseLabelError(
|
|
382
|
+
"release-already-labelled",
|
|
383
|
+
`release ${input.releaseDigest} is already labelled "${own}", and a release takes one label`,
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
if (input.label === null && alreadyCut) return { appended: false, label: null };
|
|
387
|
+
if (input.label !== null) {
|
|
388
|
+
for (const [digest, label] of labels) {
|
|
389
|
+
if (label === input.label) {
|
|
390
|
+
throw new ReleaseLabelError("release-label-taken", `the label "${input.label}" already names release ${digest}`);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
const cut: ReleaseCut = {
|
|
395
|
+
version: 1,
|
|
396
|
+
releaseDigest: input.releaseDigest,
|
|
397
|
+
label: input.label,
|
|
398
|
+
actor: input.actor,
|
|
399
|
+
timestamp: input.timestamp,
|
|
400
|
+
runId: input.runId ?? null,
|
|
401
|
+
runOrigin: input.runOrigin,
|
|
402
|
+
signature: input.signature ?? null,
|
|
403
|
+
};
|
|
404
|
+
const content = `${existing ? `${existing.replace(/\n$/, "")}\n` : ""}${JSON.stringify(cut, sortedJsonReplacer)}\n`;
|
|
405
|
+
try {
|
|
406
|
+
const dir = await ledgerDir(RELEASES_DIR, opts);
|
|
407
|
+
await writeLedgerFiles([{ path: `${dir}/${CUTS_FILE}`, content, expectPriorSha: priorSha }], "Release cut", opts);
|
|
408
|
+
return { appended: true, cut };
|
|
409
|
+
} catch (err) {
|
|
410
|
+
if (!(err instanceof RefCASConflictError)) throw err;
|
|
411
|
+
lastErr = err;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
throw lastErr;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/** A template's bytes by digest, or null when none is stored. */
|
|
418
|
+
export async function readReleaseTemplate(digest: string, opts?: LedgerLocation): Promise<string | null> {
|
|
419
|
+
return readBlobFromPath(`${RELEASES_DIR}/${TEMPLATES_SUBDIR}`, templateFile(digest), opts);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Find a release by its digest or its label. Null when neither names one.
|
|
424
|
+
* A string that names a digest is read as a digest first.
|
|
425
|
+
*/
|
|
426
|
+
export async function resolveRelease(ref: string, opts?: LedgerLocation): Promise<{ release: Release; label: string | null } | null> {
|
|
427
|
+
const { cuts } = await readCuts(opts);
|
|
428
|
+
const labels = labelsByDigest(cuts);
|
|
429
|
+
let digest: string | undefined = ref.includes(":") ? ref : undefined;
|
|
430
|
+
if (!digest) for (const [d, label] of labels) if (label === ref) digest = d;
|
|
431
|
+
if (!digest) return null;
|
|
432
|
+
const release = await readRelease(digest, opts);
|
|
433
|
+
return release ? { release, label: labels.get(digest) ?? null } : null;
|
|
434
|
+
}
|