@intentius/chant 0.124.31 → 0.124.33
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/bin/chant +7 -2
- package/dist/ci-jobs.d.ts +4 -0
- package/dist/ci-jobs.d.ts.map +1 -1
- package/dist/ci-jobs.js +9 -2
- package/dist/cli/commands/check-lexicon-plugin.d.ts +7 -4
- package/dist/cli/commands/check-lexicon-plugin.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon-plugin.js +10 -7
- package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon.js +18 -12
- package/dist/cli/commands/import.d.ts +10 -0
- package/dist/cli/commands/import.d.ts.map +1 -1
- package/dist/cli/commands/import.js +37 -0
- package/dist/cli/commands/init-lexicon/templates/project.d.ts.map +1 -1
- package/dist/cli/commands/init-lexicon/templates/project.js +4 -3
- package/dist/cli/commands/onboard.d.ts.map +1 -1
- package/dist/cli/commands/onboard.js +8 -4
- package/dist/cli/commands/update.d.ts.map +1 -1
- package/dist/cli/commands/update.js +2 -1
- package/dist/cli/dependencies.d.ts +18 -0
- package/dist/cli/dependencies.d.ts.map +1 -0
- package/dist/cli/dependencies.js +120 -0
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/build.js +38 -4
- package/dist/cli/handlers/fan-out.d.ts.map +1 -1
- package/dist/cli/handlers/fan-out.js +12 -4
- package/dist/cli/handlers/graph.d.ts.map +1 -1
- package/dist/cli/handlers/graph.js +16 -6
- package/dist/cli/handlers/lifecycle-health.d.ts +16 -0
- package/dist/cli/handlers/lifecycle-health.d.ts.map +1 -0
- package/dist/cli/handlers/lifecycle-health.js +150 -0
- package/dist/cli/handlers/lifecycle.d.ts +26 -0
- package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
- package/dist/cli/handlers/lifecycle.js +1 -1
- package/dist/cli/handlers/lint.d.ts.map +1 -1
- package/dist/cli/handlers/lint.js +6 -0
- package/dist/cli/handlers/operator.d.ts +2 -2
- package/dist/cli/handlers/operator.js +3 -3
- package/dist/cli/handlers/pr.d.ts +16 -0
- package/dist/cli/handlers/pr.d.ts.map +1 -1
- package/dist/cli/handlers/pr.js +610 -32
- package/dist/cli/handlers/promote.d.ts.map +1 -1
- package/dist/cli/handlers/promote.js +19 -2
- package/dist/cli/handlers/release.d.ts +16 -1
- package/dist/cli/handlers/release.d.ts.map +1 -1
- package/dist/cli/handlers/release.js +83 -11
- package/dist/cli/handlers/run.d.ts +13 -1
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/handlers/run.js +38 -15
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +121 -33
- package/dist/cli/parse-args.d.ts.map +1 -1
- package/dist/cli/parse-args.js +37 -0
- package/dist/cli/registry.d.ts +29 -6
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/cli/registry.js +6 -2
- package/dist/codegen/generate-post-synth-barrel.js +2 -2
- package/dist/codegen/lexicon-regen.d.ts +4 -0
- package/dist/codegen/lexicon-regen.d.ts.map +1 -1
- package/dist/codegen/lexicon-regen.js +1 -1
- package/dist/codegen/pinned-upgrade.js +2 -2
- package/dist/components/capability.d.ts +44 -3
- package/dist/components/capability.d.ts.map +1 -1
- package/dist/components/capability.js +21 -0
- package/dist/components/changed-files.d.ts +7 -1
- package/dist/components/changed-files.d.ts.map +1 -1
- package/dist/components/changed-files.js +9 -1
- package/dist/components/deploy-inputs.d.ts +91 -0
- package/dist/components/deploy-inputs.d.ts.map +1 -0
- package/dist/components/deploy-inputs.js +184 -0
- package/dist/components/driver.d.ts +38 -0
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/driver.js +56 -6
- package/dist/components/fan-out-record.d.ts +6 -1
- package/dist/components/fan-out-record.d.ts.map +1 -1
- package/dist/components/fan-out-record.js +24 -0
- package/dist/components/fan-out-run.d.ts +20 -0
- package/dist/components/fan-out-run.d.ts.map +1 -1
- package/dist/components/fan-out-run.js +21 -1
- package/dist/components/gate-plan.d.ts +8 -0
- package/dist/components/gate-plan.d.ts.map +1 -1
- package/dist/components/gate-plan.js +6 -0
- package/dist/components/pr-pipeline.d.ts +98 -12
- package/dist/components/pr-pipeline.d.ts.map +1 -1
- package/dist/components/pr-pipeline.js +146 -23
- package/dist/components/pr-run.d.ts +128 -30
- package/dist/components/pr-run.d.ts.map +1 -1
- package/dist/components/pr-run.js +290 -70
- package/dist/components/release-compile.d.ts +37 -2
- package/dist/components/release-compile.d.ts.map +1 -1
- package/dist/components/release-compile.js +64 -8
- package/dist/components/release-promote.d.ts +17 -3
- package/dist/components/release-promote.d.ts.map +1 -1
- package/dist/components/release-promote.js +79 -18
- package/dist/components/verbs/bom-writer.d.ts +1 -1
- package/dist/components/verbs/bom-writer.d.ts.map +1 -1
- package/dist/components/verbs/lockfile-sbom-generator.d.ts +20 -0
- package/dist/components/verbs/lockfile-sbom-generator.d.ts.map +1 -1
- package/dist/components/verbs/lockfile-sbom-generator.js +41 -4
- package/dist/components/wave-plan.d.ts +15 -2
- package/dist/components/wave-plan.d.ts.map +1 -1
- package/dist/components/wave-plan.js +44 -5
- package/dist/discovery/fold-import.d.ts.map +1 -1
- package/dist/discovery/fold-import.js +16 -6
- package/dist/discovery/sandbox/bundle.d.ts.map +1 -1
- package/dist/discovery/sandbox/bundle.js +6 -0
- package/dist/discovery/specifier-resolve.d.ts.map +1 -1
- package/dist/discovery/specifier-resolve.js +8 -2
- package/dist/fold/fold.d.ts.map +1 -1
- package/dist/fold/fold.js +68 -1
- package/dist/fold/subset.d.ts +37 -0
- package/dist/fold/subset.d.ts.map +1 -1
- package/dist/fold/subset.js +88 -0
- package/dist/graph-detail.d.ts.map +1 -1
- package/dist/graph-detail.js +32 -7
- package/dist/health.d.ts +110 -0
- package/dist/health.d.ts.map +1 -0
- package/dist/health.js +105 -0
- package/dist/health.schema.json +191 -0
- package/dist/lexicon-module.d.ts +10 -0
- package/dist/lexicon-module.d.ts.map +1 -1
- package/dist/lexicon-module.js +53 -8
- package/dist/lexicon-plugin-helpers.d.ts.map +1 -1
- package/dist/lexicon-plugin-helpers.js +6 -4
- package/dist/lexicon.d.ts +29 -0
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/affected.d.ts.map +1 -1
- package/dist/lifecycle/affected.js +7 -1
- package/dist/lifecycle/merge-record.d.ts +104 -0
- package/dist/lifecycle/merge-record.d.ts.map +1 -0
- package/dist/lifecycle/merge-record.js +102 -0
- package/dist/lifecycle/publish-attachments.d.ts +8 -5
- package/dist/lifecycle/publish-attachments.d.ts.map +1 -1
- package/dist/lifecycle/publish-attachments.js +27 -13
- package/dist/lifecycle/publish-npm.d.ts +3 -2
- package/dist/lifecycle/publish-npm.d.ts.map +1 -1
- package/dist/lifecycle/publish-npm.js +4 -3
- package/dist/lifecycle/release-at.d.ts +40 -0
- package/dist/lifecycle/release-at.d.ts.map +1 -0
- package/dist/lifecycle/release-at.js +83 -0
- package/dist/lifecycle/release-cut.d.ts.map +1 -1
- package/dist/lifecycle/release-cut.js +23 -12
- package/dist/lifecycle/release-ledger.d.ts +21 -0
- package/dist/lifecycle/release-ledger.d.ts.map +1 -1
- package/dist/lifecycle/release-ledger.js +24 -0
- package/dist/lifecycle/release-publish.d.ts +28 -12
- package/dist/lifecycle/release-publish.d.ts.map +1 -1
- package/dist/lifecycle/release-publish.js +29 -17
- package/dist/lifecycle/release-store.d.ts +25 -2
- package/dist/lifecycle/release-store.d.ts.map +1 -1
- package/dist/lifecycle/release-store.js +40 -13
- package/dist/lifecycle/run-ledger.d.ts.map +1 -1
- package/dist/lifecycle/run-ledger.js +1 -0
- package/dist/lifecycle/work-dir.d.ts +45 -0
- package/dist/lifecycle/work-dir.d.ts.map +1 -0
- package/dist/lifecycle/work-dir.js +78 -0
- package/dist/lint/discover.d.ts +17 -9
- package/dist/lint/discover.d.ts.map +1 -1
- package/dist/lint/discover.js +62 -54
- package/dist/lint/rules/op/index.d.ts +2 -1
- package/dist/lint/rules/op/index.d.ts.map +1 -1
- package/dist/lint/rules/op/index.js +4 -2
- package/dist/lint/rules/op/ops018-shell-image-digest.d.ts +10 -0
- package/dist/lint/rules/op/ops018-shell-image-digest.d.ts.map +1 -0
- package/dist/lint/rules/op/ops018-shell-image-digest.js +38 -0
- package/dist/module-source.d.ts +57 -0
- package/dist/module-source.d.ts.map +1 -0
- package/dist/module-source.js +104 -0
- package/dist/op/activities/activity-contracts.d.ts +5 -0
- package/dist/op/activities/activity-contracts.d.ts.map +1 -1
- package/dist/op/activities/activity-contracts.js +3 -0
- package/dist/op/activities/factory.js +1 -1
- package/dist/op/activities/shell.d.ts +38 -0
- package/dist/op/activities/shell.d.ts.map +1 -1
- package/dist/op/activities/shell.js +89 -3
- package/dist/op/builders.d.ts +7 -0
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/builders.js +7 -0
- package/dist/op/discover.d.ts.map +1 -1
- package/dist/op/discover.js +13 -3
- package/dist/op/gate.d.ts.map +1 -1
- package/dist/op/gate.js +3 -0
- package/dist/op/op-waves-run.d.ts.map +1 -1
- package/dist/op/op-waves-run.js +20 -5
- package/dist/op/operator.d.ts +2 -2
- package/dist/op/operator.js +2 -2
- package/dist/op/runtime.d.ts +2 -0
- package/dist/op/runtime.d.ts.map +1 -1
- package/dist/op/steward.d.ts +6 -4
- package/dist/op/steward.d.ts.map +1 -1
- package/dist/op/steward.js +5 -3
- package/dist/op/work-lease-run.d.ts +20 -7
- package/dist/op/work-lease-run.d.ts.map +1 -1
- package/dist/op/work-lease-run.js +56 -12
- package/dist/pr-forge.d.ts +10 -0
- package/dist/pr-forge.d.ts.map +1 -1
- package/dist/pr-forge.js +39 -0
- package/dist/pr-loop.d.ts +155 -7
- package/dist/pr-loop.d.ts.map +1 -1
- package/dist/pr-loop.js +137 -11
- package/dist/pr-report-url.d.ts +4 -4
- package/dist/pr-report-url.d.ts.map +1 -1
- package/dist/pr-report-url.js +5 -5
- package/dist/runner-start.d.ts +179 -0
- package/dist/runner-start.d.ts.map +1 -0
- package/dist/runner-start.js +277 -0
- package/dist/ts-loader.d.ts +38 -4
- package/dist/ts-loader.d.ts.map +1 -1
- package/dist/ts-loader.js +190 -16
- package/dist/wave-runtime.mjs +238 -231
- package/dist/workspace/chant-migrations/fountain-retired-kinds.d.ts +34 -0
- package/dist/workspace/chant-migrations/fountain-retired-kinds.d.ts.map +1 -0
- package/dist/workspace/chant-migrations/fountain-retired-kinds.js +176 -0
- package/dist/workspace/chant-migrations.d.ts.map +1 -1
- package/dist/workspace/chant-migrations.js +2 -0
- package/dist/workspace/check.schema.json +70 -2
- package/dist/workspace/checks/boxes.d.ts +6 -6
- package/dist/workspace/checks/boxes.js +9 -9
- package/dist/workspace/checks/security.d.ts +35 -0
- package/dist/workspace/checks/security.d.ts.map +1 -0
- package/dist/workspace/checks/security.js +305 -0
- package/dist/workspace/checks.d.ts +2 -0
- package/dist/workspace/checks.d.ts.map +1 -1
- package/dist/workspace/checks.js +5 -0
- package/dist/workspace/ci-builds.d.ts +163 -0
- package/dist/workspace/ci-builds.d.ts.map +1 -0
- package/dist/workspace/ci-builds.js +315 -0
- package/dist/workspace/ci-green-cli.d.ts +2 -1
- package/dist/workspace/ci-green-cli.d.ts.map +1 -1
- package/dist/workspace/ci-green-cli.js +25 -5
- package/dist/workspace/ci-green-forge.d.ts +18 -4
- package/dist/workspace/ci-green-forge.d.ts.map +1 -1
- package/dist/workspace/ci-green-forge.js +12 -0
- package/dist/workspace/ci-green-workflow.d.ts +55 -1
- package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
- package/dist/workspace/ci-green-workflow.js +128 -1
- package/dist/workspace/ci-green.d.ts +117 -16
- package/dist/workspace/ci-green.d.ts.map +1 -1
- package/dist/workspace/ci-green.js +358 -43
- package/dist/workspace/ci-last-green.schema.json +49 -0
- package/dist/workspace/ci-list.schema.json +47 -7
- package/dist/workspace/ci-report-url.d.ts +18 -0
- package/dist/workspace/ci-report-url.d.ts.map +1 -0
- package/dist/workspace/ci-report-url.js +33 -0
- package/dist/workspace/ci-run-index.d.ts +13 -1
- package/dist/workspace/ci-run-index.d.ts.map +1 -1
- package/dist/workspace/ci-run-index.js +6 -4
- package/dist/workspace/ci-run-index.schema.json +6 -4
- package/dist/workspace/ci-run.d.ts +85 -6
- package/dist/workspace/ci-run.d.ts.map +1 -1
- package/dist/workspace/ci-run.js +262 -64
- package/dist/workspace/ci-runner-key.d.ts +16 -3
- package/dist/workspace/ci-runner-key.d.ts.map +1 -1
- package/dist/workspace/ci-runner-key.js +30 -10
- package/dist/workspace/ci-runner-start.d.ts +77 -0
- package/dist/workspace/ci-runner-start.d.ts.map +1 -0
- package/dist/workspace/ci-runner-start.js +185 -0
- package/dist/workspace/ci-serve.d.ts +130 -11
- package/dist/workspace/ci-serve.d.ts.map +1 -1
- package/dist/workspace/ci-serve.js +323 -40
- package/dist/workspace/declaration.d.ts +25 -0
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.js +14 -2
- package/dist/workspace/declaration.schema.json +30 -2
- package/dist/workspace/export.d.ts +1 -1
- package/dist/workspace/export.d.ts.map +1 -1
- package/dist/workspace/export.js +7 -3
- package/dist/workspace/ls.d.ts +8 -1
- package/dist/workspace/ls.d.ts.map +1 -1
- package/dist/workspace/ls.js +2 -1
- package/dist/workspace/ls.schema.json +9 -2
- package/dist/workspace/member-pipeline.d.ts +1 -0
- package/dist/workspace/member-pipeline.d.ts.map +1 -1
- package/dist/workspace/member-pipeline.js +2 -0
- package/dist/workspace/merge-record.schema.json +49 -0
- package/dist/workspace/pin-integrity.d.ts.map +1 -1
- package/dist/workspace/pin-integrity.js +3 -2
- package/dist/workspace/pr-report.schema.json +75 -8
- package/dist/workspace/reason-codes.d.ts +18 -1
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/reason-codes.js +20 -2
- package/dist/workspace/release-candidates.d.ts +129 -0
- package/dist/workspace/release-candidates.d.ts.map +1 -0
- package/dist/workspace/release-candidates.js +233 -0
- package/dist/workspace/release-candidates.schema.json +353 -0
- package/dist/workspace/run-event.schema.json +3 -1
- package/dist/workspace/security.d.ts +40 -0
- package/dist/workspace/security.d.ts.map +1 -0
- package/dist/workspace/security.js +99 -0
- package/dist/workspace/status.schema.json +3 -1
- package/dist/workspace/write-scope.d.ts +9 -0
- package/dist/workspace/write-scope.d.ts.map +1 -1
- package/dist/workspace/write-scope.js +11 -1
- package/package.json +8 -1
- package/src/__fixtures__/pr-loop/pr-apply-wave-waiting.note.golden.md +48 -0
- package/src/__fixtures__/pr-loop/pr-plan.note.golden.md +1 -1
- package/src/ci-jobs.ts +11 -2
- package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +3 -3
- package/src/cli/commands/audit.test.ts +2 -2
- package/src/cli/commands/check-lexicon-plugin.test.ts +9 -0
- package/src/cli/commands/check-lexicon-plugin.ts +11 -8
- package/src/cli/commands/check-lexicon.test.ts +1 -1
- package/src/cli/commands/check-lexicon.ts +16 -11
- package/src/cli/commands/import.ts +40 -0
- package/src/cli/commands/init-lexicon/templates/project.ts +4 -3
- package/src/cli/commands/init-lexicon.test.ts +8 -5
- package/src/cli/commands/onboard.test.ts +45 -0
- package/src/cli/commands/onboard.ts +8 -4
- package/src/cli/commands/update.test.ts +6 -0
- package/src/cli/commands/update.ts +4 -1
- package/src/cli/compiled-cli.e2e.test.ts +76 -11
- package/src/cli/dependencies.e2e.test.ts +101 -0
- package/src/cli/dependencies.test.ts +93 -0
- package/src/cli/dependencies.ts +122 -0
- package/src/cli/handlers/build.test.ts +105 -0
- package/src/cli/handlers/build.ts +35 -4
- package/src/cli/handlers/fan-out.test.ts +73 -0
- package/src/cli/handlers/fan-out.ts +11 -4
- package/src/cli/handlers/graph.test.ts +76 -0
- package/src/cli/handlers/graph.ts +19 -12
- package/src/cli/handlers/lifecycle-health.test.ts +123 -0
- package/src/cli/handlers/lifecycle-health.ts +157 -0
- package/src/cli/handlers/lifecycle.ts +1 -1
- package/src/cli/handlers/lint.test.ts +42 -0
- package/src/cli/handlers/lint.ts +6 -0
- package/src/cli/handlers/operator.ts +3 -3
- package/src/cli/handlers/pr-deploy-inputs.test.ts +209 -0
- package/src/cli/handlers/pr-record.test.ts +201 -0
- package/src/cli/handlers/pr-waves.test.ts +449 -0
- package/src/cli/handlers/pr.test.ts +156 -0
- package/src/cli/handlers/pr.ts +648 -33
- package/src/cli/handlers/promote.test.ts +43 -1
- package/src/cli/handlers/promote.ts +19 -3
- package/src/cli/handlers/release-cut-at.e2e.test.ts +115 -0
- package/src/cli/handlers/release-publish-targets.e2e.test.ts +2 -2
- package/src/cli/handlers/release-verify.e2e.test.ts +12 -10
- package/src/cli/handlers/release.e2e.test.ts +19 -28
- package/src/cli/handlers/release.ts +81 -11
- package/src/cli/handlers/run-components-exit.test.ts +99 -0
- package/src/cli/handlers/run-signer.test.ts +79 -0
- package/src/cli/handlers/run.ts +34 -16
- package/src/cli/main.test.ts +18 -0
- package/src/cli/main.ts +120 -33
- package/src/cli/parse-args.ts +28 -0
- package/src/cli/registry.ts +35 -8
- package/src/codegen/generate-post-synth-barrel.ts +2 -2
- package/src/codegen/lexicon-regen.ts +1 -1
- package/src/codegen/pinned-upgrade.ts +2 -2
- package/src/codegen/post-synth-barrel-guards.test.ts +6 -11
- package/src/components/capability.ts +51 -3
- package/src/components/changed-files.test.ts +16 -1
- package/src/components/changed-files.ts +9 -1
- package/src/components/deploy-inputs.test.ts +108 -0
- package/src/components/deploy-inputs.ts +225 -0
- package/src/components/driver.test.ts +36 -1
- package/src/components/driver.ts +87 -4
- package/src/components/fan-out-record.ts +29 -1
- package/src/components/fan-out-run.test.ts +35 -0
- package/src/components/fan-out-run.ts +40 -1
- package/src/components/gate-plan.ts +9 -0
- package/src/components/pr-pipeline.test.ts +100 -0
- package/src/components/pr-pipeline.ts +184 -28
- package/src/components/pr-run.test.ts +323 -18
- package/src/components/pr-run.ts +355 -76
- package/src/components/release-compile.test.ts +34 -0
- package/src/components/release-compile.ts +89 -8
- package/src/components/release-promote.test.ts +55 -2
- package/src/components/release-promote.ts +93 -23
- package/src/components/verbs/bom-writer.ts +1 -1
- package/src/components/verbs/lockfile-sbom-generator.test.ts +36 -0
- package/src/components/verbs/lockfile-sbom-generator.ts +43 -5
- package/src/components/wave-plan.ts +48 -8
- package/src/discovery/fold-host-method.test.ts +87 -0
- package/src/discovery/fold-import.ts +15 -5
- package/src/discovery/fold-package-composite.test.ts +27 -0
- package/src/discovery/fold-specifiers.test.ts +8 -0
- package/src/discovery/sandbox/bundle.ts +6 -0
- package/src/discovery/specifier-resolve.ts +8 -2
- package/src/fold/fold.test.ts +72 -0
- package/src/fold/fold.ts +71 -0
- package/src/fold/subset.test.ts +31 -0
- package/src/fold/subset.ts +99 -0
- package/src/graph-detail.test.ts +57 -0
- package/src/graph-detail.ts +33 -7
- package/src/health.schema.json +191 -0
- package/src/health.test.ts +62 -0
- package/src/health.ts +178 -0
- package/src/lexicon-dist.e2e.test.ts +254 -0
- package/src/lexicon-module.test.ts +47 -0
- package/src/lexicon-module.ts +49 -7
- package/src/lexicon-plugin-helpers.ts +6 -4
- package/src/lexicon.ts +43 -1
- package/src/lifecycle/affected.test.ts +7 -0
- package/src/lifecycle/affected.ts +6 -1
- package/src/lifecycle/merge-record.test.ts +111 -0
- package/src/lifecycle/merge-record.ts +154 -0
- package/src/lifecycle/publish-attachments.ts +25 -13
- package/src/lifecycle/publish-npm.ts +4 -3
- package/src/lifecycle/publish-targets.test.ts +34 -4
- package/src/lifecycle/release-at.test.ts +48 -0
- package/src/lifecycle/release-at.ts +100 -0
- package/src/lifecycle/release-cut.test.ts +41 -0
- package/src/lifecycle/release-cut.ts +22 -11
- package/src/lifecycle/release-ledger.ts +34 -0
- package/src/lifecycle/release-publish.test.ts +43 -13
- package/src/lifecycle/release-publish.ts +38 -18
- package/src/lifecycle/release-store.ts +57 -13
- package/src/lifecycle/run-ledger.test.ts +13 -0
- package/src/lifecycle/run-ledger.ts +1 -0
- package/src/lifecycle/work-dir.test.ts +77 -0
- package/src/lifecycle/work-dir.ts +82 -0
- package/src/lint/discover.test.ts +46 -2
- package/src/lint/discover.ts +55 -49
- package/src/lint/rules/op/index.ts +4 -2
- package/src/lint/rules/op/ops018-shell-image-digest.test.ts +60 -0
- package/src/lint/rules/op/ops018-shell-image-digest.ts +48 -0
- package/src/module-source.test.ts +85 -0
- package/src/module-source.ts +112 -0
- package/src/op/activities/activity-contracts.ts +3 -0
- package/src/op/activities/factory.ts +1 -1
- package/src/op/activities/shell-container.e2e.test.ts +67 -0
- package/src/op/activities/shell.test.ts +44 -1
- package/src/op/activities/shell.ts +110 -3
- package/src/op/builders.ts +7 -0
- package/src/op/discover-root.test.ts +31 -2
- package/src/op/discover.ts +14 -4
- package/src/op/gate.test.ts +30 -0
- package/src/op/gate.ts +3 -0
- package/src/op/op-waves-run.ts +20 -5
- package/src/op/op-waves.test.ts +49 -0
- package/src/op/operator.ts +2 -2
- package/src/op/runtime.ts +2 -0
- package/src/op/step-console.test.ts +4 -2
- package/src/op/steward.ts +6 -4
- package/src/op/work-lease-run.test.ts +88 -2
- package/src/op/work-lease-run.ts +52 -12
- package/src/pr-forge-bundle.test.ts +2 -2
- package/src/pr-forge.test.ts +42 -0
- package/src/pr-forge.ts +40 -0
- package/src/pr-loop-bundle.test.ts +2 -1
- package/src/pr-loop.test.ts +146 -1
- package/src/pr-loop.ts +278 -17
- package/src/pr-report-url.ts +7 -7
- package/src/runner-start-bundle.test.ts +29 -0
- package/src/runner-start.test.ts +179 -0
- package/src/runner-start.ts +329 -0
- package/src/terraform/__fixtures__/advise.test.ts +21 -2
- package/src/ts-loader.test.ts +89 -1
- package/src/ts-loader.ts +180 -13
- package/src/workspace/changes.test.ts +1 -1
- package/src/workspace/chant-migrations/fountain-retired-kinds.test.ts +33 -0
- package/src/workspace/chant-migrations/fountain-retired-kinds.ts +190 -0
- package/src/workspace/chant-migrations.e2e.test.ts +5 -0
- package/src/workspace/chant-migrations.ts +2 -0
- package/src/workspace/check-contract.test.ts +3 -2
- package/src/workspace/check.schema.json +70 -2
- package/src/workspace/checks/boxes.ts +9 -9
- package/src/workspace/checks/security.test.ts +138 -0
- package/src/workspace/checks/security.ts +305 -0
- package/src/workspace/checks.ts +5 -0
- package/src/workspace/ci-builds.e2e.test.ts +208 -0
- package/src/workspace/ci-builds.test.ts +174 -0
- package/src/workspace/ci-builds.ts +385 -0
- package/src/workspace/ci-green-builds.test.ts +180 -0
- package/src/workspace/ci-green-cli.test.ts +54 -0
- package/src/workspace/ci-green-cli.ts +26 -5
- package/src/workspace/ci-green-forge.ts +30 -4
- package/src/workspace/ci-green-runs.test.ts +53 -11
- package/src/workspace/ci-green-workflow.test.ts +84 -1
- package/src/workspace/ci-green-workflow.ts +139 -2
- package/src/workspace/ci-green.test.ts +6 -3
- package/src/workspace/ci-green.ts +408 -48
- package/src/workspace/ci-last-green.schema.json +49 -0
- package/src/workspace/ci-list.schema.json +47 -7
- package/src/workspace/ci-report-url.test.ts +220 -0
- package/src/workspace/ci-report-url.ts +33 -0
- package/src/workspace/ci-run-index.schema.json +6 -4
- package/src/workspace/ci-run-index.test.ts +42 -3
- package/src/workspace/ci-run-index.ts +19 -5
- package/src/workspace/ci-run.test.ts +203 -8
- package/src/workspace/ci-run.ts +295 -63
- package/src/workspace/ci-runner-key.test.ts +55 -17
- package/src/workspace/ci-runner-key.ts +36 -11
- package/src/workspace/ci-runner-start.test.ts +151 -0
- package/src/workspace/ci-runner-start.ts +193 -0
- package/src/workspace/ci-serve-builds.test.ts +86 -0
- package/src/workspace/ci-serve.test.ts +209 -6
- package/src/workspace/ci-serve.ts +341 -42
- package/src/workspace/declaration.schema.json +30 -2
- package/src/workspace/declaration.test.ts +40 -0
- package/src/workspace/declaration.ts +40 -3
- package/src/workspace/export.ts +6 -3
- package/src/workspace/ls.schema.json +9 -2
- package/src/workspace/ls.ts +10 -4
- package/src/workspace/member-pipeline.ts +2 -1
- package/src/workspace/merge-record.schema.json +49 -0
- package/src/workspace/pin-integrity.test.ts +5 -0
- package/src/workspace/pin-integrity.ts +3 -2
- package/src/workspace/pr-report.schema.json +75 -8
- package/src/workspace/reason-codes.test.ts +2 -0
- package/src/workspace/reason-codes.ts +20 -2
- package/src/workspace/release-candidates.schema.json +353 -0
- package/src/workspace/release-candidates.test.ts +106 -0
- package/src/workspace/release-candidates.ts +315 -0
- package/src/workspace/run-event.schema.json +3 -1
- package/src/workspace/security.test.ts +70 -0
- package/src/workspace/security.ts +118 -0
- package/src/workspace/status.schema.json +3 -1
- package/src/workspace/write-scope.test.ts +29 -1
- package/src/workspace/write-scope.ts +12 -1
package/dist/cli/handlers/pr.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* chant components pr-plan --base <ref> --pr <n> --env <env> [--forge github|gitlab|forgejo]
|
|
7
7
|
* chant approve pr-<n> pr-apply --plan <digest> --approver github:<login> --sign
|
|
8
8
|
* chant components pr-apply --base <ref> --env <env> [--pr <n>] [--forge <kind>] [--require-review] [--resume <file>]
|
|
9
|
+
* [--wave-gate <gate> [--wave <k>]]
|
|
9
10
|
* ```
|
|
10
11
|
*
|
|
11
12
|
* Both measure the change from the merge base of `--base` and `HEAD`, select
|
|
@@ -27,6 +28,13 @@
|
|
|
27
28
|
* approval still stands, applies the rest without a fresh approval. When
|
|
28
29
|
* they are not covered it says why and runs as it would without a record.
|
|
29
30
|
*
|
|
31
|
+
* `--wave-gate <gate>` (#4003) applies the approved set a wave at a time,
|
|
32
|
+
* each wave planned after the one before it applied (`prApplyWaves`). A
|
|
33
|
+
* wave whose plan the approved change set covers applies under the pull
|
|
34
|
+
* request's approval; one whose plan moved waits at `<gate>-wave-<k>` under
|
|
35
|
+
* the pull request's op (exit 3). `--wave <k>` runs one wave, reading the
|
|
36
|
+
* approved set and the waves that applied from the `--resume` record.
|
|
37
|
+
*
|
|
30
38
|
* Every run writes `pr-plan.json` or `pr-apply.json` (`pr-report.schema.json`),
|
|
31
39
|
* `change-set.json` and `pr-note.md` under `--output` (default `.chant/pr`). *
|
|
32
40
|
* `--events-json` (#4041, #4007) streams the stage's run events to stdout,
|
|
@@ -48,13 +56,21 @@
|
|
|
48
56
|
* recorded under `pr-<n>-<member>`, and the note and the statuses carry the
|
|
49
57
|
* member's name, so each member's loop on one pull request stands apart.
|
|
50
58
|
* The name must be the member that owns the current directory.
|
|
59
|
+
*
|
|
60
|
+
* `--no-status` (#4161) posts neither the stage's status nor its note: the
|
|
61
|
+
* report and the note are written under `--output` only. `chant ci run
|
|
62
|
+
* --local` passes it. pr-plan then builds no forge client, since posting is
|
|
63
|
+
* all it uses one for; pr-apply still reads the pull request and its reviews
|
|
64
|
+
* through `--forge`.
|
|
51
65
|
*/
|
|
52
66
|
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
53
67
|
import { join, resolve } from "node:path";
|
|
54
68
|
import { loadChantConfig, resolveAutoReleaseDisabled } from "../../config.js";
|
|
55
69
|
import { resolveCommit, resolveMergeBase } from "../../components/changed-files.js";
|
|
56
70
|
import { deriveFanOut, fanOutRegistry } from "../../components/fan-out-support.js";
|
|
57
|
-
import { applyPrSet, approvalOf, approversWithoutReview, decidePrGate, planPrSet, prReport, readOnlyLedger, resumePrSet, } from "../../components/pr-run.js";
|
|
71
|
+
import { applyPrSet, approvalOf, approversWhoAuthored, approversWithoutReview, decidePrGate, planPrSet, prReport, readOnlyLedger, prWavePlan, resumePrSet, waveNotCovered, } from "../../components/pr-run.js";
|
|
72
|
+
import { composeChangeSet, verifyChangeSetDigest } from "../../change-set.js";
|
|
73
|
+
import { waveGateName } from "../../gated-waves.js";
|
|
58
74
|
import { readFanOutAttempt, writeFanOutAttempt } from "../../components/fan-out-record.js";
|
|
59
75
|
import { componentInputDigests, progressThatCarries } from "../../components/fan-out-inputs.js";
|
|
60
76
|
import { samePlanDigest } from "../../lifecycle/plan-digest.js";
|
|
@@ -67,9 +83,11 @@ import { FORGE_KINDS, forgeFromEnv, forgePrincipalOf } from "../../pr-forge.js";
|
|
|
67
83
|
import { GITHUB_COMMENT_LIMIT, GITLAB_NOTE_LIMIT } from "../../plan-summary.js";
|
|
68
84
|
import { resolveCliBuildParams, parseParamFlags } from "../build-params-cli.js";
|
|
69
85
|
import { formatError, formatInfo, formatSuccess, formatWarning } from "../format.js";
|
|
86
|
+
import { reportUninstalledDependencies } from "../dependencies.js";
|
|
70
87
|
import { findWorkspaceRoot } from "../../project-root.js";
|
|
71
88
|
import { resolveChangedUnits } from "./fan-out.js";
|
|
72
89
|
import { GATED_EXIT_CODE, autoReleaseRun, recordAutoReleasesForRun } from "./run.js";
|
|
90
|
+
import { failedRunExitCode } from "../../components/driver.js";
|
|
73
91
|
/** Where the reports land unless `--output` says. */
|
|
74
92
|
export const PR_REPORT_DIR = ".chant/pr";
|
|
75
93
|
const fail = (message, hint) => {
|
|
@@ -107,7 +125,7 @@ async function prepare(ctx, stage) {
|
|
|
107
125
|
return fail(`--forge ${args.forge} is not one of ${FORGE_KINDS.join(", ")}`);
|
|
108
126
|
}
|
|
109
127
|
let forge;
|
|
110
|
-
if (args.forge) {
|
|
128
|
+
if (args.forge && !(stage === "plan" && args.noStatus)) {
|
|
111
129
|
try {
|
|
112
130
|
forge = forgeFromEnv(args.forge);
|
|
113
131
|
}
|
|
@@ -138,10 +156,16 @@ async function prepare(ctx, stage) {
|
|
|
138
156
|
});
|
|
139
157
|
}
|
|
140
158
|
catch (err) {
|
|
141
|
-
|
|
159
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
160
|
+
reportUninstalledDependencies([message], projectPath); // #4116
|
|
161
|
+
return fail(message);
|
|
142
162
|
}
|
|
143
|
-
if (!derived.success)
|
|
163
|
+
if (!derived.success) {
|
|
164
|
+
// #4116: component files that import packages the project has not installed.
|
|
165
|
+
if (derived.error)
|
|
166
|
+
reportUninstalledDependencies([derived.error], projectPath);
|
|
144
167
|
return fail(derived.error ?? "Could not discover components");
|
|
168
|
+
}
|
|
145
169
|
const seededOutputs = {};
|
|
146
170
|
for (const file of args.seedOutputs ?? []) {
|
|
147
171
|
try {
|
|
@@ -215,9 +239,11 @@ function settleOf(status) {
|
|
|
215
239
|
switch (status) {
|
|
216
240
|
case "nothing":
|
|
217
241
|
case "planned":
|
|
242
|
+
case "recorded":
|
|
218
243
|
case "applied":
|
|
219
244
|
return "ok";
|
|
220
245
|
case "refused":
|
|
246
|
+
case "waiting":
|
|
221
247
|
return "gated";
|
|
222
248
|
default:
|
|
223
249
|
return "failed";
|
|
@@ -281,7 +307,8 @@ function printMembers(report) {
|
|
|
281
307
|
async function finish(ctx, p, report, upload) {
|
|
282
308
|
const note = noteFor(report, p.forge);
|
|
283
309
|
writeOutputs(ctx, report, note, upload);
|
|
284
|
-
|
|
310
|
+
if (!ctx.args.noStatus)
|
|
311
|
+
await publish(p.forge, report, note);
|
|
285
312
|
if (ctx.args.json)
|
|
286
313
|
console.log(JSON.stringify(report, null, 2));
|
|
287
314
|
}
|
|
@@ -369,6 +396,7 @@ async function prPlan(ctx, stage) {
|
|
|
369
396
|
set,
|
|
370
397
|
approval,
|
|
371
398
|
status,
|
|
399
|
+
...(ctx.args.waveGate ? { waveGate: ctx.args.waveGate } : {}),
|
|
372
400
|
});
|
|
373
401
|
await finish(ctx, p, report, upload);
|
|
374
402
|
// The plan stage settles on its planning: an approval still to come is what it reports, not a stop.
|
|
@@ -388,9 +416,20 @@ export function runComponentsPrApply(ctx) {
|
|
|
388
416
|
return withStageEvents("pr-apply", ctx, (events) => prApply(ctx, events));
|
|
389
417
|
}
|
|
390
418
|
async function prApply(ctx, stage) {
|
|
419
|
+
// #4168: the review-rules security group turns --require-review on, read from the declaration at base.
|
|
420
|
+
const { securityGroupsAt } = await import("../../workspace/security.js");
|
|
421
|
+
const reviewRules = !ctx.args.requireReview && securityGroupsAt(process.cwd(), ctx.args.base).has("review-rules");
|
|
422
|
+
if (reviewRules && !ctx.args.forge)
|
|
423
|
+
return fail("the workspace opts into review-rules, which reads the pull request's reviews, so pr-apply needs --forge <kind>");
|
|
424
|
+
if (reviewRules)
|
|
425
|
+
ctx = { ...ctx, args: { ...ctx.args, requireReview: true } };
|
|
391
426
|
const { args } = ctx;
|
|
392
427
|
if (args.requireReview && !args.forge)
|
|
393
428
|
return fail("--require-review reads the pull request's reviews, so it needs --forge <kind>");
|
|
429
|
+
if (args.wave !== undefined && !args.waveGate)
|
|
430
|
+
return fail("--wave runs one wave of a --wave-gate apply, so it needs --wave-gate <name>");
|
|
431
|
+
if (args.wave !== undefined && args.wave < 1)
|
|
432
|
+
return fail(`--wave counts from 1, not ${args.wave}`);
|
|
394
433
|
const p = await prepare(ctx, "apply");
|
|
395
434
|
if (typeof p === "number")
|
|
396
435
|
return p;
|
|
@@ -411,6 +450,8 @@ async function prApply(ctx, stage) {
|
|
|
411
450
|
const events = await openCliRunEvents(args, { kind: "component", name: `pr-apply ${op}`, runId: release.runId, cwd: resolve(".") }, upload?.line);
|
|
412
451
|
stage.stream = events;
|
|
413
452
|
events?.emit({ type: "run-start", waves: p.plan.waves });
|
|
453
|
+
if (args.waveGate)
|
|
454
|
+
return prApplyWaves(ctx, p, { pr, op, release, upload, events });
|
|
414
455
|
// `--resume` (#3464): an earlier attempt at this change on this commit
|
|
415
456
|
// applied part of an approved set. When the members left are covered by
|
|
416
457
|
// it and its approval still stands, finish it under that approval.
|
|
@@ -441,15 +482,7 @@ async function prApply(ctx, stage) {
|
|
|
441
482
|
}
|
|
442
483
|
const set = resumed?.set ?? (await planAll(p, events?.emit));
|
|
443
484
|
const base = { stage: "apply", pr, env: p.env, ...(p.member ? { member: p.member } : {}), base: p.base, head: p.head, op, gate: p.gate, plan: p.plan, signal: p.signal, set };
|
|
444
|
-
const stop =
|
|
445
|
-
events?.settle(settle ?? settleOf(report.status), report.status === "plan-failed" || report.status === "failed" ? report.message ?? `pr-apply ${report.status}` : undefined);
|
|
446
|
-
await finish(ctx, p, report, upload);
|
|
447
|
-
console.error((code === 0 ? formatInfo : (m) => formatWarning({ message: m }))(`pr-apply: ${report.status}${report.refusal ? ` (${report.refusal})` : ""}`));
|
|
448
|
-
if (report.message)
|
|
449
|
-
console.error(report.message);
|
|
450
|
-
printMembers(report);
|
|
451
|
-
return code;
|
|
452
|
-
};
|
|
485
|
+
const stop = stopper(ctx, p, events, upload);
|
|
453
486
|
if (p.plan.order.length === 0) {
|
|
454
487
|
return stop(prReport({ ...base, approval: { status: "pending" }, status: "nothing" }), 0);
|
|
455
488
|
}
|
|
@@ -503,22 +536,13 @@ async function prApply(ctx, stage) {
|
|
|
503
536
|
return stop(report, GATED_EXIT_CODE);
|
|
504
537
|
}
|
|
505
538
|
}
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
}
|
|
514
|
-
const strangers = approversWithoutReview(approval.approvedBy ?? [], reviewers);
|
|
515
|
-
if (strangers.length > 0) {
|
|
516
|
-
const message = `The plan is approved by ${strangers.join(", ")}, who ${strangers.length === 1 ? "has" : "have"} no approving review on #${pr}` +
|
|
517
|
-
` (approving reviews: ${reviewers.join(", ") || "none"}). Nothing was applied. ` +
|
|
518
|
-
`An approval counts when it names the reviewer the way the forge does: ${p.forge.principalOf("<login>")}.`;
|
|
519
|
-
emitReviewStop(events, message);
|
|
520
|
-
return stop(prReport({ ...base, approval, status: "refused", refusal: "not-a-reviewer", message }), GATED_EXIT_CODE);
|
|
521
|
-
}
|
|
539
|
+
const reviewOf = reviewCheck(args, p, pr);
|
|
540
|
+
const review = await reviewOf(approval.approvedBy ?? [], "The plan");
|
|
541
|
+
if (review && "error" in review)
|
|
542
|
+
return fail(review.error);
|
|
543
|
+
if (review) {
|
|
544
|
+
emitReviewStop(events, review.message);
|
|
545
|
+
return stop(prReport({ ...base, approval, status: "refused", refusal: review.refusal, message: review.message }), GATED_EXIT_CODE);
|
|
522
546
|
}
|
|
523
547
|
// The record is written before the first component runs and again as each
|
|
524
548
|
// one settles, so a run killed partway still knows what applied.
|
|
@@ -550,12 +574,36 @@ async function prApply(ctx, stage) {
|
|
|
550
574
|
});
|
|
551
575
|
};
|
|
552
576
|
recordAttempt();
|
|
577
|
+
// #3999: a round planned after an upstream applied waits on a gate of its
|
|
578
|
+
// own, under the same identity.gates rule and the same review checks.
|
|
579
|
+
const roundLedger = gitGateLedgerPort();
|
|
580
|
+
const decideRound = async ({ round, gate, set: planned }) => {
|
|
581
|
+
console.error(formatInfo(`round ${round}: ${planned.members.map((m) => m.component).filter((c, i, all) => all.indexOf(c) === i).join(", ")} planned again after an upstream applied, ${planned.doc.digest}`));
|
|
582
|
+
await storeChangeSetForGate(ctx, p, planned.doc);
|
|
583
|
+
const port = {
|
|
584
|
+
...roundLedger,
|
|
585
|
+
...(roundLedger.approvalRule ? { approvalRule: (g, at) => roundLedger.approvalRule(g === gate ? p.gate : g, at) } : {}),
|
|
586
|
+
};
|
|
587
|
+
const check = await emitGateStep(events?.emit, { op, gate }, () => decidePrGate(port, { op, gate, digest: planned.doc.digest, description: `pull request #${pr}, round ${round}: planned again after an upstream applied` }));
|
|
588
|
+
const roundApproval = approvalOf(check);
|
|
589
|
+
if (!check.satisfied)
|
|
590
|
+
return { satisfied: false, approval: roundApproval };
|
|
591
|
+
const refused = await reviewOf(roundApproval.approvedBy ?? [], `Round ${round}'s plan`);
|
|
592
|
+
if (refused) {
|
|
593
|
+
const message = "error" in refused ? refused.error : refused.message;
|
|
594
|
+
emitReviewStop(events, message);
|
|
595
|
+
return { satisfied: false, approval: roundApproval, message };
|
|
596
|
+
}
|
|
597
|
+
return { satisfied: true, approval: roundApproval };
|
|
598
|
+
};
|
|
553
599
|
const applied = await applyPrSet({
|
|
554
600
|
components: p.components,
|
|
555
601
|
plan: resumed?.plan ?? p.plan,
|
|
556
602
|
registry: p.registry,
|
|
557
603
|
env: p.env,
|
|
558
604
|
set,
|
|
605
|
+
gate: p.gate,
|
|
606
|
+
decideRound,
|
|
559
607
|
...(resumed ? { resumed: prior } : {}),
|
|
560
608
|
// The apply is this run's waves, so the fan-out's own run framing is
|
|
561
609
|
// dropped. The log's lines go to stderr with or without --events-json.
|
|
@@ -589,13 +637,200 @@ async function prApply(ctx, stage) {
|
|
|
589
637
|
status: applied.status,
|
|
590
638
|
members: applied.members,
|
|
591
639
|
...(resumed ? { resumed: resumed.completed } : {}),
|
|
640
|
+
...(applied.rounds ? { rounds: applied.rounds } : {}),
|
|
641
|
+
...(applied.status === "waiting" ? { message: waitingMessage(op, applied.rounds) } : {}),
|
|
592
642
|
});
|
|
593
|
-
// A component that stopped at a gate of its own leaves the apply waiting on
|
|
594
|
-
|
|
643
|
+
// A component that stopped at a gate of its own leaves the apply waiting on
|
|
644
|
+
// a person. A step that refused its plan because the target moved since
|
|
645
|
+
// the approval exits 3, as a moved digest does before anything applies (#3997).
|
|
646
|
+
const exit = applied.status === "applied" ? 0 : applied.status === "waiting" ? GATED_EXIT_CODE : failedRunExitCode(applied.run.results);
|
|
647
|
+
const code = await stop(report, exit, applied.run.status === "gated" ? "gated" : undefined);
|
|
595
648
|
if (applied.status === "applied")
|
|
596
649
|
console.error(formatSuccess(`applied the plan ${report.digest} approved by ${(approval.approvedBy ?? []).join(", ")}`));
|
|
597
650
|
return code;
|
|
598
651
|
}
|
|
652
|
+
/** chant components pr-record --base <ref> [--pr <n>] [--env <env>] [--gate <name>] [--forge <kind>] [--output <dir>] [--json] [--events-json] [--report-url <url>] */
|
|
653
|
+
export function runComponentsPrRecord(ctx) {
|
|
654
|
+
if (refusesEventsWithJson(ctx.args, "the stage's report"))
|
|
655
|
+
return Promise.resolve(1);
|
|
656
|
+
return withStageEvents("pr-record", ctx, (events) => prRecord(ctx, events));
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* The record stage (#4168 step 1): what a merge does unless apply on merge
|
|
660
|
+
* is opted in. It plans the members the change reaches on the merged
|
|
661
|
+
* commit, stores the change set, decides the pull request's gate against
|
|
662
|
+
* it without writing to the ledger, and writes the merge record
|
|
663
|
+
* (`../../lifecycle/merge-record.ts`). It applies nothing and refuses
|
|
664
|
+
* nothing. A plan that differs from the approved one is recorded, and the
|
|
665
|
+
* `chant/record` status fails (decision 1). It exits 1 only when a member
|
|
666
|
+
* failed to plan.
|
|
667
|
+
*/
|
|
668
|
+
async function prRecord(ctx, stage) {
|
|
669
|
+
const { args } = ctx;
|
|
670
|
+
const p = await prepare(ctx, "record");
|
|
671
|
+
if (typeof p === "number")
|
|
672
|
+
return p;
|
|
673
|
+
let pr = args.pr ?? null;
|
|
674
|
+
if (pr === null && p.forge) {
|
|
675
|
+
try {
|
|
676
|
+
pr = await p.forge.pullRequestFor(p.head);
|
|
677
|
+
}
|
|
678
|
+
catch (err) {
|
|
679
|
+
console.error(formatWarning({ message: `could not ask ${p.forge.kind} which pull request merged ${p.head}, so the record names none: ${err instanceof Error ? err.message : String(err)}` }));
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
const op = pr === null ? "pr-unknown" : prOp(pr, p.member);
|
|
683
|
+
const upload = openUpload(stage, "record", pr, p);
|
|
684
|
+
const events = await openCliRunEvents(args, { kind: "component", name: `pr-record ${op}`, cwd: resolve(".") }, upload?.line);
|
|
685
|
+
stage.stream = events;
|
|
686
|
+
events?.emit({ type: "run-start", waves: p.plan.waves });
|
|
687
|
+
const set = await planAll(p, events?.emit);
|
|
688
|
+
const nothing = p.plan.order.length === 0;
|
|
689
|
+
let approval = { status: "pending" };
|
|
690
|
+
if (!nothing && !set.failed) {
|
|
691
|
+
await storeChangeSetForGate(ctx, p, set.doc);
|
|
692
|
+
if (pr !== null) {
|
|
693
|
+
try {
|
|
694
|
+
const check = await emitGateStep(events?.emit, { op, gate: p.gate }, () => decidePrGate(readOnlyLedger(gitGateLedgerPort()), { op, gate: p.gate, digest: set.doc.digest }));
|
|
695
|
+
approval = approvalOf(check);
|
|
696
|
+
}
|
|
697
|
+
catch (err) {
|
|
698
|
+
console.error(formatWarning({ message: `could not read the gate ledger, so the record says no approval: ${err instanceof Error ? err.message : String(err)}` }));
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
// #4000: the record keeps an approval by someone who wrote the change, which review rules would not count.
|
|
703
|
+
let authorApproved;
|
|
704
|
+
if (pr !== null && p.forge && approval.status === "approved") {
|
|
705
|
+
try {
|
|
706
|
+
const authors = (await p.forge.authors(pr)).map((login) => p.forge.principalOf(login));
|
|
707
|
+
authorApproved = approversWhoAuthored(approval.approvedBy ?? [], authors);
|
|
708
|
+
}
|
|
709
|
+
catch (err) {
|
|
710
|
+
console.error(formatWarning({ message: `could not read the authors of #${pr}: ${err instanceof Error ? err.message : String(err)}` }));
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
const status = nothing ? "nothing" : set.failed ? "plan-failed" : "recorded";
|
|
714
|
+
const report = prReport({
|
|
715
|
+
stage: "record",
|
|
716
|
+
pr,
|
|
717
|
+
env: p.env,
|
|
718
|
+
...(p.member ? { member: p.member } : {}),
|
|
719
|
+
base: p.base,
|
|
720
|
+
head: p.head,
|
|
721
|
+
op,
|
|
722
|
+
gate: p.gate,
|
|
723
|
+
plan: p.plan,
|
|
724
|
+
signal: p.signal,
|
|
725
|
+
set,
|
|
726
|
+
approval,
|
|
727
|
+
status,
|
|
728
|
+
...(status === "plan-failed" ? { message: "A member failed to plan on the merged commit, so the record has no plan to compare with the approved one. Nothing was applied." } : {}),
|
|
729
|
+
...(status === "recorded" && approval.status === "changed"
|
|
730
|
+
? { message: `${describePlanChanged({ op, gate: p.gate, digest: set.doc.digest, approval })} The merge stands and nothing was applied; the release gate decides before this deploys.` }
|
|
731
|
+
: {}),
|
|
732
|
+
});
|
|
733
|
+
const { mergeSourceOf, writeMergeRecord, MERGE_RECORD_SCHEMA_ID, MERGE_RECORD_CONTRACT } = await import("../../lifecycle/merge-record.js");
|
|
734
|
+
const { pushLifecycleReport } = await import("../../lifecycle/git.js");
|
|
735
|
+
try {
|
|
736
|
+
const written = await writeMergeRecord({
|
|
737
|
+
$schema: MERGE_RECORD_SCHEMA_ID,
|
|
738
|
+
contract: MERGE_RECORD_CONTRACT,
|
|
739
|
+
commit: p.head,
|
|
740
|
+
base: p.base,
|
|
741
|
+
env: p.env,
|
|
742
|
+
...(p.member ? { member: p.member } : {}),
|
|
743
|
+
source: mergeSourceOf(resolve("."), p.head, pr),
|
|
744
|
+
op: pr === null ? null : op,
|
|
745
|
+
gate: p.gate,
|
|
746
|
+
digest: nothing ? null : set.doc.digest,
|
|
747
|
+
plan: nothing ? "nothing" : set.failed ? "plan-failed" : approval.status === "approved" ? "approved" : approval.status === "changed" ? "changed" : "unapproved",
|
|
748
|
+
...(approval.status === "changed" && approval.approved ? { approvedDigest: approval.approved } : {}),
|
|
749
|
+
approvedBy: approval.status === "approved" ? (approval.approvedBy ?? []) : [],
|
|
750
|
+
...(authorApproved && authorApproved.length > 0 ? { authorApproved } : {}),
|
|
751
|
+
components: p.plan.order,
|
|
752
|
+
recordedAt: new Date().toISOString(),
|
|
753
|
+
});
|
|
754
|
+
if (written.written) {
|
|
755
|
+
const pushed = await pushLifecycleReport();
|
|
756
|
+
if (pushed.pushWarning)
|
|
757
|
+
console.error(formatWarning({ message: pushed.pushWarning }));
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
catch (err) {
|
|
761
|
+
console.error(formatWarning({ message: `could not write the merge record for ${p.head}: ${err instanceof Error ? err.message : String(err)}` }));
|
|
762
|
+
}
|
|
763
|
+
const stop = stopper(ctx, p, events, upload);
|
|
764
|
+
return stop(report, status === "plan-failed" ? 1 : 0);
|
|
765
|
+
}
|
|
766
|
+
/** How a stage stops: settle its events, write and publish the report, say how it ended, and return `code`. */
|
|
767
|
+
function stopper(ctx, p, events, upload) {
|
|
768
|
+
return async (report, code, settle) => {
|
|
769
|
+
events?.settle(settle ?? settleOf(report.status), report.status === "plan-failed" || report.status === "failed" ? report.message ?? `pr-${report.stage} ${report.status}` : undefined);
|
|
770
|
+
await finish(ctx, p, report, upload);
|
|
771
|
+
console.error((code === 0 ? formatInfo : (m) => formatWarning({ message: m }))(`pr-${report.stage}: ${report.status}${report.refusal ? ` (${report.refusal})` : ""}`));
|
|
772
|
+
if (report.message)
|
|
773
|
+
console.error(report.message);
|
|
774
|
+
printMembers(report);
|
|
775
|
+
return code;
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* The `--require-review` check (#3183, #4000) of an approval's approvers,
|
|
780
|
+
* `what` naming the plan they approved. The pull request's reviews and
|
|
781
|
+
* authors are read once, at the first call. Without `--require-review` every
|
|
782
|
+
* approval counts.
|
|
783
|
+
*/
|
|
784
|
+
function reviewCheck(args, p, pr) {
|
|
785
|
+
let read;
|
|
786
|
+
return async (approvedBy, what) => {
|
|
787
|
+
const forge = p.forge;
|
|
788
|
+
if (!args.requireReview || !forge)
|
|
789
|
+
return undefined;
|
|
790
|
+
read ?? (read = (async () => ({
|
|
791
|
+
reviewers: (await forge.approvers(pr)).map((login) => forge.principalOf(login)),
|
|
792
|
+
authors: (await forge.authors(pr)).map((login) => forge.principalOf(login)),
|
|
793
|
+
}))());
|
|
794
|
+
let reviewers;
|
|
795
|
+
let authors;
|
|
796
|
+
try {
|
|
797
|
+
({ reviewers, authors } = await read);
|
|
798
|
+
}
|
|
799
|
+
catch (err) {
|
|
800
|
+
return { error: `could not read the reviews and authors of #${pr}: ${err instanceof Error ? err.message : String(err)}` };
|
|
801
|
+
}
|
|
802
|
+
// #4000: whoever wrote the change never counts toward its gate, through a forge review or a ledger line.
|
|
803
|
+
const writers = approversWhoAuthored(approvedBy, authors);
|
|
804
|
+
if (writers.length > 0) {
|
|
805
|
+
return {
|
|
806
|
+
refusal: "author-approved",
|
|
807
|
+
message: `${what} is approved by ${writers.join(", ")}, who wrote #${pr} ` +
|
|
808
|
+
`(its author and the authors and committers of its commits: ${authors.join(", ")}). ` +
|
|
809
|
+
`An approval from someone who wrote the change never counts, so nothing was applied. ` +
|
|
810
|
+
`Have someone else approve the pull request and the plan, then run the apply again.`,
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
const strangers = approversWithoutReview(approvedBy, reviewers);
|
|
814
|
+
if (strangers.length > 0) {
|
|
815
|
+
return {
|
|
816
|
+
refusal: "not-a-reviewer",
|
|
817
|
+
message: `${what} is approved by ${strangers.join(", ")}, who ${strangers.length === 1 ? "has" : "have"} no approving review on #${pr}` +
|
|
818
|
+
` (approving reviews: ${reviewers.join(", ") || "none"}). Nothing was applied. ` +
|
|
819
|
+
`An approval counts when it names the reviewer the way the forge does: ${forge.principalOf("<login>")}.`,
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
return undefined;
|
|
823
|
+
};
|
|
824
|
+
}
|
|
825
|
+
/** What a `waiting` apply prints (#3999): the round's gate and digest to approve, and how to carry on. */
|
|
826
|
+
function waitingMessage(op, rounds) {
|
|
827
|
+
const last = rounds?.at(-1);
|
|
828
|
+
if (!last)
|
|
829
|
+
return "Some members were held back after an upstream applied.";
|
|
830
|
+
return (`${last.components.join(", ")} ${last.components.length === 1 ? "was" : "were"} planned again after an upstream applied, ` +
|
|
831
|
+
`and no approval stands for that plan (${last.digest}), so ${last.components.length === 1 ? "it" : "they"} did not apply. ` +
|
|
832
|
+
`Approve it: ${prApproveCommand({ op, gate: last.gate, digest: last.digest })}; then run the apply again with the same --resume record.`);
|
|
833
|
+
}
|
|
599
834
|
/** What a component's settle says in the job log. */
|
|
600
835
|
function settleWords(body) {
|
|
601
836
|
switch (body.status) {
|
|
@@ -733,3 +968,346 @@ async function tryResume(p, attempt, file, op, inputsNow) {
|
|
|
733
968
|
console.error(formatInfo(`resume : ${completed.join(", ")} applied in an earlier attempt; applying ${resumed.plan.order.join(", ") || "nothing more"} under the approval of ${record.digest}`));
|
|
734
969
|
return { plan: resumed.plan, set: resumed.set, check, approval: approvalOf(check), completed, outputs };
|
|
735
970
|
}
|
|
971
|
+
/**
|
|
972
|
+
* Whether `attempt` carries into this wave's job. It does when it was made
|
|
973
|
+
* by an apply of this pull request's gate, on this commit, for this
|
|
974
|
+
* derivation; when its change set is the one its digest names; and when the
|
|
975
|
+
* pull request's approval still stands for that digest. Of the components it
|
|
976
|
+
* applied, those whose input is the same now carry (#4081). Otherwise it says
|
|
977
|
+
* why on stderr, and the job runs as it would with no record.
|
|
978
|
+
*/
|
|
979
|
+
async function carryWaves(p, attempt, file, op, inputsNow) {
|
|
980
|
+
const skip = (why) => {
|
|
981
|
+
console.error(formatWarning({
|
|
982
|
+
message: `--resume: "${file}" does not carry: ${why}`,
|
|
983
|
+
hint: "Wave 1 plans every member again and needs the pull request's approval of that plan; a later wave refuses until wave 1 applied.",
|
|
984
|
+
}));
|
|
985
|
+
return undefined;
|
|
986
|
+
};
|
|
987
|
+
const record = attempt.prApply;
|
|
988
|
+
if (!record)
|
|
989
|
+
return skip("it was not written by pr-apply");
|
|
990
|
+
if (record.op !== op || record.gate !== p.gate)
|
|
991
|
+
return skip(`it records ${record.op} ${record.gate}, not ${op} ${p.gate}`);
|
|
992
|
+
if (record.head !== p.head)
|
|
993
|
+
return skip(`it records an apply of ${record.head.slice(0, 12)}, not ${p.head.slice(0, 12)}`);
|
|
994
|
+
if (!samePlanDigest(attempt.digest, p.plan.digest))
|
|
995
|
+
return skip("the change selects other components than it did");
|
|
996
|
+
if (record.changeSet.digest !== record.digest || !verifyChangeSetDigest(record.changeSet))
|
|
997
|
+
return skip(`its change set is not the one its digest ${record.digest} names`);
|
|
998
|
+
const split = progressThatCarries(attempt, inputsNow);
|
|
999
|
+
if (split.unrecorded && attempt.completed.length > 0)
|
|
1000
|
+
return skip("it does not record what each component applied, since it was written before chant recorded component inputs");
|
|
1001
|
+
for (const name of split.changed)
|
|
1002
|
+
console.error(formatInfo(`${name}: changed since the earlier attempt, so it runs again`));
|
|
1003
|
+
let check;
|
|
1004
|
+
try {
|
|
1005
|
+
check = await decidePrGate(readOnlyLedger(gitGateLedgerPort()), { op, gate: p.gate, digest: record.digest });
|
|
1006
|
+
}
|
|
1007
|
+
catch (err) {
|
|
1008
|
+
return skip(`could not read the gate ledger: ${err instanceof Error ? err.message : String(err)}`);
|
|
1009
|
+
}
|
|
1010
|
+
if (!check.satisfied)
|
|
1011
|
+
return skip(`no approval stands for the plan it applied (${record.digest}) any more`);
|
|
1012
|
+
const completed = split.carried;
|
|
1013
|
+
const outputs = Object.fromEntries(Object.entries(attempt.outputs).filter(([name]) => completed.includes(name)));
|
|
1014
|
+
return { record, check, approval: approvalOf(check), completed, outputs };
|
|
1015
|
+
}
|
|
1016
|
+
/** A run event with its wave renumbered to `wave`: the fan-out that applies one wave numbers it 1. */
|
|
1017
|
+
function inWave(body, wave) {
|
|
1018
|
+
return "wave" in body && typeof body.wave === "number" ? { ...body, wave } : body;
|
|
1019
|
+
}
|
|
1020
|
+
/**
|
|
1021
|
+
* `pr-apply --wave-gate <gate>` (#4003): apply the pull request's approved
|
|
1022
|
+
* set a wave at a time, each wave planned after the one before it applied.
|
|
1023
|
+
*
|
|
1024
|
+
* The first job plans every member, as pr-apply does, and decides the pull
|
|
1025
|
+
* request's gate against that digest. Approved, wave 1 applies with those
|
|
1026
|
+
* plans. Each later wave is planned again against the outputs the waves
|
|
1027
|
+
* before it left. When the approved change set covers its plan
|
|
1028
|
+
* (`planCoveredBy`, as on a resume) it applies under the pull request's
|
|
1029
|
+
* approval; when it does not, it waits at `<gate>-wave-<k>` under the pull
|
|
1030
|
+
* request's op for an approval of its new plan, and exits 3.
|
|
1031
|
+
*
|
|
1032
|
+
* With `--wave <k>` the job runs wave k and no other, reading the approved
|
|
1033
|
+
* set and the progress from the `--resume` record the job before it left.
|
|
1034
|
+
* Without it the waves run in order until one stops.
|
|
1035
|
+
*/
|
|
1036
|
+
async function prApplyWaves(ctx, p, at) {
|
|
1037
|
+
const { args } = ctx;
|
|
1038
|
+
const { pr, op, events, upload } = at;
|
|
1039
|
+
const waveGate = args.waveGate;
|
|
1040
|
+
const only = args.wave;
|
|
1041
|
+
const total = p.plan.waves.length;
|
|
1042
|
+
const stop = stopper(ctx, p, events, upload);
|
|
1043
|
+
const log = applyLog();
|
|
1044
|
+
log({ type: "run-start", waves: p.plan.waves });
|
|
1045
|
+
const reportBase = { stage: "apply", pr, env: p.env, ...(p.member ? { member: p.member } : {}), base: p.base, head: p.head, op, gate: p.gate, plan: p.plan, signal: p.signal, waveGate };
|
|
1046
|
+
if (p.plan.order.length === 0 || pr === null) {
|
|
1047
|
+
const set = await planAll(p, events?.emit);
|
|
1048
|
+
if (p.plan.order.length === 0)
|
|
1049
|
+
return stop(prReport({ ...reportBase, set, approval: { status: "pending" }, status: "nothing" }), 0);
|
|
1050
|
+
return stop(prReport({
|
|
1051
|
+
...reportBase,
|
|
1052
|
+
set,
|
|
1053
|
+
approval: { status: "pending" },
|
|
1054
|
+
status: "refused",
|
|
1055
|
+
refusal: "no-pull-request",
|
|
1056
|
+
message: `No pull request merged ${p.head}, so no approval answers for this change. Nothing was applied. Pass --pr <n>, or land the change through a pull request.`,
|
|
1057
|
+
}), GATED_EXIT_CODE);
|
|
1058
|
+
}
|
|
1059
|
+
const resumePath = args.resume ? resolve(args.resume) : undefined;
|
|
1060
|
+
let attempt;
|
|
1061
|
+
if (resumePath) {
|
|
1062
|
+
try {
|
|
1063
|
+
attempt = readFanOutAttempt(resumePath);
|
|
1064
|
+
}
|
|
1065
|
+
catch (err) {
|
|
1066
|
+
return fail(`--resume: could not read "${args.resume}": ${err instanceof Error ? err.message : String(err)}`);
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
const inputsNow = resumePath ? componentInputDigests(p.components, { projectRoot: resolve("."), env: p.env }) : {};
|
|
1070
|
+
const carry = attempt ? await carryWaves(p, attempt, args.resume, op, inputsNow) : undefined;
|
|
1071
|
+
const completed = new Set(carry?.completed ?? []);
|
|
1072
|
+
const outputsSoFar = { ...(carry?.outputs ?? {}) };
|
|
1073
|
+
const inputsSoFar = Object.fromEntries([...completed].filter((n) => inputsNow[n] !== undefined).map((n) => [n, inputsNow[n]]));
|
|
1074
|
+
const failedSoFar = new Set();
|
|
1075
|
+
let waves = carry?.record.waves ?? [];
|
|
1076
|
+
const after = new Map();
|
|
1077
|
+
const leftIn = (k) => (p.plan.waves[k - 1] ?? []).filter((name) => !completed.has(name));
|
|
1078
|
+
const firstLeft = () => p.plan.waves.findIndex((_, i) => leftIn(i + 1).length > 0) + 1;
|
|
1079
|
+
// The approved set: carried from the record, or planned here and decided against the pull request's gate.
|
|
1080
|
+
let approved = carry
|
|
1081
|
+
? { doc: carry.record.changeSet, members: carry.record.members, plans: {}, outputs: carry.record.planOutputs, failed: false }
|
|
1082
|
+
: undefined;
|
|
1083
|
+
let approval = carry?.approval ?? { status: "pending" };
|
|
1084
|
+
const report = (input) => {
|
|
1085
|
+
const set = approved ?? { doc: composeChangeSet([]), members: [], plans: {}, outputs: {}, failed: false };
|
|
1086
|
+
const members = set.members.map((m) => {
|
|
1087
|
+
const now = after.get(m.member);
|
|
1088
|
+
if (now)
|
|
1089
|
+
return { ...m, status: now.status, ...(now.error ? { error: now.error } : {}) };
|
|
1090
|
+
return completed.has(m.component) ? { ...m, status: "applied" } : m;
|
|
1091
|
+
});
|
|
1092
|
+
return prReport({ ...reportBase, set, approval, members, waves, ...input });
|
|
1093
|
+
};
|
|
1094
|
+
// `--wave <k>` reached before an earlier wave applied: nothing runs, as `fan-out --wave` refuses.
|
|
1095
|
+
const left = firstLeft();
|
|
1096
|
+
if (only !== undefined && left > 0 && left < only) {
|
|
1097
|
+
return stop(report({
|
|
1098
|
+
status: "refused",
|
|
1099
|
+
refusal: "earlier-wave",
|
|
1100
|
+
message: `Wave ${only} cannot run yet: wave ${left} has not applied (${leftIn(left).join(", ")})${carry ? "" : ", and no --resume record of this apply says otherwise"}. ` +
|
|
1101
|
+
`Nothing was applied. Run wave ${left}'s job first, with the same --resume record.`,
|
|
1102
|
+
}), 1);
|
|
1103
|
+
}
|
|
1104
|
+
let planned;
|
|
1105
|
+
if (carry) {
|
|
1106
|
+
// What the earlier waves applied settles first (#4007), each in its own wave, as a resumed apply's does.
|
|
1107
|
+
p.plan.waves.forEach((names, i) => {
|
|
1108
|
+
for (const component of names.filter((name) => completed.has(name))) {
|
|
1109
|
+
const body = { type: "component-settle", wave: i + 1, component, status: "already-applied" };
|
|
1110
|
+
events?.emit(body);
|
|
1111
|
+
log(body);
|
|
1112
|
+
}
|
|
1113
|
+
});
|
|
1114
|
+
emitResumedGate(events, op, p.gate, carry.check);
|
|
1115
|
+
}
|
|
1116
|
+
else {
|
|
1117
|
+
const set = await planAll(p, events?.emit);
|
|
1118
|
+
approved = set;
|
|
1119
|
+
if (set.failed) {
|
|
1120
|
+
return stop(report({ status: "plan-failed", message: "A member failed to plan, so there is no plan to compare with the approved one. Nothing was applied." }), 1);
|
|
1121
|
+
}
|
|
1122
|
+
await storeChangeSetForGate(ctx, p, set.doc);
|
|
1123
|
+
const check = await emitGateStep(events?.emit, { op, gate: p.gate }, () => decidePrGate(gitGateLedgerPort(), { op, gate: p.gate, digest: set.doc.digest, description: `pull request #${pr}: ${p.plan.order.join(", ")}` }), (unapproved) => {
|
|
1124
|
+
const a = approvalOf(unapproved);
|
|
1125
|
+
return a.status === "changed"
|
|
1126
|
+
? describePlanChanged({ op, gate: p.gate, digest: set.doc.digest, approval: a })
|
|
1127
|
+
: `No approval stands for this plan. Approve it: ${prApproveCommand({ op, gate: p.gate, digest: set.doc.digest })}`;
|
|
1128
|
+
});
|
|
1129
|
+
approval = approvalOf(check);
|
|
1130
|
+
if (!check.satisfied) {
|
|
1131
|
+
return stop(approval.status === "changed"
|
|
1132
|
+
? report({ status: "refused", refusal: "plan-changed", message: describePlanChanged({ op, gate: p.gate, digest: set.doc.digest, approval }) })
|
|
1133
|
+
: report({
|
|
1134
|
+
status: "refused",
|
|
1135
|
+
refusal: "not-approved",
|
|
1136
|
+
message: `No approval stands for this plan, so nothing was applied. Approve it: ${prApproveCommand({ op, gate: p.gate, digest: set.doc.digest })}; then run the apply again.`,
|
|
1137
|
+
}), GATED_EXIT_CODE);
|
|
1138
|
+
}
|
|
1139
|
+
planned = set;
|
|
1140
|
+
}
|
|
1141
|
+
const approvedSet = approved;
|
|
1142
|
+
const review = reviewCheck(args, p, pr);
|
|
1143
|
+
const prReview = await review(approval.approvedBy ?? [], "The plan");
|
|
1144
|
+
if (prReview && "error" in prReview)
|
|
1145
|
+
return fail(prReview.error);
|
|
1146
|
+
if (prReview) {
|
|
1147
|
+
emitReviewStop(events, prReview.message);
|
|
1148
|
+
return stop(report({ status: "refused", refusal: prReview.refusal, message: prReview.message }), GATED_EXIT_CODE);
|
|
1149
|
+
}
|
|
1150
|
+
const recordAttempt = () => {
|
|
1151
|
+
if (!resumePath)
|
|
1152
|
+
return;
|
|
1153
|
+
writeFanOutAttempt(resumePath, {
|
|
1154
|
+
digest: p.plan.digest,
|
|
1155
|
+
completed: [...completed].sort(),
|
|
1156
|
+
failed: [...failedSoFar].sort(),
|
|
1157
|
+
outputs: outputsSoFar,
|
|
1158
|
+
inputs: Object.fromEntries(Object.entries(inputsSoFar).sort(([a], [b]) => a.localeCompare(b))),
|
|
1159
|
+
prApply: {
|
|
1160
|
+
op,
|
|
1161
|
+
gate: p.gate,
|
|
1162
|
+
head: p.head,
|
|
1163
|
+
digest: approvedSet.doc.digest,
|
|
1164
|
+
approvedBy: approval.approvedBy ?? [],
|
|
1165
|
+
members: approvedSet.members,
|
|
1166
|
+
changeSet: approvedSet.doc,
|
|
1167
|
+
planOutputs: approvedSet.outputs,
|
|
1168
|
+
...(waves.length > 0 ? { waves } : {}),
|
|
1169
|
+
},
|
|
1170
|
+
});
|
|
1171
|
+
};
|
|
1172
|
+
const setWave = (wave) => {
|
|
1173
|
+
waves = [...waves.filter((w) => w.wave !== wave.wave), wave].sort((a, b) => a.wave - b.wave);
|
|
1174
|
+
recordAttempt();
|
|
1175
|
+
};
|
|
1176
|
+
recordAttempt();
|
|
1177
|
+
const from = only ?? (left || total + 1);
|
|
1178
|
+
const to = only ?? total;
|
|
1179
|
+
for (let k = from; k <= to; k++) {
|
|
1180
|
+
const wavePlan = prWavePlan(p.plan, p.components, k, [...completed]);
|
|
1181
|
+
if (wavePlan.order.length === 0)
|
|
1182
|
+
continue;
|
|
1183
|
+
const inThisWave = new Set(wavePlan.order);
|
|
1184
|
+
// Wave 1 of the apply that just planned every member applies those plans:
|
|
1185
|
+
// they are the approved set's. Every other wave is planned again, against
|
|
1186
|
+
// the outputs the waves before it left.
|
|
1187
|
+
let set;
|
|
1188
|
+
let entry;
|
|
1189
|
+
if (planned && k === 1) {
|
|
1190
|
+
set = {
|
|
1191
|
+
...planned,
|
|
1192
|
+
members: planned.members.filter((m) => inThisWave.has(m.component)),
|
|
1193
|
+
plans: Object.fromEntries(Object.entries(planned.plans).filter(([name]) => inThisWave.has(name))),
|
|
1194
|
+
};
|
|
1195
|
+
entry = { wave: k, components: wavePlan.order, status: "applied", approval: "pull-request", gate: p.gate, digest: approvedSet.doc.digest, ...(approval.approvedBy ? { approvedBy: approval.approvedBy } : {}) };
|
|
1196
|
+
}
|
|
1197
|
+
else {
|
|
1198
|
+
const notApplied = Object.fromEntries(Object.entries(approvedSet.outputs).filter(([name]) => !completed.has(name)));
|
|
1199
|
+
const fresh = await planPrSet({
|
|
1200
|
+
components: p.components,
|
|
1201
|
+
plan: wavePlan,
|
|
1202
|
+
registry: p.registry,
|
|
1203
|
+
env: p.env,
|
|
1204
|
+
seededOutputs: { ...p.seededOutputs, ...notApplied, ...outputsSoFar },
|
|
1205
|
+
...(events ? { onRunEvent: (body) => events.emit(body) } : {}),
|
|
1206
|
+
});
|
|
1207
|
+
if (fresh.failed) {
|
|
1208
|
+
for (const m of fresh.members)
|
|
1209
|
+
after.set(m.member, m);
|
|
1210
|
+
setWave({ wave: k, components: wavePlan.order, status: "plan-failed", approval: "pull-request", gate: p.gate, digest: approvedSet.doc.digest });
|
|
1211
|
+
return stop(report({ status: "plan-failed", message: `A member of wave ${k} failed to plan, so nothing in wave ${k} was applied.` }), 1);
|
|
1212
|
+
}
|
|
1213
|
+
const moved = waveNotCovered(approvedSet.doc, fresh.doc, approvedSet.members);
|
|
1214
|
+
set = fresh;
|
|
1215
|
+
if (moved.length === 0) {
|
|
1216
|
+
console.error(formatInfo(`wave ${k}: the approved plan covers what it plans now, so it applies under the pull request's approval`));
|
|
1217
|
+
entry = { wave: k, components: wavePlan.order, status: "applied", approval: "pull-request", gate: p.gate, digest: approvedSet.doc.digest, ...(approval.approvedBy ? { approvedBy: approval.approvedBy } : {}) };
|
|
1218
|
+
}
|
|
1219
|
+
else {
|
|
1220
|
+
const gate = waveGateName(waveGate, k);
|
|
1221
|
+
const digest = fresh.doc.digest;
|
|
1222
|
+
console.error(formatInfo(`wave ${k}: its plan moved since the pull request's (${moved.join("; ")}), so it needs an approval of ${gate}`));
|
|
1223
|
+
await storeChangeSetForGate(ctx, p, fresh.doc);
|
|
1224
|
+
writeWaveChangeSet(ctx, k, fresh.doc, upload);
|
|
1225
|
+
const approveLine = prApproveCommand({ op, gate, digest });
|
|
1226
|
+
const check = await emitGateStep(events?.emit, { op, gate }, () => decidePrGate(gitGateLedgerPort(), { op, gate, digest, description: `pull request #${pr}, wave ${k} of ${total}: ${wavePlan.order.join(", ")}` }), (unapproved) => unapproved.mismatch
|
|
1227
|
+
? `wave ${k}'s plan changed after its approval. Approve the plan now: ${approveLine}`
|
|
1228
|
+
: `wave ${k}'s plan is not the one the pull request approved. Approve it: ${approveLine}`);
|
|
1229
|
+
const waveApproval = approvalOf(check);
|
|
1230
|
+
const waiting = {
|
|
1231
|
+
wave: k,
|
|
1232
|
+
components: wavePlan.order,
|
|
1233
|
+
status: "waiting",
|
|
1234
|
+
approval: "wave",
|
|
1235
|
+
gate,
|
|
1236
|
+
digest,
|
|
1237
|
+
moved,
|
|
1238
|
+
...(waveApproval.approved ? { approved: waveApproval.approved } : {}),
|
|
1239
|
+
};
|
|
1240
|
+
if (!check.satisfied) {
|
|
1241
|
+
setWave(waiting);
|
|
1242
|
+
const message = waveApproval.status === "changed"
|
|
1243
|
+
? `Wave ${k}'s plan changed after its approval, so nothing in it was applied. approved: ${waveApproval.approved ?? "(none)"}; planned now: ${digest}. ` +
|
|
1244
|
+
`Read the new plan, and if it is right, approve it: ${approveLine}; then run the wave's apply again.`
|
|
1245
|
+
: `Wave ${k} was planned after the waves before it applied, and the pull request's approved plan does not cover it: ${moved.join("; ")}. ` +
|
|
1246
|
+
`Nothing in wave ${k} was applied. Read its plan, and if it is right, approve it: ${approveLine}; then run the wave's apply again.`;
|
|
1247
|
+
return stop(report({ status: "refused", refusal: waveApproval.status === "changed" ? "wave-plan-changed" : "wave-not-approved", message }), GATED_EXIT_CODE);
|
|
1248
|
+
}
|
|
1249
|
+
const waveReview = await review(waveApproval.approvedBy ?? [], `Wave ${k}'s plan`);
|
|
1250
|
+
if (waveReview && "error" in waveReview)
|
|
1251
|
+
return fail(waveReview.error);
|
|
1252
|
+
if (waveReview) {
|
|
1253
|
+
setWave(waiting);
|
|
1254
|
+
emitReviewStop(events, waveReview.message);
|
|
1255
|
+
return stop(report({ status: "refused", refusal: waveReview.refusal, message: waveReview.message }), GATED_EXIT_CODE);
|
|
1256
|
+
}
|
|
1257
|
+
entry = { wave: k, components: wavePlan.order, status: "applied", approval: "wave", gate, digest, moved, ...(waveApproval.approvedBy ? { approvedBy: waveApproval.approvedBy } : {}) };
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
const applied = await applyPrSet({
|
|
1261
|
+
components: p.components,
|
|
1262
|
+
plan: wavePlan,
|
|
1263
|
+
registry: p.registry,
|
|
1264
|
+
env: p.env,
|
|
1265
|
+
set,
|
|
1266
|
+
// One wave's fan-out numbers its wave 1; the events and the log carry the derivation's number.
|
|
1267
|
+
onRunEvent: (body) => {
|
|
1268
|
+
const numbered = inWave(body, k);
|
|
1269
|
+
events?.nested(numbered);
|
|
1270
|
+
if (numbered.type !== "run-start")
|
|
1271
|
+
log(numbered);
|
|
1272
|
+
},
|
|
1273
|
+
onComponentSettled: (settled, outputs) => {
|
|
1274
|
+
if (settled.status === "ok") {
|
|
1275
|
+
completed.add(settled.component);
|
|
1276
|
+
if (inputsNow[settled.component] !== undefined)
|
|
1277
|
+
inputsSoFar[settled.component] = inputsNow[settled.component];
|
|
1278
|
+
if (outputs)
|
|
1279
|
+
outputsSoFar[settled.component] = outputs;
|
|
1280
|
+
}
|
|
1281
|
+
else if (settled.status === "fail") {
|
|
1282
|
+
failedSoFar.add(settled.component);
|
|
1283
|
+
}
|
|
1284
|
+
recordAttempt();
|
|
1285
|
+
},
|
|
1286
|
+
});
|
|
1287
|
+
for (const m of applied.members)
|
|
1288
|
+
after.set(m.member, m);
|
|
1289
|
+
for (const name of applied.run.completed) {
|
|
1290
|
+
completed.add(name);
|
|
1291
|
+
const outputs = applied.run.componentOutputs[name];
|
|
1292
|
+
if (outputs)
|
|
1293
|
+
outputsSoFar[name] = outputs;
|
|
1294
|
+
}
|
|
1295
|
+
setWave({ ...entry, status: applied.status === "applied" ? "applied" : "failed" });
|
|
1296
|
+
await recordAutoReleasesForRun(applied.run.results, p.env, at.release.runId, resolveAutoReleaseDisabled(p.config, args.noReleaseRecord), at.release.runOrigin);
|
|
1297
|
+
if (applied.status !== "applied") {
|
|
1298
|
+
return stop(report({ status: "failed" }), failedRunExitCode(applied.run.results), applied.run.status === "gated" ? "gated" : undefined);
|
|
1299
|
+
}
|
|
1300
|
+
console.error(formatSuccess(`applied wave ${k} of ${total} under ${entry.approval === "pull-request" ? `the pull request's approval of ${approvedSet.doc.digest}` : `${entry.gate}, approved for ${entry.digest}`} by ${(entry.approvedBy ?? []).join(", ")}`));
|
|
1301
|
+
}
|
|
1302
|
+
const done = p.plan.order.every((name) => completed.has(name));
|
|
1303
|
+
return stop(report({ status: done ? "applied" : "wave-applied" }), 0);
|
|
1304
|
+
}
|
|
1305
|
+
/** A wave's new plan (#4003), beside the report as `change-set-wave-<k>.json`, for whoever approves its gate. */
|
|
1306
|
+
function writeWaveChangeSet(ctx, wave, doc, upload) {
|
|
1307
|
+
const dir = resolve(ctx.args.output ?? PR_REPORT_DIR);
|
|
1308
|
+
mkdirSync(dir, { recursive: true });
|
|
1309
|
+
const name = `change-set-wave-${wave}.json`;
|
|
1310
|
+
const body = JSON.stringify(doc, null, 2) + "\n";
|
|
1311
|
+
writeFileSync(join(dir, name), body);
|
|
1312
|
+
upload?.file(name, body);
|
|
1313
|
+
}
|