@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,396 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The OCI publish target (#4047): an image pushed to an OCI registry, such
|
|
3
|
+
* as GHCR or Docker Hub, by digest, then tagged.
|
|
4
|
+
*
|
|
5
|
+
* The image is an OCI image layout (`index.json` and `blobs/`), as a
|
|
6
|
+
* directory or a tar file such as `docker save` writes. Its `index.json`
|
|
7
|
+
* names one manifest: an image manifest, or an index of them. The target
|
|
8
|
+
* speaks the registry HTTP API (the OCI distribution spec) through an
|
|
9
|
+
* injectable `fetch`, so it needs no docker, crane or oras, and the tests
|
|
10
|
+
* run it against a registry fake on 127.0.0.1. A publish is two steps
|
|
11
|
+
* (#4048). The stage pushes by digest only: for each image manifest it
|
|
12
|
+
* uploads the config and layers the registry does not hold yet, then puts
|
|
13
|
+
* the manifest by its digest, then the index when there is one. A registry
|
|
14
|
+
* that already holds the top manifest's digest is sent nothing. Every blob
|
|
15
|
+
* is checked against its digest before it is sent. Nothing a consumer pulls
|
|
16
|
+
* by tag changes until the reveal, which points each tag at that digest.
|
|
17
|
+
*
|
|
18
|
+
* The stage also pushes, by digest, two referrers of the image (#4049): its
|
|
19
|
+
* SBOM and the release's provenance statement (./publish-attachments.ts),
|
|
20
|
+
* each an OCI artifact manifest whose `subject` is the image, so a registry
|
|
21
|
+
* with the referrers API lists them under the image's digest and `oras
|
|
22
|
+
* discover` or `cosign tree` finds them. A referrer the registry already
|
|
23
|
+
* holds is not pushed again.
|
|
24
|
+
*
|
|
25
|
+
* Credentials come from `usernameEnv` and `passwordEnv` (default
|
|
26
|
+
* `OCI_USERNAME`, `OCI_PASSWORD`), or else from the `auths` entry for the
|
|
27
|
+
* registry in the docker config file (`$DOCKER_CONFIG/config.json` or
|
|
28
|
+
* `~/.docker/config.json`), which `docker login` writes; a credential helper
|
|
29
|
+
* (`credsStore`) is not read. With none, the push is anonymous. The registry
|
|
30
|
+
* decides how they are used: a `Basic` challenge takes them as they are, and
|
|
31
|
+
* a `Bearer` challenge (GHCR, Docker Hub) exchanges them for a token at the
|
|
32
|
+
* realm it names. The password and the token are masked in all output.
|
|
33
|
+
*
|
|
34
|
+
* A host of `localhost`, `127.0.0.1` or `[::1]` is spoken to over http, as
|
|
35
|
+
* docker does; any other over https. `docker.io` is `registry-1.docker.io`,
|
|
36
|
+
* and a one-part Docker Hub name is under `library/`.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
import { execFileSync } from "node:child_process";
|
|
40
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
41
|
+
import { tmpdir } from "node:os";
|
|
42
|
+
import { join } from "node:path";
|
|
43
|
+
import { memberSbom, releaseProvenance, type Attachment } from "./publish-attachments";
|
|
44
|
+
import { readCredential, type Credential } from "./publish-credentials";
|
|
45
|
+
import type { OciPublishDeclaration } from "./publish-declaration";
|
|
46
|
+
import { bytesDigest, checkReleaseDigest, fileDigest, isDir, memberFile, publishTargetKey } from "./publish-targets";
|
|
47
|
+
import { PublishTargetError, type PublishContext, type PublishDeps, type PublishFetch, type PublishItem, type PublishTarget } from "./release-publish";
|
|
48
|
+
|
|
49
|
+
const INDEX_TYPES = new Set(["application/vnd.oci.image.index.v1+json", "application/vnd.docker.distribution.manifest.list.v2+json"]);
|
|
50
|
+
|
|
51
|
+
/** A repository reference split into where the registry is and the name in it. */
|
|
52
|
+
export interface OciRepository {
|
|
53
|
+
/** The host as written, used to find docker config credentials. */
|
|
54
|
+
host: string;
|
|
55
|
+
/** `http(s)://<host>` the API is under. */
|
|
56
|
+
base: string;
|
|
57
|
+
/** The repository's name in the registry. */
|
|
58
|
+
name: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function parseRepository(repository: string): OciRepository {
|
|
62
|
+
const parts = repository.split("/");
|
|
63
|
+
const first = parts[0]!;
|
|
64
|
+
const isHost = parts.length > 1 && (first.includes(".") || first.includes(":") || first === "localhost");
|
|
65
|
+
let host = isHost ? first : "docker.io";
|
|
66
|
+
let path = isHost ? parts.slice(1) : parts;
|
|
67
|
+
if (host === "docker.io" || host === "index.docker.io") {
|
|
68
|
+
host = "docker.io";
|
|
69
|
+
if (path.length === 1) path = ["library", ...path];
|
|
70
|
+
}
|
|
71
|
+
const apiHost = host === "docker.io" ? "registry-1.docker.io" : host;
|
|
72
|
+
const local = /^(localhost|127\.0\.0\.1|\[::1\])(:\d+)?$/.test(apiHost);
|
|
73
|
+
return { host, base: `${local ? "http" : "https"}://${apiHost}`, name: path.join("/") };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
interface Descriptor {
|
|
77
|
+
mediaType: string;
|
|
78
|
+
digest: string;
|
|
79
|
+
size?: number;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** What a layout holds, in the order it is pushed. */
|
|
83
|
+
interface LayoutImage {
|
|
84
|
+
top: Descriptor & { bytes: Uint8Array };
|
|
85
|
+
/** The image manifests: one, or an index's children. */
|
|
86
|
+
manifests: Array<Descriptor & { bytes: Uint8Array; blobs: Descriptor[] }>;
|
|
87
|
+
configDigests: string[];
|
|
88
|
+
readBlob(digest: string): Uint8Array;
|
|
89
|
+
cleanup(): void;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function readLayout(abs: string): LayoutImage {
|
|
93
|
+
let dir = abs;
|
|
94
|
+
let tmp: string | null = null;
|
|
95
|
+
if (!isDir(abs)) {
|
|
96
|
+
tmp = mkdtempSync(join(tmpdir(), "chant-oci-"));
|
|
97
|
+
try {
|
|
98
|
+
execFileSync("tar", ["-xf", abs, "-C", tmp], { stdio: "ignore" });
|
|
99
|
+
} catch {
|
|
100
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
101
|
+
throw new PublishTargetError("publish-oci-not-a-layout", `${abs} is neither an OCI image layout directory nor a tar file of one`);
|
|
102
|
+
}
|
|
103
|
+
dir = tmp;
|
|
104
|
+
}
|
|
105
|
+
const cleanup = () => {
|
|
106
|
+
if (tmp) rmSync(tmp, { recursive: true, force: true });
|
|
107
|
+
};
|
|
108
|
+
try {
|
|
109
|
+
const readBlob = (digest: string): Uint8Array => {
|
|
110
|
+
const [alg, hex] = digest.split(":");
|
|
111
|
+
const file = join(dir, "blobs", alg ?? "", hex ?? "");
|
|
112
|
+
if (!alg || !hex || !existsSync(file)) throw new PublishTargetError("publish-oci-not-a-layout", `the layout has no blob ${digest}`);
|
|
113
|
+
const bytes = readFileSync(file);
|
|
114
|
+
if (bytesDigest(bytes) !== digest) throw new PublishTargetError("publish-oci-blob-corrupt", `blob ${digest} in the layout does not match its digest`);
|
|
115
|
+
return bytes;
|
|
116
|
+
};
|
|
117
|
+
const indexFile = join(dir, "index.json");
|
|
118
|
+
if (!existsSync(indexFile)) throw new PublishTargetError("publish-oci-not-a-layout", `${abs} has no index.json, so it is not an OCI image layout`);
|
|
119
|
+
const index = JSON.parse(readFileSync(indexFile, "utf-8")) as { manifests?: Descriptor[] };
|
|
120
|
+
const tops = index.manifests ?? [];
|
|
121
|
+
if (tops.length !== 1) {
|
|
122
|
+
throw new PublishTargetError("publish-oci-not-one-image", `${abs} names ${tops.length} manifests in its index.json, and a target pushes one image`, "Save one image per layout.");
|
|
123
|
+
}
|
|
124
|
+
const topDesc = tops[0]!;
|
|
125
|
+
const topBytes = readBlob(topDesc.digest);
|
|
126
|
+
const manifests: LayoutImage["manifests"] = [];
|
|
127
|
+
const configDigests: string[] = [];
|
|
128
|
+
const image = (d: Descriptor, bytes: Uint8Array) => {
|
|
129
|
+
const m = JSON.parse(Buffer.from(bytes).toString("utf-8")) as { config?: Descriptor; layers?: Descriptor[]; mediaType?: string };
|
|
130
|
+
if (!m.config) throw new PublishTargetError("publish-oci-not-a-layout", `manifest ${d.digest} has no config`);
|
|
131
|
+
configDigests.push(m.config.digest);
|
|
132
|
+
manifests.push({ ...d, mediaType: d.mediaType ?? m.mediaType, bytes, blobs: [m.config, ...(m.layers ?? [])] });
|
|
133
|
+
};
|
|
134
|
+
const topParsed = JSON.parse(Buffer.from(topBytes).toString("utf-8")) as { mediaType?: string; manifests?: Descriptor[] };
|
|
135
|
+
const topType = topDesc.mediaType ?? topParsed.mediaType ?? "application/vnd.oci.image.manifest.v1+json";
|
|
136
|
+
if (INDEX_TYPES.has(topType)) {
|
|
137
|
+
for (const child of topParsed.manifests ?? []) image(child, readBlob(child.digest));
|
|
138
|
+
} else {
|
|
139
|
+
image({ ...topDesc, mediaType: topType }, topBytes);
|
|
140
|
+
}
|
|
141
|
+
return { top: { ...topDesc, mediaType: topType, bytes: topBytes }, manifests, configDigests, readBlob, cleanup };
|
|
142
|
+
} catch (err) {
|
|
143
|
+
cleanup();
|
|
144
|
+
if (err instanceof SyntaxError) throw new PublishTargetError("publish-oci-not-a-layout", `${abs} holds a manifest or index.json that is not JSON`);
|
|
145
|
+
throw err;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** The credential for `repo`: the declared variables, else the docker config's `auths` entry. */
|
|
150
|
+
function ociCredential(decl: OciPublishDeclaration, repo: OciRepository, ctx: PublishContext): { username: string; password: Credential } | null {
|
|
151
|
+
const password = readCredential(ctx.env, [decl.passwordEnv ?? "OCI_PASSWORD"], ctx.redactor);
|
|
152
|
+
if (password) {
|
|
153
|
+
const user = ctx.env[decl.usernameEnv ?? "OCI_USERNAME"];
|
|
154
|
+
return { username: user && user !== "" ? user : "oauth2", password };
|
|
155
|
+
}
|
|
156
|
+
const dir = ctx.env.DOCKER_CONFIG ?? (ctx.env.HOME ? join(ctx.env.HOME, ".docker") : null);
|
|
157
|
+
if (!dir || !existsSync(join(dir, "config.json"))) return null;
|
|
158
|
+
try {
|
|
159
|
+
const conf = JSON.parse(readFileSync(join(dir, "config.json"), "utf-8")) as { auths?: Record<string, { auth?: string }> };
|
|
160
|
+
const keys = repo.host === "docker.io" ? ["https://index.docker.io/v1/", "docker.io", "index.docker.io"] : [repo.host, `https://${repo.host}`];
|
|
161
|
+
for (const k of keys) {
|
|
162
|
+
const auth = conf.auths?.[k]?.auth;
|
|
163
|
+
if (!auth) continue;
|
|
164
|
+
ctx.redactor.add(auth);
|
|
165
|
+
const decoded = Buffer.from(auth, "base64").toString("utf-8");
|
|
166
|
+
const at = decoded.indexOf(":");
|
|
167
|
+
if (at < 0) continue;
|
|
168
|
+
const value = decoded.slice(at + 1);
|
|
169
|
+
ctx.redactor.add(value);
|
|
170
|
+
return { username: decoded.slice(0, at), password: { value, from: "docker config" } };
|
|
171
|
+
}
|
|
172
|
+
} catch {
|
|
173
|
+
// An unreadable docker config gives no credential.
|
|
174
|
+
}
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** A registry client that answers the registry's auth challenge once and keeps the token. */
|
|
179
|
+
class Registry {
|
|
180
|
+
private auth: string | null = null;
|
|
181
|
+
constructor(
|
|
182
|
+
private readonly repo: OciRepository,
|
|
183
|
+
private readonly fetch: PublishFetch,
|
|
184
|
+
private readonly cred: { username: string; password: Credential } | null,
|
|
185
|
+
private readonly ctx: PublishContext,
|
|
186
|
+
) {}
|
|
187
|
+
|
|
188
|
+
private fail(what: string, status: number, body: string): never {
|
|
189
|
+
throw new PublishTargetError("publish-oci-failed", this.ctx.redactor.redact(`${what} at ${this.repo.base} answered ${status}${body ? `: ${body.slice(0, 300)}` : ""}`));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async request(method: string, path: string, headers: Record<string, string> = {}, body?: Uint8Array): Promise<Awaited<ReturnType<PublishFetch>>> {
|
|
193
|
+
const url = /^https?:\/\//.test(path) ? path : `${this.repo.base}${path}`;
|
|
194
|
+
const send = () => this.fetch(url, { method, headers: { ...headers, ...(this.auth ? { authorization: this.auth } : {}) }, ...(body ? { body } : {}) });
|
|
195
|
+
let res = await send();
|
|
196
|
+
if (res.status === 401 && this.auth === null) {
|
|
197
|
+
await this.answerChallenge(res.headers.get("www-authenticate") ?? "");
|
|
198
|
+
res = await send();
|
|
199
|
+
}
|
|
200
|
+
return res;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private async answerChallenge(header: string): Promise<void> {
|
|
204
|
+
const basic = this.cred ? `Basic ${Buffer.from(`${this.cred.username}:${this.cred.password.value}`).toString("base64")}` : null;
|
|
205
|
+
if (basic) this.ctx.redactor.add(basic.slice(6));
|
|
206
|
+
if (/^basic/i.test(header)) {
|
|
207
|
+
if (!basic) throw new PublishTargetError("publish-credentials-missing", `${this.repo.base} asks for credentials, and none are set`, "Set OCI_USERNAME and OCI_PASSWORD, or the variables usernameEnv and passwordEnv name, or docker login.");
|
|
208
|
+
this.auth = basic;
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
if (!/^bearer/i.test(header)) this.fail("the registry", 401, "with no challenge chant can answer");
|
|
212
|
+
const params = Object.fromEntries([...header.matchAll(/(\w+)="([^"]*)"/g)].map((m) => [m[1]!.toLowerCase(), m[2]!]));
|
|
213
|
+
if (!params.realm) this.fail("the registry", 401, "with a Bearer challenge that names no realm");
|
|
214
|
+
const u = new URL(params.realm);
|
|
215
|
+
if (params.service) u.searchParams.set("service", params.service);
|
|
216
|
+
u.searchParams.set("scope", `repository:${this.repo.name}:pull,push`);
|
|
217
|
+
const res = await this.fetch(u.toString(), { method: "GET", headers: basic ? { authorization: basic } : {} });
|
|
218
|
+
const text = await res.text();
|
|
219
|
+
if (!res.ok) this.fail("the token service", res.status, text);
|
|
220
|
+
const json = JSON.parse(text) as { token?: string; access_token?: string };
|
|
221
|
+
const token = json.token ?? json.access_token;
|
|
222
|
+
if (!token) this.fail("the token service", res.status, "with no token");
|
|
223
|
+
this.ctx.redactor.add(token);
|
|
224
|
+
this.auth = `Bearer ${token}`;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** Upload a blob unless the registry holds it. True when it was uploaded. */
|
|
228
|
+
async pushBlob(digest: string, bytes: () => Uint8Array): Promise<boolean> {
|
|
229
|
+
const head = await this.request("HEAD", `/v2/${this.repo.name}/blobs/${digest}`);
|
|
230
|
+
if (head.status === 200) return false;
|
|
231
|
+
const start = await this.request("POST", `/v2/${this.repo.name}/blobs/uploads/`, { "content-length": "0" });
|
|
232
|
+
if (start.status !== 202) this.fail(`starting an upload of ${digest}`, start.status, await start.text());
|
|
233
|
+
const location = start.headers.get("location");
|
|
234
|
+
if (!location) this.fail(`starting an upload of ${digest}`, start.status, "with no Location");
|
|
235
|
+
const abs = /^https?:\/\//.test(location) ? location : `${this.repo.base}${location.startsWith("/") ? "" : "/"}${location}`;
|
|
236
|
+
const put = `${abs}${abs.includes("?") ? "&" : "?"}digest=${encodeURIComponent(digest)}`;
|
|
237
|
+
const data = bytes();
|
|
238
|
+
const done = await this.request("PUT", put, { "content-type": "application/octet-stream", "content-length": String(data.byteLength) }, data);
|
|
239
|
+
if (done.status !== 201) this.fail(`uploading ${digest}`, done.status, await done.text());
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** Whether the registry holds the manifest `digest`. */
|
|
244
|
+
async hasManifest(digest: string, mediaType: string): Promise<boolean> {
|
|
245
|
+
const res = await this.request("HEAD", `/v2/${this.repo.name}/manifests/${digest}`, { accept: mediaType });
|
|
246
|
+
return res.status === 200;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async putManifest(ref: string, mediaType: string, bytes: Uint8Array): Promise<void> {
|
|
250
|
+
const res = await this.request("PUT", `/v2/${this.repo.name}/manifests/${ref}`, { "content-type": mediaType }, bytes);
|
|
251
|
+
if (res.status !== 201) this.fail(`putting manifest ${ref}`, res.status, await res.text());
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** The media type of the empty config an artifact manifest names. */
|
|
256
|
+
const EMPTY_CONFIG = { mediaType: "application/vnd.oci.empty.v1+json", bytes: new Uint8Array(Buffer.from("{}")) };
|
|
257
|
+
const IMAGE_MANIFEST = "application/vnd.oci.image.manifest.v1+json";
|
|
258
|
+
|
|
259
|
+
/** One referrer of the image: an attached file and the artifact manifest that names the image as its subject. */
|
|
260
|
+
interface Referrer {
|
|
261
|
+
attachment: Attachment;
|
|
262
|
+
manifest: Uint8Array;
|
|
263
|
+
digest: string;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* The artifact manifest that attaches `a` to the image `subject` (OCI image
|
|
268
|
+
* spec 1.1): `artifactType` is the file's media type, the config is the empty
|
|
269
|
+
* descriptor, the one layer is the file, and `subject` is the image.
|
|
270
|
+
*/
|
|
271
|
+
export function referrerManifest(a: Attachment, subject: { mediaType: string; digest: string; size: number }): Uint8Array {
|
|
272
|
+
const manifest = {
|
|
273
|
+
schemaVersion: 2,
|
|
274
|
+
mediaType: IMAGE_MANIFEST,
|
|
275
|
+
artifactType: a.mediaType,
|
|
276
|
+
config: { mediaType: EMPTY_CONFIG.mediaType, digest: bytesDigest(EMPTY_CONFIG.bytes), size: EMPTY_CONFIG.bytes.byteLength },
|
|
277
|
+
layers: [{ mediaType: a.mediaType, digest: a.digest, size: a.bytes.byteLength, annotations: { "org.opencontainers.image.title": a.name } }],
|
|
278
|
+
subject,
|
|
279
|
+
annotations: { "org.opencontainers.image.title": a.name },
|
|
280
|
+
};
|
|
281
|
+
return new Uint8Array(Buffer.from(JSON.stringify(manifest)));
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/** The tags to point at the digest: the context's tag when set, else the declared ones with `{label}` filled in. */
|
|
285
|
+
export function ociTags(decl: OciPublishDeclaration, ctx: Pick<PublishContext, "tag" | "label">): string[] {
|
|
286
|
+
if (ctx.tag) return [ctx.tag];
|
|
287
|
+
const declared = decl.tags ?? (ctx.label ? ["{label}"] : []);
|
|
288
|
+
return declared.filter((t) => ctx.label !== null || !t.includes("{label}")).map((t) => t.split("{label}").join(ctx.label ?? ""));
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export function ociTarget(decl: OciPublishDeclaration, deps: PublishDeps): PublishTarget {
|
|
292
|
+
const repo = parseRepository(decl.repository);
|
|
293
|
+
const fetchImpl: PublishFetch = deps.fetch ?? ((url, init) => fetch(url, init as RequestInit));
|
|
294
|
+
const inspect = (ctx: PublishContext) => {
|
|
295
|
+
const abs = memberFile(ctx.cwd, decl.image);
|
|
296
|
+
const layout = readLayout(abs);
|
|
297
|
+
try {
|
|
298
|
+
const identities = [layout.top.digest, ...layout.configDigests, ...(isDir(abs) ? [] : [fileDigest(abs)])];
|
|
299
|
+
checkReleaseDigest(ctx.release, "image", ctx.cwd, decl.image, identities);
|
|
300
|
+
} catch (err) {
|
|
301
|
+
layout.cleanup();
|
|
302
|
+
throw err;
|
|
303
|
+
}
|
|
304
|
+
return layout;
|
|
305
|
+
};
|
|
306
|
+
const item = (ctx: PublishContext, digest: string): PublishItem => {
|
|
307
|
+
const tags = ociTags(decl, ctx);
|
|
308
|
+
return {
|
|
309
|
+
name: `${decl.image}${tags.length > 0 ? ` (tag${tags.length === 1 ? "" : "s"} ${tags.join(", ")})` : ""}`,
|
|
310
|
+
destination: `${decl.repository}@${digest}`,
|
|
311
|
+
digest,
|
|
312
|
+
};
|
|
313
|
+
};
|
|
314
|
+
/** The SBOM and the provenance statement, each with the artifact manifest that refers to the image. */
|
|
315
|
+
const referrers = (ctx: PublishContext, layout: LayoutImage): Referrer[] => {
|
|
316
|
+
const subject = { mediaType: layout.top.mediaType, digest: layout.top.digest, size: layout.top.bytes.byteLength };
|
|
317
|
+
const files = [
|
|
318
|
+
memberSbom(ctx, decl.sbom, `${decl.repository}@${layout.top.digest}`, `oci ${decl.repository}`),
|
|
319
|
+
releaseProvenance(ctx, [{ name: `${decl.repository}@${layout.top.digest}`, digest: layout.top.digest }]),
|
|
320
|
+
].filter((a): a is Attachment => a !== null);
|
|
321
|
+
return files.map((attachment) => {
|
|
322
|
+
const manifest = referrerManifest(attachment, subject);
|
|
323
|
+
return { attachment, manifest, digest: bytesDigest(manifest) };
|
|
324
|
+
});
|
|
325
|
+
};
|
|
326
|
+
const referrerItem = (r: Referrer): PublishItem => ({ name: r.attachment.name, destination: `${decl.repository}@${r.digest}, a referrer of the image`, digest: r.digest, attachment: r.attachment.kind });
|
|
327
|
+
const connect = (ctx: PublishContext) => {
|
|
328
|
+
const cred = ociCredential(decl, repo, ctx);
|
|
329
|
+
return { cred, registry: new Registry(repo, fetchImpl, cred, ctx) };
|
|
330
|
+
};
|
|
331
|
+
return {
|
|
332
|
+
kind: "oci",
|
|
333
|
+
name: `oci ${decl.repository}`,
|
|
334
|
+
destination: decl.repository,
|
|
335
|
+
credentialEnv: [decl.passwordEnv ?? "OCI_PASSWORD"],
|
|
336
|
+
key: publishTargetKey(decl),
|
|
337
|
+
async plan(ctx) {
|
|
338
|
+
const layout = inspect(ctx);
|
|
339
|
+
try {
|
|
340
|
+
return [item(ctx, layout.top.digest), ...referrers(ctx, layout).map(referrerItem)];
|
|
341
|
+
} finally {
|
|
342
|
+
layout.cleanup();
|
|
343
|
+
}
|
|
344
|
+
},
|
|
345
|
+
async stage(ctx) {
|
|
346
|
+
const layout = inspect(ctx);
|
|
347
|
+
try {
|
|
348
|
+
const attached = referrers(ctx, layout);
|
|
349
|
+
const { cred, registry } = connect(ctx);
|
|
350
|
+
const staged: PublishItem = { ...item(ctx, layout.top.digest), name: decl.image };
|
|
351
|
+
const items: PublishItem[] = [];
|
|
352
|
+
if (await registry.hasManifest(layout.top.digest, layout.top.mediaType)) {
|
|
353
|
+
ctx.log(`${decl.repository} already holds ${layout.top.digest}; the image is not pushed again`);
|
|
354
|
+
items.push({ ...staged, present: true });
|
|
355
|
+
} else {
|
|
356
|
+
ctx.log(`pushing ${decl.image} to ${decl.repository} by digest ${layout.top.digest}, ${cred ? `with credentials from ${cred.password.from}` : "anonymously"}; no tag moves yet`);
|
|
357
|
+
let uploaded = 0;
|
|
358
|
+
for (const m of layout.manifests) {
|
|
359
|
+
for (const b of m.blobs) if (await registry.pushBlob(b.digest, () => layout.readBlob(b.digest))) uploaded++;
|
|
360
|
+
await registry.putManifest(m.digest, m.mediaType, m.bytes);
|
|
361
|
+
}
|
|
362
|
+
if (INDEX_TYPES.has(layout.top.mediaType)) await registry.putManifest(layout.top.digest, layout.top.mediaType, layout.top.bytes);
|
|
363
|
+
ctx.log(` ${uploaded} blob(s) uploaded`);
|
|
364
|
+
items.push(staged);
|
|
365
|
+
}
|
|
366
|
+
// #4049: the SBOM and the provenance statement, as referrers of the image, by digest.
|
|
367
|
+
for (const r of attached) {
|
|
368
|
+
if (await registry.hasManifest(r.digest, IMAGE_MANIFEST)) {
|
|
369
|
+
items.push({ ...referrerItem(r), present: true });
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
ctx.log(`attaching ${r.attachment.name} to ${layout.top.digest} as referrer ${r.digest}`);
|
|
373
|
+
await registry.pushBlob(bytesDigest(EMPTY_CONFIG.bytes), () => EMPTY_CONFIG.bytes);
|
|
374
|
+
await registry.pushBlob(r.attachment.digest, () => r.attachment.bytes);
|
|
375
|
+
await registry.putManifest(r.digest, IMAGE_MANIFEST, r.manifest);
|
|
376
|
+
items.push(referrerItem(r));
|
|
377
|
+
}
|
|
378
|
+
return { target: this.name, destination: this.destination, items, state: {} };
|
|
379
|
+
} finally {
|
|
380
|
+
layout.cleanup();
|
|
381
|
+
}
|
|
382
|
+
},
|
|
383
|
+
async reveal(ctx) {
|
|
384
|
+
const layout = inspect(ctx);
|
|
385
|
+
try {
|
|
386
|
+
const { registry } = connect(ctx);
|
|
387
|
+
const tags = ociTags(decl, ctx);
|
|
388
|
+
for (const t of tags) await registry.putManifest(t, layout.top.mediaType, layout.top.bytes);
|
|
389
|
+
ctx.log(tags.length > 0 ? `tagged ${layout.top.digest} ${tags.join(", ")}` : `no tag to point at ${layout.top.digest}`);
|
|
390
|
+
return { target: this.name, destination: this.destination, items: [item(ctx, layout.top.digest), ...referrers(ctx, layout).map(referrerItem)] };
|
|
391
|
+
} finally {
|
|
392
|
+
layout.cleanup();
|
|
393
|
+
}
|
|
394
|
+
},
|
|
395
|
+
};
|
|
396
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The publish record (#4048): what `chant release publish` has staged and
|
|
3
|
+
* revealed for a release, so a publish that failed part way resumes without
|
|
4
|
+
* publishing again what already went out.
|
|
5
|
+
*
|
|
6
|
+
* One line per target per step in `_releases/publishes.jsonl` on
|
|
7
|
+
* `chant/lifecycle`, under the member's prefix like `cuts.jsonl` beside it,
|
|
8
|
+
* append-only. A target that staged gets a `staged` line with its items and
|
|
9
|
+
* the state its reveal needs (a draft release's id); one that revealed gets
|
|
10
|
+
* a `revealed` line. Each line is pushed as soon as it is written, so a CI
|
|
11
|
+
* job that resumes on a fresh checkout reads it after `fetchLifecycle`.
|
|
12
|
+
*
|
|
13
|
+
* Why a record and not only the registries: a registry says whether a version
|
|
14
|
+
* or a digest is there, which each target's stage asks so that it never sends
|
|
15
|
+
* an item twice, but not whether a draft release, a version under `next` or
|
|
16
|
+
* a pushed digest belongs to this release's publish, nor whether every
|
|
17
|
+
* target of the set has staged, which is when the reveals may start. A
|
|
18
|
+
* record that was not written or not pushed costs only speed: the stage runs
|
|
19
|
+
* again, asks the registry, and sends nothing that is there.
|
|
20
|
+
*
|
|
21
|
+
* A line holds names, destinations, digests and draft ids. It never holds a
|
|
22
|
+
* credential, and every line is redacted before it is written.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { sortedJsonReplacer } from "../utils";
|
|
26
|
+
import { appendLedgerLine, readBlobFromPath, type LedgerLocation } from "./git";
|
|
27
|
+
import { RELEASES_DIR } from "./release-store";
|
|
28
|
+
import type { PublishedTarget, PublishItem, PublishTarget, StagedTarget } from "./release-publish";
|
|
29
|
+
import type { Redactor } from "./publish-credentials";
|
|
30
|
+
|
|
31
|
+
/** The file the record is appended to, under `_releases/`. */
|
|
32
|
+
export const PUBLISHES_FILE = "publishes.jsonl";
|
|
33
|
+
|
|
34
|
+
/** One line of `_releases/publishes.jsonl`. Version 1, written by chant 0.124.29 and newer. */
|
|
35
|
+
export interface PublishRecord {
|
|
36
|
+
version: 1;
|
|
37
|
+
releaseDigest: string;
|
|
38
|
+
label: string | null;
|
|
39
|
+
/** The target's key ({@link PublishTarget.key}). */
|
|
40
|
+
target: string;
|
|
41
|
+
kind: PublishTarget["kind"];
|
|
42
|
+
/** The target's name, for a reader. */
|
|
43
|
+
name: string;
|
|
44
|
+
step: "staged" | "revealed";
|
|
45
|
+
items: PublishItem[];
|
|
46
|
+
/** What the reveal needs, on a `staged` line; `{}` on a `revealed` one. */
|
|
47
|
+
state: Record<string, string>;
|
|
48
|
+
runId: string;
|
|
49
|
+
timestamp: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Parse the lines of `publishes.jsonl`. A line that is not a record is counted and skipped. */
|
|
53
|
+
export function parsePublishRecords(text: string | null): { records: PublishRecord[]; malformed: number } {
|
|
54
|
+
const records: PublishRecord[] = [];
|
|
55
|
+
let malformed = 0;
|
|
56
|
+
for (const line of (text ?? "").split("\n").map((l) => l.trim()).filter(Boolean)) {
|
|
57
|
+
try {
|
|
58
|
+
const r = JSON.parse(line) as Partial<PublishRecord>;
|
|
59
|
+
const ok =
|
|
60
|
+
r &&
|
|
61
|
+
r.version === 1 &&
|
|
62
|
+
typeof r.releaseDigest === "string" &&
|
|
63
|
+
typeof r.target === "string" &&
|
|
64
|
+
(r.step === "staged" || r.step === "revealed") &&
|
|
65
|
+
Array.isArray(r.items) &&
|
|
66
|
+
typeof r.timestamp === "string";
|
|
67
|
+
if (!ok) {
|
|
68
|
+
malformed++;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
records.push({ ...(r as PublishRecord), state: r.state && typeof r.state === "object" ? r.state : {} });
|
|
72
|
+
} catch {
|
|
73
|
+
malformed++;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return { records, malformed };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Every publish record of `releaseDigest`, oldest first. */
|
|
80
|
+
export async function readPublishRecords(releaseDigest: string, opts?: LedgerLocation): Promise<PublishRecord[]> {
|
|
81
|
+
const text = await readBlobFromPath(RELEASES_DIR, PUBLISHES_FILE, opts);
|
|
82
|
+
return parsePublishRecords(text).records.filter((r) => r.releaseDigest === releaseDigest);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Where each target of a release stands, by key: what it staged, and what it revealed. */
|
|
86
|
+
export interface PublishProgress {
|
|
87
|
+
staged: Map<string, StagedTarget>;
|
|
88
|
+
revealed: Map<string, PublishedTarget>;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** The progress `records` show. A later line for a target replaces an earlier one of the same step. */
|
|
92
|
+
export function publishProgress(records: readonly PublishRecord[]): PublishProgress {
|
|
93
|
+
const staged = new Map<string, StagedTarget>();
|
|
94
|
+
const revealed = new Map<string, PublishedTarget>();
|
|
95
|
+
for (const r of records) {
|
|
96
|
+
if (r.step === "staged") staged.set(r.target, { target: r.name, destination: "", items: r.items, state: r.state });
|
|
97
|
+
else revealed.set(r.target, { target: r.name, destination: "", items: r.items });
|
|
98
|
+
}
|
|
99
|
+
return { staged, revealed };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Where `target` stands in `progress`: `revealed`, `staged`, or `none`. */
|
|
103
|
+
export function progressOf(progress: PublishProgress, target: Pick<PublishTarget, "key">): "revealed" | "staged" | "none" {
|
|
104
|
+
if (progress.revealed.has(target.key)) return "revealed";
|
|
105
|
+
if (progress.staged.has(target.key)) return "staged";
|
|
106
|
+
return "none";
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Append one record, redacted. Does not push. */
|
|
110
|
+
export async function appendPublishRecord(record: PublishRecord, redactor: Redactor, opts?: { cwd?: string }): Promise<void> {
|
|
111
|
+
const line = redactor.redact(JSON.stringify(record, sortedJsonReplacer));
|
|
112
|
+
await appendLedgerLine(RELEASES_DIR, PUBLISHES_FILE, line, "Release publish", opts);
|
|
113
|
+
}
|