@intentius/chant 0.124.31 → 0.124.32
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/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 +23 -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/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 +14 -0
- package/dist/cli/handlers/pr.d.ts.map +1 -1
- package/dist/cli/handlers/pr.js +488 -32
- package/dist/cli/handlers/promote.d.ts.map +1 -1
- package/dist/cli/handlers/promote.js +3 -1
- 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 +44 -16
- package/dist/cli/parse-args.d.ts.map +1 -1
- package/dist/cli/parse-args.js +15 -0
- package/dist/cli/registry.d.ts +13 -4
- 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/driver.d.ts +38 -0
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/driver.js +47 -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/pr-pipeline.d.ts +65 -2
- package/dist/components/pr-pipeline.d.ts.map +1 -1
- package/dist/components/pr-pipeline.js +107 -15
- package/dist/components/pr-run.d.ts +127 -29
- package/dist/components/pr-run.d.ts.map +1 -1
- package/dist/components/pr-run.js +290 -70
- package/dist/components/wave-plan.d.ts +5 -2
- package/dist/components/wave-plan.d.ts.map +1 -1
- package/dist/components/wave-plan.js +12 -1
- 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/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 +2 -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/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/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/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/factory.js +1 -1
- 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/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 +141 -4
- package/dist/pr-loop.d.ts.map +1 -1
- package/dist/pr-loop.js +122 -6
- package/dist/pr-report-url.d.ts +2 -2
- package/dist/pr-report-url.d.ts.map +1 -1
- package/dist/pr-report-url.js +5 -5
- 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 +208 -208
- 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/checks/boxes.d.ts +6 -6
- package/dist/workspace/checks/boxes.js +9 -9
- package/dist/workspace/ci-green-forge.d.ts +6 -4
- package/dist/workspace/ci-green-forge.d.ts.map +1 -1
- package/dist/workspace/ci-green.d.ts +28 -12
- package/dist/workspace/ci-green.d.ts.map +1 -1
- package/dist/workspace/ci-green.js +49 -21
- package/dist/workspace/ci-list.schema.json +12 -6
- 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 +12 -0
- 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 +5 -3
- package/dist/workspace/ci-run.d.ts +75 -2
- package/dist/workspace/ci-run.d.ts.map +1 -1
- package/dist/workspace/ci-run.js +195 -53
- 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-serve.d.ts +54 -4
- package/dist/workspace/ci-serve.d.ts.map +1 -1
- package/dist/workspace/ci-serve.js +175 -26
- package/dist/workspace/declaration.d.ts +8 -0
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.js +11 -2
- package/dist/workspace/declaration.schema.json +7 -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 +6 -1
- package/dist/workspace/ls.d.ts.map +1 -1
- package/dist/workspace/ls.js +1 -0
- package/dist/workspace/ls.schema.json +7 -1
- 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 +74 -7
- package/dist/workspace/reason-codes.d.ts +1 -1
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/reason-codes.js +2 -2
- package/dist/workspace/run-event.schema.json +3 -1
- 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 +1 -1
- package/src/__fixtures__/pr-loop/pr-apply-wave-waiting.note.golden.md +48 -0
- 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 +69 -0
- package/src/cli/handlers/build.ts +20 -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/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-waves.test.ts +449 -0
- package/src/cli/handlers/pr.test.ts +134 -0
- package/src/cli/handlers/pr.ts +523 -31
- package/src/cli/handlers/promote.test.ts +26 -1
- package/src/cli/handlers/promote.ts +3 -2
- 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 +43 -16
- package/src/cli/parse-args.ts +12 -0
- package/src/cli/registry.ts +19 -6
- 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/driver.test.ts +36 -1
- package/src/components/driver.ts +76 -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/pr-pipeline.test.ts +100 -0
- package/src/components/pr-pipeline.ts +132 -17
- package/src/components/pr-run.test.ts +323 -18
- package/src/components/pr-run.ts +354 -75
- package/src/components/wave-plan.ts +16 -4
- 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/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 +2 -0
- package/src/lifecycle/affected.test.ts +7 -0
- package/src/lifecycle/affected.ts +6 -1
- package/src/lifecycle/release-ledger.ts +34 -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/module-source.test.ts +85 -0
- package/src/module-source.ts +112 -0
- package/src/op/activities/factory.ts +1 -1
- 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/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 +117 -1
- package/src/pr-loop.ts +252 -10
- package/src/pr-report-url.ts +5 -5
- 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/checks/boxes.ts +9 -9
- package/src/workspace/ci-green-forge.ts +6 -4
- package/src/workspace/ci-green-runs.test.ts +53 -11
- package/src/workspace/ci-green.test.ts +1 -1
- package/src/workspace/ci-green.ts +50 -22
- package/src/workspace/ci-list.schema.json +12 -6
- 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 +5 -3
- package/src/workspace/ci-run-index.test.ts +42 -3
- package/src/workspace/ci-run-index.ts +18 -4
- package/src/workspace/ci-run.test.ts +185 -7
- package/src/workspace/ci-run.ts +224 -54
- package/src/workspace/ci-runner-key.test.ts +55 -17
- package/src/workspace/ci-runner-key.ts +36 -11
- package/src/workspace/ci-serve.test.ts +150 -6
- package/src/workspace/ci-serve.ts +169 -25
- package/src/workspace/declaration.schema.json +7 -2
- package/src/workspace/declaration.test.ts +23 -0
- package/src/workspace/declaration.ts +19 -3
- package/src/workspace/export.ts +6 -3
- package/src/workspace/ls.schema.json +7 -1
- package/src/workspace/ls.ts +7 -2
- package/src/workspace/pin-integrity.test.ts +5 -0
- package/src/workspace/pin-integrity.ts +3 -2
- package/src/workspace/pr-report.schema.json +74 -7
- package/src/workspace/reason-codes.ts +2 -2
- package/src/workspace/run-event.schema.json +3 -1
- package/src/workspace/status.schema.json +3 -1
- package/src/workspace/write-scope.test.ts +29 -1
- package/src/workspace/write-scope.ts +12 -1
package/dist/cli/handlers/pr.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* chant components pr-plan --base <ref> --pr <n> --env <env> [--forge github|gitlab|forgejo]
|
|
7
7
|
* chant approve pr-<n> pr-apply --plan <digest> --approver github:<login> --sign
|
|
8
8
|
* chant components pr-apply --base <ref> --env <env> [--pr <n>] [--forge <kind>] [--require-review] [--resume <file>]
|
|
9
|
+
* [--wave-gate <gate> [--wave <k>]]
|
|
9
10
|
* ```
|
|
10
11
|
*
|
|
11
12
|
* Both measure the change from the merge base of `--base` and `HEAD`, select
|
|
@@ -27,6 +28,13 @@
|
|
|
27
28
|
* approval still stands, applies the rest without a fresh approval. When
|
|
28
29
|
* they are not covered it says why and runs as it would without a record.
|
|
29
30
|
*
|
|
31
|
+
* `--wave-gate <gate>` (#4003) applies the approved set a wave at a time,
|
|
32
|
+
* each wave planned after the one before it applied (`prApplyWaves`). A
|
|
33
|
+
* wave whose plan the approved change set covers applies under the pull
|
|
34
|
+
* request's approval; one whose plan moved waits at `<gate>-wave-<k>` under
|
|
35
|
+
* the pull request's op (exit 3). `--wave <k>` runs one wave, reading the
|
|
36
|
+
* approved set and the waves that applied from the `--resume` record.
|
|
37
|
+
*
|
|
30
38
|
* Every run writes `pr-plan.json` or `pr-apply.json` (`pr-report.schema.json`),
|
|
31
39
|
* `change-set.json` and `pr-note.md` under `--output` (default `.chant/pr`). *
|
|
32
40
|
* `--events-json` (#4041, #4007) streams the stage's run events to stdout,
|
|
@@ -48,13 +56,21 @@
|
|
|
48
56
|
* recorded under `pr-<n>-<member>`, and the note and the statuses carry the
|
|
49
57
|
* member's name, so each member's loop on one pull request stands apart.
|
|
50
58
|
* The name must be the member that owns the current directory.
|
|
59
|
+
*
|
|
60
|
+
* `--no-status` (#4161) posts neither the stage's status nor its note: the
|
|
61
|
+
* report and the note are written under `--output` only. `chant ci run
|
|
62
|
+
* --local` passes it. pr-plan then builds no forge client, since posting is
|
|
63
|
+
* all it uses one for; pr-apply still reads the pull request and its reviews
|
|
64
|
+
* through `--forge`.
|
|
51
65
|
*/
|
|
52
66
|
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
53
67
|
import { join, resolve } from "node:path";
|
|
54
68
|
import { loadChantConfig, resolveAutoReleaseDisabled } from "../../config.js";
|
|
55
69
|
import { resolveCommit, resolveMergeBase } from "../../components/changed-files.js";
|
|
56
70
|
import { deriveFanOut, fanOutRegistry } from "../../components/fan-out-support.js";
|
|
57
|
-
import { applyPrSet, approvalOf, approversWithoutReview, decidePrGate, planPrSet, prReport, readOnlyLedger, resumePrSet, } from "../../components/pr-run.js";
|
|
71
|
+
import { applyPrSet, approvalOf, approversWhoAuthored, approversWithoutReview, decidePrGate, planPrSet, prReport, readOnlyLedger, prWavePlan, resumePrSet, waveNotCovered, } from "../../components/pr-run.js";
|
|
72
|
+
import { composeChangeSet, verifyChangeSetDigest } from "../../change-set.js";
|
|
73
|
+
import { waveGateName } from "../../gated-waves.js";
|
|
58
74
|
import { readFanOutAttempt, writeFanOutAttempt } from "../../components/fan-out-record.js";
|
|
59
75
|
import { componentInputDigests, progressThatCarries } from "../../components/fan-out-inputs.js";
|
|
60
76
|
import { samePlanDigest } from "../../lifecycle/plan-digest.js";
|
|
@@ -67,9 +83,11 @@ import { FORGE_KINDS, forgeFromEnv, forgePrincipalOf } from "../../pr-forge.js";
|
|
|
67
83
|
import { GITHUB_COMMENT_LIMIT, GITLAB_NOTE_LIMIT } from "../../plan-summary.js";
|
|
68
84
|
import { resolveCliBuildParams, parseParamFlags } from "../build-params-cli.js";
|
|
69
85
|
import { formatError, formatInfo, formatSuccess, formatWarning } from "../format.js";
|
|
86
|
+
import { reportUninstalledDependencies } from "../dependencies.js";
|
|
70
87
|
import { findWorkspaceRoot } from "../../project-root.js";
|
|
71
88
|
import { resolveChangedUnits } from "./fan-out.js";
|
|
72
89
|
import { GATED_EXIT_CODE, autoReleaseRun, recordAutoReleasesForRun } from "./run.js";
|
|
90
|
+
import { failedRunExitCode } from "../../components/driver.js";
|
|
73
91
|
/** Where the reports land unless `--output` says. */
|
|
74
92
|
export const PR_REPORT_DIR = ".chant/pr";
|
|
75
93
|
const fail = (message, hint) => {
|
|
@@ -107,7 +125,7 @@ async function prepare(ctx, stage) {
|
|
|
107
125
|
return fail(`--forge ${args.forge} is not one of ${FORGE_KINDS.join(", ")}`);
|
|
108
126
|
}
|
|
109
127
|
let forge;
|
|
110
|
-
if (args.forge) {
|
|
128
|
+
if (args.forge && !(stage === "plan" && args.noStatus)) {
|
|
111
129
|
try {
|
|
112
130
|
forge = forgeFromEnv(args.forge);
|
|
113
131
|
}
|
|
@@ -138,10 +156,16 @@ async function prepare(ctx, stage) {
|
|
|
138
156
|
});
|
|
139
157
|
}
|
|
140
158
|
catch (err) {
|
|
141
|
-
|
|
159
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
160
|
+
reportUninstalledDependencies([message], projectPath); // #4116
|
|
161
|
+
return fail(message);
|
|
142
162
|
}
|
|
143
|
-
if (!derived.success)
|
|
163
|
+
if (!derived.success) {
|
|
164
|
+
// #4116: component files that import packages the project has not installed.
|
|
165
|
+
if (derived.error)
|
|
166
|
+
reportUninstalledDependencies([derived.error], projectPath);
|
|
144
167
|
return fail(derived.error ?? "Could not discover components");
|
|
168
|
+
}
|
|
145
169
|
const seededOutputs = {};
|
|
146
170
|
for (const file of args.seedOutputs ?? []) {
|
|
147
171
|
try {
|
|
@@ -218,6 +242,7 @@ function settleOf(status) {
|
|
|
218
242
|
case "applied":
|
|
219
243
|
return "ok";
|
|
220
244
|
case "refused":
|
|
245
|
+
case "waiting":
|
|
221
246
|
return "gated";
|
|
222
247
|
default:
|
|
223
248
|
return "failed";
|
|
@@ -281,7 +306,8 @@ function printMembers(report) {
|
|
|
281
306
|
async function finish(ctx, p, report, upload) {
|
|
282
307
|
const note = noteFor(report, p.forge);
|
|
283
308
|
writeOutputs(ctx, report, note, upload);
|
|
284
|
-
|
|
309
|
+
if (!ctx.args.noStatus)
|
|
310
|
+
await publish(p.forge, report, note);
|
|
285
311
|
if (ctx.args.json)
|
|
286
312
|
console.log(JSON.stringify(report, null, 2));
|
|
287
313
|
}
|
|
@@ -369,6 +395,7 @@ async function prPlan(ctx, stage) {
|
|
|
369
395
|
set,
|
|
370
396
|
approval,
|
|
371
397
|
status,
|
|
398
|
+
...(ctx.args.waveGate ? { waveGate: ctx.args.waveGate } : {}),
|
|
372
399
|
});
|
|
373
400
|
await finish(ctx, p, report, upload);
|
|
374
401
|
// The plan stage settles on its planning: an approval still to come is what it reports, not a stop.
|
|
@@ -391,6 +418,10 @@ async function prApply(ctx, stage) {
|
|
|
391
418
|
const { args } = ctx;
|
|
392
419
|
if (args.requireReview && !args.forge)
|
|
393
420
|
return fail("--require-review reads the pull request's reviews, so it needs --forge <kind>");
|
|
421
|
+
if (args.wave !== undefined && !args.waveGate)
|
|
422
|
+
return fail("--wave runs one wave of a --wave-gate apply, so it needs --wave-gate <name>");
|
|
423
|
+
if (args.wave !== undefined && args.wave < 1)
|
|
424
|
+
return fail(`--wave counts from 1, not ${args.wave}`);
|
|
394
425
|
const p = await prepare(ctx, "apply");
|
|
395
426
|
if (typeof p === "number")
|
|
396
427
|
return p;
|
|
@@ -411,6 +442,8 @@ async function prApply(ctx, stage) {
|
|
|
411
442
|
const events = await openCliRunEvents(args, { kind: "component", name: `pr-apply ${op}`, runId: release.runId, cwd: resolve(".") }, upload?.line);
|
|
412
443
|
stage.stream = events;
|
|
413
444
|
events?.emit({ type: "run-start", waves: p.plan.waves });
|
|
445
|
+
if (args.waveGate)
|
|
446
|
+
return prApplyWaves(ctx, p, { pr, op, release, upload, events });
|
|
414
447
|
// `--resume` (#3464): an earlier attempt at this change on this commit
|
|
415
448
|
// applied part of an approved set. When the members left are covered by
|
|
416
449
|
// it and its approval still stands, finish it under that approval.
|
|
@@ -441,15 +474,7 @@ async function prApply(ctx, stage) {
|
|
|
441
474
|
}
|
|
442
475
|
const set = resumed?.set ?? (await planAll(p, events?.emit));
|
|
443
476
|
const base = { stage: "apply", pr, env: p.env, ...(p.member ? { member: p.member } : {}), base: p.base, head: p.head, op, gate: p.gate, plan: p.plan, signal: p.signal, set };
|
|
444
|
-
const stop =
|
|
445
|
-
events?.settle(settle ?? settleOf(report.status), report.status === "plan-failed" || report.status === "failed" ? report.message ?? `pr-apply ${report.status}` : undefined);
|
|
446
|
-
await finish(ctx, p, report, upload);
|
|
447
|
-
console.error((code === 0 ? formatInfo : (m) => formatWarning({ message: m }))(`pr-apply: ${report.status}${report.refusal ? ` (${report.refusal})` : ""}`));
|
|
448
|
-
if (report.message)
|
|
449
|
-
console.error(report.message);
|
|
450
|
-
printMembers(report);
|
|
451
|
-
return code;
|
|
452
|
-
};
|
|
477
|
+
const stop = stopper(ctx, p, events, upload);
|
|
453
478
|
if (p.plan.order.length === 0) {
|
|
454
479
|
return stop(prReport({ ...base, approval: { status: "pending" }, status: "nothing" }), 0);
|
|
455
480
|
}
|
|
@@ -503,22 +528,13 @@ async function prApply(ctx, stage) {
|
|
|
503
528
|
return stop(report, GATED_EXIT_CODE);
|
|
504
529
|
}
|
|
505
530
|
}
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
}
|
|
514
|
-
const strangers = approversWithoutReview(approval.approvedBy ?? [], reviewers);
|
|
515
|
-
if (strangers.length > 0) {
|
|
516
|
-
const message = `The plan is approved by ${strangers.join(", ")}, who ${strangers.length === 1 ? "has" : "have"} no approving review on #${pr}` +
|
|
517
|
-
` (approving reviews: ${reviewers.join(", ") || "none"}). Nothing was applied. ` +
|
|
518
|
-
`An approval counts when it names the reviewer the way the forge does: ${p.forge.principalOf("<login>")}.`;
|
|
519
|
-
emitReviewStop(events, message);
|
|
520
|
-
return stop(prReport({ ...base, approval, status: "refused", refusal: "not-a-reviewer", message }), GATED_EXIT_CODE);
|
|
521
|
-
}
|
|
531
|
+
const reviewOf = reviewCheck(args, p, pr);
|
|
532
|
+
const review = await reviewOf(approval.approvedBy ?? [], "The plan");
|
|
533
|
+
if (review && "error" in review)
|
|
534
|
+
return fail(review.error);
|
|
535
|
+
if (review) {
|
|
536
|
+
emitReviewStop(events, review.message);
|
|
537
|
+
return stop(prReport({ ...base, approval, status: "refused", refusal: review.refusal, message: review.message }), GATED_EXIT_CODE);
|
|
522
538
|
}
|
|
523
539
|
// The record is written before the first component runs and again as each
|
|
524
540
|
// one settles, so a run killed partway still knows what applied.
|
|
@@ -550,12 +566,36 @@ async function prApply(ctx, stage) {
|
|
|
550
566
|
});
|
|
551
567
|
};
|
|
552
568
|
recordAttempt();
|
|
569
|
+
// #3999: a round planned after an upstream applied waits on a gate of its
|
|
570
|
+
// own, under the same identity.gates rule and the same review checks.
|
|
571
|
+
const roundLedger = gitGateLedgerPort();
|
|
572
|
+
const decideRound = async ({ round, gate, set: planned }) => {
|
|
573
|
+
console.error(formatInfo(`round ${round}: ${planned.members.map((m) => m.component).filter((c, i, all) => all.indexOf(c) === i).join(", ")} planned again after an upstream applied, ${planned.doc.digest}`));
|
|
574
|
+
await storeChangeSetForGate(ctx, p, planned.doc);
|
|
575
|
+
const port = {
|
|
576
|
+
...roundLedger,
|
|
577
|
+
...(roundLedger.approvalRule ? { approvalRule: (g, at) => roundLedger.approvalRule(g === gate ? p.gate : g, at) } : {}),
|
|
578
|
+
};
|
|
579
|
+
const check = await emitGateStep(events?.emit, { op, gate }, () => decidePrGate(port, { op, gate, digest: planned.doc.digest, description: `pull request #${pr}, round ${round}: planned again after an upstream applied` }));
|
|
580
|
+
const roundApproval = approvalOf(check);
|
|
581
|
+
if (!check.satisfied)
|
|
582
|
+
return { satisfied: false, approval: roundApproval };
|
|
583
|
+
const refused = await reviewOf(roundApproval.approvedBy ?? [], `Round ${round}'s plan`);
|
|
584
|
+
if (refused) {
|
|
585
|
+
const message = "error" in refused ? refused.error : refused.message;
|
|
586
|
+
emitReviewStop(events, message);
|
|
587
|
+
return { satisfied: false, approval: roundApproval, message };
|
|
588
|
+
}
|
|
589
|
+
return { satisfied: true, approval: roundApproval };
|
|
590
|
+
};
|
|
553
591
|
const applied = await applyPrSet({
|
|
554
592
|
components: p.components,
|
|
555
593
|
plan: resumed?.plan ?? p.plan,
|
|
556
594
|
registry: p.registry,
|
|
557
595
|
env: p.env,
|
|
558
596
|
set,
|
|
597
|
+
gate: p.gate,
|
|
598
|
+
decideRound,
|
|
559
599
|
...(resumed ? { resumed: prior } : {}),
|
|
560
600
|
// The apply is this run's waves, so the fan-out's own run framing is
|
|
561
601
|
// dropped. The log's lines go to stderr with or without --events-json.
|
|
@@ -589,13 +629,86 @@ async function prApply(ctx, stage) {
|
|
|
589
629
|
status: applied.status,
|
|
590
630
|
members: applied.members,
|
|
591
631
|
...(resumed ? { resumed: resumed.completed } : {}),
|
|
632
|
+
...(applied.rounds ? { rounds: applied.rounds } : {}),
|
|
633
|
+
...(applied.status === "waiting" ? { message: waitingMessage(op, applied.rounds) } : {}),
|
|
592
634
|
});
|
|
593
|
-
// A component that stopped at a gate of its own leaves the apply waiting on
|
|
594
|
-
|
|
635
|
+
// A component that stopped at a gate of its own leaves the apply waiting on
|
|
636
|
+
// a person. A step that refused its plan because the target moved since
|
|
637
|
+
// the approval exits 3, as a moved digest does before anything applies (#3997).
|
|
638
|
+
const exit = applied.status === "applied" ? 0 : applied.status === "waiting" ? GATED_EXIT_CODE : failedRunExitCode(applied.run.results);
|
|
639
|
+
const code = await stop(report, exit, applied.run.status === "gated" ? "gated" : undefined);
|
|
595
640
|
if (applied.status === "applied")
|
|
596
641
|
console.error(formatSuccess(`applied the plan ${report.digest} approved by ${(approval.approvedBy ?? []).join(", ")}`));
|
|
597
642
|
return code;
|
|
598
643
|
}
|
|
644
|
+
/** How a stage stops: settle its events, write and publish the report, say how it ended, and return `code`. */
|
|
645
|
+
function stopper(ctx, p, events, upload) {
|
|
646
|
+
return async (report, code, settle) => {
|
|
647
|
+
events?.settle(settle ?? settleOf(report.status), report.status === "plan-failed" || report.status === "failed" ? report.message ?? `pr-apply ${report.status}` : undefined);
|
|
648
|
+
await finish(ctx, p, report, upload);
|
|
649
|
+
console.error((code === 0 ? formatInfo : (m) => formatWarning({ message: m }))(`pr-apply: ${report.status}${report.refusal ? ` (${report.refusal})` : ""}`));
|
|
650
|
+
if (report.message)
|
|
651
|
+
console.error(report.message);
|
|
652
|
+
printMembers(report);
|
|
653
|
+
return code;
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* The `--require-review` check (#3183, #4000) of an approval's approvers,
|
|
658
|
+
* `what` naming the plan they approved. The pull request's reviews and
|
|
659
|
+
* authors are read once, at the first call. Without `--require-review` every
|
|
660
|
+
* approval counts.
|
|
661
|
+
*/
|
|
662
|
+
function reviewCheck(args, p, pr) {
|
|
663
|
+
let read;
|
|
664
|
+
return async (approvedBy, what) => {
|
|
665
|
+
const forge = p.forge;
|
|
666
|
+
if (!args.requireReview || !forge)
|
|
667
|
+
return undefined;
|
|
668
|
+
read ?? (read = (async () => ({
|
|
669
|
+
reviewers: (await forge.approvers(pr)).map((login) => forge.principalOf(login)),
|
|
670
|
+
authors: (await forge.authors(pr)).map((login) => forge.principalOf(login)),
|
|
671
|
+
}))());
|
|
672
|
+
let reviewers;
|
|
673
|
+
let authors;
|
|
674
|
+
try {
|
|
675
|
+
({ reviewers, authors } = await read);
|
|
676
|
+
}
|
|
677
|
+
catch (err) {
|
|
678
|
+
return { error: `could not read the reviews and authors of #${pr}: ${err instanceof Error ? err.message : String(err)}` };
|
|
679
|
+
}
|
|
680
|
+
// #4000: whoever wrote the change never counts toward its gate, through a forge review or a ledger line.
|
|
681
|
+
const writers = approversWhoAuthored(approvedBy, authors);
|
|
682
|
+
if (writers.length > 0) {
|
|
683
|
+
return {
|
|
684
|
+
refusal: "author-approved",
|
|
685
|
+
message: `${what} is approved by ${writers.join(", ")}, who wrote #${pr} ` +
|
|
686
|
+
`(its author and the authors and committers of its commits: ${authors.join(", ")}). ` +
|
|
687
|
+
`An approval from someone who wrote the change never counts, so nothing was applied. ` +
|
|
688
|
+
`Have someone else approve the pull request and the plan, then run the apply again.`,
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
const strangers = approversWithoutReview(approvedBy, reviewers);
|
|
692
|
+
if (strangers.length > 0) {
|
|
693
|
+
return {
|
|
694
|
+
refusal: "not-a-reviewer",
|
|
695
|
+
message: `${what} is approved by ${strangers.join(", ")}, who ${strangers.length === 1 ? "has" : "have"} no approving review on #${pr}` +
|
|
696
|
+
` (approving reviews: ${reviewers.join(", ") || "none"}). Nothing was applied. ` +
|
|
697
|
+
`An approval counts when it names the reviewer the way the forge does: ${forge.principalOf("<login>")}.`,
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
return undefined;
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
/** What a `waiting` apply prints (#3999): the round's gate and digest to approve, and how to carry on. */
|
|
704
|
+
function waitingMessage(op, rounds) {
|
|
705
|
+
const last = rounds?.at(-1);
|
|
706
|
+
if (!last)
|
|
707
|
+
return "Some members were held back after an upstream applied.";
|
|
708
|
+
return (`${last.components.join(", ")} ${last.components.length === 1 ? "was" : "were"} planned again after an upstream applied, ` +
|
|
709
|
+
`and no approval stands for that plan (${last.digest}), so ${last.components.length === 1 ? "it" : "they"} did not apply. ` +
|
|
710
|
+
`Approve it: ${prApproveCommand({ op, gate: last.gate, digest: last.digest })}; then run the apply again with the same --resume record.`);
|
|
711
|
+
}
|
|
599
712
|
/** What a component's settle says in the job log. */
|
|
600
713
|
function settleWords(body) {
|
|
601
714
|
switch (body.status) {
|
|
@@ -733,3 +846,346 @@ async function tryResume(p, attempt, file, op, inputsNow) {
|
|
|
733
846
|
console.error(formatInfo(`resume : ${completed.join(", ")} applied in an earlier attempt; applying ${resumed.plan.order.join(", ") || "nothing more"} under the approval of ${record.digest}`));
|
|
734
847
|
return { plan: resumed.plan, set: resumed.set, check, approval: approvalOf(check), completed, outputs };
|
|
735
848
|
}
|
|
849
|
+
/**
|
|
850
|
+
* Whether `attempt` carries into this wave's job. It does when it was made
|
|
851
|
+
* by an apply of this pull request's gate, on this commit, for this
|
|
852
|
+
* derivation; when its change set is the one its digest names; and when the
|
|
853
|
+
* pull request's approval still stands for that digest. Of the components it
|
|
854
|
+
* applied, those whose input is the same now carry (#4081). Otherwise it says
|
|
855
|
+
* why on stderr, and the job runs as it would with no record.
|
|
856
|
+
*/
|
|
857
|
+
async function carryWaves(p, attempt, file, op, inputsNow) {
|
|
858
|
+
const skip = (why) => {
|
|
859
|
+
console.error(formatWarning({
|
|
860
|
+
message: `--resume: "${file}" does not carry: ${why}`,
|
|
861
|
+
hint: "Wave 1 plans every member again and needs the pull request's approval of that plan; a later wave refuses until wave 1 applied.",
|
|
862
|
+
}));
|
|
863
|
+
return undefined;
|
|
864
|
+
};
|
|
865
|
+
const record = attempt.prApply;
|
|
866
|
+
if (!record)
|
|
867
|
+
return skip("it was not written by pr-apply");
|
|
868
|
+
if (record.op !== op || record.gate !== p.gate)
|
|
869
|
+
return skip(`it records ${record.op} ${record.gate}, not ${op} ${p.gate}`);
|
|
870
|
+
if (record.head !== p.head)
|
|
871
|
+
return skip(`it records an apply of ${record.head.slice(0, 12)}, not ${p.head.slice(0, 12)}`);
|
|
872
|
+
if (!samePlanDigest(attempt.digest, p.plan.digest))
|
|
873
|
+
return skip("the change selects other components than it did");
|
|
874
|
+
if (record.changeSet.digest !== record.digest || !verifyChangeSetDigest(record.changeSet))
|
|
875
|
+
return skip(`its change set is not the one its digest ${record.digest} names`);
|
|
876
|
+
const split = progressThatCarries(attempt, inputsNow);
|
|
877
|
+
if (split.unrecorded && attempt.completed.length > 0)
|
|
878
|
+
return skip("it does not record what each component applied, since it was written before chant recorded component inputs");
|
|
879
|
+
for (const name of split.changed)
|
|
880
|
+
console.error(formatInfo(`${name}: changed since the earlier attempt, so it runs again`));
|
|
881
|
+
let check;
|
|
882
|
+
try {
|
|
883
|
+
check = await decidePrGate(readOnlyLedger(gitGateLedgerPort()), { op, gate: p.gate, digest: record.digest });
|
|
884
|
+
}
|
|
885
|
+
catch (err) {
|
|
886
|
+
return skip(`could not read the gate ledger: ${err instanceof Error ? err.message : String(err)}`);
|
|
887
|
+
}
|
|
888
|
+
if (!check.satisfied)
|
|
889
|
+
return skip(`no approval stands for the plan it applied (${record.digest}) any more`);
|
|
890
|
+
const completed = split.carried;
|
|
891
|
+
const outputs = Object.fromEntries(Object.entries(attempt.outputs).filter(([name]) => completed.includes(name)));
|
|
892
|
+
return { record, check, approval: approvalOf(check), completed, outputs };
|
|
893
|
+
}
|
|
894
|
+
/** A run event with its wave renumbered to `wave`: the fan-out that applies one wave numbers it 1. */
|
|
895
|
+
function inWave(body, wave) {
|
|
896
|
+
return "wave" in body && typeof body.wave === "number" ? { ...body, wave } : body;
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* `pr-apply --wave-gate <gate>` (#4003): apply the pull request's approved
|
|
900
|
+
* set a wave at a time, each wave planned after the one before it applied.
|
|
901
|
+
*
|
|
902
|
+
* The first job plans every member, as pr-apply does, and decides the pull
|
|
903
|
+
* request's gate against that digest. Approved, wave 1 applies with those
|
|
904
|
+
* plans. Each later wave is planned again against the outputs the waves
|
|
905
|
+
* before it left. When the approved change set covers its plan
|
|
906
|
+
* (`planCoveredBy`, as on a resume) it applies under the pull request's
|
|
907
|
+
* approval; when it does not, it waits at `<gate>-wave-<k>` under the pull
|
|
908
|
+
* request's op for an approval of its new plan, and exits 3.
|
|
909
|
+
*
|
|
910
|
+
* With `--wave <k>` the job runs wave k and no other, reading the approved
|
|
911
|
+
* set and the progress from the `--resume` record the job before it left.
|
|
912
|
+
* Without it the waves run in order until one stops.
|
|
913
|
+
*/
|
|
914
|
+
async function prApplyWaves(ctx, p, at) {
|
|
915
|
+
const { args } = ctx;
|
|
916
|
+
const { pr, op, events, upload } = at;
|
|
917
|
+
const waveGate = args.waveGate;
|
|
918
|
+
const only = args.wave;
|
|
919
|
+
const total = p.plan.waves.length;
|
|
920
|
+
const stop = stopper(ctx, p, events, upload);
|
|
921
|
+
const log = applyLog();
|
|
922
|
+
log({ type: "run-start", waves: p.plan.waves });
|
|
923
|
+
const reportBase = { stage: "apply", pr, env: p.env, ...(p.member ? { member: p.member } : {}), base: p.base, head: p.head, op, gate: p.gate, plan: p.plan, signal: p.signal, waveGate };
|
|
924
|
+
if (p.plan.order.length === 0 || pr === null) {
|
|
925
|
+
const set = await planAll(p, events?.emit);
|
|
926
|
+
if (p.plan.order.length === 0)
|
|
927
|
+
return stop(prReport({ ...reportBase, set, approval: { status: "pending" }, status: "nothing" }), 0);
|
|
928
|
+
return stop(prReport({
|
|
929
|
+
...reportBase,
|
|
930
|
+
set,
|
|
931
|
+
approval: { status: "pending" },
|
|
932
|
+
status: "refused",
|
|
933
|
+
refusal: "no-pull-request",
|
|
934
|
+
message: `No pull request merged ${p.head}, so no approval answers for this change. Nothing was applied. Pass --pr <n>, or land the change through a pull request.`,
|
|
935
|
+
}), GATED_EXIT_CODE);
|
|
936
|
+
}
|
|
937
|
+
const resumePath = args.resume ? resolve(args.resume) : undefined;
|
|
938
|
+
let attempt;
|
|
939
|
+
if (resumePath) {
|
|
940
|
+
try {
|
|
941
|
+
attempt = readFanOutAttempt(resumePath);
|
|
942
|
+
}
|
|
943
|
+
catch (err) {
|
|
944
|
+
return fail(`--resume: could not read "${args.resume}": ${err instanceof Error ? err.message : String(err)}`);
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
const inputsNow = resumePath ? componentInputDigests(p.components, { projectRoot: resolve("."), env: p.env }) : {};
|
|
948
|
+
const carry = attempt ? await carryWaves(p, attempt, args.resume, op, inputsNow) : undefined;
|
|
949
|
+
const completed = new Set(carry?.completed ?? []);
|
|
950
|
+
const outputsSoFar = { ...(carry?.outputs ?? {}) };
|
|
951
|
+
const inputsSoFar = Object.fromEntries([...completed].filter((n) => inputsNow[n] !== undefined).map((n) => [n, inputsNow[n]]));
|
|
952
|
+
const failedSoFar = new Set();
|
|
953
|
+
let waves = carry?.record.waves ?? [];
|
|
954
|
+
const after = new Map();
|
|
955
|
+
const leftIn = (k) => (p.plan.waves[k - 1] ?? []).filter((name) => !completed.has(name));
|
|
956
|
+
const firstLeft = () => p.plan.waves.findIndex((_, i) => leftIn(i + 1).length > 0) + 1;
|
|
957
|
+
// The approved set: carried from the record, or planned here and decided against the pull request's gate.
|
|
958
|
+
let approved = carry
|
|
959
|
+
? { doc: carry.record.changeSet, members: carry.record.members, plans: {}, outputs: carry.record.planOutputs, failed: false }
|
|
960
|
+
: undefined;
|
|
961
|
+
let approval = carry?.approval ?? { status: "pending" };
|
|
962
|
+
const report = (input) => {
|
|
963
|
+
const set = approved ?? { doc: composeChangeSet([]), members: [], plans: {}, outputs: {}, failed: false };
|
|
964
|
+
const members = set.members.map((m) => {
|
|
965
|
+
const now = after.get(m.member);
|
|
966
|
+
if (now)
|
|
967
|
+
return { ...m, status: now.status, ...(now.error ? { error: now.error } : {}) };
|
|
968
|
+
return completed.has(m.component) ? { ...m, status: "applied" } : m;
|
|
969
|
+
});
|
|
970
|
+
return prReport({ ...reportBase, set, approval, members, waves, ...input });
|
|
971
|
+
};
|
|
972
|
+
// `--wave <k>` reached before an earlier wave applied: nothing runs, as `fan-out --wave` refuses.
|
|
973
|
+
const left = firstLeft();
|
|
974
|
+
if (only !== undefined && left > 0 && left < only) {
|
|
975
|
+
return stop(report({
|
|
976
|
+
status: "refused",
|
|
977
|
+
refusal: "earlier-wave",
|
|
978
|
+
message: `Wave ${only} cannot run yet: wave ${left} has not applied (${leftIn(left).join(", ")})${carry ? "" : ", and no --resume record of this apply says otherwise"}. ` +
|
|
979
|
+
`Nothing was applied. Run wave ${left}'s job first, with the same --resume record.`,
|
|
980
|
+
}), 1);
|
|
981
|
+
}
|
|
982
|
+
let planned;
|
|
983
|
+
if (carry) {
|
|
984
|
+
// What the earlier waves applied settles first (#4007), each in its own wave, as a resumed apply's does.
|
|
985
|
+
p.plan.waves.forEach((names, i) => {
|
|
986
|
+
for (const component of names.filter((name) => completed.has(name))) {
|
|
987
|
+
const body = { type: "component-settle", wave: i + 1, component, status: "already-applied" };
|
|
988
|
+
events?.emit(body);
|
|
989
|
+
log(body);
|
|
990
|
+
}
|
|
991
|
+
});
|
|
992
|
+
emitResumedGate(events, op, p.gate, carry.check);
|
|
993
|
+
}
|
|
994
|
+
else {
|
|
995
|
+
const set = await planAll(p, events?.emit);
|
|
996
|
+
approved = set;
|
|
997
|
+
if (set.failed) {
|
|
998
|
+
return stop(report({ status: "plan-failed", message: "A member failed to plan, so there is no plan to compare with the approved one. Nothing was applied." }), 1);
|
|
999
|
+
}
|
|
1000
|
+
await storeChangeSetForGate(ctx, p, set.doc);
|
|
1001
|
+
const check = await emitGateStep(events?.emit, { op, gate: p.gate }, () => decidePrGate(gitGateLedgerPort(), { op, gate: p.gate, digest: set.doc.digest, description: `pull request #${pr}: ${p.plan.order.join(", ")}` }), (unapproved) => {
|
|
1002
|
+
const a = approvalOf(unapproved);
|
|
1003
|
+
return a.status === "changed"
|
|
1004
|
+
? describePlanChanged({ op, gate: p.gate, digest: set.doc.digest, approval: a })
|
|
1005
|
+
: `No approval stands for this plan. Approve it: ${prApproveCommand({ op, gate: p.gate, digest: set.doc.digest })}`;
|
|
1006
|
+
});
|
|
1007
|
+
approval = approvalOf(check);
|
|
1008
|
+
if (!check.satisfied) {
|
|
1009
|
+
return stop(approval.status === "changed"
|
|
1010
|
+
? report({ status: "refused", refusal: "plan-changed", message: describePlanChanged({ op, gate: p.gate, digest: set.doc.digest, approval }) })
|
|
1011
|
+
: report({
|
|
1012
|
+
status: "refused",
|
|
1013
|
+
refusal: "not-approved",
|
|
1014
|
+
message: `No approval stands for this plan, so nothing was applied. Approve it: ${prApproveCommand({ op, gate: p.gate, digest: set.doc.digest })}; then run the apply again.`,
|
|
1015
|
+
}), GATED_EXIT_CODE);
|
|
1016
|
+
}
|
|
1017
|
+
planned = set;
|
|
1018
|
+
}
|
|
1019
|
+
const approvedSet = approved;
|
|
1020
|
+
const review = reviewCheck(args, p, pr);
|
|
1021
|
+
const prReview = await review(approval.approvedBy ?? [], "The plan");
|
|
1022
|
+
if (prReview && "error" in prReview)
|
|
1023
|
+
return fail(prReview.error);
|
|
1024
|
+
if (prReview) {
|
|
1025
|
+
emitReviewStop(events, prReview.message);
|
|
1026
|
+
return stop(report({ status: "refused", refusal: prReview.refusal, message: prReview.message }), GATED_EXIT_CODE);
|
|
1027
|
+
}
|
|
1028
|
+
const recordAttempt = () => {
|
|
1029
|
+
if (!resumePath)
|
|
1030
|
+
return;
|
|
1031
|
+
writeFanOutAttempt(resumePath, {
|
|
1032
|
+
digest: p.plan.digest,
|
|
1033
|
+
completed: [...completed].sort(),
|
|
1034
|
+
failed: [...failedSoFar].sort(),
|
|
1035
|
+
outputs: outputsSoFar,
|
|
1036
|
+
inputs: Object.fromEntries(Object.entries(inputsSoFar).sort(([a], [b]) => a.localeCompare(b))),
|
|
1037
|
+
prApply: {
|
|
1038
|
+
op,
|
|
1039
|
+
gate: p.gate,
|
|
1040
|
+
head: p.head,
|
|
1041
|
+
digest: approvedSet.doc.digest,
|
|
1042
|
+
approvedBy: approval.approvedBy ?? [],
|
|
1043
|
+
members: approvedSet.members,
|
|
1044
|
+
changeSet: approvedSet.doc,
|
|
1045
|
+
planOutputs: approvedSet.outputs,
|
|
1046
|
+
...(waves.length > 0 ? { waves } : {}),
|
|
1047
|
+
},
|
|
1048
|
+
});
|
|
1049
|
+
};
|
|
1050
|
+
const setWave = (wave) => {
|
|
1051
|
+
waves = [...waves.filter((w) => w.wave !== wave.wave), wave].sort((a, b) => a.wave - b.wave);
|
|
1052
|
+
recordAttempt();
|
|
1053
|
+
};
|
|
1054
|
+
recordAttempt();
|
|
1055
|
+
const from = only ?? (left || total + 1);
|
|
1056
|
+
const to = only ?? total;
|
|
1057
|
+
for (let k = from; k <= to; k++) {
|
|
1058
|
+
const wavePlan = prWavePlan(p.plan, p.components, k, [...completed]);
|
|
1059
|
+
if (wavePlan.order.length === 0)
|
|
1060
|
+
continue;
|
|
1061
|
+
const inThisWave = new Set(wavePlan.order);
|
|
1062
|
+
// Wave 1 of the apply that just planned every member applies those plans:
|
|
1063
|
+
// they are the approved set's. Every other wave is planned again, against
|
|
1064
|
+
// the outputs the waves before it left.
|
|
1065
|
+
let set;
|
|
1066
|
+
let entry;
|
|
1067
|
+
if (planned && k === 1) {
|
|
1068
|
+
set = {
|
|
1069
|
+
...planned,
|
|
1070
|
+
members: planned.members.filter((m) => inThisWave.has(m.component)),
|
|
1071
|
+
plans: Object.fromEntries(Object.entries(planned.plans).filter(([name]) => inThisWave.has(name))),
|
|
1072
|
+
};
|
|
1073
|
+
entry = { wave: k, components: wavePlan.order, status: "applied", approval: "pull-request", gate: p.gate, digest: approvedSet.doc.digest, ...(approval.approvedBy ? { approvedBy: approval.approvedBy } : {}) };
|
|
1074
|
+
}
|
|
1075
|
+
else {
|
|
1076
|
+
const notApplied = Object.fromEntries(Object.entries(approvedSet.outputs).filter(([name]) => !completed.has(name)));
|
|
1077
|
+
const fresh = await planPrSet({
|
|
1078
|
+
components: p.components,
|
|
1079
|
+
plan: wavePlan,
|
|
1080
|
+
registry: p.registry,
|
|
1081
|
+
env: p.env,
|
|
1082
|
+
seededOutputs: { ...p.seededOutputs, ...notApplied, ...outputsSoFar },
|
|
1083
|
+
...(events ? { onRunEvent: (body) => events.emit(body) } : {}),
|
|
1084
|
+
});
|
|
1085
|
+
if (fresh.failed) {
|
|
1086
|
+
for (const m of fresh.members)
|
|
1087
|
+
after.set(m.member, m);
|
|
1088
|
+
setWave({ wave: k, components: wavePlan.order, status: "plan-failed", approval: "pull-request", gate: p.gate, digest: approvedSet.doc.digest });
|
|
1089
|
+
return stop(report({ status: "plan-failed", message: `A member of wave ${k} failed to plan, so nothing in wave ${k} was applied.` }), 1);
|
|
1090
|
+
}
|
|
1091
|
+
const moved = waveNotCovered(approvedSet.doc, fresh.doc, approvedSet.members);
|
|
1092
|
+
set = fresh;
|
|
1093
|
+
if (moved.length === 0) {
|
|
1094
|
+
console.error(formatInfo(`wave ${k}: the approved plan covers what it plans now, so it applies under the pull request's approval`));
|
|
1095
|
+
entry = { wave: k, components: wavePlan.order, status: "applied", approval: "pull-request", gate: p.gate, digest: approvedSet.doc.digest, ...(approval.approvedBy ? { approvedBy: approval.approvedBy } : {}) };
|
|
1096
|
+
}
|
|
1097
|
+
else {
|
|
1098
|
+
const gate = waveGateName(waveGate, k);
|
|
1099
|
+
const digest = fresh.doc.digest;
|
|
1100
|
+
console.error(formatInfo(`wave ${k}: its plan moved since the pull request's (${moved.join("; ")}), so it needs an approval of ${gate}`));
|
|
1101
|
+
await storeChangeSetForGate(ctx, p, fresh.doc);
|
|
1102
|
+
writeWaveChangeSet(ctx, k, fresh.doc, upload);
|
|
1103
|
+
const approveLine = prApproveCommand({ op, gate, digest });
|
|
1104
|
+
const check = await emitGateStep(events?.emit, { op, gate }, () => decidePrGate(gitGateLedgerPort(), { op, gate, digest, description: `pull request #${pr}, wave ${k} of ${total}: ${wavePlan.order.join(", ")}` }), (unapproved) => unapproved.mismatch
|
|
1105
|
+
? `wave ${k}'s plan changed after its approval. Approve the plan now: ${approveLine}`
|
|
1106
|
+
: `wave ${k}'s plan is not the one the pull request approved. Approve it: ${approveLine}`);
|
|
1107
|
+
const waveApproval = approvalOf(check);
|
|
1108
|
+
const waiting = {
|
|
1109
|
+
wave: k,
|
|
1110
|
+
components: wavePlan.order,
|
|
1111
|
+
status: "waiting",
|
|
1112
|
+
approval: "wave",
|
|
1113
|
+
gate,
|
|
1114
|
+
digest,
|
|
1115
|
+
moved,
|
|
1116
|
+
...(waveApproval.approved ? { approved: waveApproval.approved } : {}),
|
|
1117
|
+
};
|
|
1118
|
+
if (!check.satisfied) {
|
|
1119
|
+
setWave(waiting);
|
|
1120
|
+
const message = waveApproval.status === "changed"
|
|
1121
|
+
? `Wave ${k}'s plan changed after its approval, so nothing in it was applied. approved: ${waveApproval.approved ?? "(none)"}; planned now: ${digest}. ` +
|
|
1122
|
+
`Read the new plan, and if it is right, approve it: ${approveLine}; then run the wave's apply again.`
|
|
1123
|
+
: `Wave ${k} was planned after the waves before it applied, and the pull request's approved plan does not cover it: ${moved.join("; ")}. ` +
|
|
1124
|
+
`Nothing in wave ${k} was applied. Read its plan, and if it is right, approve it: ${approveLine}; then run the wave's apply again.`;
|
|
1125
|
+
return stop(report({ status: "refused", refusal: waveApproval.status === "changed" ? "wave-plan-changed" : "wave-not-approved", message }), GATED_EXIT_CODE);
|
|
1126
|
+
}
|
|
1127
|
+
const waveReview = await review(waveApproval.approvedBy ?? [], `Wave ${k}'s plan`);
|
|
1128
|
+
if (waveReview && "error" in waveReview)
|
|
1129
|
+
return fail(waveReview.error);
|
|
1130
|
+
if (waveReview) {
|
|
1131
|
+
setWave(waiting);
|
|
1132
|
+
emitReviewStop(events, waveReview.message);
|
|
1133
|
+
return stop(report({ status: "refused", refusal: waveReview.refusal, message: waveReview.message }), GATED_EXIT_CODE);
|
|
1134
|
+
}
|
|
1135
|
+
entry = { wave: k, components: wavePlan.order, status: "applied", approval: "wave", gate, digest, moved, ...(waveApproval.approvedBy ? { approvedBy: waveApproval.approvedBy } : {}) };
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
const applied = await applyPrSet({
|
|
1139
|
+
components: p.components,
|
|
1140
|
+
plan: wavePlan,
|
|
1141
|
+
registry: p.registry,
|
|
1142
|
+
env: p.env,
|
|
1143
|
+
set,
|
|
1144
|
+
// One wave's fan-out numbers its wave 1; the events and the log carry the derivation's number.
|
|
1145
|
+
onRunEvent: (body) => {
|
|
1146
|
+
const numbered = inWave(body, k);
|
|
1147
|
+
events?.nested(numbered);
|
|
1148
|
+
if (numbered.type !== "run-start")
|
|
1149
|
+
log(numbered);
|
|
1150
|
+
},
|
|
1151
|
+
onComponentSettled: (settled, outputs) => {
|
|
1152
|
+
if (settled.status === "ok") {
|
|
1153
|
+
completed.add(settled.component);
|
|
1154
|
+
if (inputsNow[settled.component] !== undefined)
|
|
1155
|
+
inputsSoFar[settled.component] = inputsNow[settled.component];
|
|
1156
|
+
if (outputs)
|
|
1157
|
+
outputsSoFar[settled.component] = outputs;
|
|
1158
|
+
}
|
|
1159
|
+
else if (settled.status === "fail") {
|
|
1160
|
+
failedSoFar.add(settled.component);
|
|
1161
|
+
}
|
|
1162
|
+
recordAttempt();
|
|
1163
|
+
},
|
|
1164
|
+
});
|
|
1165
|
+
for (const m of applied.members)
|
|
1166
|
+
after.set(m.member, m);
|
|
1167
|
+
for (const name of applied.run.completed) {
|
|
1168
|
+
completed.add(name);
|
|
1169
|
+
const outputs = applied.run.componentOutputs[name];
|
|
1170
|
+
if (outputs)
|
|
1171
|
+
outputsSoFar[name] = outputs;
|
|
1172
|
+
}
|
|
1173
|
+
setWave({ ...entry, status: applied.status === "applied" ? "applied" : "failed" });
|
|
1174
|
+
await recordAutoReleasesForRun(applied.run.results, p.env, at.release.runId, resolveAutoReleaseDisabled(p.config, args.noReleaseRecord), at.release.runOrigin);
|
|
1175
|
+
if (applied.status !== "applied") {
|
|
1176
|
+
return stop(report({ status: "failed" }), failedRunExitCode(applied.run.results), applied.run.status === "gated" ? "gated" : undefined);
|
|
1177
|
+
}
|
|
1178
|
+
console.error(formatSuccess(`applied wave ${k} of ${total} under ${entry.approval === "pull-request" ? `the pull request's approval of ${approvedSet.doc.digest}` : `${entry.gate}, approved for ${entry.digest}`} by ${(entry.approvedBy ?? []).join(", ")}`));
|
|
1179
|
+
}
|
|
1180
|
+
const done = p.plan.order.every((name) => completed.has(name));
|
|
1181
|
+
return stop(report({ status: done ? "applied" : "wave-applied" }), 0);
|
|
1182
|
+
}
|
|
1183
|
+
/** A wave's new plan (#4003), beside the report as `change-set-wave-<k>.json`, for whoever approves its gate. */
|
|
1184
|
+
function writeWaveChangeSet(ctx, wave, doc, upload) {
|
|
1185
|
+
const dir = resolve(ctx.args.output ?? PR_REPORT_DIR);
|
|
1186
|
+
mkdirSync(dir, { recursive: true });
|
|
1187
|
+
const name = `change-set-wave-${wave}.json`;
|
|
1188
|
+
const body = JSON.stringify(doc, null, 2) + "\n";
|
|
1189
|
+
writeFileSync(join(dir, name), body);
|
|
1190
|
+
upload?.file(name, body);
|
|
1191
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"promote.d.ts","sourceRoot":"","sources":["../../../src/cli/handlers/promote.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AA+CH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"promote.d.ts","sourceRoot":"","sources":["../../../src/cli/handlers/promote.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AA+CH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAwkBlD,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CA2E/E;AAED,wBAAsB,qBAAqB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAsChF;AAsCD,wBAAsB,qBAAqB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CA6BhF"}
|