@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
|
@@ -342,9 +342,10 @@ describe("an Op that changes the checkout", () => {
|
|
|
342
342
|
expect(result.workLease).toMatchObject({ item: "W-7", branch: "chant/work/W-7", released: true, outcome: "done" });
|
|
343
343
|
expect(result.workLease?.holder).toBe(stewardWorkHolder("box", "dispatch", "h1"));
|
|
344
344
|
|
|
345
|
-
// The worktree sat
|
|
346
|
-
expect(worktree.
|
|
345
|
+
// The worktree sat at .chant/work/<item> in the checkout and is gone, with the directories it left empty; the branch keeps the build.
|
|
346
|
+
expect(worktree).toBe(join(dir, ".chant", "work", "W-7"));
|
|
347
347
|
expect(existsSync(worktree)).toBe(false);
|
|
348
|
+
expect(existsSync(join(dir, ".chant"))).toBe(false);
|
|
348
349
|
expect(git(["show", "chant/work/W-7:app/feature.ts"], dir).stdout).toBe("export const feature = true;\n");
|
|
349
350
|
|
|
350
351
|
// The coding agent's checkout: its edit, its lock, HEAD and the index, as they were.
|
|
@@ -403,3 +404,88 @@ describe("an Op that changes the checkout", () => {
|
|
|
403
404
|
});
|
|
404
405
|
});
|
|
405
406
|
});
|
|
407
|
+
|
|
408
|
+
describe("where the worktree is (#2893)", () => {
|
|
409
|
+
const record = (seen: { worktree?: string; status?: string }) =>
|
|
410
|
+
new Map<string, ActivityFn>([
|
|
411
|
+
[
|
|
412
|
+
"build",
|
|
413
|
+
async (args) => {
|
|
414
|
+
seen.worktree = String(args.worktree);
|
|
415
|
+
writeFileSync(join(seen.worktree, "app", "feature.ts"), "export const feature = true;\n");
|
|
416
|
+
seen.status = git(["status", "--porcelain", "--untracked-files=all"], dir()).stdout;
|
|
417
|
+
return {};
|
|
418
|
+
},
|
|
419
|
+
],
|
|
420
|
+
]);
|
|
421
|
+
let current = "";
|
|
422
|
+
const dir = () => current;
|
|
423
|
+
const op = (item: string) => dispatchOp({ workLease: { item }, changesCheckout: true }, [step("build", { worktree: workLeaseOutput("worktree") })]);
|
|
424
|
+
|
|
425
|
+
test("it is .chant/work/<item> in the checkout, info/exclude gets /.chant/work/ once, and git status stays clean while it is there", async () => {
|
|
426
|
+
await withTestDir(async (d) => {
|
|
427
|
+
current = d;
|
|
428
|
+
await initRepo(d);
|
|
429
|
+
const seen: { worktree?: string; status?: string } = {};
|
|
430
|
+
const first = await run(op("W-20"), record(seen), d);
|
|
431
|
+
expect(first.workLease).toMatchObject({ released: true, outcome: "done" });
|
|
432
|
+
expect(seen.worktree).toBe(join(d, ".chant", "work", "W-20"));
|
|
433
|
+
expect(seen.status).toBe("");
|
|
434
|
+
const exclude = () => readFileSync(join(d, ".git", "info", "exclude"), "utf-8").split("\n").filter((l) => l === "/.chant/work/");
|
|
435
|
+
expect(exclude()).toHaveLength(1);
|
|
436
|
+
await run(op("W-21"), record(seen), d);
|
|
437
|
+
expect(exclude()).toHaveLength(1);
|
|
438
|
+
expect(git(["status", "--porcelain", "--untracked-files=all"], d).stdout).toBe("");
|
|
439
|
+
expect(git(["worktree", "list", "--porcelain"], d).stdout).not.toContain(".chant/work");
|
|
440
|
+
});
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
test("info/exclude is left alone when the project already ignores .chant/work", async () => {
|
|
444
|
+
await withTestDir(async (d) => {
|
|
445
|
+
current = d;
|
|
446
|
+
await initRepo(d);
|
|
447
|
+
writeFileSync(join(d, ".gitignore"), ".chant/\n");
|
|
448
|
+
git(["add", ".gitignore"], d);
|
|
449
|
+
git(["commit", "-q", "-m", "ignore"], d);
|
|
450
|
+
const before = existsSync(join(d, ".git", "info", "exclude")) ? readFileSync(join(d, ".git", "info", "exclude"), "utf-8") : "";
|
|
451
|
+
const seen: { worktree?: string; status?: string } = {};
|
|
452
|
+
await run(op("W-22"), record(seen), d);
|
|
453
|
+
expect(seen.status).toBe("");
|
|
454
|
+
const after = existsSync(join(d, ".git", "info", "exclude")) ? readFileSync(join(d, ".git", "info", "exclude"), "utf-8") : "";
|
|
455
|
+
expect(after).toBe(before);
|
|
456
|
+
});
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
test("a worktree a crashed run left under .git/chant-work with the branch checked out is removed, and the run goes on from the branch", async () => {
|
|
460
|
+
await withTestDir(async (d) => {
|
|
461
|
+
current = d;
|
|
462
|
+
await initRepo(d);
|
|
463
|
+
const old = join(d, ".git", "chant-work", "W-23");
|
|
464
|
+
expect(git(["worktree", "add", "-q", "-b", "chant/work/W-23", old, "HEAD"], d).status).toBe(0);
|
|
465
|
+
writeFileSync(join(old, "app", "earlier.ts"), "export const earlier = 1;\n");
|
|
466
|
+
git(["add", "-A"], old);
|
|
467
|
+
git(["commit", "-q", "-m", "an earlier run"], old);
|
|
468
|
+
const seen: { worktree?: string; status?: string } = {};
|
|
469
|
+
const result = await run(op("W-23"), record(seen), d);
|
|
470
|
+
expect(result.workLease).toMatchObject({ released: true, outcome: "done" });
|
|
471
|
+
expect(seen.worktree).toBe(join(d, ".chant", "work", "W-23"));
|
|
472
|
+
expect(existsSync(old)).toBe(false);
|
|
473
|
+
expect(git(["worktree", "list", "--porcelain"], d).stdout).not.toContain("chant-work");
|
|
474
|
+
expect(git(["show", "chant/work/W-23:app/earlier.ts"], d).stdout).toBe("export const earlier = 1;\n");
|
|
475
|
+
});
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
test("a worktree a crashed run left at .chant/work with the branch checked out is replaced", async () => {
|
|
479
|
+
await withTestDir(async (d) => {
|
|
480
|
+
current = d;
|
|
481
|
+
await initRepo(d);
|
|
482
|
+
const left = join(d, ".chant", "work", "W-24");
|
|
483
|
+
expect(git(["worktree", "add", "-q", "-b", "chant/work/W-24", left, "HEAD"], d).status).toBe(0);
|
|
484
|
+
writeFileSync(join(left, "app", "stray.ts"), "stray\n");
|
|
485
|
+
const seen: { worktree?: string; status?: string } = {};
|
|
486
|
+
const result = await run(op("W-24"), record(seen), d);
|
|
487
|
+
expect(result.workLease).toMatchObject({ released: true, outcome: "done" });
|
|
488
|
+
expect(existsSync(left)).toBe(false);
|
|
489
|
+
});
|
|
490
|
+
});
|
|
491
|
+
});
|
package/src/op/work-lease-run.ts
CHANGED
|
@@ -39,10 +39,11 @@
|
|
|
39
39
|
*
|
|
40
40
|
* An Op with `changesCheckout` (applying a build, `chant workspace upgrade`)
|
|
41
41
|
* must declare `workLease`, and its leased steps get a git worktree of their
|
|
42
|
-
* own: `chant/work/<item>` checked out
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
42
|
+
* own: `chant/work/<item>` checked out at `.chant/work/<item>` in the
|
|
43
|
+
* checkout (#2893), which `<git-common-dir>/info/exclude` keeps out of
|
|
44
|
+
* `git status`, from the checkout's HEAD or from the branch as an earlier run
|
|
45
|
+
* left it. The worktree is removed when the run ends and the branch keeps
|
|
46
|
+
* whatever the steps committed, for the runner to deliver. The checkout a coding agent is
|
|
46
47
|
* editing, its index and its uncommitted files, are never touched.
|
|
47
48
|
*
|
|
48
49
|
* A run that ends `not_done` keeps its attempt aside (#3147): whatever the
|
|
@@ -54,7 +55,7 @@
|
|
|
54
55
|
*/
|
|
55
56
|
|
|
56
57
|
import { existsSync, rmSync } from "node:fs";
|
|
57
|
-
import { isAbsolute,
|
|
58
|
+
import { isAbsolute, resolve, sep } from "node:path";
|
|
58
59
|
import { getRuntime } from "../runtime-adapter";
|
|
59
60
|
import {
|
|
60
61
|
claimWorkLease,
|
|
@@ -69,6 +70,7 @@ import {
|
|
|
69
70
|
type WorkLease,
|
|
70
71
|
} from "../lifecycle/work-lease";
|
|
71
72
|
import { resolveMemberLedger } from "../lifecycle/member-ledger";
|
|
73
|
+
import { excludeWorkDir, legacyWorkWorktreePath, LEGACY_WORK_DIR, removeEmptyWorkDirs, WORK_DIR, workWorktreePath } from "../lifecycle/work-dir";
|
|
72
74
|
import { currentHolderId } from "../lifecycle/lease";
|
|
73
75
|
import { parseDuration } from "./duration";
|
|
74
76
|
import { isStepOutputRef, stepOutput, type StepOutputRef } from "./step-output-ref";
|
|
@@ -271,21 +273,36 @@ export class RunWorkLease {
|
|
|
271
273
|
}
|
|
272
274
|
|
|
273
275
|
/**
|
|
274
|
-
* A worktree for `id` on `chant/work/[<member>/]<id>`,
|
|
275
|
-
*
|
|
276
|
-
*
|
|
276
|
+
* A worktree for `id` on `chant/work/[<member>/]<id>`, at
|
|
277
|
+
* `<checkout>/.chant/work/[<member>/]<id>` (#2893): inside the checkout, so
|
|
278
|
+
* a coding agent may write there and `node_modules` resolves, and in
|
|
279
|
+
* `<git-common-dir>/info/exclude`, so the checkout's own `git status` never
|
|
280
|
+
* lists it. Starts from the branch when an earlier run left it, from HEAD
|
|
281
|
+
* otherwise. A worktree an earlier run left on the branch, here or where
|
|
282
|
+
* older versions made it under the git directory, is removed first.
|
|
277
283
|
*/
|
|
278
284
|
private async makeWorktree(id: string, members: readonly string[]): Promise<{ worktree: string; branch: string }> {
|
|
279
285
|
const cwd = this.opts.cwd;
|
|
280
286
|
const common = await git(["rev-parse", "--git-common-dir"], cwd);
|
|
281
287
|
if (!common.ok) throw new Error(`Op "${this.config.name}" changes the checkout, but ${cwd} is not a git checkout: ${common.stderr}`);
|
|
282
288
|
const commonDir = isAbsolute(common.stdout) ? common.stdout : resolve(cwd, common.stdout);
|
|
289
|
+
// The top as the caller spells it (git's --show-toplevel resolves symbolic links).
|
|
290
|
+
const cdup = await git(["rev-parse", "--show-cdup"], cwd);
|
|
291
|
+
if (!cdup.ok) throw new Error(`Op "${this.config.name}" changes the checkout, but ${cwd} has no working tree: ${cdup.stderr}`);
|
|
292
|
+
const top = resolve(cwd, cdup.stdout);
|
|
283
293
|
const scope = members.join("/");
|
|
284
294
|
const branch = `${WORK_BRANCH_PREFIX}${scope ? `${scope}/` : ""}${id}`;
|
|
285
|
-
const worktree =
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
295
|
+
const worktree = workWorktreePath(top, members, id);
|
|
296
|
+
const ignored = (await git(["check-ignore", "-q", `${WORK_DIR}/${id}`], top)).ok;
|
|
297
|
+
excludeWorkDir(commonDir, ignored);
|
|
298
|
+
for (const stale of await this.staleWorktrees(branch, commonDir, members, id)) {
|
|
299
|
+
await git(["worktree", "remove", "--force", stale], cwd);
|
|
300
|
+
rmSync(stale, { recursive: true, force: true });
|
|
301
|
+
}
|
|
302
|
+
for (const dir of [worktree, legacyWorkWorktreePath(commonDir, members, id)]) {
|
|
303
|
+
if (!existsSync(dir)) continue;
|
|
304
|
+
await git(["worktree", "remove", "--force", dir], cwd);
|
|
305
|
+
rmSync(dir, { recursive: true, force: true });
|
|
289
306
|
}
|
|
290
307
|
await git(["worktree", "prune"], cwd);
|
|
291
308
|
const exists = (await git(["rev-parse", "--verify", "--quiet", `refs/heads/${branch}`], cwd)).ok;
|
|
@@ -298,6 +315,28 @@ export class RunWorkLease {
|
|
|
298
315
|
return { worktree, branch };
|
|
299
316
|
}
|
|
300
317
|
|
|
318
|
+
/**
|
|
319
|
+
* The registered worktrees that have `branch` checked out at a path chant
|
|
320
|
+
* makes work worktrees at: `.chant/work/[<member>/]<id>` in any checkout,
|
|
321
|
+
* or `<git-common-dir>/chant-work/[<member>/]<id>` where versions before
|
|
322
|
+
* #2893 made them. A run that crashed leaves one behind, and `git worktree
|
|
323
|
+
* add` then refuses the branch as already checked out.
|
|
324
|
+
*/
|
|
325
|
+
private async staleWorktrees(branch: string, commonDir: string, members: readonly string[], id: string): Promise<string[]> {
|
|
326
|
+
const list = await git(["worktree", "list", "--porcelain"], this.opts.cwd);
|
|
327
|
+
if (!list.ok) return [];
|
|
328
|
+
const tail = [...members, id].join("/");
|
|
329
|
+
const legacy = legacyWorkWorktreePath(commonDir, members, id).split(sep).join("/");
|
|
330
|
+
const out: string[] = [];
|
|
331
|
+
for (const block of list.stdout.split(/\n\s*\n/)) {
|
|
332
|
+
const path = /^worktree (.+)$/m.exec(block)?.[1];
|
|
333
|
+
if (!path || !block.split("\n").includes(`branch refs/heads/${branch}`)) continue;
|
|
334
|
+
const posixPath = path.split(sep).join("/");
|
|
335
|
+
if (posixPath.endsWith(`/${WORK_DIR}/${tail}`) || posixPath === legacy || posixPath.endsWith(`/${LEGACY_WORK_DIR}/${tail}`)) out.push(path);
|
|
336
|
+
}
|
|
337
|
+
return out;
|
|
338
|
+
}
|
|
339
|
+
|
|
301
340
|
private lose(why: string): void {
|
|
302
341
|
if (this.lost !== undefined) return;
|
|
303
342
|
this.lost = why;
|
|
@@ -408,6 +447,7 @@ export class RunWorkLease {
|
|
|
408
447
|
if (lease.worktree) {
|
|
409
448
|
await git(["worktree", "remove", "--force", lease.worktree], this.opts.cwd).catch(() => undefined);
|
|
410
449
|
rmSync(lease.worktree, { recursive: true, force: true });
|
|
450
|
+
removeEmptyWorkDirs(lease.worktree);
|
|
411
451
|
await git(["worktree", "prune"], this.opts.cwd).catch(() => undefined);
|
|
412
452
|
}
|
|
413
453
|
if (this.lost !== undefined) return base;
|
|
@@ -34,7 +34,7 @@ describe("the pr-forge bundle", () => {
|
|
|
34
34
|
expect(inputs.filter((i) => pattern.test(i)), String(pattern)).toEqual([]);
|
|
35
35
|
}
|
|
36
36
|
expect(inputs.map((i) => i.replace(/^.*?packages\//, "packages/")).sort()).toEqual(["packages/core/src/pr-forge.ts"]);
|
|
37
|
-
// About
|
|
38
|
-
expect(result.outputFiles.reduce((n, f) => n + f.contents.byteLength, 0)).toBeLessThan(
|
|
37
|
+
// About 33 KB today, after the pull request authors (#4000).
|
|
38
|
+
expect(result.outputFiles.reduce((n, f) => n + f.contents.byteLength, 0)).toBeLessThan(36 * 1024);
|
|
39
39
|
});
|
|
40
40
|
});
|
package/src/pr-forge.test.ts
CHANGED
|
@@ -85,6 +85,21 @@ describe("github", () => {
|
|
|
85
85
|
expect(calls[0].body).toEqual({ state: "success", context: "chant/plan", description: "2 members", target_url: "https://ci/run/1" });
|
|
86
86
|
});
|
|
87
87
|
|
|
88
|
+
test("the authors of a pull request are its opener and every commit's author and committer, every page read (#4000)", async () => {
|
|
89
|
+
const page1 = Array.from({ length: 100 }, () => ({ author: { login: "carol" }, committer: { login: "carol" } }));
|
|
90
|
+
const { fetch, calls } = fakeFetch({
|
|
91
|
+
"GET /repos/acme/infra/pulls/12": { number: 12, user: { login: "carol" } },
|
|
92
|
+
"GET /repos/acme/infra/pulls/12/commits?page=1": page1,
|
|
93
|
+
"GET /repos/acme/infra/pulls/12/commits?page=2": [{ author: null, committer: { login: "dave" } }, { author: { login: "erin" }, committer: { login: "web-flow" } }],
|
|
94
|
+
});
|
|
95
|
+
expect(await githubForge({ ...opts, fetch }).authors(12)).toEqual(["carol", "dave", "erin", "web-flow"]);
|
|
96
|
+
expect(calls.map((c) => c.url)).toEqual([
|
|
97
|
+
"https://api.github.com/repos/acme/infra/pulls/12",
|
|
98
|
+
"https://api.github.com/repos/acme/infra/pulls/12/commits?per_page=100&page=1",
|
|
99
|
+
"https://api.github.com/repos/acme/infra/pulls/12/commits?per_page=100&page=2",
|
|
100
|
+
]);
|
|
101
|
+
});
|
|
102
|
+
|
|
88
103
|
test("an approver is someone whose newest deciding review approves", async () => {
|
|
89
104
|
const { fetch } = fakeFetch({
|
|
90
105
|
"GET /repos/acme/infra/pulls/12/reviews": [
|
|
@@ -195,6 +210,15 @@ describe("forgejo", () => {
|
|
|
195
210
|
expect(calls[0].headers.Authorization).toBe("token t0k");
|
|
196
211
|
});
|
|
197
212
|
|
|
213
|
+
test("the authors of a pull request come from the same endpoints, paged by limit (#4000)", async () => {
|
|
214
|
+
const { fetch, calls } = fakeFetch({
|
|
215
|
+
"GET /api/v1/repos/acme/infra/pulls/4": { number: 4, user: { login: "ana" } },
|
|
216
|
+
"GET /api/v1/repos/acme/infra/pulls/4/commits": [{ author: { login: "ana" }, committer: { login: "ben" } }],
|
|
217
|
+
});
|
|
218
|
+
expect(await forgejoForge({ ...opts, fetch }).authors(4)).toEqual(["ana", "ben"]);
|
|
219
|
+
expect(calls.map((c) => c.url)).toContain("https://codeberg.org/api/v1/repos/acme/infra/pulls/4/commits?limit=50&page=1");
|
|
220
|
+
});
|
|
221
|
+
|
|
198
222
|
test("a dismissed approval no longer counts, and a comment review decides nothing", async () => {
|
|
199
223
|
const { fetch } = fakeFetch({
|
|
200
224
|
"GET /api/v1/repos/acme/infra/pulls/4/reviews": [
|
|
@@ -240,6 +264,24 @@ describe("gitlab", () => {
|
|
|
240
264
|
expect(calls[0].body).toEqual({ state: "failed", name: "chant/apply", description: "refused" });
|
|
241
265
|
});
|
|
242
266
|
|
|
267
|
+
test("the authors of a merge request: its author, and each commit email's account by the users search (#4000)", async () => {
|
|
268
|
+
const { fetch, calls } = fakeFetch({
|
|
269
|
+
"GET /api/v4/projects/42/merge_requests/5": { iid: 5, author: { username: "ana" } },
|
|
270
|
+
"GET /api/v4/projects/42/merge_requests/5/commits": [
|
|
271
|
+
{ author_email: "ana@example.com", committer_email: "Ben@Example.com" },
|
|
272
|
+
{ author_email: "ghost@example.com", committer_email: "ana@example.com" },
|
|
273
|
+
],
|
|
274
|
+
"GET /api/v4/users": (c: Call) =>
|
|
275
|
+
({ "ana@example.com": [{ username: "ana" }], "ben@example.com": [{ username: "ben" }] })[new URL(c.url).searchParams.get("search")!] ?? [],
|
|
276
|
+
});
|
|
277
|
+
expect(await gitlabForge({ ...opts, fetch }).authors(5)).toEqual(["ana", "ben"]);
|
|
278
|
+
expect(calls.filter((c) => new URL(c.url).pathname === "/api/v4/users").map((c) => new URL(c.url).searchParams.get("search"))).toEqual([
|
|
279
|
+
"ana@example.com",
|
|
280
|
+
"ben@example.com",
|
|
281
|
+
"ghost@example.com",
|
|
282
|
+
]);
|
|
283
|
+
});
|
|
284
|
+
|
|
243
285
|
test("approvers come from the approvals endpoint, merged requests from the commit", async () => {
|
|
244
286
|
const { fetch } = fakeFetch({
|
|
245
287
|
"GET /api/v4/projects/42/merge_requests/5/approvals": { approved_by: [{ user: { username: "ana" } }, { user: { username: "ben" } }] },
|
package/src/pr-forge.ts
CHANGED
|
@@ -349,6 +349,16 @@ export interface PrForge {
|
|
|
349
349
|
upsertIssue(marker: string, title: string, body: string): Promise<string | undefined>;
|
|
350
350
|
/** Logins whose standing review approves `pr`. */
|
|
351
351
|
approvers(pr: number): Promise<string[]>;
|
|
352
|
+
/**
|
|
353
|
+
* Logins of the people who wrote `pr` (#4000): its author, and the author
|
|
354
|
+
* and committer of each of its commits. GitHub and Forgejo name a commit's
|
|
355
|
+
* people by the account its email belongs to; a commit whose email is on no
|
|
356
|
+
* account adds nobody. GitLab names them by email only, so each email is
|
|
357
|
+
* looked up among the users (`GET /users?search=`), which matches an
|
|
358
|
+
* account's public email; an email no account shows publicly adds nobody.
|
|
359
|
+
* Sorted, without repeats.
|
|
360
|
+
*/
|
|
361
|
+
authors(pr: number): Promise<string[]>;
|
|
352
362
|
/** The merged pull request that produced `sha`, or null. */
|
|
353
363
|
pullRequestFor(sha: string): Promise<number | null>;
|
|
354
364
|
/**
|
|
@@ -667,6 +677,20 @@ function githubShaped(kind: "github" | "forgejo", opts: GithubForgeOptions): Git
|
|
|
667
677
|
}
|
|
668
678
|
return [...standing].filter(([, state]) => state === "APPROVED").map(([login]) => login).sort();
|
|
669
679
|
},
|
|
680
|
+
async authors(pr) {
|
|
681
|
+
const logins = new Set<string>();
|
|
682
|
+
const add = (v: unknown) => {
|
|
683
|
+
if (isObject(v) && typeof v.login === "string" && v.login !== "") logins.add(v.login);
|
|
684
|
+
};
|
|
685
|
+
const opened = await api.call("GET", `${repo}/pulls/${pr}`);
|
|
686
|
+
if (isObject(opened)) add(opened.user);
|
|
687
|
+
for (const c of await api.list(`${repo}/pulls/${pr}/commits`, pageParams, pageSize)) {
|
|
688
|
+
if (!isObject(c)) continue;
|
|
689
|
+
add(c.author);
|
|
690
|
+
add(c.committer);
|
|
691
|
+
}
|
|
692
|
+
return [...logins].sort();
|
|
693
|
+
},
|
|
670
694
|
async pullRequestFor(sha) {
|
|
671
695
|
if (kind === "forgejo") {
|
|
672
696
|
try {
|
|
@@ -922,6 +946,22 @@ export function gitlabForge(opts: GitlabForgeOptions): PrForge {
|
|
|
922
946
|
.filter((l): l is string => l !== undefined);
|
|
923
947
|
return [...new Set(logins)].sort();
|
|
924
948
|
},
|
|
949
|
+
async authors(mr) {
|
|
950
|
+
const logins = new Set<string>();
|
|
951
|
+
const merge = await api.call("GET", `${notes}/${mr}`);
|
|
952
|
+
const author = isObject(merge) ? authorOf(merge) : undefined;
|
|
953
|
+
if (author) logins.add(author);
|
|
954
|
+
const emails = new Set<string>();
|
|
955
|
+
for (const c of await api.list(`${notes}/${mr}/commits`, pageParams, 100)) {
|
|
956
|
+
if (!isObject(c)) continue;
|
|
957
|
+
for (const email of [str(c.author_email), str(c.committer_email)]) if (email) emails.add(email.toLowerCase());
|
|
958
|
+
}
|
|
959
|
+
for (const email of [...emails].sort()) {
|
|
960
|
+
const users = await api.call("GET", `/users?search=${encodeURIComponent(email)}`);
|
|
961
|
+
for (const u of Array.isArray(users) ? users : []) if (isObject(u) && typeof u.username === "string") logins.add(u.username);
|
|
962
|
+
}
|
|
963
|
+
return [...logins].sort();
|
|
964
|
+
},
|
|
925
965
|
async pullRequestFor(sha) {
|
|
926
966
|
const mrs = await api.call("GET", `${project}/repository/commits/${sha}/merge_requests`);
|
|
927
967
|
if (!Array.isArray(mrs)) return null;
|
|
@@ -41,6 +41,7 @@ describe("the pull-request loop bundle", () => {
|
|
|
41
41
|
"packages/core/src/pr-forge.ts",
|
|
42
42
|
"packages/core/src/pr-loop.ts",
|
|
43
43
|
]);
|
|
44
|
-
|
|
44
|
+
// About 65 KB today, after the pull request authors (#4000).
|
|
45
|
+
expect(result.outputFiles.reduce((n, f) => n + f.contents.byteLength, 0)).toBeLessThan(72 * 1024);
|
|
45
46
|
});
|
|
46
47
|
});
|
package/src/pr-loop.test.ts
CHANGED
|
@@ -14,7 +14,10 @@ import {
|
|
|
14
14
|
describePlanChanged,
|
|
15
15
|
memberCounts,
|
|
16
16
|
PR_REPORT_DEPLOYED_OUTPUTS_FLOOR,
|
|
17
|
+
PR_REPORT_PLANNED_OUTPUTS_FLOOR,
|
|
18
|
+
prRoundGate,
|
|
17
19
|
PR_REPORT_SCHEMA_ID,
|
|
20
|
+
PR_REPORT_WAVES_FLOOR,
|
|
18
21
|
prApproveCommand,
|
|
19
22
|
prNoteMarker,
|
|
20
23
|
PR_GATE_OP,
|
|
@@ -113,6 +116,34 @@ const resumedReport: PrReport = {
|
|
|
113
116
|
/** A plan whose members read `net` and `a` from the deployed estate (#4104). */
|
|
114
117
|
const deployedReport: PrReport = { ...planReport, deployedOutputs: { a: ["id"], net: ["cidr"], dns: [] } };
|
|
115
118
|
|
|
119
|
+
/** A plan where `app` reads `a.id`, which `a`'s plan knows only after apply, and `a.arn`, which it knows (#3999). */
|
|
120
|
+
const unknownReport: PrReport = {
|
|
121
|
+
...planReport,
|
|
122
|
+
deployedOutputs: { net: ["cidr"] },
|
|
123
|
+
plannedOutputs: { a: ["arn"] },
|
|
124
|
+
members: [planReport.members[0]!, { ...planReport.members[1]!, unknownInputs: ["a.id"], buildInputs: ["img.publish.digest"] }],
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const roundDoc = composeChangeSet([part("app", 3, [{ address: "terraform_data.app", action: "update" }])]);
|
|
128
|
+
/** The apply of {@link unknownReport}: `a` applied, and `app`, planned again, waits on round 2's gate (#3999). */
|
|
129
|
+
const waitingReport: PrReport = {
|
|
130
|
+
...unknownReport,
|
|
131
|
+
stage: "apply",
|
|
132
|
+
status: "waiting",
|
|
133
|
+
approval: { status: "approved", approvedBy: ["github:alice"] },
|
|
134
|
+
rounds: [{ round: 2, gate: prRoundGate("pr-apply", 2), digest: roundDoc.digest, components: ["app"], approval: { status: "pending" }, status: "waiting", changeSet: roundDoc }],
|
|
135
|
+
members: [
|
|
136
|
+
{ ...planReport.members[0]!, status: "applied" },
|
|
137
|
+
{
|
|
138
|
+
...unknownReport.members[1]!,
|
|
139
|
+
status: "waiting",
|
|
140
|
+
round: 2,
|
|
141
|
+
planDigest: `jcs1-sha256:${hex(3)}`,
|
|
142
|
+
held: "its plan read a.id before that output applied as another value",
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
};
|
|
146
|
+
|
|
116
147
|
describe("the pr-report schema", () => {
|
|
117
148
|
test("is a valid schema and names this module's id", () => {
|
|
118
149
|
expect(validSchema(schema)).toBe(true);
|
|
@@ -120,7 +151,7 @@ describe("the pr-report schema", () => {
|
|
|
120
151
|
});
|
|
121
152
|
|
|
122
153
|
test("every stage's report validates, and its change set validates against the change-set schema", () => {
|
|
123
|
-
for (const report of [planReport, refusedReport, appliedReport, resumedReport, deployedReport]) {
|
|
154
|
+
for (const report of [planReport, refusedReport, appliedReport, resumedReport, deployedReport, unknownReport, waitingReport]) {
|
|
124
155
|
expectValid(report);
|
|
125
156
|
expectValidChangeSet(report.changeSet);
|
|
126
157
|
expect(report.changeSet.digest).toBe(report.digest);
|
|
@@ -170,6 +201,27 @@ describe("the note", () => {
|
|
|
170
201
|
expect(schema.required).not.toContain("deployedOutputs");
|
|
171
202
|
});
|
|
172
203
|
|
|
204
|
+
test("a plan names the outputs read from upstream plans and the ones known only after apply (#3999)", () => {
|
|
205
|
+
const note = renderPrNote(unknownReport);
|
|
206
|
+
expect(note).toContain("Planned against the outputs this change's plans say its upstreams will have: `a` (`arn`).");
|
|
207
|
+
expect(note).toContain("Known only after apply: `app` reads `a.id`. This approval does not cover what those members do with them.");
|
|
208
|
+
expect(renderPrNote(planReport)).not.toContain("Known only after apply");
|
|
209
|
+
expect(note).toContain("From this pull request's build: `app` reads `img.publish.digest`. The image digest comes from this pull request's build");
|
|
210
|
+
expect(renderPrNote(planReport)).not.toContain("From this pull request's build");
|
|
211
|
+
expect(PR_REPORT_PLANNED_OUTPUTS_FLOOR).toBe("0.124.32");
|
|
212
|
+
expect(schema.required).not.toContain("plannedOutputs");
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test("a waiting apply names the round's digest and the command that approves it (#3999)", () => {
|
|
216
|
+
const note = renderPrNote(waitingReport, { approver: "github:<your-login>" });
|
|
217
|
+
expect(note).toContain("Applied 1 of 2 members; 1 wait on an approval of the plan made after their upstream applied.");
|
|
218
|
+
expect(note).toContain(`chant approve pr-12 pr-apply-round-2 --plan ${roundDoc.digest} --approver github:<your-login> --sign`);
|
|
219
|
+
expect(note).toContain("| `app` | app | waiting on its new plan |");
|
|
220
|
+
expect(prStatusState(waitingReport)).toBe("pending");
|
|
221
|
+
expect(prStatusDescription(waitingReport)).toBe("waiting: app planned again after an upstream applied, not approved yet");
|
|
222
|
+
expect(prRoundGate("pr-apply", 1)).toBe("pr-apply");
|
|
223
|
+
});
|
|
224
|
+
|
|
173
225
|
test("a small limit leaves the summary out rather than cutting the table", () => {
|
|
174
226
|
const note = renderPrNote(planReport, { limit: 800 });
|
|
175
227
|
expect(note).toContain("| `app` |");
|
|
@@ -177,10 +229,40 @@ describe("the note", () => {
|
|
|
177
229
|
});
|
|
178
230
|
});
|
|
179
231
|
|
|
232
|
+
describe("the record stage (#4168)", () => {
|
|
233
|
+
const recorded: PrReport = { ...planReport, stage: "record", status: "recorded", approval: { status: "approved", approvedBy: ["github:rev"] } };
|
|
234
|
+
const moved: PrReport = { ...recorded, approval: { status: "changed", approved: changedDigest } };
|
|
235
|
+
const unapproved: PrReport = { ...recorded, approval: { status: "pending" } };
|
|
236
|
+
|
|
237
|
+
test("its reports validate", () => {
|
|
238
|
+
for (const report of [recorded, moved, unapproved]) expectValid(report);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
test("a plan that differs from the approved one fails the chant/record status; an approved or unapproved one passes", () => {
|
|
242
|
+
expect(prStatusContext("record")).toBe("chant/record");
|
|
243
|
+
expect(prStatusContext("record", "net")).toBe("chant/record/net");
|
|
244
|
+
expect(prStatusState(recorded)).toBe("success");
|
|
245
|
+
expect(prStatusState(unapproved)).toBe("success");
|
|
246
|
+
expect(prStatusState(moved)).toBe("failure");
|
|
247
|
+
expect(prStatusDescription(recorded)).toMatch(/^recorded 2 members: the approved plan; plan [0-9a-f]{12}$/);
|
|
248
|
+
expect(prStatusDescription(moved)).toMatch(/^recorded 2 members: the plan differs from the approved one/);
|
|
249
|
+
expect(prStatusDescription(unapproved)).toMatch(/^recorded 2 members: no approval for this plan/);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
test("the note says nothing applied and a release deploys it", () => {
|
|
253
|
+
const note = renderPrNote(unapproved);
|
|
254
|
+
expect(note).toContain("### chant record for `prod`");
|
|
255
|
+
expect(note).toContain("Recorded the plan of 2 members on merge. Nothing applied: a release that carries this commit deploys it.");
|
|
256
|
+
expect(note).toContain("No approval stands for this plan. The release gate decides before it deploys.");
|
|
257
|
+
expect(renderPrNote(moved)).toContain("the plan changed since it was approved");
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
|
|
180
261
|
describe("the status", () => {
|
|
181
262
|
test("describes each stage in at most 140 characters", () => {
|
|
182
263
|
expect(prStatusDescription(planReport)).toMatch(/^2 members: 1 to change, 0 to destroy or replace; plan [0-9a-f]{12}$/);
|
|
183
264
|
expect(prStatusDescription(refusedReport)).toBe("refused: the plan changed after review");
|
|
265
|
+
expect(prStatusDescription({ ...refusedReport, refusal: "author-approved" })).toBe("refused: the approver wrote the change");
|
|
184
266
|
expect(prStatusDescription(appliedReport)).toMatch(/^applied 2 members/);
|
|
185
267
|
for (const r of [planReport, refusedReport, appliedReport]) expect(prStatusDescription(r).length).toBeLessThanOrEqual(140);
|
|
186
268
|
});
|
|
@@ -221,3 +303,66 @@ describe("a workspace member's loop (#3465)", () => {
|
|
|
221
303
|
expect(note).toContain("chant approve pr-12-network pr-apply");
|
|
222
304
|
});
|
|
223
305
|
});
|
|
306
|
+
|
|
307
|
+
describe("a gate per wave on merge (#4003)", () => {
|
|
308
|
+
const waveDigest = `jcs1-sha256:${hex(7)}`;
|
|
309
|
+
const wave1 = { wave: 1, components: ["a"], status: "applied" as const, approval: "pull-request" as const, gate: "pr-apply", digest: doc.digest, approvedBy: ["github:alice"] };
|
|
310
|
+
const waveApplied: PrReport = {
|
|
311
|
+
...appliedReport,
|
|
312
|
+
status: "wave-applied",
|
|
313
|
+
waveGate: "release",
|
|
314
|
+
waves: [wave1],
|
|
315
|
+
members: [{ ...planReport.members[0], status: "applied" }, planReport.members[1]],
|
|
316
|
+
};
|
|
317
|
+
const waiting: PrReport = {
|
|
318
|
+
...waveApplied,
|
|
319
|
+
status: "refused",
|
|
320
|
+
refusal: "wave-not-approved",
|
|
321
|
+
message: "Wave 2 was planned after the waves before it applied, and the pull request's approved plan does not cover it.",
|
|
322
|
+
waves: [
|
|
323
|
+
wave1,
|
|
324
|
+
{ wave: 2, components: ["app"], status: "waiting", approval: "wave", gate: "release-wave-2", digest: waveDigest, moved: ["app now writes app.main.cidr to a different value than the approved plan"] },
|
|
325
|
+
],
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
test("the reports validate, waveGate and waves are optional, and both came with the same chant", () => {
|
|
329
|
+
for (const r of [waveApplied, waiting]) expectValid(r);
|
|
330
|
+
expect(schema.required).not.toContain("waves");
|
|
331
|
+
expect(schema.required).not.toContain("waveGate");
|
|
332
|
+
expect(PR_REPORT_WAVES_FLOOR).toBe("0.124.32");
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
test("the note has a row per wave saying whose approval it applied under", () => {
|
|
336
|
+
const note = renderPrNote(waveApplied);
|
|
337
|
+
expect(note).toContain("Applied wave 1 of 2. The next wave runs in its own job.");
|
|
338
|
+
expect(note).toContain("| 1 | `a` | applied | the pull request's `pr-apply` by `github:alice` |");
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
test("a wave that waits names why its plan moved and the approve command for its own gate", () => {
|
|
342
|
+
const note = renderPrNote(waiting, { approver: "github:<your-login>" });
|
|
343
|
+
expect(note).toContain("Wave 2 waits at its gate `release-wave-2`; nothing in it applied.");
|
|
344
|
+
expect(note).toContain("| 2 | `app` | waiting | its own `release-wave-2`, not approved yet |");
|
|
345
|
+
expect(note).toContain("- app now writes app.main.cidr to a different value than the approved plan");
|
|
346
|
+
expect(note).toContain(`chant approve pr-12 release-wave-2 --plan ${waveDigest} --approver github:<your-login> --sign`);
|
|
347
|
+
golden("pr-apply-wave-waiting.note.golden.md", note);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
test("with --wave-gate the plan's note says later waves are planned again and may stop at their own gate; without it, nothing", () => {
|
|
351
|
+
const sentence = "On merge each wave after the first is planned again once the waves before it apply, and a wave whose plan moved stops at its own gate, `release-wave-<k>`, for approval.";
|
|
352
|
+
const planned = renderPrNote({ ...planReport, waveGate: "release" });
|
|
353
|
+
expect(planned).toContain(sentence);
|
|
354
|
+
expect(renderPrNote({ ...planReport, waveGate: "release", approval: { status: "approved", approvedBy: ["github:alice"] } })).toContain(sentence);
|
|
355
|
+
expect(renderPrNote(planReport)).not.toContain("planned again");
|
|
356
|
+
expect(renderPrNote(waveApplied)).not.toContain("planned again");
|
|
357
|
+
expectValid({ ...planReport, waveGate: "release" });
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
test("the status: a wave applied succeeds, a wave waiting on its approval is pending, a moved approval fails", () => {
|
|
361
|
+
expect(prStatusDescription(waveApplied)).toBe("applied wave 1 of 2 under the pull request's approval");
|
|
362
|
+
expect(prStatusState(waveApplied)).toBe("success");
|
|
363
|
+
expect(prStatusDescription(waiting)).toBe("wave 2 waits at release-wave-2: its plan moved, approve it");
|
|
364
|
+
expect(prStatusState(waiting)).toBe("pending");
|
|
365
|
+
expect(prStatusState({ ...waiting, refusal: "wave-plan-changed" })).toBe("failure");
|
|
366
|
+
expect(prStatusDescription({ ...waiting, waves: [wave1], refusal: "earlier-wave" })).toBe("refused: an earlier wave has not applied");
|
|
367
|
+
});
|
|
368
|
+
});
|