@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
|
@@ -0,0 +1,571 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `chant ci run plan|apply` (#4094, epic #4101): run one pull-request job as
|
|
3
|
+
* a pipeline Op, the first slice of the chant runner.
|
|
4
|
+
*
|
|
5
|
+
* The job is the typed value `prLoopJobs` (../components/pr-pipeline.ts)
|
|
6
|
+
* returns, the one the github, gitlab and forgejo generators wrap in a
|
|
7
|
+
* workflow, so the runner and a generated workflow run the same command with
|
|
8
|
+
* the same values. Nothing reads generated YAML.
|
|
9
|
+
*
|
|
10
|
+
* The job runs as an Op on the local executor (../op/local-executor.ts):
|
|
11
|
+
*
|
|
12
|
+
* - a `setup` phase with the setup lines the default image needs,
|
|
13
|
+
* - a `build` phase with the lines given to run before the command
|
|
14
|
+
* (`--before-script`, which a generated workflow runs in the same place),
|
|
15
|
+
* then the job's build and lint lines with `--pr-build` (#4088),
|
|
16
|
+
* - and a phase named for the job, `plan` or `apply`, with the job's own
|
|
17
|
+
* setup lines and its command in one shell.
|
|
18
|
+
*
|
|
19
|
+
* Each line is one shell step with a clean environment: a short list of the
|
|
20
|
+
* runner's variables (PATH, HOME and the like), the forge's connection and
|
|
21
|
+
* token variables the commands read, any `--pass-env` names, and the job's
|
|
22
|
+
* own variables. The forge values a generated job reads from `${{ ... }}`
|
|
23
|
+
* expressions or GitLab's predefined variables come from flags here:
|
|
24
|
+
* `BASE_SHA` from `--base`, `PR_NUMBER` from `--pr`, and the commit from
|
|
25
|
+
* `--commit`.
|
|
26
|
+
*
|
|
27
|
+
* The steps run in a fresh checkout of the commit: a detached `git worktree`
|
|
28
|
+
* in a temporary directory outside the repository, removed when the run
|
|
29
|
+
* ends. A worktree shares the repository's refs, so what the job records on
|
|
30
|
+
* `chant/lifecycle` (pr-apply's pending gate facts) and the remote it pushes
|
|
31
|
+
* to are the repository's own, as in a forge job's checkout. The report the
|
|
32
|
+
* job writes under `.chant/pr` is copied out of the worktree before it goes,
|
|
33
|
+
* where a forge would keep it as an artifact.
|
|
34
|
+
*
|
|
35
|
+
* The run is recorded as Op `pr-plan` or `pr-apply` in the run ledger
|
|
36
|
+
* (`<env>/runs__<op>.jsonl` on `chant/lifecycle`), and signed when a runner
|
|
37
|
+
* key is given, so `chant ci tick --source runs` (#4044) judges the commit
|
|
38
|
+
* from it. Its events are `kind: "pipeline"` and `runtime: "ci"`, and the
|
|
39
|
+
* record and the events carry `runOrigin: { forge: "chant" }`.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
import { execFileSync } from "node:child_process";
|
|
43
|
+
import { copyFileSync, cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync } from "node:fs";
|
|
44
|
+
import { tmpdir } from "node:os";
|
|
45
|
+
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
46
|
+
import { PR_APPLY_RECORD, PR_LOOP_REPORT_DIR, prLoopJobs, prLoopSetup, PR_LOOP_IMAGE, type PrLoopJob, type PrLoopPipelineOptions } from "../components/pr-pipeline";
|
|
47
|
+
import { detectForge, FORGE_KINDS, type ForgeKind } from "../pr-forge";
|
|
48
|
+
import type { CommandContext } from "../cli/registry";
|
|
49
|
+
import { formatError, formatWarning } from "../cli/format";
|
|
50
|
+
import type { RunOrigin } from "../lifecycle/release-ledger";
|
|
51
|
+
import { pushLifecycleReport } from "../lifecycle/git";
|
|
52
|
+
import { ACTIVITY_PROFILES } from "../op/activity-profiles";
|
|
53
|
+
import type { ActivityFn, ActivityProfile } from "../op/activity-registry";
|
|
54
|
+
import { shellCmd } from "../op/activities/shell";
|
|
55
|
+
import { OpRunFailure, runOpLocally, type OpRunResult, type StepRecord } from "../op/local-executor";
|
|
56
|
+
import type { OpRunFailureReason, OpRunRecord } from "../op/runtime";
|
|
57
|
+
import type { ActivityStep, OpConfig, PhaseDefinition } from "../op/types";
|
|
58
|
+
import { findWorkspaceRoot } from "../project-root";
|
|
59
|
+
import { ciTagSigner, RUNNER_KEY_ENV } from "./ci-green";
|
|
60
|
+
import { resolveMemberContext } from "./member-pipeline";
|
|
61
|
+
import { signOpRunRecord } from "./trust/op-run-statement";
|
|
62
|
+
import { gitTop } from "./tree";
|
|
63
|
+
|
|
64
|
+
/** The two jobs a pull-request pipeline has. */
|
|
65
|
+
export type CiJobName = PrLoopJob["jobName"];
|
|
66
|
+
export const CI_JOB_NAMES: readonly CiJobName[] = ["plan", "apply"];
|
|
67
|
+
|
|
68
|
+
/** The Op each job is recorded as, which `ci.green` phases match. */
|
|
69
|
+
export const CI_JOB_OPS: Record<CiJobName, string> = { plan: "pr-plan", apply: "pr-apply" };
|
|
70
|
+
|
|
71
|
+
/** Where a job the chant runner ran says it ran (#4094). */
|
|
72
|
+
export const CHANT_RUNNER_ORIGIN: RunOrigin = { forge: "chant" };
|
|
73
|
+
|
|
74
|
+
/** The runner's variables every job gets, when the runner has them. */
|
|
75
|
+
export const CI_RUN_BASE_ENV = ["PATH", "HOME", "USER", "LOGNAME", "SHELL", "LANG", "LC_ALL", "LC_CTYPE", "TMPDIR", "TZ"] as const;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The forge's connection and token variables the pull-request commands read
|
|
79
|
+
* (`forgeFromEnv` in ../pr-forge.ts), which a forge job gets from the forge
|
|
80
|
+
* and a job here gets from the runner's environment.
|
|
81
|
+
*/
|
|
82
|
+
export const CI_RUN_FORGE_ENV = [
|
|
83
|
+
"CHANT_FORGE_TOKEN",
|
|
84
|
+
"GITHUB_TOKEN",
|
|
85
|
+
"GH_TOKEN",
|
|
86
|
+
"GITHUB_REPOSITORY",
|
|
87
|
+
"GITHUB_SERVER_URL",
|
|
88
|
+
"GITHUB_API_URL",
|
|
89
|
+
"GITLAB_TOKEN",
|
|
90
|
+
"CI_API_V4_URL",
|
|
91
|
+
"CI_PROJECT_ID",
|
|
92
|
+
"CI_SERVER_HOST",
|
|
93
|
+
"CI_SERVER_URL",
|
|
94
|
+
] as const;
|
|
95
|
+
|
|
96
|
+
/** How long one step may run: a forge job's own limit, six hours. */
|
|
97
|
+
const STEP_TIMEOUT = "6h";
|
|
98
|
+
|
|
99
|
+
/** The forge values a job reads, given on the command line in place of a forge's expressions. */
|
|
100
|
+
export interface CiRunValues {
|
|
101
|
+
/** `--base`: the commit the change is measured from, `BASE_SHA`. */
|
|
102
|
+
base?: string;
|
|
103
|
+
/** `--pr`: the pull request, `PR_NUMBER`. The plan needs it. */
|
|
104
|
+
pr?: number;
|
|
105
|
+
/** The full commit the job runs at. */
|
|
106
|
+
commit: string;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export class CiRunError extends Error {
|
|
110
|
+
constructor(message: string) {
|
|
111
|
+
super(message);
|
|
112
|
+
this.name = "CiRunError";
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* The job's environment: the runner's base variables, the forge variables
|
|
118
|
+
* and `passEnv` names it has, `CHANT_ENV` as a generated workflow sets it,
|
|
119
|
+
* the commit as the forge spells it, and the job's own variables. A job
|
|
120
|
+
* variable the forge fills from an expression comes from a flag, or from
|
|
121
|
+
* the runner's environment for a token such as `GITHUB_TOKEN`; a literal
|
|
122
|
+
* one, such as `CHANT_RUN_EVENTS_DIR`, is kept as it is.
|
|
123
|
+
*/
|
|
124
|
+
export function ciJobEnv(
|
|
125
|
+
job: PrLoopJob,
|
|
126
|
+
input: { forge: ForgeKind; env: string; values: CiRunValues; hostEnv: Record<string, string | undefined>; passEnv?: readonly string[] },
|
|
127
|
+
): Record<string, string> {
|
|
128
|
+
const out: Record<string, string> = {};
|
|
129
|
+
for (const name of [...CI_RUN_BASE_ENV, ...CI_RUN_FORGE_ENV, ...(input.passEnv ?? [])]) {
|
|
130
|
+
const value = input.hostEnv[name];
|
|
131
|
+
if (value !== undefined) out[name] = value;
|
|
132
|
+
}
|
|
133
|
+
out.CHANT_ENV = input.env;
|
|
134
|
+
out[input.forge === "gitlab" ? "CI_COMMIT_SHA" : "GITHUB_SHA"] = input.values.commit;
|
|
135
|
+
const flags: Record<string, { value: string | undefined; flag: string }> = {
|
|
136
|
+
BASE_SHA: { value: input.values.base, flag: "--base <sha>" },
|
|
137
|
+
PR_NUMBER: { value: input.values.pr !== undefined ? String(input.values.pr) : undefined, flag: "--pr <number>" },
|
|
138
|
+
};
|
|
139
|
+
for (const [name, value] of Object.entries(job.env)) {
|
|
140
|
+
const given = flags[name];
|
|
141
|
+
if (given) {
|
|
142
|
+
if (given.value === undefined) throw new CiRunError(`the ${job.jobName} job reads ${name}, which a forge fills from the event; pass ${given.flag}`);
|
|
143
|
+
out[name] = given.value;
|
|
144
|
+
} else if (!value.includes("$")) {
|
|
145
|
+
out[name] = value;
|
|
146
|
+
} else if (input.hostEnv[name] !== undefined) {
|
|
147
|
+
out[name] = input.hostEnv[name]!;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return out;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** What {@link ciJobOp} builds the Op from. */
|
|
154
|
+
export interface CiJobOpInput {
|
|
155
|
+
job: PrLoopJob;
|
|
156
|
+
env: string;
|
|
157
|
+
/** The directory the steps run in, inside the fresh checkout. */
|
|
158
|
+
cwd: string;
|
|
159
|
+
/** The job's environment, from {@link ciJobEnv}. */
|
|
160
|
+
jobEnv: Record<string, string>;
|
|
161
|
+
/** Setup lines, from `prLoopSetup`. */
|
|
162
|
+
setup: readonly string[];
|
|
163
|
+
/** Lines to run before the command, in order: `--before-script`, then the job's build and lint lines (#4088). */
|
|
164
|
+
build: readonly string[];
|
|
165
|
+
/** What runs chant, in place of `chant` at the start of the command. */
|
|
166
|
+
chant?: string;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** `line` with a `chant` at its start replaced by the command the caller names, when it names one. */
|
|
170
|
+
export function withChant(line: string, chant?: string): string {
|
|
171
|
+
return chant && line.startsWith("chant ") ? `${chant}${line.slice("chant".length)}` : line;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** The job's own setup lines and its command, as one shell script, with `chant` at its start replaced by the command the caller names. */
|
|
175
|
+
export function ciJobCommand(job: PrLoopJob, chant?: string): string {
|
|
176
|
+
return [...(job.setup ?? []), withChant(job.command, chant)].join("\n");
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* The job as an Op: `setup`, `build` and the job's own phase, each line one
|
|
181
|
+
* shell step in `cwd` with the job's environment and nothing else. A phase
|
|
182
|
+
* with no lines is left out.
|
|
183
|
+
*/
|
|
184
|
+
export function ciJobOp(input: CiJobOpInput): OpConfig {
|
|
185
|
+
const step = (cmd: string): ActivityStep => ({
|
|
186
|
+
kind: "activity",
|
|
187
|
+
fn: "shellCmd",
|
|
188
|
+
args: { cmd, cwd: input.cwd, env: input.jobEnv, inheritEnv: false },
|
|
189
|
+
profile: "atMostOnce",
|
|
190
|
+
timeout: STEP_TIMEOUT,
|
|
191
|
+
});
|
|
192
|
+
const phases: PhaseDefinition[] = [
|
|
193
|
+
{ name: "setup", steps: input.setup.map(step) },
|
|
194
|
+
{ name: "build", steps: input.build.map((line) => step(withChant(line, input.chant))) },
|
|
195
|
+
{ name: input.job.jobName, steps: [step(ciJobCommand(input.job, input.chant))] },
|
|
196
|
+
].filter((p) => p.steps.length > 0);
|
|
197
|
+
const op = CI_JOB_OPS[input.job.jobName];
|
|
198
|
+
return {
|
|
199
|
+
name: op,
|
|
200
|
+
overview: input.job.jobName === "plan" ? "Plan the members this pull request reaches" : "Apply the plan a reviewer approved",
|
|
201
|
+
labels: { Env: input.env, Pipeline: "pr", Job: input.job.jobName },
|
|
202
|
+
phases,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** The pull-request report statuses that mean the job did not do its work, which a failed job's `failure.code` takes. */
|
|
207
|
+
const FAILED_REPORT_STATUSES = new Set(["plan-failed", "failed", "refused"]);
|
|
208
|
+
|
|
209
|
+
/** The exit code a failed shell step's error names (`command exited <n>`), when it names one. */
|
|
210
|
+
function exitCodeOf(error: string | undefined): number | undefined {
|
|
211
|
+
const m = /command exited (\d+)/.exec(error ?? "");
|
|
212
|
+
return m ? Number(m[1]) : undefined;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** The first line of a step's error, for a reason. */
|
|
216
|
+
const firstLine = (text: string): string => text.split("\n").find((l) => l.trim() !== "")?.trim() ?? text;
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Why a failed job failed (#4094), from its step records and, when the
|
|
220
|
+
* command wrote one, its pull-request report (`pr-plan.json` or
|
|
221
|
+
* `pr-apply.json` under `reportDir`):
|
|
222
|
+
*
|
|
223
|
+
* - a failed setup or build line: `setup-failed` or `build-failed`, the line
|
|
224
|
+
* and its exit code as the reason, and the phase;
|
|
225
|
+
* - a failed command with a report: the report's `refusal`, else its
|
|
226
|
+
* `status`, with its `message` as the reason, else the members that failed;
|
|
227
|
+
* - a failed command with no report: `command-failed` and the step's error.
|
|
228
|
+
*
|
|
229
|
+
* Undefined when no step failed, such as a run that stopped on an error of
|
|
230
|
+
* its own before any step ran.
|
|
231
|
+
*/
|
|
232
|
+
export function ciJobFailure(input: { job: CiJobName; op: OpConfig; records: readonly StepRecord[]; reportDir: string }): OpRunFailureReason | undefined {
|
|
233
|
+
const failed = input.records.find((r) => r.status === "fail");
|
|
234
|
+
if (!failed) return undefined;
|
|
235
|
+
const exitCode = exitCodeOf(failed.error);
|
|
236
|
+
const exit = exitCode !== undefined ? { exitCode } : {};
|
|
237
|
+
if (failed.phase === "setup" || failed.phase === "build") {
|
|
238
|
+
const at = input.records.filter((r) => r.phase === failed.phase).indexOf(failed);
|
|
239
|
+
const step = input.op.phases.find((p) => p.name === failed.phase)?.steps[at] as ActivityStep | undefined;
|
|
240
|
+
const line = typeof step?.args?.cmd === "string" ? step.args.cmd : undefined;
|
|
241
|
+
const reason = line !== undefined
|
|
242
|
+
? `${line} ${exitCode !== undefined ? `exited ${exitCode}` : `failed: ${firstLine(failed.error ?? "")}`}`
|
|
243
|
+
: firstLine(failed.error ?? "the step failed");
|
|
244
|
+
return { code: `${failed.phase}-failed`, reason, phase: failed.phase, ...exit };
|
|
245
|
+
}
|
|
246
|
+
const file = join(input.reportDir, `${CI_JOB_OPS[input.job]}.json`);
|
|
247
|
+
let report: { status?: unknown; refusal?: unknown; message?: unknown; members?: unknown } | undefined;
|
|
248
|
+
try {
|
|
249
|
+
if (existsSync(file)) report = JSON.parse(readFileSync(file, "utf-8"));
|
|
250
|
+
} catch {
|
|
251
|
+
report = undefined;
|
|
252
|
+
}
|
|
253
|
+
const status = typeof report?.status === "string" ? report.status : undefined;
|
|
254
|
+
if (report && status && (FAILED_REPORT_STATUSES.has(status) || typeof report.refusal === "string")) {
|
|
255
|
+
const code = typeof report.refusal === "string" ? report.refusal : status;
|
|
256
|
+
let reason = typeof report.message === "string" && report.message.trim() !== "" ? firstLine(report.message) : undefined;
|
|
257
|
+
if (reason === undefined) {
|
|
258
|
+
const members = Array.isArray(report.members) ? (report.members as { member?: unknown; status?: unknown; error?: unknown }[]) : [];
|
|
259
|
+
const bad = members.filter((m) => m.status === "failed" || m.status === "plan-failed");
|
|
260
|
+
reason = bad.length > 0
|
|
261
|
+
? `${bad.length === 1 ? "member" : "members"} ${bad.map((m) => `${String(m.member)} ${String(m.status)}${typeof m.error === "string" ? ` (${firstLine(m.error)})` : ""}`).join(", ")}`
|
|
262
|
+
: `the ${CI_JOB_OPS[input.job]} report says ${status}`;
|
|
263
|
+
}
|
|
264
|
+
return { code, reason, phase: failed.phase, ...exit };
|
|
265
|
+
}
|
|
266
|
+
return { code: "command-failed", reason: firstLine(failed.error ?? "the command failed"), phase: failed.phase, ...exit };
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** What `chant ci run` runs. */
|
|
270
|
+
export interface CiRunRequest {
|
|
271
|
+
/** Where the command was run: the project, or a workspace member's directory. */
|
|
272
|
+
cwd: string;
|
|
273
|
+
job: CiJobName;
|
|
274
|
+
/** `--env`: the environment the pipeline is for. */
|
|
275
|
+
env: string;
|
|
276
|
+
forge: ForgeKind;
|
|
277
|
+
/** `--base`, `--pr` and `--commit` (default `HEAD`). */
|
|
278
|
+
base?: string;
|
|
279
|
+
pr?: number;
|
|
280
|
+
commit?: string;
|
|
281
|
+
/** The generator's options: `--gate`, `--branch`, `--events-json`, `--report-url`, `--pr-build` and `--pr-build-command`. */
|
|
282
|
+
loop?: PrLoopPipelineOptions;
|
|
283
|
+
/** `--image`: the image a generated pipeline names. Only the default gets setup lines. */
|
|
284
|
+
image?: string;
|
|
285
|
+
/** `--before-script` lines, run in the build phase before the job's build lines. */
|
|
286
|
+
beforeScript?: readonly string[];
|
|
287
|
+
/** `--chant`: what runs chant in the job. Default `chant`. */
|
|
288
|
+
chant?: string;
|
|
289
|
+
/** `--pass-env`: more of the runner's variables the job gets. */
|
|
290
|
+
passEnv?: readonly string[];
|
|
291
|
+
/** The runner's environment. Default `process.env`. */
|
|
292
|
+
hostEnv?: Record<string, string | undefined>;
|
|
293
|
+
/** `--output`: where the job's report is copied, from `cwd`. Default `.chant/pr`. */
|
|
294
|
+
output?: string;
|
|
295
|
+
/** A runner key, PEM, that signs the run's record. */
|
|
296
|
+
runnerKey?: string;
|
|
297
|
+
/**
|
|
298
|
+
* The key came from `CHANT_RUNNER_KEY`, not `--key`: one `.chant/trust.json`
|
|
299
|
+
* does not list leaves the run unsigned with a warning, as `chant run` does,
|
|
300
|
+
* where `--key` refuses the run.
|
|
301
|
+
*/
|
|
302
|
+
runnerKeyFromEnv?: boolean;
|
|
303
|
+
/** The run id. Default `chant-<epoch ms>`. */
|
|
304
|
+
runId?: string;
|
|
305
|
+
/** Where the run's events go as they happen (`--events-json`). They are kept on this host either way. */
|
|
306
|
+
runEvents?: (line: string) => void;
|
|
307
|
+
/** Where each line a step prints goes. */
|
|
308
|
+
stepOutput?: (line: string) => void;
|
|
309
|
+
/** Push `chant/lifecycle` once the record is written. Default true. */
|
|
310
|
+
push?: boolean;
|
|
311
|
+
/**
|
|
312
|
+
* The apply's resume record on this host (#4095), in place of the forge's
|
|
313
|
+
* CI cache: copied into the checkout at `PR_APPLY_RECORD` before the job,
|
|
314
|
+
* when it exists, and the job's record copied back to it after, passed or
|
|
315
|
+
* failed. `chant ci serve` names one per `prApplyRecordKey`.
|
|
316
|
+
*/
|
|
317
|
+
resumeRecord?: string;
|
|
318
|
+
signal?: AbortSignal;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export interface CiRunResult {
|
|
322
|
+
op: string;
|
|
323
|
+
/** The executor's outcome: `ok` or `fail` for a job. */
|
|
324
|
+
status: OpRunResult["status"];
|
|
325
|
+
record: OpRunRecord;
|
|
326
|
+
/** The step that failed and why, for a failed run. */
|
|
327
|
+
failure?: { phase: string; error: string };
|
|
328
|
+
/** Where the job's report was copied, or null when it wrote none. */
|
|
329
|
+
report: string | null;
|
|
330
|
+
/** The runner the record was signed as, or null. */
|
|
331
|
+
signer: string | null;
|
|
332
|
+
pushed: boolean;
|
|
333
|
+
pushWarning?: string;
|
|
334
|
+
/** What went wrong beside the job, such as a record the key could not sign. */
|
|
335
|
+
warnings: string[];
|
|
336
|
+
/** The checkout the job ran in, removed by now. */
|
|
337
|
+
checkout: string;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const message = (err: unknown): string => (err instanceof Error ? err.message : String(err));
|
|
341
|
+
|
|
342
|
+
function git(cwd: string, args: string[]): string {
|
|
343
|
+
return execFileSync("git", args, { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function tryGit(cwd: string, args: string[]): string | undefined {
|
|
347
|
+
try {
|
|
348
|
+
return git(cwd, args);
|
|
349
|
+
} catch {
|
|
350
|
+
return undefined;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/** The full commit `rev` names, fetched from `origin` when the repository lacks it. */
|
|
355
|
+
function resolveJobCommit(top: string, rev: string): string {
|
|
356
|
+
if (rev.startsWith("-")) throw new CiRunError(`--commit takes a commit, not ${JSON.stringify(rev)}`);
|
|
357
|
+
const local = tryGit(top, ["rev-parse", "--verify", "--quiet", `${rev}^{commit}`]);
|
|
358
|
+
if (local) return local;
|
|
359
|
+
if (tryGit(top, ["fetch", "--quiet", "--no-tags", "origin", rev]) !== undefined) {
|
|
360
|
+
const fetched = tryGit(top, ["rev-parse", "--verify", "--quiet", "FETCH_HEAD^{commit}"]);
|
|
361
|
+
if (fetched && (fetched === rev || fetched.startsWith(rev))) return fetched;
|
|
362
|
+
}
|
|
363
|
+
throw new CiRunError(`${rev} names no commit here or on origin`);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/** The member a job runs for, and its directory from the repository root, as the generator decides it. */
|
|
367
|
+
export function ciJobPlace(cwd: string, top: string): { member?: string; dir: string } {
|
|
368
|
+
const found = findWorkspaceRoot(cwd);
|
|
369
|
+
const member = found ? resolveMemberContext(cwd, found) : undefined;
|
|
370
|
+
const at = member ? member.memberRoot : cwd;
|
|
371
|
+
const dir = relative(top, realpathSync(at)).split(sep).join("/");
|
|
372
|
+
return { ...(member ? { member: member.member.name } : {}), dir: dir === "" ? "." : dir };
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/** A detached worktree of `commit` in a new temporary directory, and how to remove it. */
|
|
376
|
+
function freshCheckout(top: string, commit: string): { dir: string; remove(): void } {
|
|
377
|
+
const holder = mkdtempSync(join(realpathSync(tmpdir()), "chant-ci-run-"));
|
|
378
|
+
const dir = join(holder, "checkout");
|
|
379
|
+
try {
|
|
380
|
+
git(top, ["worktree", "add", "--quiet", "--detach", dir, commit]);
|
|
381
|
+
} catch (err) {
|
|
382
|
+
rmSync(holder, { recursive: true, force: true });
|
|
383
|
+
const stderr = (err as { stderr?: Buffer | string }).stderr;
|
|
384
|
+
throw new CiRunError(`could not check out ${commit.slice(0, 12)} in a fresh worktree: ${String(stderr ?? err).trim()}`);
|
|
385
|
+
}
|
|
386
|
+
return {
|
|
387
|
+
dir,
|
|
388
|
+
remove() {
|
|
389
|
+
tryGit(top, ["worktree", "remove", "--force", dir]);
|
|
390
|
+
rmSync(holder, { recursive: true, force: true });
|
|
391
|
+
tryGit(top, ["worktree", "prune"]);
|
|
392
|
+
},
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const ACTIVITIES = new Map<string, ActivityFn>([["shellCmd", shellCmd as unknown as ActivityFn]]);
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Run one pull-request job as a pipeline Op in a fresh checkout of the
|
|
400
|
+
* commit, record it, and copy its report out. Throws {@link CiRunError}
|
|
401
|
+
* before anything runs when the request can't be run.
|
|
402
|
+
*/
|
|
403
|
+
export async function ciRun(req: CiRunRequest): Promise<CiRunResult> {
|
|
404
|
+
const hostEnv = req.hostEnv ?? process.env;
|
|
405
|
+
const cwd = realpathSync(resolve(req.cwd));
|
|
406
|
+
const top = gitTop(cwd);
|
|
407
|
+
if (!top) throw new CiRunError("a job runs in a fresh checkout of a commit, and this directory is not in a git repository");
|
|
408
|
+
const commit = resolveJobCommit(top, req.commit ?? "HEAD");
|
|
409
|
+
const place = ciJobPlace(cwd, top);
|
|
410
|
+
const jobs = prLoopJobs(req.forge, req.env, req.loop ?? {}, place.member);
|
|
411
|
+
const job = jobs.find((j) => j.jobName === req.job);
|
|
412
|
+
if (!job) throw new CiRunError(`the pull-request pipeline has no ${req.job} job; it has ${jobs.map((j) => j.jobName).join(" and ")}`);
|
|
413
|
+
const jobEnv = ciJobEnv(job, { forge: req.forge, env: req.env, values: { base: req.base, pr: req.pr, commit }, hostEnv, passEnv: req.passEnv });
|
|
414
|
+
|
|
415
|
+
// The record is signed against .chant/trust.json at the commit the job runs, so a key that commit does not list is refused before anything runs.
|
|
416
|
+
let signer: ReturnType<typeof ciTagSigner> | undefined;
|
|
417
|
+
const early: string[] = [];
|
|
418
|
+
if (req.runnerKey !== undefined) {
|
|
419
|
+
try {
|
|
420
|
+
signer = ciTagSigner(top, commit, req.runnerKey, "the runs it signed");
|
|
421
|
+
} catch (err) {
|
|
422
|
+
if (!req.runnerKeyFromEnv) throw new CiRunError(`the run would not be signed: ${message(err)}`);
|
|
423
|
+
early.push(`${RUNNER_KEY_ENV} is set, and the run will not be signed: ${message(err)}`);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const checkout = freshCheckout(top, commit);
|
|
428
|
+
try {
|
|
429
|
+
const jobDir = place.dir === "." ? checkout.dir : join(checkout.dir, place.dir);
|
|
430
|
+
const op = ciJobOp({
|
|
431
|
+
job,
|
|
432
|
+
env: req.env,
|
|
433
|
+
cwd: jobDir,
|
|
434
|
+
jobEnv,
|
|
435
|
+
setup: prLoopSetup(req.image ?? PR_LOOP_IMAGE),
|
|
436
|
+
build: [...(req.beforeScript ?? []), ...(job.build ?? [])],
|
|
437
|
+
...(req.chant ? { chant: req.chant } : {}),
|
|
438
|
+
});
|
|
439
|
+
const runId = req.runId ?? `chant-${Date.now()}`;
|
|
440
|
+
const warnings: string[] = [...early];
|
|
441
|
+
const resume = req.job === "apply" && req.resumeRecord ? { host: resolve(req.resumeRecord), job: join(jobDir, PR_APPLY_RECORD) } : undefined;
|
|
442
|
+
if (resume && existsSync(resume.host)) {
|
|
443
|
+
mkdirSync(dirname(resume.job), { recursive: true });
|
|
444
|
+
copyFileSync(resume.host, resume.job);
|
|
445
|
+
}
|
|
446
|
+
let ledgerError: string | undefined;
|
|
447
|
+
let result: OpRunResult;
|
|
448
|
+
try {
|
|
449
|
+
result = await runOpLocally(op, ACTIVITIES, ACTIVITY_PROFILES as unknown as Record<string, ActivityProfile>, req.signal, {
|
|
450
|
+
runId,
|
|
451
|
+
env: req.env,
|
|
452
|
+
cwd: jobDir,
|
|
453
|
+
commit,
|
|
454
|
+
ledger: { cwd: jobDir },
|
|
455
|
+
runner: { kind: "pipeline", runtime: "ci", runOrigin: CHANT_RUNNER_ORIGIN },
|
|
456
|
+
describeFailure: (records) => ciJobFailure({ job: req.job, op, records, reportDir: join(jobDir, PR_LOOP_REPORT_DIR) }),
|
|
457
|
+
...(req.runEvents ? { runEvents: { write: req.runEvents } } : {}),
|
|
458
|
+
...(req.stepOutput ? { stepOutput: req.stepOutput } : {}),
|
|
459
|
+
...(signer ? { signRecord: (record: OpRunRecord) => signOpRunRecord(record, signer!) } : {}),
|
|
460
|
+
onSignError: (err) => warnings.push(`the run was not signed: ${message(err)}`),
|
|
461
|
+
onLedgerError: (err) => {
|
|
462
|
+
ledgerError = `the job ran, and its record could not be written to chant/lifecycle: ${message(err)}`;
|
|
463
|
+
},
|
|
464
|
+
});
|
|
465
|
+
} catch (err) {
|
|
466
|
+
if (!(err instanceof OpRunFailure)) throw err;
|
|
467
|
+
result = err.result;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (resume && existsSync(resume.job)) {
|
|
471
|
+
try {
|
|
472
|
+
mkdirSync(dirname(resume.host), { recursive: true });
|
|
473
|
+
copyFileSync(resume.job, resume.host);
|
|
474
|
+
} catch (err) {
|
|
475
|
+
warnings.push(`the resume record could not be kept at ${resume.host}: ${message(err)}`);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
const written = join(jobDir, PR_LOOP_REPORT_DIR);
|
|
479
|
+
let report: string | null = null;
|
|
480
|
+
if (existsSync(written)) {
|
|
481
|
+
report = resolve(cwd, req.output ?? PR_LOOP_REPORT_DIR);
|
|
482
|
+
cpSync(written, report, { recursive: true });
|
|
483
|
+
}
|
|
484
|
+
if (ledgerError) throw new CiRunError(ledgerError);
|
|
485
|
+
const pushed: { pushed: boolean; pushWarning?: string } = req.push === false ? { pushed: false, pushWarning: "chant/lifecycle was not pushed" } : await pushLifecycleReport({ cwd: top });
|
|
486
|
+
const failed = result.records.find((r) => r.status === "fail");
|
|
487
|
+
return {
|
|
488
|
+
op: op.name,
|
|
489
|
+
status: result.status,
|
|
490
|
+
record: result.record,
|
|
491
|
+
...(failed ? { failure: { phase: failed.phase, error: failed.error ?? "the step failed" } } : {}),
|
|
492
|
+
report,
|
|
493
|
+
signer: signer?.principal ?? null,
|
|
494
|
+
pushed: pushed.pushed,
|
|
495
|
+
...(pushed.pushWarning ? { pushWarning: pushed.pushWarning } : {}),
|
|
496
|
+
warnings,
|
|
497
|
+
checkout: checkout.dir,
|
|
498
|
+
};
|
|
499
|
+
} finally {
|
|
500
|
+
checkout.remove();
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/** The text `chant ci run` prints when the run settles. */
|
|
505
|
+
export function formatCiRun(r: CiRunResult): string {
|
|
506
|
+
const lines = [`${r.op} ${r.status === "ok" ? "passed" : r.status === "fail" ? "failed" : r.status} at ${r.record.gitSha?.slice(0, 12) ?? "an unknown commit"}, run ${r.record.id} in ${r.record.env}`];
|
|
507
|
+
const why = r.record.failure;
|
|
508
|
+
if (why) lines.push(` failure: ${why.code}${why.phase ? ` in ${why.phase}` : ""}${why.exitCode !== undefined ? `, exit ${why.exitCode}` : ""}: ${why.reason}`);
|
|
509
|
+
else if (r.failure) lines.push(` ${r.failure.phase}: ${r.failure.error}`);
|
|
510
|
+
lines.push(` record: ${r.record.env}/runs__${r.op}.jsonl on chant/lifecycle, ${r.signer ? `signed by ${r.signer}` : "unsigned, so ci tick from runs does not count it"}`);
|
|
511
|
+
if (r.report) lines.push(` report: ${r.report}`);
|
|
512
|
+
if (!r.pushed) lines.push(` ${r.pushWarning ?? "chant/lifecycle was not pushed"}`);
|
|
513
|
+
return lines.join("\n");
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// ── The command ──────────────────────────────────────────────────────────────
|
|
517
|
+
|
|
518
|
+
const USAGE =
|
|
519
|
+
"chant ci run plan|apply --env <env> --base <sha> [--pr <n>] [--commit <sha>] [--forge github|gitlab|forgejo] " +
|
|
520
|
+
"[--gate <name>] [--branch <name>] [--events-json] [--report-url <url>] [--image <image>] [--before-script <line>]... " +
|
|
521
|
+
"[--pr-build] [--pr-build-command <line>] " +
|
|
522
|
+
"[--chant <command>] [--pass-env <NAME>]... [--key <runner key.pem>] [-o <dir>]";
|
|
523
|
+
|
|
524
|
+
const ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
525
|
+
|
|
526
|
+
/** `chant ci run plan|apply` (#4094). */
|
|
527
|
+
export async function runCiRun(ctx: CommandContext): Promise<number> {
|
|
528
|
+
const { args } = ctx;
|
|
529
|
+
try {
|
|
530
|
+
const job = args.extraPositional;
|
|
531
|
+
if (job !== "plan" && job !== "apply") throw new CiRunError(`ci run takes the job to run, plan or apply${job ? `, not ${JSON.stringify(job)}` : ""}`);
|
|
532
|
+
if (!args.env) throw new CiRunError("--env names the environment the pipeline is for, as chant build --components --generate <lexicon> --pr-loop took it: --env <env>");
|
|
533
|
+
const forge = args.forge ?? detectForge(process.env);
|
|
534
|
+
if (!forge) throw new CiRunError("--forge names the forge the job posts to: --forge github|gitlab|forgejo");
|
|
535
|
+
if (!(FORGE_KINDS as readonly string[]).includes(forge)) throw new CiRunError(`--forge takes github, gitlab or forgejo, not ${JSON.stringify(forge)}`);
|
|
536
|
+
for (const name of args.passEnv ?? []) if (!ENV_NAME.test(name)) throw new CiRunError(`--pass-env takes a variable's name, not ${JSON.stringify(name)}`);
|
|
537
|
+
const fromFlag = args.key !== undefined;
|
|
538
|
+
const runnerKey = fromFlag ? readFileSync(resolve(args.key!), "utf-8") : process.env[RUNNER_KEY_ENV] || undefined;
|
|
539
|
+
const result = await ciRun({
|
|
540
|
+
cwd: process.cwd(),
|
|
541
|
+
job,
|
|
542
|
+
env: args.env,
|
|
543
|
+
forge: forge as ForgeKind,
|
|
544
|
+
...(args.base ? { base: args.base } : {}),
|
|
545
|
+
...(args.pr !== undefined ? { pr: args.pr } : {}),
|
|
546
|
+
...(args.commit ? { commit: args.commit } : {}),
|
|
547
|
+
loop: {
|
|
548
|
+
...(args.gate ? { gate: args.gate } : {}),
|
|
549
|
+
...(args.branch ? { branch: args.branch } : {}),
|
|
550
|
+
...(args.eventsJson ? { events: true } : {}),
|
|
551
|
+
...(args.reportUrl ? { reportUrl: args.reportUrl } : {}),
|
|
552
|
+
...(args.prBuild ? { build: true } : {}),
|
|
553
|
+
...(args.prBuildCommand ? { buildCommand: args.prBuildCommand } : {}),
|
|
554
|
+
},
|
|
555
|
+
...(args.image ? { image: args.image } : {}),
|
|
556
|
+
...(args.beforeScript?.length ? { beforeScript: args.beforeScript } : {}),
|
|
557
|
+
...(args.ciChant ? { chant: args.ciChant } : {}),
|
|
558
|
+
...(args.passEnv?.length ? { passEnv: args.passEnv } : {}),
|
|
559
|
+
...(args.output ? { output: args.output } : {}),
|
|
560
|
+
...(runnerKey !== undefined ? { runnerKey, runnerKeyFromEnv: !fromFlag } : {}),
|
|
561
|
+
...(args.eventsJson ? { runEvents: (line: string) => void process.stdout.write(line) } : {}),
|
|
562
|
+
stepOutput: (line) => void process.stderr.write(`${line}\n`),
|
|
563
|
+
});
|
|
564
|
+
for (const w of result.warnings) console.error(formatWarning({ message: w }));
|
|
565
|
+
console.error(formatCiRun(result));
|
|
566
|
+
return result.status === "ok" ? 0 : 1;
|
|
567
|
+
} catch (err) {
|
|
568
|
+
console.error(formatError({ message: `chant ci run: ${message(err)}`, hint: USAGE }));
|
|
569
|
+
return 1;
|
|
570
|
+
}
|
|
571
|
+
}
|