@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
|
@@ -3,11 +3,15 @@
|
|
|
3
3
|
* host. It polls the forge for the open pull requests into the target branch
|
|
4
4
|
* and for the branch's head, and runs the pull-request jobs with
|
|
5
5
|
* {@link ciRun} (./ci-run.ts): `plan` for each new pull request head, and
|
|
6
|
-
* `
|
|
7
|
-
* the previous poll saw (the push's `before`).
|
|
6
|
+
* `record` for each new commit on the target branch (#4168), measured from
|
|
7
|
+
* the head the previous poll saw (the push's `before`). The record applies
|
|
8
|
+
* nothing; with `applyOnMerge` (`--apply-on-merge`) the commit gets `apply`
|
|
9
|
+
* in its place, as before #4168. After each job settles it runs `ci tick`
|
|
10
|
+
* from the runs the jobs recorded, when the declaration has `ci.green`.
|
|
11
|
+
* What the list below says of the apply holds for it under `applyOnMerge`.
|
|
8
12
|
*
|
|
9
13
|
* - Dedupe. A state file in the state directory keeps every job it started,
|
|
10
|
-
* keyed by job and commit (`plan:<pr>:<sha>`, `apply:<sha>`), with how it
|
|
14
|
+
* keyed by job and commit (`plan:<pr>:<sha>`, `record:<sha>`, `apply:<sha>`), with how it
|
|
11
15
|
* ended, and the target head the last apply was measured to. A job already
|
|
12
16
|
* in it is not started again, unless it failed and `rerunFailed` asks for
|
|
13
17
|
* it: the forge's "re-run failed jobs".
|
|
@@ -23,8 +27,34 @@
|
|
|
23
27
|
* as its `--resume` record, so a re-run of a failed apply at the same
|
|
24
28
|
* commit finishes under the approval it has. This replaces the forge's CI
|
|
25
29
|
* cache.
|
|
30
|
+
* - A gate per wave (#4003). With `--wave-gate` the apply job runs
|
|
31
|
+
* `pr-apply --wave-gate`, which applies the waves in order and stops at
|
|
32
|
+
* the first whose moved plan waits on its own gate. The job fails, and the
|
|
33
|
+
* resume record keeps the waves that applied; once the wave's plan is
|
|
34
|
+
* approved, `rerunFailed` runs the apply again and it carries on from
|
|
35
|
+
* that wave.
|
|
26
36
|
* - Capacity. At most `maxJobs` jobs run at once. A job over the limit is not
|
|
27
37
|
* recorded, so a later poll starts it.
|
|
38
|
+
* - Fast-forwards only (#4122). An apply runs only when the target's new head
|
|
39
|
+
* descends from the head seen before it. Any other move, such as a force
|
|
40
|
+
* push back to an older commit, is recorded as seen and not applied.
|
|
41
|
+
* - `[skip ci]` (#4122). A target head or pull request head whose commit
|
|
42
|
+
* message says `[skip ci]` or `[ci skip]` is recorded as seen and not run,
|
|
43
|
+
* as Actions does.
|
|
44
|
+
* - Build requests (#4169). With `buildCommand`, each `ci/build/<sha>`
|
|
45
|
+
* tag the tick or `ci run --at` pushed is served once: the command runs
|
|
46
|
+
* in a fresh detached worktree of the commit, with `CHANT_BUILD_COMMIT`,
|
|
47
|
+
* `CHANT_BUILD_PHASES` (comma-separated) and `CHANT_BUILD_REASON` set,
|
|
48
|
+
* and is expected to run the phases' Ops (`chant run <op>`), whose
|
|
49
|
+
* records the tick reads. A build counts against `maxJobs`, and the state
|
|
50
|
+
* file keeps it under `build:<sha>`. Without `buildCommand` the tags are
|
|
51
|
+
* left alone.
|
|
52
|
+
* - The forge's ledger (#4121). Before each job, when no other job of this
|
|
53
|
+
* serve runs, the clone's `chant/lifecycle` follows the forge's: it is
|
|
54
|
+
* deleted when the forge has none, and reset to the forge's when neither
|
|
55
|
+
* contains the other. Each is a warning. A local branch ahead of the
|
|
56
|
+
* forge's keeps its facts, and the job's push sends them. Nothing here
|
|
57
|
+
* pushes the ledger.
|
|
28
58
|
*
|
|
29
59
|
* Host mode only: each job runs in a fresh worktree on this host as this
|
|
30
60
|
* user, with the clean environment `ciRun` gives it. A pull request from a
|
|
@@ -36,17 +66,20 @@
|
|
|
36
66
|
* turn a checked payload into the same triggers and schedule them.
|
|
37
67
|
*/
|
|
38
68
|
|
|
39
|
-
import { execFileSync } from "node:child_process";
|
|
40
|
-
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
69
|
+
import { execFileSync, spawn, spawnSync } from "node:child_process";
|
|
70
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
71
|
+
import { tmpdir } from "node:os";
|
|
41
72
|
import { join, resolve } from "node:path";
|
|
73
|
+
import { CI_BUILD_TAG_PREFIX, readBuildRequests, type BuildRequest } from "./ci-builds";
|
|
42
74
|
import { prApplyGroup, prApplyRecordKey } from "../components/pr-pipeline";
|
|
43
75
|
import { forgeFromEnv, type OpenPullRequest } from "../pr-forge";
|
|
44
76
|
import type { CommandContext } from "../cli/registry";
|
|
45
77
|
import { formatError, formatWarning } from "../cli/format";
|
|
46
78
|
import { acquireLease, currentHolderId, releaseLease, DEFAULT_LEASE_TTL_MS, type LeaseRecord } from "../lifecycle/lease";
|
|
47
79
|
import { parseDuration } from "../op/duration";
|
|
48
|
-
import {
|
|
49
|
-
import {
|
|
80
|
+
import { reportUrlOf } from "./ci-report-url";
|
|
81
|
+
import { CI_JOB_OPS, ciJobPlace, ciLoopOptions, ciSecretsOf, ciRun, ciStatusFromEnv, CiRunError, type CiJobName, type CiRunRequest, type CiRunResult } from "./ci-run";
|
|
82
|
+
import { CiGreenError, ciTick, RUNNER_KEY_ENV, signedRunsRequired } from "./ci-green";
|
|
50
83
|
import { gitTop } from "./tree";
|
|
51
84
|
import { ciServeRunnerKey, formatRunnerKeySetup, setupRunnerKey } from "./ci-runner-key";
|
|
52
85
|
|
|
@@ -61,10 +94,14 @@ export interface CiServeForge {
|
|
|
61
94
|
pullRequestFor?(sha: string): Promise<number | null>;
|
|
62
95
|
}
|
|
63
96
|
|
|
64
|
-
/**
|
|
97
|
+
/**
|
|
98
|
+
* A job the forge's state calls for: the plan of a pull request's head, or
|
|
99
|
+
* for a target-branch commit its record (#4168) or, with `applyOnMerge`,
|
|
100
|
+
* its apply.
|
|
101
|
+
*/
|
|
65
102
|
export type CiServeTrigger =
|
|
66
103
|
| { job: "plan"; commit: string; base: string; pr: number; rerun?: boolean }
|
|
67
|
-
| { job: "apply"; commit: string; base: string; rerun?: boolean };
|
|
104
|
+
| { job: "apply" | "record"; commit: string; base: string; rerun?: boolean };
|
|
68
105
|
|
|
69
106
|
/** One job in the state file. */
|
|
70
107
|
export interface CiServeJobState {
|
|
@@ -92,9 +129,13 @@ export interface CiServeState {
|
|
|
92
129
|
env: string;
|
|
93
130
|
branch: string;
|
|
94
131
|
member?: string;
|
|
95
|
-
/** The target branch's head the last apply was measured to, or the head
|
|
132
|
+
/** The target branch's head the last apply was measured to, or the last head seen and not applied. */
|
|
96
133
|
target?: string;
|
|
97
134
|
jobs: Record<string, CiServeJobState>;
|
|
135
|
+
/** Plans seen and not run, by state key, with why (#4122). */
|
|
136
|
+
skipped?: Record<string, "skip-ci">;
|
|
137
|
+
/** The build requests served (#4169), by `build:<sha>`, with how each ended. */
|
|
138
|
+
builds?: Record<string, { tag: string; status: "running" | "ok" | "fail"; startedAt: string; settledAt?: string }>;
|
|
98
139
|
}
|
|
99
140
|
|
|
100
141
|
/** What a pass did, in order, for the log. */
|
|
@@ -105,6 +146,11 @@ export type CiServeEvent =
|
|
|
105
146
|
| { type: "held"; job: CiJobName; commit: string; pr?: number; reason: "capacity" | "lease"; heldBy?: string }
|
|
106
147
|
| { type: "started"; job: CiJobName; commit: string; pr?: number; base: string; runId: string; attempt: number; resume?: string }
|
|
107
148
|
| { type: "settled"; job: CiJobName; commit: string; pr?: number; status: "ok" | "fail"; failure?: string; report?: string }
|
|
149
|
+
| { type: "not-run"; job: CiJobName; commit: string; pr?: number; reason: "not-fast-forward" | "skip-ci"; branch?: string; from?: string }
|
|
150
|
+
| { type: "ledger"; action: "deleted" | "reset"; local: string; forge?: string }
|
|
151
|
+
| { type: "build"; commit: string; phases: string[]; status: "started" | "ok" | "fail" | "held"; exitCode?: number }
|
|
152
|
+
/** `ci tick` after the pass (#4168): the tags it made from the runs. */
|
|
153
|
+
| { type: "tick"; made: string[] }
|
|
108
154
|
| { type: "error"; message: string };
|
|
109
155
|
|
|
110
156
|
/** The `ciRun` request fields `ci serve` passes through to every job: the generator's options and the runner's. */
|
|
@@ -126,6 +172,18 @@ export interface CiServeOptions {
|
|
|
126
172
|
authors?: readonly string[];
|
|
127
173
|
/** Run again the failed apply at the target's head, and the failed plans of open heads. */
|
|
128
174
|
rerunFailed?: boolean;
|
|
175
|
+
/**
|
|
176
|
+
* Apply each new target-branch commit (#4168), as `ci serve` did before
|
|
177
|
+
* #4168. Default false: each new commit gets the record job, which applies
|
|
178
|
+
* nothing.
|
|
179
|
+
*/
|
|
180
|
+
applyOnMerge?: boolean;
|
|
181
|
+
/**
|
|
182
|
+
* Run after each pass that settled a job (#4168): `ci tick` from the runs
|
|
183
|
+
* the jobs recorded. Returns the tags it made, or undefined when the
|
|
184
|
+
* declaration has no `ci.green`. The command passes one; a test may not.
|
|
185
|
+
*/
|
|
186
|
+
tick?: () => Promise<string[] | undefined>;
|
|
129
187
|
/** Passed to each job. `job.hostEnv` gets `GITHUB_REPOSITORY` set to `repo`. */
|
|
130
188
|
job?: CiServeJobOptions;
|
|
131
189
|
/** This process's lease identity. Default `currentHolderId()`. */
|
|
@@ -135,18 +193,26 @@ export interface CiServeOptions {
|
|
|
135
193
|
now?: () => Date;
|
|
136
194
|
/** Runs a job. Default {@link ciRun}. */
|
|
137
195
|
run?: (req: CiRunRequest) => Promise<CiRunResult>;
|
|
196
|
+
/**
|
|
197
|
+
* The shell line that builds a requested commit (#4169), run in a fresh
|
|
198
|
+
* worktree of it. Without it `ci/build/<sha>` tags are not served.
|
|
199
|
+
*/
|
|
200
|
+
buildCommand?: string;
|
|
201
|
+
/** Runs a build. Default: `sh -c <buildCommand>` in a detached worktree of the commit. */
|
|
202
|
+
build?: (input: { top: string; request: BuildRequest; command: string }) => Promise<number>;
|
|
138
203
|
}
|
|
139
204
|
|
|
140
205
|
/** The jobs this process runs now, by state key, shared by every pass of one loop. */
|
|
141
206
|
export interface CiServeRuntime {
|
|
142
|
-
|
|
207
|
+
/** Jobs and build requests (#4169) alike, by state key: they share `maxJobs`. */
|
|
208
|
+
inflight: Map<string, Promise<unknown>>;
|
|
143
209
|
}
|
|
144
210
|
|
|
145
211
|
export const newCiServeRuntime = (): CiServeRuntime => ({ inflight: new Map() });
|
|
146
212
|
|
|
147
213
|
/** The state key of a job. */
|
|
148
214
|
export function ciServeKey(t: { job: CiJobName; commit: string; pr?: number }): string {
|
|
149
|
-
return t.job === "plan" ? `plan:${t.pr}:${t.commit}` :
|
|
215
|
+
return t.job === "plan" ? `plan:${t.pr}:${t.commit}` : `${t.job}:${t.commit}`;
|
|
150
216
|
}
|
|
151
217
|
|
|
152
218
|
const message = (err: unknown): string => (err instanceof Error ? err.message : String(err));
|
|
@@ -159,6 +225,78 @@ function tryGit(cwd: string, args: string[]): string | undefined {
|
|
|
159
225
|
}
|
|
160
226
|
}
|
|
161
227
|
|
|
228
|
+
const LEDGER = "chant/lifecycle";
|
|
229
|
+
/** Where the forge's ledger tip is fetched to, a ref only this module writes. */
|
|
230
|
+
const FORGE_LEDGER = "refs/chant/ci-serve/forge-ledger";
|
|
231
|
+
|
|
232
|
+
/** `git` with its exit status, for the calls whose answer is the status. */
|
|
233
|
+
function gitStatus(cwd: string, args: string[]): { code: number; out: string } {
|
|
234
|
+
const r = spawnSync("git", args, { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"], env: { ...process.env, LC_ALL: "C" } });
|
|
235
|
+
return { code: r.status ?? -1, out: (r.stdout ?? "").trim() };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Make the clone's `chant/lifecycle` follow the forge's (#4121). The clone's
|
|
240
|
+
* branch outlives the forge's, so a ledger the forge deleted or rewrote would
|
|
241
|
+
* make the next pr-apply find the two diverged and stall.
|
|
242
|
+
*
|
|
243
|
+
* - The forge has none: the local branch and its remote-tracking ref go.
|
|
244
|
+
* - Neither contains the other: the local branch and its remote-tracking ref
|
|
245
|
+
* are reset to the forge's tip.
|
|
246
|
+
* - The local branch is ahead of the forge's, or behind it, or the same: it
|
|
247
|
+
* is left, and an apply's push sends what it holds that the forge lacks.
|
|
248
|
+
*
|
|
249
|
+
* Nothing is pushed, so a rewritten ledger never goes back to the forge. A
|
|
250
|
+
* forge that can't be reached leaves everything as it is; the job's own
|
|
251
|
+
* fetch says why.
|
|
252
|
+
*/
|
|
253
|
+
export function reconcileCiServeLedger(top: string): CiServeEvent | undefined {
|
|
254
|
+
const local = tryGit(top, ["rev-parse", "--verify", "--quiet", `refs/heads/${LEDGER}^{commit}`]);
|
|
255
|
+
const listed = gitStatus(top, ["ls-remote", "--exit-code", "origin", `refs/heads/${LEDGER}`]);
|
|
256
|
+
if (listed.code === 2) {
|
|
257
|
+
// A remote-tracking ref left from a deleted branch would be the next push's lease, and refuse it.
|
|
258
|
+
tryGit(top, ["update-ref", "-d", `refs/remotes/origin/${LEDGER}`]);
|
|
259
|
+
if (!local) return undefined;
|
|
260
|
+
tryGit(top, ["update-ref", "-d", `refs/heads/${LEDGER}`, local]);
|
|
261
|
+
return { type: "ledger", action: "deleted", local };
|
|
262
|
+
}
|
|
263
|
+
if (listed.code !== 0 || !local) return undefined;
|
|
264
|
+
const forge = listed.out.split(/\s+/)[0];
|
|
265
|
+
if (!forge || forge === local) return undefined;
|
|
266
|
+
if (gitStatus(top, ["fetch", "--quiet", "--no-tags", "origin", `+refs/heads/${LEDGER}:${FORGE_LEDGER}`]).code !== 0) return undefined;
|
|
267
|
+
try {
|
|
268
|
+
const ahead = gitStatus(top, ["merge-base", "--is-ancestor", forge, local]).code === 0;
|
|
269
|
+
const behind = gitStatus(top, ["merge-base", "--is-ancestor", local, forge]).code === 0;
|
|
270
|
+
if (ahead || behind) return undefined;
|
|
271
|
+
tryGit(top, ["update-ref", `refs/heads/${LEDGER}`, forge, local]);
|
|
272
|
+
tryGit(top, ["update-ref", `refs/remotes/origin/${LEDGER}`, forge]);
|
|
273
|
+
return { type: "ledger", action: "reset", local, forge };
|
|
274
|
+
} finally {
|
|
275
|
+
tryGit(top, ["update-ref", "-d", FORGE_LEDGER]);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** A commit message that asks for no CI run, as Actions and GitLab read it. */
|
|
280
|
+
const SKIP_CI = /\[(?:skip ci|ci skip)\]/i;
|
|
281
|
+
|
|
282
|
+
/** Whether `commit`'s message asks for no CI run. A commit this clone can't read is not skipped. */
|
|
283
|
+
function skipsCi(top: string, commit: string): boolean {
|
|
284
|
+
const body = tryGit(top, ["log", "-1", "--format=%B", commit]);
|
|
285
|
+
return body !== undefined && SKIP_CI.test(body);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* How the target branch moved from `from` to `head` (#4122): `fast-forward`
|
|
290
|
+
* when `head` descends from `from`, `other` when it does not, and `unknown`
|
|
291
|
+
* when `head` could not be fetched. Once `head` is here its history is too,
|
|
292
|
+
* so a `from` this clone lacks is not in that history.
|
|
293
|
+
*/
|
|
294
|
+
function targetMove(top: string, branch: string, from: string, head: string): "fast-forward" | "other" | "unknown" {
|
|
295
|
+
tryGit(top, ["fetch", "--quiet", "--no-tags", "origin", branch]);
|
|
296
|
+
if (gitStatus(top, ["cat-file", "-e", `${head}^{commit}`]).code !== 0) return "unknown";
|
|
297
|
+
return gitStatus(top, ["merge-base", "--is-ancestor", from, head]).code === 0 ? "fast-forward" : "other";
|
|
298
|
+
}
|
|
299
|
+
|
|
162
300
|
/** Where a serve keeps its state when `--state` names nowhere: beside the run events, in the repository's common git directory. */
|
|
163
301
|
export function defaultCiServeStateDir(cwd: string): string {
|
|
164
302
|
const common = tryGit(cwd, ["rev-parse", "--path-format=absolute", "--git-common-dir"]);
|
|
@@ -209,34 +347,55 @@ const failedEntry = (entry: CiServeJobState | undefined, key: string, rt: CiServ
|
|
|
209
347
|
|
|
210
348
|
/**
|
|
211
349
|
* One poll of the forge, as triggers: an apply when the target's head moved
|
|
212
|
-
* since the last apply (none on the first poll, which only
|
|
350
|
+
* since the last apply by a fast-forward (none on the first poll, which only
|
|
351
|
+
* notes the head, and none for a head whose message says `[skip ci]`),
|
|
213
352
|
* then a plan for each open pull request head not yet planned, oldest pull
|
|
214
353
|
* request first. A job already in the state file is left out unless it
|
|
215
|
-
* failed and `rerunFailed` is set. The first poll's head
|
|
216
|
-
* state file.
|
|
354
|
+
* failed and `rerunFailed` is set. The first poll's head, and a head seen
|
|
355
|
+
* and not applied, are written to the state file.
|
|
217
356
|
*/
|
|
218
357
|
export async function ciServeObserve(opts: CiServeOptions, rt: CiServeRuntime = newCiServeRuntime()): Promise<{ triggers: CiServeTrigger[]; events: CiServeEvent[] }> {
|
|
219
358
|
const place = servePlace(opts);
|
|
220
|
-
const
|
|
359
|
+
const fresh = { forge: opts.forge, repo: opts.repo, env: opts.env, branch: place.branch, ...(place.member ? { member: place.member } : {}) };
|
|
360
|
+
const state = readCiServeState(place.stateFile, fresh);
|
|
361
|
+
// Read again at the write: a job of this serve may have settled while the forge answered.
|
|
362
|
+
const update = (fn: (s: CiServeState) => void): void => {
|
|
363
|
+
const s = readCiServeState(place.stateFile, fresh);
|
|
364
|
+
fn(s);
|
|
365
|
+
writeState(place.stateFile, s);
|
|
366
|
+
};
|
|
221
367
|
const triggers: CiServeTrigger[] = [];
|
|
222
368
|
const events: CiServeEvent[] = [];
|
|
223
369
|
const [head, prs] = await Promise.all([opts.client.branchHead(place.branch), opts.client.openPullRequests(place.branch)]);
|
|
370
|
+
// #4168: a new target commit is recorded, and applied only with applyOnMerge.
|
|
371
|
+
const onMerge: "apply" | "record" = opts.applyOnMerge ? "apply" : "record";
|
|
224
372
|
|
|
225
373
|
if (head === undefined) {
|
|
226
374
|
events.push({ type: "no-branch", branch: place.branch });
|
|
227
375
|
} else if (state.target === undefined) {
|
|
228
376
|
// What the branch held before this runner watched it was applied by whatever ran then.
|
|
229
|
-
|
|
230
|
-
|
|
377
|
+
update((s) => {
|
|
378
|
+
s.target = head;
|
|
379
|
+
});
|
|
231
380
|
events.push({ type: "baseline", commit: head });
|
|
232
381
|
} else if (head !== state.target) {
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
382
|
+
const from = state.target;
|
|
383
|
+
const move = targetMove(place.top, place.branch, from, head);
|
|
384
|
+
const entry = state.jobs[ciServeKey({ job: onMerge, commit: head })];
|
|
385
|
+
if (move === "unknown") {
|
|
386
|
+
events.push({ type: "error", message: `${place.branch} is at ${head.slice(0, 12)} on the forge, and that commit could not be fetched; the next poll tries again` });
|
|
387
|
+
} else if (move === "other" || (!entry && skipsCi(place.top, head))) {
|
|
388
|
+
// #4122: a move that is not a fast-forward, or a head that asks for no run, is seen and not applied.
|
|
389
|
+
update((s) => {
|
|
390
|
+
s.target = head;
|
|
391
|
+
});
|
|
392
|
+
events.push({ type: "not-run", job: onMerge, commit: head, reason: move === "other" ? "not-fast-forward" : "skip-ci", ...(move === "other" ? { branch: place.branch, from } : {}) });
|
|
393
|
+
} else if (!entry) triggers.push({ job: onMerge, commit: head, base: from });
|
|
394
|
+
else if (opts.rerunFailed && failedEntry(entry, ciServeKey(entry), rt)) triggers.push({ job: onMerge, commit: head, base: entry.base, rerun: true });
|
|
236
395
|
} else {
|
|
237
|
-
const key = ciServeKey({ job:
|
|
396
|
+
const key = ciServeKey({ job: onMerge, commit: head });
|
|
238
397
|
const entry = state.jobs[key];
|
|
239
|
-
if (opts.rerunFailed && failedEntry(entry, key, rt)) triggers.push({ job:
|
|
398
|
+
if (opts.rerunFailed && failedEntry(entry, key, rt)) triggers.push({ job: onMerge, commit: head, base: entry.base, rerun: true });
|
|
240
399
|
}
|
|
241
400
|
|
|
242
401
|
const authors = opts.authors?.length ? new Set(opts.authors.map((a) => a.toLowerCase())) : undefined;
|
|
@@ -252,8 +411,17 @@ export async function ciServeObserve(opts: CiServeOptions, rt: CiServeRuntime =
|
|
|
252
411
|
const key = ciServeKey({ job: "plan", commit: pr.headSha, pr: pr.number });
|
|
253
412
|
const entry = state.jobs[key];
|
|
254
413
|
const base = pr.baseSha ?? head;
|
|
255
|
-
if (!base) continue;
|
|
256
|
-
if (!entry)
|
|
414
|
+
if (!base || state.skipped?.[key]) continue;
|
|
415
|
+
if (!entry) {
|
|
416
|
+
// #4122: a head whose message asks for no run is seen and not planned.
|
|
417
|
+
if (gitStatus(place.top, ["cat-file", "-e", `${pr.headSha}^{commit}`]).code !== 0) tryGit(place.top, ["fetch", "--quiet", "--no-tags", "origin", `refs/pull/${pr.number}/head`]);
|
|
418
|
+
if (skipsCi(place.top, pr.headSha)) {
|
|
419
|
+
update((s) => {
|
|
420
|
+
s.skipped = { ...s.skipped, [key]: "skip-ci" };
|
|
421
|
+
});
|
|
422
|
+
events.push({ type: "not-run", job: "plan", commit: pr.headSha, pr: pr.number, reason: "skip-ci" });
|
|
423
|
+
} else triggers.push({ job: "plan", commit: pr.headSha, base, pr: pr.number });
|
|
424
|
+
}
|
|
257
425
|
else if (opts.rerunFailed && failedEntry(entry, key, rt)) triggers.push({ job: "plan", commit: pr.headSha, base: entry.base, pr: pr.number, rerun: true });
|
|
258
426
|
}
|
|
259
427
|
return { triggers, events };
|
|
@@ -316,6 +484,12 @@ export async function ciServeSchedule(opts: CiServeOptions, triggers: readonly C
|
|
|
316
484
|
lease = got.lease;
|
|
317
485
|
}
|
|
318
486
|
|
|
487
|
+
// #4121: with no other job of this serve writing it, the clone's ledger follows the forge's.
|
|
488
|
+
if (rt.inflight.size === 0) {
|
|
489
|
+
const ledger = reconcileCiServeLedger(place.top);
|
|
490
|
+
if (ledger) emit(ledger);
|
|
491
|
+
}
|
|
492
|
+
|
|
319
493
|
const previous = readCiServeState(place.stateFile, fresh).jobs[key];
|
|
320
494
|
const attempt = (previous?.attempts ?? 0) + 1;
|
|
321
495
|
const runId = `chant-serve-${now().getTime()}-${t.job}-${t.commit.slice(0, 8)}`;
|
|
@@ -324,7 +498,7 @@ export async function ciServeSchedule(opts: CiServeOptions, triggers: readonly C
|
|
|
324
498
|
const entry: CiServeJobState = { job: t.job, commit: t.commit, base: t.base, ...pr, status: "running", runId, attempts: attempt, startedAt: now().toISOString() };
|
|
325
499
|
update((s) => {
|
|
326
500
|
s.jobs[key] = entry;
|
|
327
|
-
if (t.job
|
|
501
|
+
if (t.job !== "plan" && !t.rerun) s.target = t.commit;
|
|
328
502
|
});
|
|
329
503
|
emit({ type: "started", job: t.job, commit: t.commit, ...pr, base: t.base, runId, attempt, ...(resume && existsSync(resume) ? { resume } : {}) });
|
|
330
504
|
|
|
@@ -339,7 +513,7 @@ export async function ciServeSchedule(opts: CiServeOptions, triggers: readonly C
|
|
|
339
513
|
emit({ type: "error", message: `${key} settled, and the state file could not be written: ${message(err)}` });
|
|
340
514
|
}
|
|
341
515
|
emit({ type: "settled", job: t.job, commit: t.commit, ...pr, status: settled.status === "ok" ? "ok" : "fail", ...(settled.failure ? { failure: settled.failure } : {}), ...(settled.report ? { report: settled.report } : {}) });
|
|
342
|
-
return settled;
|
|
516
|
+
return serveTick(opts).then(() => settled);
|
|
343
517
|
});
|
|
344
518
|
rt.inflight.set(key, job);
|
|
345
519
|
started.push(job);
|
|
@@ -367,7 +541,7 @@ async function runJob(
|
|
|
367
541
|
tryGit(place.top, ["fetch", "--quiet", "--no-tags", "origin", t.job === "plan" ? `refs/pull/${t.pr}/head` : place.branch]);
|
|
368
542
|
const hostEnv = { ...(opts.job?.hostEnv ?? process.env), GITHUB_REPOSITORY: opts.repo };
|
|
369
543
|
// An apply is for a merge: pass the pull request whose merge commit it is, so the commit status link and the run index use `<pr>/<commit>`. None found, or a forge that errs, is today's behaviour.
|
|
370
|
-
if (t.job
|
|
544
|
+
if (t.job !== "plan" && opts.client.pullRequestFor) merged = (await opts.client.pullRequestFor(t.commit).catch(() => null)) ?? undefined;
|
|
371
545
|
const run = opts.run ?? ciRun;
|
|
372
546
|
const r = await run({
|
|
373
547
|
...opts.job,
|
|
@@ -393,13 +567,101 @@ async function runJob(
|
|
|
393
567
|
}
|
|
394
568
|
}
|
|
395
569
|
|
|
396
|
-
/** One pass: observe, schedule, and with `wait` wait for the jobs it started. */
|
|
570
|
+
/** One pass: observe, schedule, serve build requests, and with `wait` wait for the jobs it started. */
|
|
397
571
|
export async function ciServePass(opts: CiServeOptions, rt: CiServeRuntime = newCiServeRuntime(), wait = true): Promise<{ events: CiServeEvent[]; jobs: CiServeJobState[] }> {
|
|
398
572
|
const observed = await ciServeObserve(opts, rt);
|
|
399
573
|
for (const e of observed.events) opts.log?.(e);
|
|
400
574
|
const scheduled = await ciServeSchedule(opts, observed.triggers, rt);
|
|
575
|
+
const builds = opts.buildCommand ? ciServeBuilds(opts, rt) : { started: [], events: [] };
|
|
401
576
|
const jobs = wait ? await Promise.all(scheduled.started) : [];
|
|
402
|
-
|
|
577
|
+
if (wait) await Promise.all(builds.started);
|
|
578
|
+
return { events: [...observed.events, ...scheduled.events, ...builds.events], jobs };
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* Serve the build requests (#4169): fetch the `ci/build/*` tags, and start
|
|
583
|
+
* `buildCommand` for each one not in the state file yet, oldest first, while
|
|
584
|
+
* fewer than `maxJobs` jobs run. A build that fails is recorded and not run
|
|
585
|
+
* again; the tick decides what its phases say.
|
|
586
|
+
*/
|
|
587
|
+
export function ciServeBuilds(opts: CiServeOptions, rt: CiServeRuntime): { started: Promise<void>[]; events: CiServeEvent[] } {
|
|
588
|
+
const place = servePlace(opts);
|
|
589
|
+
const fresh = { forge: opts.forge, repo: opts.repo, env: opts.env, branch: place.branch, ...(place.member ? { member: place.member } : {}) };
|
|
590
|
+
const update = (fn: (s: CiServeState) => void): void => {
|
|
591
|
+
const st = readCiServeState(place.stateFile, fresh);
|
|
592
|
+
fn(st);
|
|
593
|
+
writeState(place.stateFile, st);
|
|
594
|
+
};
|
|
595
|
+
const events: CiServeEvent[] = [];
|
|
596
|
+
const started: Promise<void>[] = [];
|
|
597
|
+
tryGit(place.top, ["fetch", "--quiet", "--prune", "origin", `+refs/tags/${CI_BUILD_TAG_PREFIX}*:refs/tags/${CI_BUILD_TAG_PREFIX}*`]);
|
|
598
|
+
const served = readCiServeState(place.stateFile, fresh).builds ?? {};
|
|
599
|
+
const max = opts.maxJobs ?? 1;
|
|
600
|
+
const now = opts.now ?? (() => new Date());
|
|
601
|
+
const requests = [...readBuildRequests(place.top).values()].sort((a, b) => a.at.localeCompare(b.at));
|
|
602
|
+
for (const request of requests) {
|
|
603
|
+
const key = `build:${request.commit}`;
|
|
604
|
+
if (served[key]?.tag === request.tag || rt.inflight.has(key)) continue;
|
|
605
|
+
const log = (e: CiServeEvent) => {
|
|
606
|
+
events.push(e);
|
|
607
|
+
opts.log?.(e);
|
|
608
|
+
};
|
|
609
|
+
if (rt.inflight.size >= max) {
|
|
610
|
+
log({ type: "build", commit: request.commit, phases: request.phases, status: "held" });
|
|
611
|
+
continue;
|
|
612
|
+
}
|
|
613
|
+
update((st) => {
|
|
614
|
+
st.builds = { ...st.builds, [key]: { tag: request.tag, status: "running", startedAt: now().toISOString() } };
|
|
615
|
+
});
|
|
616
|
+
log({ type: "build", commit: request.commit, phases: request.phases, status: "started" });
|
|
617
|
+
const build = opts.build ?? runBuild;
|
|
618
|
+
const job = build({ top: place.top, request, command: opts.buildCommand! })
|
|
619
|
+
.catch(() => 1)
|
|
620
|
+
.then((code) => {
|
|
621
|
+
update((st) => {
|
|
622
|
+
st.builds = { ...st.builds, [key]: { tag: request.tag, status: code === 0 ? "ok" : "fail", startedAt: st.builds?.[key]?.startedAt ?? now().toISOString(), settledAt: now().toISOString() } };
|
|
623
|
+
});
|
|
624
|
+
opts.log?.({ type: "build", commit: request.commit, phases: request.phases, status: code === 0 ? "ok" : "fail", exitCode: code });
|
|
625
|
+
});
|
|
626
|
+
const tracked = job.finally(() => rt.inflight.delete(key));
|
|
627
|
+
rt.inflight.set(key, tracked);
|
|
628
|
+
started.push(tracked);
|
|
629
|
+
}
|
|
630
|
+
return { started, events };
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/** `sh -c <command>` in a fresh detached worktree of the request's commit, removed after. Resolves to the exit code. */
|
|
634
|
+
async function runBuild(input: { top: string; request: BuildRequest; command: string }): Promise<number> {
|
|
635
|
+
const dir = mkdtempSync(join(tmpdir(), "chant-ci-build-"));
|
|
636
|
+
try {
|
|
637
|
+
execFileSync("git", ["worktree", "add", "--quiet", "--detach", dir, input.request.commit], { cwd: input.top, stdio: ["ignore", "pipe", "pipe"] });
|
|
638
|
+
return await new Promise<number>((done) => {
|
|
639
|
+
const child = spawn("sh", ["-c", input.command], {
|
|
640
|
+
cwd: dir,
|
|
641
|
+
stdio: ["ignore", "inherit", "inherit"],
|
|
642
|
+
env: { ...process.env, CHANT_BUILD_COMMIT: input.request.commit, CHANT_BUILD_PHASES: input.request.phases.join(","), CHANT_BUILD_REASON: input.request.reason },
|
|
643
|
+
});
|
|
644
|
+
child.on("error", () => done(127));
|
|
645
|
+
child.on("close", (code) => done(code ?? 1));
|
|
646
|
+
});
|
|
647
|
+
} finally {
|
|
648
|
+
tryGit(input.top, ["worktree", "remove", "--force", dir]);
|
|
649
|
+
rmSync(dir, { recursive: true, force: true });
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/** `ci tick` after a job settled (#4168), logged; a tick that fails is an error event and never fails the job. */
|
|
654
|
+
async function serveTick(opts: CiServeOptions): Promise<CiServeEvent | undefined> {
|
|
655
|
+
if (!opts.tick) return undefined;
|
|
656
|
+
let event: CiServeEvent | undefined;
|
|
657
|
+
try {
|
|
658
|
+
const made = await opts.tick();
|
|
659
|
+
event = made === undefined ? undefined : { type: "tick", made };
|
|
660
|
+
} catch (err) {
|
|
661
|
+
event = { type: "error", message: `ci tick after a job settled: ${message(err)}` };
|
|
662
|
+
}
|
|
663
|
+
if (event) opts.log?.(event);
|
|
664
|
+
return event;
|
|
403
665
|
}
|
|
404
666
|
|
|
405
667
|
/**
|
|
@@ -442,6 +704,21 @@ export function formatCiServeEvent(e: CiServeEvent): string {
|
|
|
442
704
|
return `${at(e.job, e.commit, e.pr)} started, run ${e.runId}, attempt ${e.attempt}${e.resume ? ", resuming" : ""}`;
|
|
443
705
|
case "settled":
|
|
444
706
|
return `${at(e.job, e.commit, e.pr)} ${e.status === "ok" ? "passed" : `failed${e.failure ? `: ${e.failure}` : ""}`}${e.report ? `; report ${e.report}` : ""}`;
|
|
707
|
+
case "not-run":
|
|
708
|
+
return e.reason === "skip-ci"
|
|
709
|
+
? `${at(e.job, e.commit, e.pr)} is not run: its commit message says [skip ci]`
|
|
710
|
+
: `${e.branch ?? "the target branch"} moved from ${e.from?.slice(0, 12) ?? "a commit"} to ${e.commit.slice(0, 12)}, which is not a fast-forward, so ${e.commit.slice(0, 12)} is recorded as seen and not applied`;
|
|
711
|
+
case "ledger":
|
|
712
|
+
return e.action === "deleted"
|
|
713
|
+
? `the forge has no ${LEDGER}, so this clone's, at ${e.local.slice(0, 12)}, was deleted`
|
|
714
|
+
: `the forge's ${LEDGER} at ${e.forge!.slice(0, 12)} does not contain this clone's at ${e.local.slice(0, 12)}, so this clone's was reset to the forge's and not pushed`;
|
|
715
|
+
case "build": {
|
|
716
|
+
const what = `the build of ${e.phases.join(", ")} at ${e.commit.slice(0, 12)}`;
|
|
717
|
+
if (e.status === "held") return `${what} waits: --max-jobs jobs run`;
|
|
718
|
+
return e.status === "started" ? `${what} started` : e.status === "ok" ? `${what} passed` : `${what} failed${e.exitCode !== undefined ? ` with exit code ${e.exitCode}` : ""}`;
|
|
719
|
+
}
|
|
720
|
+
case "tick":
|
|
721
|
+
return e.made.length > 0 ? `ci tick made ${e.made.join(", ")}` : "ci tick: no tag due";
|
|
445
722
|
case "error":
|
|
446
723
|
return `error: ${e.message}`;
|
|
447
724
|
}
|
|
@@ -478,7 +755,7 @@ export function runnerKeySetupCommand(ctx: CommandContext): number {
|
|
|
478
755
|
const USAGE =
|
|
479
756
|
"chant ci serve --setup-key [--trust [--principal <name>]] [--state <dir>] [--branch <name>] | " +
|
|
480
757
|
"chant ci serve --forge forgejo|github --repo <owner/repo> --env <env> [--interval <s>] [--max-jobs <n>] [--state <dir>] " +
|
|
481
|
-
"[--author <login>]... [--once] [--rerun-failed] [--gate <name>] [--branch <name>] [--events-json] [--report-url <url>] [--no-status] [--status-url <url template>] " +
|
|
758
|
+
"[--author <login>]... [--once] [--rerun-failed] [--build-command <line>] [--gate <name>] [--wave-gate <name>] [--branch <name>] [--events-json] [--report-url <url>] [--no-status] [--status-url <url template>] " +
|
|
482
759
|
"[--image <image>] [--before-script <line>]... [--pr-build] [--pr-build-command <line>] [--chant <command>] [--pass-env <NAME>]... [--secrets <file>] [--key <runner key.pem>] [--unsigned]";
|
|
483
760
|
|
|
484
761
|
const ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
@@ -501,6 +778,8 @@ export async function runCiServe(ctx: CommandContext): Promise<number> {
|
|
|
501
778
|
if (!args.ciRepo) throw new CiRunError("--repo names the repository whose pull requests ci serve polls: --repo <owner/repo>");
|
|
502
779
|
if (!args.env) throw new CiRunError("--env names the environment the pipeline is for: --env <env>");
|
|
503
780
|
for (const name of args.passEnv ?? []) if (!ENV_NAME.test(name)) throw new CiRunError(`--pass-env takes a variable's name, not ${JSON.stringify(name)}`);
|
|
781
|
+
// #4168: a gate per wave gates the apply on merge, which a merge runs only when opted in.
|
|
782
|
+
if (args.waveGate && !args.applyOnMerge) throw new CiRunError("--wave-gate gates the apply on merge, so it needs --apply-on-merge (#4168); without it a merge gets the record job, which applies nothing");
|
|
504
783
|
const intervalMs = ciServeInterval(args.interval);
|
|
505
784
|
const cwd = process.cwd();
|
|
506
785
|
const top = gitTop(cwd);
|
|
@@ -508,17 +787,33 @@ export async function runCiServe(ctx: CommandContext): Promise<number> {
|
|
|
508
787
|
const stateDir = args.statePath ? resolve(args.statePath) : defaultCiServeStateDir(cwd);
|
|
509
788
|
const branch = args.branch ?? "main";
|
|
510
789
|
// #4099: the host's key, checked against .chant/trust.json at the target's head before the first poll.
|
|
511
|
-
|
|
790
|
+
// #4156: signing is opt-in. Without ci.green.signedSince no key is quiet, and an untrusted one warns once and serves unsigned.
|
|
791
|
+
const keyed = ciServeRunnerKey({
|
|
792
|
+
top,
|
|
793
|
+
stateDir,
|
|
794
|
+
branch,
|
|
795
|
+
...(args.key !== undefined ? { keyFile: resolve(args.key) } : {}),
|
|
796
|
+
envKey: process.env[RUNNER_KEY_ENV] || undefined,
|
|
797
|
+
unsigned: args.unsigned,
|
|
798
|
+
required: signedRunsRequired(cwd),
|
|
799
|
+
});
|
|
512
800
|
const fromFlag = keyed.fromFlag;
|
|
513
801
|
const runnerKey = keyed.pem;
|
|
514
|
-
console.error(
|
|
802
|
+
if (keyed.warning) console.error(formatWarning({ message: keyed.warning }));
|
|
803
|
+
if (runnerKey !== undefined) console.error(`ci serve: signing runs with the runner key from ${keyed.source}`);
|
|
804
|
+
else if (keyed.note && args.verbose) console.error(`ci serve: ${keyed.note}`);
|
|
515
805
|
// #4096: read once, so a loose or broken secrets file stops the serve before its first poll.
|
|
516
806
|
const secrets = ciSecretsOf(args.ciSecrets);
|
|
807
|
+
// #4160: without --report-url, the declaration's ci.reportUrl.
|
|
808
|
+
const reportUrl = await reportUrlOf(args.reportUrl, cwd);
|
|
809
|
+
if (reportUrl && !args.reportUrl) console.error(`ci serve: reports and the run index go to ${reportUrl}, the declaration's ci.reportUrl`);
|
|
517
810
|
const hostEnv = { ...process.env, GITHUB_REPOSITORY: args.ciRepo };
|
|
518
811
|
const client = forgeFromEnv(forge, hostEnv);
|
|
519
812
|
const { status, warning } = ciStatusFromEnv({ forge, hostEnv, noStatus: args.noStatus, statusUrl: args.statusUrl });
|
|
520
813
|
if (warning) console.error(formatWarning({ message: warning }));
|
|
521
814
|
if (!client.openPullRequests || !client.branchHead) throw new CiRunError(`the ${forge} client cannot list pull requests`);
|
|
815
|
+
// #4168: a host that applied each merge before the upgrade hears that it no longer does.
|
|
816
|
+
if (!args.applyOnMerge) console.error("ci serve: each new commit on the target branch gets the record job, which applies nothing (#4168); --apply-on-merge applies it");
|
|
522
817
|
const opts: CiServeOptions = {
|
|
523
818
|
cwd,
|
|
524
819
|
forge,
|
|
@@ -529,15 +824,19 @@ export async function runCiServe(ctx: CommandContext): Promise<number> {
|
|
|
529
824
|
...(args.maxJobs ? { maxJobs: args.maxJobs } : {}),
|
|
530
825
|
...(args.ciAuthors?.length ? { authors: args.ciAuthors } : {}),
|
|
531
826
|
...(args.rerunFailed ? { rerunFailed: true } : {}),
|
|
827
|
+
...(args.ciBuildCommand ? { buildCommand: args.ciBuildCommand } : {}),
|
|
828
|
+
...(args.applyOnMerge ? { applyOnMerge: true } : {}),
|
|
829
|
+
// #4168: the tick from the runs the jobs recorded, signed with the runs' key. No ci.green, no tick.
|
|
830
|
+
tick: async () => {
|
|
831
|
+
try {
|
|
832
|
+
return (await ciTick({ cwd, source: "runs", ...(runnerKey !== undefined ? { runnerKey } : {}) })).made;
|
|
833
|
+
} catch (err) {
|
|
834
|
+
if (err instanceof CiGreenError && err.code === "ci-green-undeclared") return undefined;
|
|
835
|
+
throw err;
|
|
836
|
+
}
|
|
837
|
+
},
|
|
532
838
|
job: {
|
|
533
|
-
loop: {
|
|
534
|
-
...(args.gate ? { gate: args.gate } : {}),
|
|
535
|
-
...(args.branch ? { branch: args.branch } : {}),
|
|
536
|
-
...(args.eventsJson ? { events: true } : {}),
|
|
537
|
-
...(args.reportUrl ? { reportUrl: args.reportUrl } : {}),
|
|
538
|
-
...(args.prBuild ? { build: true } : {}),
|
|
539
|
-
...(args.prBuildCommand ? { buildCommand: args.prBuildCommand } : {}),
|
|
540
|
-
},
|
|
839
|
+
loop: ciLoopOptions(reportUrl ? { ...args, reportUrl } : args),
|
|
541
840
|
...(args.runIndexLimit ? { runIndexLimit: args.runIndexLimit } : {}),
|
|
542
841
|
...(args.image ? { image: args.image } : {}),
|
|
543
842
|
...(args.beforeScript?.length ? { beforeScript: args.beforeScript } : {}),
|
|
@@ -550,7 +849,7 @@ export async function runCiServe(ctx: CommandContext): Promise<number> {
|
|
|
550
849
|
hostEnv,
|
|
551
850
|
stepOutput: (line) => void process.stderr.write(`${line}\n`),
|
|
552
851
|
},
|
|
553
|
-
log: (e) => console.error(e.type === "error" ? formatWarning({ message: formatCiServeEvent(e) }) : `ci serve: ${formatCiServeEvent(e)}`),
|
|
852
|
+
log: (e) => console.error(e.type === "error" || e.type === "ledger" ? formatWarning({ message: formatCiServeEvent(e) }) : `ci serve: ${formatCiServeEvent(e)}`),
|
|
554
853
|
};
|
|
555
854
|
if (args.once) {
|
|
556
855
|
const pass = await ciServePass(opts);
|