@intentius/chant 0.124.28 → 0.124.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build-params.d.ts +34 -1
- package/dist/build-params.d.ts.map +1 -1
- package/dist/build-params.js +31 -1
- package/dist/change-set-graph.d.ts +8 -1
- package/dist/change-set-graph.d.ts.map +1 -1
- package/dist/change-set-graph.js +4 -2
- package/dist/change-set.d.ts +10 -0
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +4 -3
- package/dist/cli/build-params-cli.d.ts +7 -1
- package/dist/cli/build-params-cli.d.ts.map +1 -1
- package/dist/cli/build-params-cli.js +17 -8
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/build.js +6 -3
- package/dist/cli/handlers/approve.d.ts.map +1 -1
- package/dist/cli/handlers/approve.js +45 -0
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/build.js +18 -4
- package/dist/cli/handlers/fan-out.d.ts +9 -0
- package/dist/cli/handlers/fan-out.d.ts.map +1 -1
- package/dist/cli/handlers/fan-out.js +67 -8
- package/dist/cli/handlers/lifecycle-gc.d.ts +19 -0
- package/dist/cli/handlers/lifecycle-gc.d.ts.map +1 -0
- package/dist/cli/handlers/lifecycle-gc.js +143 -0
- package/dist/cli/handlers/pr.d.ts +11 -3
- package/dist/cli/handlers/pr.d.ts.map +1 -1
- package/dist/cli/handlers/pr.js +134 -26
- package/dist/cli/handlers/promote.d.ts +7 -0
- package/dist/cli/handlers/promote.d.ts.map +1 -1
- package/dist/cli/handlers/promote.js +210 -33
- package/dist/cli/handlers/release.d.ts +62 -5
- package/dist/cli/handlers/release.d.ts.map +1 -1
- package/dist/cli/handlers/release.js +414 -28
- package/dist/cli/handlers/run.d.ts +11 -1
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/handlers/run.js +67 -6
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +91 -10
- package/dist/cli/parse-args.d.ts.map +1 -1
- package/dist/cli/parse-args.js +73 -2
- package/dist/cli/registry.d.ts +32 -4
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/cli/run-events.d.ts +3 -2
- package/dist/cli/run-events.d.ts.map +1 -1
- package/dist/cli/run-events.js +5 -3
- package/dist/components/auto-release.d.ts +2 -0
- package/dist/components/auto-release.d.ts.map +1 -1
- package/dist/components/auto-release.js +1 -0
- package/dist/components/capability.d.ts +9 -0
- package/dist/components/capability.d.ts.map +1 -1
- package/dist/components/driver.d.ts +8 -0
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/driver.js +35 -0
- package/dist/components/fan-out-inputs.d.ts +69 -0
- package/dist/components/fan-out-inputs.d.ts.map +1 -0
- package/dist/components/fan-out-inputs.js +169 -0
- package/dist/components/fan-out-record.d.ts +13 -0
- package/dist/components/fan-out-record.d.ts.map +1 -1
- package/dist/components/fan-out-record.js +16 -0
- package/dist/components/fan-out-run.d.ts.map +1 -1
- package/dist/components/fan-out-run.js +50 -0
- package/dist/components/fan-out.d.ts +4 -0
- package/dist/components/fan-out.d.ts.map +1 -1
- package/dist/components/pr-pipeline.d.ts +49 -0
- package/dist/components/pr-pipeline.d.ts.map +1 -1
- package/dist/components/pr-pipeline.js +38 -4
- package/dist/components/pr-run.d.ts +20 -2
- package/dist/components/pr-run.d.ts.map +1 -1
- package/dist/components/pr-run.js +26 -13
- package/dist/components/promote.d.ts +23 -0
- package/dist/components/promote.d.ts.map +1 -1
- package/dist/components/promote.js +60 -2
- package/dist/components/release-compile.d.ts +9 -0
- package/dist/components/release-compile.d.ts.map +1 -1
- package/dist/components/release-compile.js +9 -3
- package/dist/components/release-gate.d.ts +11 -1
- package/dist/components/release-gate.d.ts.map +1 -1
- package/dist/components/release-gate.js +10 -4
- package/dist/components/wave-plan.d.ts +16 -0
- package/dist/components/wave-plan.d.ts.map +1 -1
- package/dist/components/wave-plan.js +36 -2
- package/dist/config.d.ts +38 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -0
- package/dist/flag-params.d.ts +54 -0
- package/dist/flag-params.d.ts.map +1 -0
- package/dist/flag-params.js +130 -0
- package/dist/lifecycle/__fixtures__/publish-fakes.d.ts +120 -0
- package/dist/lifecycle/__fixtures__/publish-fakes.d.ts.map +1 -0
- package/dist/lifecycle/__fixtures__/publish-fakes.js +304 -0
- package/dist/lifecycle/change-set-store.d.ts +2 -1
- package/dist/lifecycle/change-set-store.d.ts.map +1 -1
- package/dist/lifecycle/change-set-store.js +2 -1
- package/dist/lifecycle/delivery-metrics.d.ts +139 -0
- package/dist/lifecycle/delivery-metrics.d.ts.map +1 -0
- package/dist/lifecycle/delivery-metrics.js +145 -0
- package/dist/lifecycle/deploy-outcome.d.ts +81 -0
- package/dist/lifecycle/deploy-outcome.d.ts.map +1 -0
- package/dist/lifecycle/deploy-outcome.js +101 -0
- package/dist/lifecycle/gate-change-set.d.ts +10 -1
- package/dist/lifecycle/gate-change-set.d.ts.map +1 -1
- package/dist/lifecycle/gate-change-set.js +35 -3
- package/dist/lifecycle/gate-ledger.d.ts +2 -3
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
- package/dist/lifecycle/git.d.ts +30 -1
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lifecycle/git.js +31 -5
- package/dist/lifecycle/index.d.ts +4 -0
- package/dist/lifecycle/index.d.ts.map +1 -1
- package/dist/lifecycle/index.js +4 -0
- package/dist/lifecycle/mixed-environment.d.ts +95 -0
- package/dist/lifecycle/mixed-environment.d.ts.map +1 -0
- package/dist/lifecycle/mixed-environment.js +154 -0
- package/dist/lifecycle/publish-attachments.d.ts +79 -0
- package/dist/lifecycle/publish-attachments.d.ts.map +1 -0
- package/dist/lifecycle/publish-attachments.js +179 -0
- package/dist/lifecycle/publish-credentials.d.ts +37 -0
- package/dist/lifecycle/publish-credentials.d.ts.map +1 -0
- package/dist/lifecycle/publish-credentials.js +69 -0
- package/dist/lifecycle/publish-declaration.d.ts +97 -0
- package/dist/lifecycle/publish-declaration.d.ts.map +1 -0
- package/dist/lifecycle/publish-declaration.js +83 -0
- package/dist/lifecycle/publish-forge.d.ts +53 -0
- package/dist/lifecycle/publish-forge.d.ts.map +1 -0
- package/dist/lifecycle/publish-forge.js +298 -0
- package/dist/lifecycle/publish-npm.d.ts +61 -0
- package/dist/lifecycle/publish-npm.d.ts.map +1 -0
- package/dist/lifecycle/publish-npm.js +242 -0
- package/dist/lifecycle/publish-oci.d.ts +64 -0
- package/dist/lifecycle/publish-oci.d.ts.map +1 -0
- package/dist/lifecycle/publish-oci.js +383 -0
- package/dist/lifecycle/publish-record.d.ts +66 -0
- package/dist/lifecycle/publish-record.d.ts.map +1 -0
- package/dist/lifecycle/publish-record.js +84 -0
- package/dist/lifecycle/publish-targets.d.ts +45 -0
- package/dist/lifecycle/publish-targets.d.ts.map +1 -0
- package/dist/lifecycle/publish-targets.js +85 -0
- package/dist/lifecycle/release-cut.d.ts +48 -1
- package/dist/lifecycle/release-cut.d.ts.map +1 -1
- package/dist/lifecycle/release-cut.js +116 -23
- package/dist/lifecycle/release-join.d.ts +95 -0
- package/dist/lifecycle/release-join.d.ts.map +1 -0
- package/dist/lifecycle/release-join.js +139 -0
- package/dist/lifecycle/release-ledger.d.ts +8 -3
- package/dist/lifecycle/release-ledger.d.ts.map +1 -1
- package/dist/lifecycle/release-ledger.js +2 -0
- package/dist/lifecycle/release-publish.d.ts +312 -0
- package/dist/lifecycle/release-publish.d.ts.map +1 -0
- package/dist/lifecycle/release-publish.js +335 -0
- package/dist/lifecycle/release-revocation.d.ts +80 -0
- package/dist/lifecycle/release-revocation.d.ts.map +1 -0
- package/dist/lifecycle/release-revocation.js +123 -0
- package/dist/lifecycle/release-store.d.ts +12 -2
- package/dist/lifecycle/release-store.d.ts.map +1 -1
- package/dist/lifecycle/release-store.js +4 -2
- package/dist/lifecycle/release-verify.d.ts +61 -0
- package/dist/lifecycle/release-verify.d.ts.map +1 -0
- package/dist/lifecycle/release-verify.js +92 -0
- package/dist/lifecycle/release-workstation.d.ts +91 -0
- package/dist/lifecycle/release-workstation.d.ts.map +1 -0
- package/dist/lifecycle/release-workstation.js +180 -0
- package/dist/lifecycle/retention-policy.d.ts +45 -0
- package/dist/lifecycle/retention-policy.d.ts.map +1 -0
- package/dist/lifecycle/retention-policy.js +43 -0
- package/dist/lifecycle/retention.d.ts +185 -0
- package/dist/lifecycle/retention.d.ts.map +1 -0
- package/dist/lifecycle/retention.js +400 -0
- package/dist/lifecycle/run-ledger.d.ts +1 -1
- package/dist/lifecycle/run-ledger.d.ts.map +1 -1
- package/dist/lifecycle/run-ledger.js +9 -0
- package/dist/op/activities/activity-contracts.d.ts +1 -0
- package/dist/op/activities/activity-contracts.d.ts.map +1 -1
- package/dist/op/activities/activity-contracts.js +1 -0
- package/dist/op/activities/shell.d.ts +6 -0
- package/dist/op/activities/shell.d.ts.map +1 -1
- package/dist/op/activities/shell.js +1 -1
- package/dist/op/local-executor.d.ts +23 -1
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/local-executor.js +9 -3
- package/dist/op/runtime.d.ts +27 -0
- package/dist/op/runtime.d.ts.map +1 -1
- package/dist/op/runtime.js +20 -0
- package/dist/plan-summary.d.ts.map +1 -1
- package/dist/plan-summary.js +8 -2
- package/dist/pr-forge.d.ts +23 -0
- package/dist/pr-forge.d.ts.map +1 -1
- package/dist/pr-forge.js +44 -0
- package/dist/pr-loop.d.ts +17 -0
- package/dist/pr-loop.d.ts.map +1 -1
- package/dist/pr-loop.js +30 -4
- package/dist/pr-report-url.d.ts +82 -0
- package/dist/pr-report-url.d.ts.map +1 -0
- package/dist/pr-report-url.js +174 -0
- package/dist/provenance.d.ts +2 -2
- package/dist/provenance.d.ts.map +1 -1
- package/dist/run-event-store.d.ts +29 -2
- package/dist/run-event-store.d.ts.map +1 -1
- package/dist/run-event-store.js +50 -3
- package/dist/run-event-stream.d.ts +4 -3
- package/dist/run-event-stream.d.ts.map +1 -1
- package/dist/run-event-stream.js +7 -3
- package/dist/run-event.d.ts +34 -1
- package/dist/run-event.d.ts.map +1 -1
- package/dist/run-event.js +4 -0
- package/dist/wave-runtime.mjs +260 -229
- package/dist/workspace/__fixtures__/flag-workspace.d.ts +31 -0
- package/dist/workspace/__fixtures__/flag-workspace.d.ts.map +1 -0
- package/dist/workspace/__fixtures__/flag-workspace.js +114 -0
- package/dist/workspace/change-set.schema.json +5 -1
- package/dist/workspace/check.schema.json +4 -2
- package/dist/workspace/checks/records.d.ts +9 -1
- package/dist/workspace/checks/records.d.ts.map +1 -1
- package/dist/workspace/checks/records.js +35 -1
- package/dist/workspace/checks.d.ts +1 -0
- package/dist/workspace/checks.d.ts.map +1 -1
- package/dist/workspace/checks.js +2 -1
- package/dist/workspace/ci-green-forge.d.ts +2 -0
- package/dist/workspace/ci-green-forge.d.ts.map +1 -1
- package/dist/workspace/ci-green.d.ts +10 -0
- package/dist/workspace/ci-green.d.ts.map +1 -1
- package/dist/workspace/ci-green.js +14 -5
- package/dist/workspace/ci-run.d.ts +222 -0
- package/dist/workspace/ci-run.d.ts.map +1 -0
- package/dist/workspace/ci-run.js +464 -0
- package/dist/workspace/ci-serve.d.ts +214 -0
- package/dist/workspace/ci-serve.d.ts.map +1 -0
- package/dist/workspace/ci-serve.js +412 -0
- package/dist/workspace/declaration.d.ts +16 -0
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.js +26 -0
- package/dist/workspace/declaration.schema.json +64 -0
- package/dist/workspace/declared-kinds.d.ts +16 -0
- package/dist/workspace/declared-kinds.d.ts.map +1 -1
- package/dist/workspace/declared-kinds.js +25 -0
- package/dist/workspace/delivery-metrics.d.ts +91 -0
- package/dist/workspace/delivery-metrics.d.ts.map +1 -0
- package/dist/workspace/delivery-metrics.js +217 -0
- package/dist/workspace/delivery-metrics.schema.json +519 -0
- package/dist/workspace/flags-read.d.ts +37 -0
- package/dist/workspace/flags-read.d.ts.map +1 -0
- package/dist/workspace/flags-read.js +103 -0
- package/dist/workspace/flags.d.ts +100 -0
- package/dist/workspace/flags.d.ts.map +1 -0
- package/dist/workspace/flags.js +98 -0
- package/dist/workspace/graph-cli.js +1 -1
- package/dist/workspace/intent-cli.d.ts +11 -2
- package/dist/workspace/intent-cli.d.ts.map +1 -1
- package/dist/workspace/intent-cli.js +54 -5
- package/dist/workspace/intent-trace.d.ts +219 -0
- package/dist/workspace/intent-trace.d.ts.map +1 -0
- package/dist/workspace/intent-trace.js +314 -0
- package/dist/workspace/intent.d.ts +30 -6
- package/dist/workspace/intent.d.ts.map +1 -1
- package/dist/workspace/intent.js +141 -21
- package/dist/workspace/intent.schema.json +702 -6
- package/dist/workspace/overlap.d.ts +146 -0
- package/dist/workspace/overlap.d.ts.map +1 -0
- package/dist/workspace/overlap.js +276 -0
- package/dist/workspace/pr-report.schema.json +5 -0
- package/dist/workspace/reason-codes.d.ts +2 -0
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/reason-codes.js +3 -0
- package/dist/workspace/records.d.ts +15 -2
- package/dist/workspace/records.d.ts.map +1 -1
- package/dist/workspace/records.js +32 -0
- package/dist/workspace/records.schema.json +21 -2
- package/dist/workspace/reference-kinds/flag/flag.kind.mjs +32 -0
- package/dist/workspace/reference-kinds/flag/flag.schema.json +53 -0
- package/dist/workspace/releases.d.ts +9 -0
- package/dist/workspace/releases.d.ts.map +1 -1
- package/dist/workspace/releases.js +9 -0
- package/dist/workspace/releases.schema.json +123 -1
- package/dist/workspace/run-event.schema.json +24 -6
- package/dist/workspace/status-gates.d.ts +8 -0
- package/dist/workspace/status-gates.d.ts.map +1 -1
- package/dist/workspace/status-stewards.d.ts +2 -0
- package/dist/workspace/status-stewards.d.ts.map +1 -1
- package/dist/workspace/status-stewards.js +1 -0
- package/dist/workspace/status.d.ts +78 -0
- package/dist/workspace/status.d.ts.map +1 -1
- package/dist/workspace/status.js +170 -3
- package/dist/workspace/status.schema.json +210 -2
- package/dist/workspace/wip.d.ts.map +1 -1
- package/dist/workspace/wip.js +11 -3
- package/package.json +1 -1
- package/src/build-params.test.ts +46 -0
- package/src/build-params.ts +55 -1
- package/src/change-set-graph.ts +10 -2
- package/src/change-set.ts +14 -3
- package/src/cli/build-params-cli.ts +22 -9
- package/src/cli/commands/build.ts +6 -3
- package/src/cli/handlers/approve-release.test.ts +5 -1
- package/src/cli/handlers/approve-revoked.test.ts +155 -0
- package/src/cli/handlers/approve.ts +47 -0
- package/src/cli/handlers/build.test.ts +30 -0
- package/src/cli/handlers/build.ts +20 -4
- package/src/cli/handlers/fan-out.test.ts +262 -1
- package/src/cli/handlers/fan-out.ts +72 -8
- package/src/cli/handlers/intent-trace.e2e.test.ts +201 -0
- package/src/cli/handlers/lifecycle-gc.test.ts +126 -0
- package/src/cli/handlers/lifecycle-gc.ts +146 -0
- package/src/cli/handlers/pr.test.ts +148 -1
- package/src/cli/handlers/pr.ts +144 -28
- package/src/cli/handlers/promote.test.ts +280 -2
- package/src/cli/handlers/promote.ts +210 -34
- package/src/cli/handlers/release-implicit.e2e.test.ts +12 -4
- package/src/cli/handlers/release-join.e2e.test.ts +185 -0
- package/src/cli/handlers/release-publish-targets.e2e.test.ts +295 -0
- package/src/cli/handlers/release-verify.e2e.test.ts +117 -0
- package/src/cli/handlers/release-workstation-ci.e2e.test.ts +135 -0
- package/src/cli/handlers/release.e2e.test.ts +146 -32
- package/src/cli/handlers/release.ts +415 -28
- package/src/cli/handlers/run-auto-release.test.ts +9 -0
- package/src/cli/handlers/run-release.test.ts +55 -0
- package/src/cli/handlers/run.ts +69 -5
- package/src/cli/main.test.ts +1 -0
- package/src/cli/main.ts +91 -10
- package/src/cli/parse-args.ts +54 -2
- package/src/cli/registry.ts +32 -4
- package/src/cli/run-events.ts +5 -2
- package/src/components/auto-release.test.ts +9 -0
- package/src/components/auto-release.ts +3 -0
- package/src/components/capability.ts +9 -0
- package/src/components/driver.test.ts +24 -0
- package/src/components/driver.ts +36 -0
- package/src/components/fan-out-inputs.test.ts +85 -0
- package/src/components/fan-out-inputs.ts +192 -0
- package/src/components/fan-out-record.test.ts +10 -0
- package/src/components/fan-out-record.ts +19 -0
- package/src/components/fan-out-run.ts +48 -0
- package/src/components/fan-out.ts +4 -0
- package/src/components/pr-pipeline.ts +67 -4
- package/src/components/pr-run.test.ts +127 -0
- package/src/components/pr-run.ts +32 -12
- package/src/components/promote.ts +68 -2
- package/src/components/release-compile.ts +17 -3
- package/src/components/release-gate.ts +18 -4
- package/src/components/wave-plan.ts +45 -2
- package/src/config.ts +8 -0
- package/src/flag-params.test.ts +144 -0
- package/src/flag-params.ts +134 -0
- package/src/lifecycle/__fixtures__/publish-fakes.ts +351 -0
- package/src/lifecycle/change-set-store.ts +2 -1
- package/src/lifecycle/delivery-metrics.ts +263 -0
- package/src/lifecycle/deploy-outcome.ts +147 -0
- package/src/lifecycle/gate-change-set.ts +37 -6
- package/src/lifecycle/gate-ledger.ts +2 -2
- package/src/lifecycle/git.ts +61 -8
- package/src/lifecycle/index.ts +4 -0
- package/src/lifecycle/mixed-environment.test.ts +97 -0
- package/src/lifecycle/mixed-environment.ts +204 -0
- package/src/lifecycle/publish-attachments.ts +196 -0
- package/src/lifecycle/publish-credentials.ts +84 -0
- package/src/lifecycle/publish-declaration.ts +95 -0
- package/src/lifecycle/publish-forge.ts +325 -0
- package/src/lifecycle/publish-npm.ts +259 -0
- package/src/lifecycle/publish-oci.ts +396 -0
- package/src/lifecycle/publish-record.ts +113 -0
- package/src/lifecycle/publish-resume.test.ts +168 -0
- package/src/lifecycle/publish-targets.test.ts +494 -0
- package/src/lifecycle/publish-targets.ts +100 -0
- package/src/lifecycle/release-cut.test.ts +74 -2
- package/src/lifecycle/release-cut.ts +158 -22
- package/src/lifecycle/release-flag.test.ts +112 -0
- package/src/lifecycle/release-join.test.ts +68 -0
- package/src/lifecycle/release-join.ts +182 -0
- package/src/lifecycle/release-ledger.ts +9 -3
- package/src/lifecycle/release-publish.test.ts +225 -0
- package/src/lifecycle/release-publish.ts +568 -0
- package/src/lifecycle/release-revocation.ts +147 -0
- package/src/lifecycle/release-store.ts +15 -4
- package/src/lifecycle/release-verify.test.ts +110 -0
- package/src/lifecycle/release-verify.ts +120 -0
- package/src/lifecycle/release-workstation.test.ts +204 -0
- package/src/lifecycle/release-workstation.ts +216 -0
- package/src/lifecycle/retention-growth.e2e.test.ts +279 -0
- package/src/lifecycle/retention-policy.ts +63 -0
- package/src/lifecycle/retention.test.ts +363 -0
- package/src/lifecycle/retention.ts +508 -0
- package/src/lifecycle/run-ledger.ts +7 -1
- package/src/op/activities/activity-contracts.ts +1 -0
- package/src/op/activities/shell.ts +7 -1
- package/src/op/local-executor.ts +27 -4
- package/src/op/runtime.ts +42 -0
- package/src/plan-summary.test.ts +20 -0
- package/src/plan-summary.ts +8 -1
- package/src/pr-forge.test.ts +25 -0
- package/src/pr-forge.ts +62 -0
- package/src/pr-loop.test.ts +13 -1
- package/src/pr-loop.ts +41 -4
- package/src/pr-report-url.test.ts +131 -0
- package/src/pr-report-url.ts +209 -0
- package/src/provenance.ts +2 -2
- package/src/run-event-store.ts +63 -3
- package/src/run-event-stream.ts +8 -4
- package/src/run-event.ts +38 -1
- package/src/workspace/__fixtures__/flag-workspace.ts +132 -0
- package/src/workspace/__fixtures__/older-records/change-set-984534ea9.json +100 -0
- package/src/workspace/__fixtures__/older-records/older-records.json +326 -0
- package/src/workspace/change-set.schema.json +5 -1
- package/src/workspace/check.schema.json +4 -2
- package/src/workspace/checks/records.ts +35 -1
- package/src/workspace/checks.ts +2 -1
- package/src/workspace/ci-green-forge.ts +2 -0
- package/src/workspace/ci-green.ts +20 -5
- package/src/workspace/ci-run.test.ts +416 -0
- package/src/workspace/ci-run.ts +571 -0
- package/src/workspace/ci-serve.test.ts +268 -0
- package/src/workspace/ci-serve.ts +521 -0
- package/src/workspace/declaration.schema.json +64 -0
- package/src/workspace/declaration.test.ts +28 -0
- package/src/workspace/declaration.ts +38 -0
- package/src/workspace/declared-kinds.ts +40 -1
- package/src/workspace/delivery-metrics.schema.json +519 -0
- package/src/workspace/delivery-metrics.test.ts +236 -0
- package/src/workspace/delivery-metrics.ts +286 -0
- package/src/workspace/flags-read.ts +121 -0
- package/src/workspace/flags.test.ts +141 -0
- package/src/workspace/flags.ts +157 -0
- package/src/workspace/graph-cli.ts +1 -1
- package/src/workspace/intent-cli.ts +46 -5
- package/src/workspace/intent-trace.test.ts +173 -0
- package/src/workspace/intent-trace.ts +484 -0
- package/src/workspace/intent.schema.json +702 -6
- package/src/workspace/intent.ts +148 -25
- package/src/workspace/older-records.test.ts +412 -0
- package/src/workspace/overlap.test.ts +165 -0
- package/src/workspace/overlap.ts +340 -0
- package/src/workspace/pr-report.schema.json +5 -0
- package/src/workspace/read-contract.e2e.test.ts +2 -1
- package/src/workspace/reason-codes.test.ts +5 -0
- package/src/workspace/reason-codes.ts +3 -0
- package/src/workspace/record-assets.test.ts +2 -1
- package/src/workspace/records-contract.e2e.test.ts +2 -1
- package/src/workspace/records.schema.json +21 -2
- package/src/workspace/records.ts +36 -2
- package/src/workspace/reference-kinds/flag/flag.kind.mjs +32 -0
- package/src/workspace/reference-kinds/flag/flag.schema.json +53 -0
- package/src/workspace/releases.schema.json +123 -1
- package/src/workspace/releases.ts +11 -0
- package/src/workspace/run-event.schema.json +24 -6
- package/src/workspace/status-contract.test.ts +29 -0
- package/src/workspace/status-gates.ts +8 -0
- package/src/workspace/status-mixed.test.ts +152 -0
- package/src/workspace/status-revoked.test.ts +153 -0
- package/src/workspace/status-stewards.ts +3 -0
- package/src/workspace/status.schema.json +210 -2
- package/src/workspace/status.ts +254 -3
- package/src/workspace/wip.ts +11 -2
package/src/op/runtime.ts
CHANGED
|
@@ -183,6 +183,48 @@ export interface OpRunRecord {
|
|
|
183
183
|
* written before 0.124.28.
|
|
184
184
|
*/
|
|
185
185
|
statement?: { payloadType: string; payload: string; signatures: { keyid: string; sig: string }[] };
|
|
186
|
+
/**
|
|
187
|
+
* Why a failed run failed (#4094), for a reader that acts on it without
|
|
188
|
+
* reading the log: a short `code`, a one-line `reason`, the phase that
|
|
189
|
+
* failed and the exit code of the command that failed. `chant ci run`
|
|
190
|
+
* writes it on every failed job, from the job's pull-request report when
|
|
191
|
+
* it wrote one (`not-a-reviewer`, `plan-failed`, `failed` and the other
|
|
192
|
+
* refusals and statuses), else `command-failed`, `setup-failed` or
|
|
193
|
+
* `build-failed`. Optional within contract 1, written by chant
|
|
194
|
+
* {@link OP_RUN_FAILURE_FLOOR} and newer; absent on a run that passed and
|
|
195
|
+
* on a run nothing explained.
|
|
196
|
+
*/
|
|
197
|
+
failure?: OpRunFailureReason;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** Why a run failed, on {@link OpRunRecord.failure} and on the run's `run-settle` event (#4094). */
|
|
201
|
+
export interface OpRunFailureReason {
|
|
202
|
+
/** A short code: a pull-request report's refusal or status, or `command-failed`, `setup-failed`, `build-failed`. */
|
|
203
|
+
code: string;
|
|
204
|
+
/** One line a person reads. */
|
|
205
|
+
reason: string;
|
|
206
|
+
/** The phase that failed, when one did. */
|
|
207
|
+
phase?: string;
|
|
208
|
+
/** The exit code of the command that failed, when it exited with one. */
|
|
209
|
+
exitCode?: number;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** The first chant that writes {@link OpRunRecord.failure} and `failure` on `run-settle` (#4094). */
|
|
213
|
+
export const OP_RUN_FAILURE_FLOOR = "0.124.30";
|
|
214
|
+
|
|
215
|
+
/** `value` as an {@link OpRunFailureReason}, or undefined when it is not one, so a reader drops a malformed field and keeps the record. */
|
|
216
|
+
export function opRunFailureOf(value: unknown): OpRunFailureReason | undefined {
|
|
217
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return undefined;
|
|
218
|
+
const v = value as Record<string, unknown>;
|
|
219
|
+
if (typeof v.code !== "string" || v.code === "" || typeof v.reason !== "string") return undefined;
|
|
220
|
+
if (v.phase !== undefined && typeof v.phase !== "string") return undefined;
|
|
221
|
+
if (v.exitCode !== undefined && !Number.isInteger(v.exitCode)) return undefined;
|
|
222
|
+
return {
|
|
223
|
+
code: v.code,
|
|
224
|
+
reason: v.reason,
|
|
225
|
+
...(typeof v.phase === "string" ? { phase: v.phase } : {}),
|
|
226
|
+
...(typeof v.exitCode === "number" ? { exitCode: v.exitCode } : {}),
|
|
227
|
+
};
|
|
186
228
|
}
|
|
187
229
|
|
|
188
230
|
/** An {@link OpRunRecord} before the ledger stamps its version and mints an id. */
|
package/src/plan-summary.test.ts
CHANGED
|
@@ -261,3 +261,23 @@ describe("addresses", () => {
|
|
|
261
261
|
expect(escapeMarkerAddress('aws_s3_bucket.logs["a*b"]')).toBe("aws_s3_bucket.logs:a+00002Ab");
|
|
262
262
|
});
|
|
263
263
|
});
|
|
264
|
+
|
|
265
|
+
describe("a no-change group with holes (#4084)", () => {
|
|
266
|
+
const part = (member: string, holes: ChangeSetPart["member"]["holes"] = []): ChangeSetPart => ({
|
|
267
|
+
member: { member, lexicon: "aws", planner: "chant", status: "planned", planDigest: `jcs1-sha256:${member}`, holes },
|
|
268
|
+
entries: [],
|
|
269
|
+
});
|
|
270
|
+
const hole = { address: "cluster-a", type: "AWS::CloudFormation::Stack", reason: "inputs from network; re-checked on apply" };
|
|
271
|
+
|
|
272
|
+
test("does not say no changes for a member whose plan has holes, and keeps the group and its id", () => {
|
|
273
|
+
const withHole = groupChangeSet(composeChangeSet([part("app-01"), part("app-02"), part("cluster-a", [hole])]));
|
|
274
|
+
const without = groupChangeSet(composeChangeSet([part("app-01"), part("app-02"), part("cluster-a")]));
|
|
275
|
+
expect(withHole.groups.map((g) => [g.id, g.units, g.noChanges])).toEqual(without.groups.map((g) => [g.id, g.units, g.noChanges]));
|
|
276
|
+
const id = withHole.groups[0].id;
|
|
277
|
+
const text = renderPlanSummaryText(withHole);
|
|
278
|
+
expect(text).toContain(`Group ${id}: 3 members, no changes known: 1 with holes, listed above:`);
|
|
279
|
+
expect(text).toContain("cluster-a: cluster-a: inputs from network; re-checked on apply");
|
|
280
|
+
expect(renderPlanSummaryMarkdown(withHole)).toContain(`#### Group ${id}: 3 members, no changes known: 1 with holes, listed above`);
|
|
281
|
+
expect(renderPlanSummaryText(without)).toContain(`Group ${id}: 3 members, no changes:`);
|
|
282
|
+
});
|
|
283
|
+
});
|
package/src/plan-summary.ts
CHANGED
|
@@ -689,7 +689,14 @@ function groupTitle(s: PlanSummary, g: PlanSummaryGroup): string {
|
|
|
689
689
|
if (g.resource) t += ` of ${g.resource}`;
|
|
690
690
|
if (g.outlier) t += " (outlier)";
|
|
691
691
|
if (g.provisional) t += " (provisional)";
|
|
692
|
-
if (g.noChanges)
|
|
692
|
+
if (g.noChanges) {
|
|
693
|
+
// A member with holes planned no entries, but its plan says nothing about
|
|
694
|
+
// what the holes cover, so the group cannot say "no changes" for it
|
|
695
|
+
// (#4084). Only the heading says so: the group and its id are unchanged.
|
|
696
|
+
const holed = s.unit === "member" ? new Set(s.holes.map((h) => h.member)) : new Set<string>();
|
|
697
|
+
const unknown = g.units.filter((u) => holed.has(u)).length;
|
|
698
|
+
t += unknown > 0 ? `, no changes known: ${unknown} with holes, listed above` : ", no changes";
|
|
699
|
+
}
|
|
693
700
|
else if (g.extends) t += `, group ${g.extends}'s change plus`;
|
|
694
701
|
else if (g.units.length > 1) t += ", identical change";
|
|
695
702
|
else t += ", change";
|
package/src/pr-forge.test.ts
CHANGED
|
@@ -782,6 +782,31 @@ describe("pull requests over REST (#3798)", () => {
|
|
|
782
782
|
expect((calls[1].body as { target_branch: string }).target_branch).toBe("main");
|
|
783
783
|
});
|
|
784
784
|
|
|
785
|
+
test("openPullRequests lists the heads into a base and marks forks; branchHead reads a branch's commit (#4095)", async () => {
|
|
786
|
+
const f = fakeFetch({
|
|
787
|
+
"GET /api/v1/repos/acme/infra/pulls": [
|
|
788
|
+
{ number: 12, head: { ref: "x", sha: "c".repeat(40), repo: { full_name: "mallory/infra" } }, base: { ref: "main", sha: "b".repeat(40) }, user: { login: "mallory" } },
|
|
789
|
+
{ number: 4, head: { ref: "y", sha: "d".repeat(40), repo: { full_name: "acme/infra" } }, base: { ref: "main", sha: "b".repeat(40) }, user: { login: "driver" } },
|
|
790
|
+
{ number: 5, head: { ref: "z", sha: "e".repeat(40) }, base: { ref: "release" } },
|
|
791
|
+
],
|
|
792
|
+
"GET /api/v1/repos/acme/infra/branches/main": { name: "main", commit: { id: "f".repeat(40) } },
|
|
793
|
+
});
|
|
794
|
+
const forge = forgejoForge({ ...fjOpts, fetch: f.fetch });
|
|
795
|
+
expect(await forge.openPullRequests!("main")).toEqual([
|
|
796
|
+
{ number: 4, head: "y", headSha: "d".repeat(40), base: "main", baseSha: "b".repeat(40), author: "driver", fork: false },
|
|
797
|
+
{ number: 12, head: "x", headSha: "c".repeat(40), base: "main", baseSha: "b".repeat(40), author: "mallory", fork: true },
|
|
798
|
+
]);
|
|
799
|
+
expect(f.calls[0].url).toBe("https://forge.example/api/v1/repos/acme/infra/pulls?state=open&limit=50&page=1");
|
|
800
|
+
expect(await forge.branchHead!("main")).toBe("f".repeat(40));
|
|
801
|
+
expect(await forge.branchHead!("gone")).toBeUndefined();
|
|
802
|
+
|
|
803
|
+
const g = fakeFetch({ "GET /repos/acme/infra/pulls": [], "GET /repos/acme/infra/branches/main": { commit: { sha: SHA } } });
|
|
804
|
+
const github = githubForge({ ...gh, fetch: g.fetch });
|
|
805
|
+
expect(await github.openPullRequests!("main")).toEqual([]);
|
|
806
|
+
expect(g.calls[0].url).toBe("https://api.github.com/repos/acme/infra/pulls?state=open&base=main&per_page=100&page=1");
|
|
807
|
+
expect(await github.branchHead!("main")).toBe(SHA);
|
|
808
|
+
});
|
|
809
|
+
|
|
785
810
|
test("findPullRequest takes the oldest open one from this repository, not a fork's", async () => {
|
|
786
811
|
const f = fakeFetch({
|
|
787
812
|
"GET /api/v1/repos/acme/infra/pulls": [
|
package/src/pr-forge.ts
CHANGED
|
@@ -212,6 +212,23 @@ export interface PullRequest {
|
|
|
212
212
|
base?: string;
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
+
/** An open pull request as `chant ci serve` polls it (#4095). */
|
|
216
|
+
export interface OpenPullRequest {
|
|
217
|
+
number: number;
|
|
218
|
+
/** Its head branch. */
|
|
219
|
+
head: string;
|
|
220
|
+
/** The commit its head branch points at. */
|
|
221
|
+
headSha: string;
|
|
222
|
+
/** The branch it merges into. */
|
|
223
|
+
base: string;
|
|
224
|
+
/** The base branch's commit as the forge last measured the pull request against it. */
|
|
225
|
+
baseSha?: string;
|
|
226
|
+
/** Who opened it. */
|
|
227
|
+
author?: string;
|
|
228
|
+
/** Its head branch lives in another repository, a fork. */
|
|
229
|
+
fork: boolean;
|
|
230
|
+
}
|
|
231
|
+
|
|
215
232
|
/** What {@link PrForge.mergePullRequest} merges, and how. */
|
|
216
233
|
export interface MergeOptions {
|
|
217
234
|
/** The head commit the caller reviewed. The merge happens only while the pull request's head is still this commit. */
|
|
@@ -339,6 +356,13 @@ export interface PrForge {
|
|
|
339
356
|
* still throw.
|
|
340
357
|
*/
|
|
341
358
|
mergePullRequest(pr: number, opts: MergeOptions): Promise<MergeResult>;
|
|
359
|
+
/**
|
|
360
|
+
* GitHub and Forgejo (#4095): every open pull request into `base`, with
|
|
361
|
+
* its head commit, oldest first. `chant ci serve` polls it.
|
|
362
|
+
*/
|
|
363
|
+
openPullRequests?(base: string): Promise<OpenPullRequest[]>;
|
|
364
|
+
/** GitHub and Forgejo (#4095): the commit `branch` points at, or undefined when there is no such branch. */
|
|
365
|
+
branchHead?(branch: string): Promise<string | undefined>;
|
|
342
366
|
}
|
|
343
367
|
|
|
344
368
|
/** A GitHub or Forgejo client. */
|
|
@@ -667,6 +691,44 @@ function githubShaped(kind: "github" | "forgejo", opts: GithubForgeOptions): Git
|
|
|
667
691
|
const open = listed.filter((p) => fromHere(p, head));
|
|
668
692
|
return open.map(pull).filter((p): p is PullRequest => p !== undefined).sort((a, b) => a.number - b.number)[0];
|
|
669
693
|
},
|
|
694
|
+
async openPullRequests(base) {
|
|
695
|
+
// GitHub filters by base server-side; Forgejo's list is matched here.
|
|
696
|
+
const query = kind === "github" ? `?state=open&base=${encodeURIComponent(base)}` : "?state=open";
|
|
697
|
+
let listed: unknown[];
|
|
698
|
+
try {
|
|
699
|
+
listed = await api.list(`${repo}/pulls${query}`, pageParams, pageSize);
|
|
700
|
+
} catch (err) {
|
|
701
|
+
// Forgejo answers 404 while it counts the repository as empty, as findPullRequest notes.
|
|
702
|
+
if (kind !== "forgejo" || !(err instanceof ForgeApiError) || err.status !== 404) throw err;
|
|
703
|
+
await api.call("GET", repo);
|
|
704
|
+
return [];
|
|
705
|
+
}
|
|
706
|
+
const out: OpenPullRequest[] = [];
|
|
707
|
+
for (const v of listed) {
|
|
708
|
+
if (!isObject(v) || !isObject(v.head) || !isObject(v.base)) continue;
|
|
709
|
+
const number = num(v.number);
|
|
710
|
+
const head = str(v.head.ref);
|
|
711
|
+
const headSha = str(v.head.sha);
|
|
712
|
+
const into = str(v.base.ref);
|
|
713
|
+
if (number === undefined || !head || !headSha || into !== base) continue;
|
|
714
|
+
const baseSha = str(v.base.sha);
|
|
715
|
+
const author = isObject(v.user) ? str(v.user.login) : undefined;
|
|
716
|
+
out.push({ number, head, headSha, base: into, ...(baseSha ? { baseSha } : {}), ...(author ? { author } : {}), fork: !fromHere(v, head) });
|
|
717
|
+
}
|
|
718
|
+
return out.sort((a, b) => a.number - b.number);
|
|
719
|
+
},
|
|
720
|
+
async branchHead(branch) {
|
|
721
|
+
let b: unknown;
|
|
722
|
+
try {
|
|
723
|
+
b = await api.call("GET", `${repo}/branches/${branch.split("/").map(encodeURIComponent).join("/")}`);
|
|
724
|
+
} catch (err) {
|
|
725
|
+
if (err instanceof ForgeApiError && err.status === 404) return undefined;
|
|
726
|
+
throw err;
|
|
727
|
+
}
|
|
728
|
+
// GitHub names the commit `sha`, Forgejo `id`.
|
|
729
|
+
const commit = isObject(b) && isObject(b.commit) ? b.commit : undefined;
|
|
730
|
+
return commit ? str(commit.sha) ?? str(commit.id) : undefined;
|
|
731
|
+
},
|
|
670
732
|
async editPullRequest(pr, change) {
|
|
671
733
|
return pull(await api.call("PATCH", `${repo}/pulls/${pr}`, { ...(change.title !== undefined ? { title: change.title } : {}), ...(change.body !== undefined ? { body: change.body } : {}) }));
|
|
672
734
|
},
|
package/src/pr-loop.test.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { composeChangeSet, type ChangeSetPart } from "./change-set";
|
|
|
13
13
|
import {
|
|
14
14
|
describePlanChanged,
|
|
15
15
|
memberCounts,
|
|
16
|
+
PR_REPORT_DEPLOYED_OUTPUTS_FLOOR,
|
|
16
17
|
PR_REPORT_SCHEMA_ID,
|
|
17
18
|
prApproveCommand,
|
|
18
19
|
prNoteMarker,
|
|
@@ -109,6 +110,9 @@ const resumedReport: PrReport = {
|
|
|
109
110
|
resumed: { applied: ["a"] },
|
|
110
111
|
};
|
|
111
112
|
|
|
113
|
+
/** A plan whose members read `net` and `a` from the deployed estate (#4104). */
|
|
114
|
+
const deployedReport: PrReport = { ...planReport, deployedOutputs: { a: ["id"], net: ["cidr"], dns: [] } };
|
|
115
|
+
|
|
112
116
|
describe("the pr-report schema", () => {
|
|
113
117
|
test("is a valid schema and names this module's id", () => {
|
|
114
118
|
expect(validSchema(schema)).toBe(true);
|
|
@@ -116,7 +120,7 @@ describe("the pr-report schema", () => {
|
|
|
116
120
|
});
|
|
117
121
|
|
|
118
122
|
test("every stage's report validates, and its change set validates against the change-set schema", () => {
|
|
119
|
-
for (const report of [planReport, refusedReport, appliedReport, resumedReport]) {
|
|
123
|
+
for (const report of [planReport, refusedReport, appliedReport, resumedReport, deployedReport]) {
|
|
120
124
|
expectValid(report);
|
|
121
125
|
expectValidChangeSet(report.changeSet);
|
|
122
126
|
expect(report.changeSet.digest).toBe(report.digest);
|
|
@@ -158,6 +162,14 @@ describe("the note", () => {
|
|
|
158
162
|
expect(renderPrNote(appliedReport)).not.toContain("Resumed");
|
|
159
163
|
});
|
|
160
164
|
|
|
165
|
+
test("a plan that read dependencies from the deployed estate names each and its outputs (#4104)", () => {
|
|
166
|
+
const note = renderPrNote(deployedReport);
|
|
167
|
+
expect(note).toContain("Planned against outputs read from the deployed estate: `a` (`id`); `net` (`cidr`); `dns` (none yet).");
|
|
168
|
+
expect(renderPrNote(planReport)).not.toContain("deployed estate");
|
|
169
|
+
expect(PR_REPORT_DEPLOYED_OUTPUTS_FLOOR).toBe("0.124.30");
|
|
170
|
+
expect(schema.required).not.toContain("deployedOutputs");
|
|
171
|
+
});
|
|
172
|
+
|
|
161
173
|
test("a small limit leaves the summary out rather than cutting the table", () => {
|
|
162
174
|
const note = renderPrNote(planReport, { limit: 800 });
|
|
163
175
|
expect(note).toContain("| `app` |");
|
package/src/pr-loop.ts
CHANGED
|
@@ -46,6 +46,9 @@ export const PR_REPORT_SCHEMA_ID = "https://intentius.io/chant/schemas/workspace
|
|
|
46
46
|
/** The read-contract version the report follows. */
|
|
47
47
|
export const PR_REPORT_CONTRACT = 1;
|
|
48
48
|
|
|
49
|
+
/** The first chant whose report can carry `deployedOutputs` (#4104). */
|
|
50
|
+
export const PR_REPORT_DEPLOYED_OUTPUTS_FLOOR = "0.124.30";
|
|
51
|
+
|
|
49
52
|
/** The gate a pull request's apply waits on, unless the pipeline names another. */
|
|
50
53
|
export const PR_APPLY_GATE = "pr-apply";
|
|
51
54
|
|
|
@@ -183,6 +186,14 @@ export interface PrReport {
|
|
|
183
186
|
* the rest under the same approval and did not run them again.
|
|
184
187
|
*/
|
|
185
188
|
resumed?: { applied: string[] };
|
|
189
|
+
/**
|
|
190
|
+
* Per dependency a member was planned against, the names of the outputs
|
|
191
|
+
* read from the deployed estate (#4104), sorted. A dependency outside the
|
|
192
|
+
* change is here too. Seeded outputs (`--seed-outputs`, a resume record)
|
|
193
|
+
* are not. Absent when nothing was read. Added within contract 1 by chant
|
|
194
|
+
* {@link PR_REPORT_DEPLOYED_OUTPUTS_FLOOR}.
|
|
195
|
+
*/
|
|
196
|
+
deployedOutputs?: Record<string, string[]>;
|
|
186
197
|
approval: PrApproval;
|
|
187
198
|
/** Component names: which changed, which run because they depend on one, and what the change touched that no component deploys. */
|
|
188
199
|
selection: {
|
|
@@ -204,6 +215,17 @@ export function memberCounts(doc: Pick<ChangeSetDocument, "summary">, member: st
|
|
|
204
215
|
return { create: n("create"), update: n("update"), replace: n("replace"), delete: n("delete") };
|
|
205
216
|
}
|
|
206
217
|
|
|
218
|
+
/**
|
|
219
|
+
* Why a member's plan cannot say everything it will change: its holes'
|
|
220
|
+
* reasons, each once, in order (#4084). A `cfn-deploy` member whose inputs
|
|
221
|
+
* come from a stack the same run applies first says `inputs from <stack>;
|
|
222
|
+
* re-checked on apply`.
|
|
223
|
+
*/
|
|
224
|
+
export function memberHoleReasons(report: Pick<PrReport, "changeSet">, member: string): string[] {
|
|
225
|
+
const m = report.changeSet.members.find((x) => x.member === member);
|
|
226
|
+
return [...new Set((m?.holes ?? []).map((h) => h.reason))];
|
|
227
|
+
}
|
|
228
|
+
|
|
207
229
|
/** The approve command a reviewer runs for this report's digest. */
|
|
208
230
|
export function prApproveCommand(report: Pick<PrReport, "op" | "gate" | "digest">, approver = "<you>"): string {
|
|
209
231
|
return `chant approve ${report.op} ${report.gate} --plan ${report.digest} --approver ${approver} --sign`;
|
|
@@ -295,8 +317,16 @@ export function renderPrNote(report: PrReport, options: { limit?: number; approv
|
|
|
295
317
|
if (report.members.length > 0) {
|
|
296
318
|
out.push("", "| Member | Component | Status | Create | Update | Replace | Delete |", "|---|---|---|---:|---:|---:|---:|");
|
|
297
319
|
for (const m of report.members) {
|
|
298
|
-
const
|
|
299
|
-
|
|
320
|
+
const holes = memberHoleReasons(report, m.member);
|
|
321
|
+
const status =
|
|
322
|
+
STATUS_WORDS[m.status] +
|
|
323
|
+
(m.inputsMoved?.length ? `, inputs from ${m.inputsMoved.join(", ")} moved` : "") +
|
|
324
|
+
(holes.length > 0 ? `; ${holes.length > 2 ? `${holes[0]}; and ${holes.length - 1} more, listed under Holes` : holes.join("; ")}` : "");
|
|
325
|
+
// A member whose plan has holes and no entries says nothing about what
|
|
326
|
+
// it changes, so its counts are unknown, not zero (#4084).
|
|
327
|
+
const unknown = holes.length > 0 && !report.changeSet.entries.some((e) => e.member === m.member);
|
|
328
|
+
const n = (k: keyof PrMemberCounts): string => (unknown ? "?" : String(m.counts[k]));
|
|
329
|
+
out.push(`| ${code(m.member)} | ${m.component} | ${status} | ${n("create")} | ${n("update")} | ${n("replace")} | ${n("delete")} |`);
|
|
300
330
|
}
|
|
301
331
|
if (report.members.some((m) => m.inputsMoved?.length)) {
|
|
302
332
|
out.push("", "A member whose inputs moved applied the plan it was approved with. The next run plans it against the new values.");
|
|
@@ -305,6 +335,11 @@ export function renderPrNote(report: PrReport, options: { limit?: number; approv
|
|
|
305
335
|
if (report.selection.unclaimed.length > 0) {
|
|
306
336
|
out.push("", `Changed, and deployed by no component: ${report.selection.unclaimed.map(code).join(", ")}.`);
|
|
307
337
|
}
|
|
338
|
+
const deployed = Object.entries(report.deployedOutputs ?? {});
|
|
339
|
+
if (deployed.length > 0) {
|
|
340
|
+
const each = deployed.map(([dep, names]) => `${code(dep)} (${names.length > 0 ? names.map(code).join(", ") : "none yet"})`);
|
|
341
|
+
out.push("", `Planned against outputs read from the deployed estate: ${each.join("; ")}.`);
|
|
342
|
+
}
|
|
308
343
|
if (report.status !== "nothing") {
|
|
309
344
|
out.push("", `Plan digest: ${code(report.digest)}`, "", ...approvalLines(report, options.approver ?? "<you>"));
|
|
310
345
|
}
|
|
@@ -328,9 +363,11 @@ export function prStatusDescription(report: PrReport): string {
|
|
|
328
363
|
case "nothing":
|
|
329
364
|
text = "nothing to plan";
|
|
330
365
|
break;
|
|
331
|
-
case "planned":
|
|
332
|
-
|
|
366
|
+
case "planned": {
|
|
367
|
+
const unknown = report.members.filter((m) => memberHoleReasons(report, m.member).length > 0).length;
|
|
368
|
+
text = `${n} member${n === 1 ? "" : "s"}: ${sum.change} to change, ${sum.destroy} to destroy or replace${unknown > 0 ? `, ${unknown} with holes` : ""}${digest}`;
|
|
333
369
|
break;
|
|
370
|
+
}
|
|
334
371
|
case "plan-failed":
|
|
335
372
|
text = `a member failed to plan${digest}`;
|
|
336
373
|
break;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `--report-url` (#4007): the URL forms, the key, and the upload of a run's
|
|
3
|
+
* events as it goes: one upload in flight at a time, a failure that warns
|
|
4
|
+
* and never stops the run, and a last upload that carries everything. The
|
|
5
|
+
* s3 scheme runs a fake AWS CLI; nothing here reaches a network.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
|
9
|
+
import { tmpdir } from "node:os";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { pathToFileURL } from "node:url";
|
|
12
|
+
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
|
13
|
+
import { openPrReportUpload, parseReportUrl, reportKey, reportPut, type ReportPut } from "./pr-report-url";
|
|
14
|
+
|
|
15
|
+
const line = (type: string, extra: Record<string, unknown> = {}): string => JSON.stringify({ type, ...extra }) + "\n";
|
|
16
|
+
|
|
17
|
+
let dir: string;
|
|
18
|
+
beforeEach(async () => {
|
|
19
|
+
dir = await mkdtemp(join(tmpdir(), "pr-report-url-"));
|
|
20
|
+
});
|
|
21
|
+
afterEach(async () => {
|
|
22
|
+
await rm(dir, { recursive: true, force: true });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe("parseReportUrl", () => {
|
|
26
|
+
test("reads s3 and file URLs and refuses anything else", () => {
|
|
27
|
+
expect(parseReportUrl("s3://bucket/a/b/")).toEqual({ scheme: "s3", url: "s3://bucket/a/b", bucket: "bucket", prefix: "a/b" });
|
|
28
|
+
expect(parseReportUrl("s3://bucket")).toEqual({ scheme: "s3", url: "s3://bucket", bucket: "bucket", prefix: "" });
|
|
29
|
+
expect(parseReportUrl(pathToFileURL(dir).href)).toMatchObject({ scheme: "file", dir });
|
|
30
|
+
expect(parseReportUrl("https://example.invalid/x")).toHaveProperty("error");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("keys by pull request and commit, and by member after them", () => {
|
|
34
|
+
expect(reportKey(12, "abc")).toBe("12/abc");
|
|
35
|
+
expect(reportKey(null, "abc")).toBe("unknown/abc");
|
|
36
|
+
expect(reportKey(12, "abc", "api")).toBe("12/abc/api");
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe("openPrReportUpload", () => {
|
|
41
|
+
/** A put whose uploads finish only when the test says, recording what each carried. */
|
|
42
|
+
function heldPut() {
|
|
43
|
+
const calls: Array<{ key: string; body: string; finish: (err?: Error) => void }> = [];
|
|
44
|
+
let inFlight = 0;
|
|
45
|
+
let most = 0;
|
|
46
|
+
const put: ReportPut = (key, body) =>
|
|
47
|
+
new Promise<void>((resolve, reject) => {
|
|
48
|
+
inFlight++;
|
|
49
|
+
most = Math.max(most, inFlight);
|
|
50
|
+
calls.push({ key, body, finish: (err) => { inFlight--; if (err) reject(err); else resolve(); } });
|
|
51
|
+
});
|
|
52
|
+
return { put, calls, most: () => most };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
test("uploads the events on each settle, never two at once, and the settles that came meanwhile in one more", async () => {
|
|
56
|
+
const held = heldPut();
|
|
57
|
+
const upload = openPrReportUpload({ target: { scheme: "s3", url: "s3://b", bucket: "b", prefix: "" }, key: "12/abc", stage: "apply", warn: () => {}, put: held.put });
|
|
58
|
+
upload.line(line("run-start"));
|
|
59
|
+
expect(held.calls).toHaveLength(0);
|
|
60
|
+
upload.line(line("wave-start", { wave: 1 }));
|
|
61
|
+
upload.line(line("component-settle", { wave: 1 }));
|
|
62
|
+
expect(held.calls).toHaveLength(1);
|
|
63
|
+
upload.line(line("wave-settle", { wave: 1 }));
|
|
64
|
+
upload.line(line("wave-start", { wave: 2 }));
|
|
65
|
+
upload.line(line("component-settle", { wave: 2 }));
|
|
66
|
+
// Both settles wait behind the one in flight, and go up as one.
|
|
67
|
+
expect(held.calls).toHaveLength(1);
|
|
68
|
+
held.calls[0]!.finish();
|
|
69
|
+
await new Promise((r) => setImmediate(r));
|
|
70
|
+
expect(held.calls).toHaveLength(2);
|
|
71
|
+
expect(held.calls[1]!.body.split("\n").filter(Boolean)).toHaveLength(6);
|
|
72
|
+
expect(held.calls[1]!.key).toBe("12/abc/pr-apply.events.jsonl");
|
|
73
|
+
|
|
74
|
+
upload.file("pr-apply.json", "{}\n");
|
|
75
|
+
const closing = upload.close();
|
|
76
|
+
await new Promise((r) => setImmediate(r));
|
|
77
|
+
// The last upload waits for the one in flight.
|
|
78
|
+
expect(held.calls).toHaveLength(2);
|
|
79
|
+
held.calls[1]!.finish();
|
|
80
|
+
await new Promise((r) => setImmediate(r));
|
|
81
|
+
held.calls[2]!.finish();
|
|
82
|
+
await new Promise((r) => setImmediate(r));
|
|
83
|
+
held.calls[3]!.finish();
|
|
84
|
+
await closing;
|
|
85
|
+
expect(held.calls.map((c) => c.key)).toEqual(["12/abc/pr-apply.events.jsonl", "12/abc/pr-apply.events.jsonl", "12/abc/pr-apply.events.jsonl", "12/abc/pr-apply.json"]);
|
|
86
|
+
expect(held.most()).toBe(1);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("a failed upload as the run goes warns once and the run goes on; the last one is tried again", async () => {
|
|
90
|
+
const warnings: string[] = [];
|
|
91
|
+
let fail = true;
|
|
92
|
+
const put: ReportPut = async (key) => {
|
|
93
|
+
if (fail) throw new Error(`no bucket for ${key}`);
|
|
94
|
+
};
|
|
95
|
+
const upload = openPrReportUpload({ target: { scheme: "s3", url: "s3://b", bucket: "b", prefix: "" }, key: "12/abc", stage: "apply", warn: (m) => warnings.push(m), put });
|
|
96
|
+
upload.line(line("component-settle"));
|
|
97
|
+
await new Promise((r) => setImmediate(r));
|
|
98
|
+
upload.line(line("wave-settle"));
|
|
99
|
+
await new Promise((r) => setImmediate(r));
|
|
100
|
+
expect(warnings).toHaveLength(1);
|
|
101
|
+
expect(warnings[0]).toMatch(/as the run went: no bucket .*The run goes on/);
|
|
102
|
+
fail = false;
|
|
103
|
+
await upload.close();
|
|
104
|
+
expect(warnings).toHaveLength(1);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("file://: each settle replaces the events file whole, so it holds a wave's settle before the next wave starts", async () => {
|
|
108
|
+
const target = parseReportUrl(pathToFileURL(dir).href);
|
|
109
|
+
if ("error" in target) throw new Error(target.error);
|
|
110
|
+
const upload = openPrReportUpload({ target, key: "12/abc", stage: "apply", warn: () => {} });
|
|
111
|
+
upload.line(line("run-start"));
|
|
112
|
+
upload.line(line("wave-settle", { wave: 1 }));
|
|
113
|
+
await new Promise((r) => setImmediate(r));
|
|
114
|
+
const events = join(dir, "12", "abc", "pr-apply.events.jsonl");
|
|
115
|
+
expect(await readFile(events, "utf8")).toBe(line("run-start") + line("wave-settle", { wave: 1 }));
|
|
116
|
+
upload.line(line("run-settle"));
|
|
117
|
+
upload.file("change-set.json", "{}\n");
|
|
118
|
+
await upload.close();
|
|
119
|
+
expect((await readFile(events, "utf8")).split("\n").filter(Boolean)).toHaveLength(3);
|
|
120
|
+
expect(await readFile(join(dir, "12", "abc", "change-set.json"), "utf8")).toBe("{}\n");
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("s3://: the AWS CLI copies each object from its stdin, with the content type, and chant hands it no credential", async () => {
|
|
124
|
+
const runs: Array<{ args: string[]; stdin: string }> = [];
|
|
125
|
+
const put = reportPut({ scheme: "s3", url: "s3://b/p", bucket: "b", prefix: "p" }, async (args, stdin) => {
|
|
126
|
+
runs.push({ args, stdin });
|
|
127
|
+
});
|
|
128
|
+
await put("12/abc/pr-plan.json", "{}\n", "application/json");
|
|
129
|
+
expect(runs).toEqual([{ args: ["s3", "cp", "-", "s3://b/p/12/abc/pr-plan.json", "--content-type", "application/json", "--only-show-errors"], stdin: "{}\n" }]);
|
|
130
|
+
});
|
|
131
|
+
});
|