@intentius/chant 0.124.30 → 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 +74 -12
- package/dist/cli/parse-args.d.ts.map +1 -1
- package/dist/cli/parse-args.js +65 -0
- package/dist/cli/registry.d.ts +29 -2
- 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/local-executor.d.ts +12 -0
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/local-executor.js +22 -3
- 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/redact.d.ts +34 -0
- package/dist/op/redact.d.ts.map +1 -0
- package/dist/op/redact.js +94 -0
- package/dist/op/step-console.d.ts +2 -0
- package/dist/op/step-console.d.ts.map +1 -1
- package/dist/op/step-console.js +5 -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 +27 -0
- package/dist/pr-forge.d.ts.map +1 -1
- package/dist/pr-forge.js +51 -6
- 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 +10 -2
- package/dist/pr-report-url.d.ts.map +1 -1
- package/dist/pr-report-url.js +52 -6
- 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 +108 -0
- package/dist/workspace/ci-run-index.d.ts.map +1 -0
- package/dist/workspace/ci-run-index.js +164 -0
- package/dist/workspace/ci-run-index.schema.json +52 -0
- package/dist/workspace/ci-run.d.ts +159 -9
- package/dist/workspace/ci-run.d.ts.map +1 -1
- package/dist/workspace/ci-run.js +419 -50
- package/dist/workspace/ci-runner-key.d.ts +133 -0
- package/dist/workspace/ci-runner-key.d.ts.map +1 -0
- package/dist/workspace/ci-runner-key.js +237 -0
- package/dist/workspace/ci-secrets.d.ts +65 -0
- package/dist/workspace/ci-secrets.d.ts.map +1 -0
- package/dist/workspace/ci-secrets.js +211 -0
- package/dist/workspace/ci-serve.d.ts +67 -4
- package/dist/workspace/ci-serve.d.ts.map +1 -1
- package/dist/workspace/ci-serve.js +240 -35
- 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 +7 -3
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/reason-codes.js +9 -4
- package/dist/workspace/run-event.schema.json +3 -1
- package/dist/workspace/status.schema.json +3 -1
- package/dist/workspace/trust/trust-write.d.ts +109 -0
- package/dist/workspace/trust/trust-write.d.ts.map +1 -0
- package/dist/workspace/trust/trust-write.js +320 -0
- package/dist/workspace/trust-write.schema.json +265 -0
- 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 +19 -0
- package/src/cli/main.ts +73 -12
- package/src/cli/parse-args.ts +48 -0
- package/src/cli/registry.ts +35 -4
- 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/local-executor.ts +31 -3
- 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/redact.ts +93 -0
- package/src/op/step-console.ts +7 -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 +71 -0
- package/src/pr-forge.ts +70 -6
- 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 +58 -6
- 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 +52 -0
- package/src/workspace/ci-run-index.test.ts +392 -0
- package/src/workspace/ci-run-index.ts +206 -0
- package/src/workspace/ci-run.test.ts +520 -42
- package/src/workspace/ci-run.ts +483 -51
- package/src/workspace/ci-runner-key.test.ts +204 -0
- package/src/workspace/ci-runner-key.ts +275 -0
- package/src/workspace/ci-secrets.test.ts +106 -0
- package/src/workspace/ci-secrets.ts +213 -0
- package/src/workspace/ci-serve.test.ts +282 -39
- package/src/workspace/ci-serve.ts +231 -34
- 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.test.ts +2 -0
- package/src/workspace/reason-codes.ts +9 -4
- package/src/workspace/run-event.schema.json +3 -1
- package/src/workspace/status.schema.json +3 -1
- package/src/workspace/trust/trust-write.test.ts +179 -0
- package/src/workspace/trust/trust-write.ts +377 -0
- package/src/workspace/trust-write.schema.json +265 -0
- package/src/workspace/write-scope.test.ts +29 -1
- package/src/workspace/write-scope.ts +12 -1
package/src/workspace/ci-run.ts
CHANGED
|
@@ -22,7 +22,10 @@
|
|
|
22
22
|
* own variables. The forge values a generated job reads from `${{ ... }}`
|
|
23
23
|
* expressions or GitLab's predefined variables come from flags here:
|
|
24
24
|
* `BASE_SHA` from `--base`, `PR_NUMBER` from `--pr`, and the commit from
|
|
25
|
-
* `--commit`.
|
|
25
|
+
* `--commit`. The values a forge job gets from repository secrets come from
|
|
26
|
+
* the host's secrets file (`--secrets`, ./ci-secrets.ts, #4096), and every
|
|
27
|
+
* secret value is redacted from the run's events, record, copied report and
|
|
28
|
+
* output by the executor's `redact` option.
|
|
26
29
|
*
|
|
27
30
|
* The steps run in a fresh checkout of the commit: a detached `git worktree`
|
|
28
31
|
* in a temporary directory outside the repository, removed when the run
|
|
@@ -37,29 +40,65 @@
|
|
|
37
40
|
* key is given, so `chant ci tick --source runs` (#4044) judges the commit
|
|
38
41
|
* from it. Its events are `kind: "pipeline"` and `runtime: "ci"`, and the
|
|
39
42
|
* record and the events carry `runOrigin: { forge: "chant" }`.
|
|
43
|
+
*
|
|
44
|
+
* With a forge client (#4097) the job also sets a commit status at its
|
|
45
|
+
* commit: `pending` when it starts, `success` or `failure` when it settles.
|
|
46
|
+
* A status the forge refuses is a warning and never fails the job.
|
|
47
|
+
*
|
|
48
|
+
* Signing is opt-in (#4156). With no runner key the record is unsigned, and
|
|
49
|
+
* a reader counts it with no signer. A key `.chant/trust.json` does not list
|
|
50
|
+
* leaves it unsigned with a note `--verbose` prints, unless the declaration
|
|
51
|
+
* sets `ci.green.signedSince`; then a run left unsigned is a warning.
|
|
52
|
+
*
|
|
53
|
+
* In place (`--in-place`, #4156) the job runs in the checkout it is started
|
|
54
|
+
* in, as a generated forge job does: no worktree, no setup phase and no
|
|
55
|
+
* commit status of its own (the forge shows the job). The steps get the
|
|
56
|
+
* job's whole environment less `CHANT_RUNNER_KEY`, since the forge job is
|
|
57
|
+
* already the boundary, and the record's `runOrigin` names the forge's run,
|
|
58
|
+
* job and attempt (`resolveCiJob`).
|
|
59
|
+
*
|
|
60
|
+
* Local (`--local`, #4161) runs the job the same way on this machine and
|
|
61
|
+
* leaves nothing outside it. It implies `--no-push` and `--no-status`, and
|
|
62
|
+
* writes nothing under a report URL, given or declared: the command gets no
|
|
63
|
+
* `--report-url`, and gets `--no-status`, so pr-plan and pr-apply post no
|
|
64
|
+
* status or note either. The record is still written to the local
|
|
65
|
+
* `chant/lifecycle`, where a reader on this machine finds it beside CI's for
|
|
66
|
+
* the same commit. It says `runOrigin: { forge: "local" }` and its events say
|
|
67
|
+
* `runtime: "local"`. `ci tick --source runs` and `ci list` never count it.
|
|
68
|
+
* A pr-apply still pushes its own release records and gate facts, as the
|
|
69
|
+
* command always does, since a local apply changes the same infrastructure
|
|
70
|
+
* a CI one does.
|
|
40
71
|
*/
|
|
41
72
|
|
|
42
73
|
import { execFileSync } from "node:child_process";
|
|
43
|
-
import { copyFileSync, cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync } from "node:fs";
|
|
74
|
+
import { copyFileSync, cpSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
44
75
|
import { tmpdir } from "node:os";
|
|
45
76
|
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
46
77
|
import { PR_APPLY_RECORD, PR_LOOP_REPORT_DIR, prLoopJobs, prLoopSetup, PR_LOOP_IMAGE, type PrLoopJob, type PrLoopPipelineOptions } from "../components/pr-pipeline";
|
|
47
|
-
import { detectForge, FORGE_KINDS, type ForgeKind } from "../pr-forge";
|
|
78
|
+
import { detectForge, ForgeEnvironmentError, forgeFromEnv, FORGE_KINDS, type CommitStatus, type ForgeKind } from "../pr-forge";
|
|
79
|
+
import { PR_APPLY_GATE } from "../pr-loop";
|
|
48
80
|
import type { CommandContext } from "../cli/registry";
|
|
49
81
|
import { formatError, formatWarning } from "../cli/format";
|
|
50
|
-
import type
|
|
51
|
-
import { pushLifecycleReport } from "../lifecycle/git";
|
|
82
|
+
import { resolveCiJob, type RunOrigin } from "../lifecycle/release-ledger";
|
|
83
|
+
import { fetchLifecycleStatus, pushLifecycleReport } from "../lifecycle/git";
|
|
52
84
|
import { ACTIVITY_PROFILES } from "../op/activity-profiles";
|
|
53
85
|
import type { ActivityFn, ActivityProfile } from "../op/activity-registry";
|
|
54
86
|
import { shellCmd } from "../op/activities/shell";
|
|
55
87
|
import { OpRunFailure, runOpLocally, type OpRunResult, type StepRecord } from "../op/local-executor";
|
|
56
88
|
import type { OpRunFailureReason, OpRunRecord } from "../op/runtime";
|
|
57
89
|
import type { ActivityStep, OpConfig, PhaseDefinition } from "../op/types";
|
|
90
|
+
import { redactText, type Redact } from "../op/redact";
|
|
58
91
|
import { findWorkspaceRoot } from "../project-root";
|
|
59
|
-
import {
|
|
92
|
+
import { parseReportUrl, reportKey } from "../pr-report-url";
|
|
93
|
+
import { reportUrlOf } from "./ci-report-url";
|
|
94
|
+
import { ciTagSigner, RUNNER_KEY_ENV, signedRunsRequired } from "./ci-green";
|
|
95
|
+
import { loadRunnerKey } from "./trust/dsse";
|
|
96
|
+
import { ciRedactor, ciSecretsFor, CI_SECRETS_ENV, readCiSecrets, type CiSecrets } from "./ci-secrets";
|
|
97
|
+
import { updateCiRunIndex, type CiRunIndexEntry } from "./ci-run-index";
|
|
60
98
|
import { resolveMemberContext } from "./member-pipeline";
|
|
61
99
|
import { signOpRunRecord } from "./trust/op-run-statement";
|
|
62
100
|
import { gitTop } from "./tree";
|
|
101
|
+
import { hostRunnerKey } from "./ci-runner-key";
|
|
63
102
|
|
|
64
103
|
/** The two jobs a pull-request pipeline has. */
|
|
65
104
|
export type CiJobName = PrLoopJob["jobName"];
|
|
@@ -71,6 +110,9 @@ export const CI_JOB_OPS: Record<CiJobName, string> = { plan: "pr-plan", apply: "
|
|
|
71
110
|
/** Where a job the chant runner ran says it ran (#4094). */
|
|
72
111
|
export const CHANT_RUNNER_ORIGIN: RunOrigin = { forge: "chant" };
|
|
73
112
|
|
|
113
|
+
/** Where a job `chant ci run --local` ran says it ran (#4161): this machine, outside CI. */
|
|
114
|
+
export const CHANT_LOCAL_ORIGIN: RunOrigin = { forge: "local" };
|
|
115
|
+
|
|
74
116
|
/** The runner's variables every job gets, when the runner has them. */
|
|
75
117
|
export const CI_RUN_BASE_ENV = ["PATH", "HOME", "USER", "LOGNAME", "SHELL", "LANG", "LC_ALL", "LC_CTYPE", "TMPDIR", "TZ"] as const;
|
|
76
118
|
|
|
@@ -115,21 +157,34 @@ export class CiRunError extends Error {
|
|
|
115
157
|
|
|
116
158
|
/**
|
|
117
159
|
* The job's environment: the runner's base variables, the forge variables
|
|
118
|
-
* and `passEnv` names it has,
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
160
|
+
* and `passEnv` names it has, the job's secrets from the host's secrets file
|
|
161
|
+
* (#4096), `CHANT_ENV` as a generated workflow sets it, the commit as the
|
|
162
|
+
* forge spells it, and the job's own variables. A job variable the forge
|
|
163
|
+
* fills from an expression comes from a flag, or from a secret or the
|
|
164
|
+
* runner's environment for a token such as `GITHUB_TOKEN`; a literal one,
|
|
165
|
+
* such as `CHANT_RUN_EVENTS_DIR`, is kept as it is. A secret replaces the
|
|
166
|
+
* runner's variable of the same name, and none replaces a variable the
|
|
167
|
+
* runner sets for the job. `CHANT_RUNNER_KEY` is never in it.
|
|
123
168
|
*/
|
|
124
169
|
export function ciJobEnv(
|
|
125
170
|
job: PrLoopJob,
|
|
126
|
-
input: {
|
|
171
|
+
input: {
|
|
172
|
+
forge: ForgeKind;
|
|
173
|
+
env: string;
|
|
174
|
+
values: CiRunValues;
|
|
175
|
+
hostEnv: Record<string, string | undefined>;
|
|
176
|
+
passEnv?: readonly string[];
|
|
177
|
+
/** The secrets this job gets, from {@link ciSecretsFor}. */
|
|
178
|
+
secrets?: Record<string, string>;
|
|
179
|
+
},
|
|
127
180
|
): Record<string, string> {
|
|
181
|
+
if (input.passEnv?.includes(RUNNER_KEY_ENV)) throw new CiRunError(`--pass-env names ${RUNNER_KEY_ENV}, and the runner key is never passed to a job`);
|
|
128
182
|
const out: Record<string, string> = {};
|
|
129
183
|
for (const name of [...CI_RUN_BASE_ENV, ...CI_RUN_FORGE_ENV, ...(input.passEnv ?? [])]) {
|
|
130
184
|
const value = input.hostEnv[name];
|
|
131
185
|
if (value !== undefined) out[name] = value;
|
|
132
186
|
}
|
|
187
|
+
for (const [name, value] of Object.entries(input.secrets ?? {})) if (name !== RUNNER_KEY_ENV) out[name] = value;
|
|
133
188
|
out.CHANT_ENV = input.env;
|
|
134
189
|
out[input.forge === "gitlab" ? "CI_COMMIT_SHA" : "GITHUB_SHA"] = input.values.commit;
|
|
135
190
|
const flags: Record<string, { value: string | undefined; flag: string }> = {
|
|
@@ -143,10 +198,13 @@ export function ciJobEnv(
|
|
|
143
198
|
out[name] = given.value;
|
|
144
199
|
} else if (!value.includes("$")) {
|
|
145
200
|
out[name] = value;
|
|
201
|
+
} else if (input.secrets?.[name] !== undefined) {
|
|
202
|
+
out[name] = input.secrets[name]!;
|
|
146
203
|
} else if (input.hostEnv[name] !== undefined) {
|
|
147
204
|
out[name] = input.hostEnv[name]!;
|
|
148
205
|
}
|
|
149
206
|
}
|
|
207
|
+
delete out[RUNNER_KEY_ENV];
|
|
150
208
|
return out;
|
|
151
209
|
}
|
|
152
210
|
|
|
@@ -212,6 +270,17 @@ function exitCodeOf(error: string | undefined): number | undefined {
|
|
|
212
270
|
return m ? Number(m[1]) : undefined;
|
|
213
271
|
}
|
|
214
272
|
|
|
273
|
+
/**
|
|
274
|
+
* The code `chant ci run` exits with for a settled job: 0 when it passed,
|
|
275
|
+
* else its command's own code when that is 1 to 255, such as 3 for a moved
|
|
276
|
+
* plan or a waiting wave (#4156), else 1. The run index records the same
|
|
277
|
+
* code (#4165).
|
|
278
|
+
*/
|
|
279
|
+
export function ciJobExitCode(result: { status: OpRunResult["status"]; exitCode?: number }): number {
|
|
280
|
+
if (result.status === "ok") return 0;
|
|
281
|
+
return result.exitCode && result.exitCode > 0 && result.exitCode < 256 ? result.exitCode : 1;
|
|
282
|
+
}
|
|
283
|
+
|
|
215
284
|
/** The first line of a step's error, for a reason. */
|
|
216
285
|
const firstLine = (text: string): string => text.split("\n").find((l) => l.trim() !== "")?.trim() ?? text;
|
|
217
286
|
|
|
@@ -266,6 +335,77 @@ export function ciJobFailure(input: { job: CiJobName; op: OpConfig; records: rea
|
|
|
266
335
|
return { code: "command-failed", reason: firstLine(failed.error ?? "the command failed"), phase: failed.phase, ...exit };
|
|
267
336
|
}
|
|
268
337
|
|
|
338
|
+
/** What posts a job's commit status (#4097): the `setCommitStatus` of a GitHub or Forgejo client. */
|
|
339
|
+
export interface CiStatusClient {
|
|
340
|
+
setCommitStatus(sha: string, status: CommitStatus): Promise<void>;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** Where a job's status goes (#4097). */
|
|
344
|
+
export interface CiStatusConfig {
|
|
345
|
+
client: CiStatusClient;
|
|
346
|
+
/** `--status-url`: the link the status carries, `{key}` standing for `<pr>/<commit>[/<member>]`. Otherwise an https `--report-url` is used. */
|
|
347
|
+
urlTemplate?: string;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/** A status description is cut to what GitHub and Forgejo keep. */
|
|
351
|
+
const STATUS_DESCRIPTION = 140;
|
|
352
|
+
|
|
353
|
+
const cut = (text: string, max = STATUS_DESCRIPTION): string => (text.length > max ? `${text.slice(0, max - 3)}...` : text);
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* A job's status context: `chant/pr-plan` for the plan, and for the apply
|
|
357
|
+
* `chant/<gate>`, which is `chant/pr-apply` unless `--gate` renamed the gate.
|
|
358
|
+
* pr-plan's own status is `chant/plan` (../pr-loop.ts), a different context.
|
|
359
|
+
*/
|
|
360
|
+
export function ciStatusContext(job: CiJobName, gate?: string): string {
|
|
361
|
+
return job === "apply" ? `chant/${gate ?? PR_APPLY_GATE}` : `chant/${CI_JOB_OPS.plan}`;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* The link a job's status carries: the `--status-url` template with `{key}`
|
|
366
|
+
* filled, else the https `--report-url` followed by the key and a slash, else
|
|
367
|
+
* none (an `s3://` or `file://` URL is not one a browser opens).
|
|
368
|
+
*/
|
|
369
|
+
export function ciStatusUrl(input: { urlTemplate?: string; reportUrl?: string; pr?: number; commit: string; member?: string }): string | undefined {
|
|
370
|
+
const key = reportKey(input.pr ?? null, input.commit, input.member);
|
|
371
|
+
if (input.urlTemplate) return input.urlTemplate.split("{key}").join(key);
|
|
372
|
+
const url = input.reportUrl?.trim();
|
|
373
|
+
if (url && /^https:\/\//.test(url)) return `${url.replace(/\/+$/, "")}/${key}/`;
|
|
374
|
+
return undefined;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/** What the settled status says: a short summary, or on failure the record's code and reason. */
|
|
378
|
+
export function ciStatusSettled(op: string, result: Pick<CiRunResult, "status" | "record" | "failure">, seconds: number): Pick<CommitStatus, "state" | "description"> {
|
|
379
|
+
if (result.status === "ok") return { state: "success", description: cut(`${op} passed in ${seconds}s`) };
|
|
380
|
+
const why = result.record.failure;
|
|
381
|
+
const text = why ? `${why.code}: ${why.reason}` : result.failure ? `${result.failure.phase}: ${firstLine(result.failure.error)}` : `${op} failed`;
|
|
382
|
+
return { state: "failure", description: cut(text) };
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* The status client for `chant ci run` and `chant ci serve` (#4097), or none:
|
|
387
|
+
* off with `--no-status`, on GitLab (its client has no commit status call
|
|
388
|
+
* here), and without a forge token. A token with the rest of the forge's
|
|
389
|
+
* variables missing gives a warning instead.
|
|
390
|
+
*/
|
|
391
|
+
export function ciStatusFromEnv(input: { forge: ForgeKind; hostEnv: Record<string, string | undefined>; noStatus?: boolean; statusUrl?: string }): { status?: CiStatusConfig; warning?: string } {
|
|
392
|
+
if (input.noStatus || input.forge === "gitlab") return {};
|
|
393
|
+
if (input.statusUrl !== undefined && !/^https?:\/\/\S+$/.test(input.statusUrl)) {
|
|
394
|
+
throw new CiRunError(`--status-url takes an http or https URL template such as https://host/reports/{key}, not ${JSON.stringify(input.statusUrl)}`);
|
|
395
|
+
}
|
|
396
|
+
const env = input.hostEnv;
|
|
397
|
+
const hasToken = Boolean(env.CHANT_FORGE_TOKEN ?? env.GITHUB_TOKEN ?? env.GH_TOKEN);
|
|
398
|
+
if (!hasToken) return {};
|
|
399
|
+
try {
|
|
400
|
+
const client = forgeFromEnv(input.forge, env);
|
|
401
|
+
if (!client.setCommitStatus) return {};
|
|
402
|
+
return { status: { client: { setCommitStatus: client.setCommitStatus.bind(client) }, ...(input.statusUrl ? { urlTemplate: input.statusUrl } : {}) } };
|
|
403
|
+
} catch (err) {
|
|
404
|
+
if (err instanceof ForgeEnvironmentError) return { warning: `no commit status will be set: ${err.message}` };
|
|
405
|
+
throw err;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
269
409
|
/** What `chant ci run` runs. */
|
|
270
410
|
export interface CiRunRequest {
|
|
271
411
|
/** Where the command was run: the project, or a workspace member's directory. */
|
|
@@ -278,7 +418,12 @@ export interface CiRunRequest {
|
|
|
278
418
|
base?: string;
|
|
279
419
|
pr?: number;
|
|
280
420
|
commit?: string;
|
|
281
|
-
/**
|
|
421
|
+
/**
|
|
422
|
+
* The generator's options: `--gate`, `--branch`, `--events-json`,
|
|
423
|
+
* `--report-url`, `--pr-build` and `--pr-build-command`, and `--wave-gate`
|
|
424
|
+
* (#4003), with which the apply job runs the waves in order and stops at
|
|
425
|
+
* the first whose moved plan waits on its own gate.
|
|
426
|
+
*/
|
|
282
427
|
loop?: PrLoopPipelineOptions;
|
|
283
428
|
/** `--image`: the image a generated pipeline names. Only the default gets setup lines. */
|
|
284
429
|
image?: string;
|
|
@@ -290,8 +435,16 @@ export interface CiRunRequest {
|
|
|
290
435
|
passEnv?: readonly string[];
|
|
291
436
|
/** The runner's environment. Default `process.env`. */
|
|
292
437
|
hostEnv?: Record<string, string | undefined>;
|
|
438
|
+
/**
|
|
439
|
+
* The host's secrets file, read (#4096, `--secrets`): the job gets the
|
|
440
|
+
* secrets its allowlist names, and every value in it is redacted from the
|
|
441
|
+
* run's events, record, copied report and output.
|
|
442
|
+
*/
|
|
443
|
+
secrets?: CiSecrets;
|
|
293
444
|
/** `--output`: where the job's report is copied, from `cwd`. Default `.chant/pr`. */
|
|
294
445
|
output?: string;
|
|
446
|
+
/** `--run-index-limit`: how many runs the index at `loop.reportUrl` keeps (#4098). Default 200. */
|
|
447
|
+
runIndexLimit?: number;
|
|
295
448
|
/** A runner key, PEM, that signs the run's record. */
|
|
296
449
|
runnerKey?: string;
|
|
297
450
|
/**
|
|
@@ -315,6 +468,26 @@ export interface CiRunRequest {
|
|
|
315
468
|
* failed. `chant ci serve` names one per `prApplyRecordKey`.
|
|
316
469
|
*/
|
|
317
470
|
resumeRecord?: string;
|
|
471
|
+
/** Set a commit status at the job's commit (#4097). Absent: none is set. `chant ci run` builds it from the environment. */
|
|
472
|
+
status?: CiStatusConfig;
|
|
473
|
+
/**
|
|
474
|
+
* `--in-place` (#4156): run in the checkout `cwd` is in, at its `HEAD`, as
|
|
475
|
+
* a generated forge job does, with the runner's whole environment less
|
|
476
|
+
* `CHANT_RUNNER_KEY`, no setup phase, and the forge's run as `runOrigin`.
|
|
477
|
+
*/
|
|
478
|
+
inPlace?: boolean;
|
|
479
|
+
/**
|
|
480
|
+
* `--local` (#4161): run on this machine and write nothing beyond it. It
|
|
481
|
+
* implies `push: false` and no status (`status` is ignored), writes
|
|
482
|
+
* nothing under `loop.reportUrl`, and the record and events say
|
|
483
|
+
* `runOrigin: { forge: "local" }` and `runtime: "local"`.
|
|
484
|
+
*/
|
|
485
|
+
local?: boolean;
|
|
486
|
+
/**
|
|
487
|
+
* `--wave <k>` (#4003, #4156): the one wave a generated apply job runs, as
|
|
488
|
+
* `pr-apply --wave <k>`. Needs `--wave-gate`.
|
|
489
|
+
*/
|
|
490
|
+
wave?: number;
|
|
318
491
|
signal?: AbortSignal;
|
|
319
492
|
}
|
|
320
493
|
|
|
@@ -333,8 +506,14 @@ export interface CiRunResult {
|
|
|
333
506
|
pushWarning?: string;
|
|
334
507
|
/** What went wrong beside the job, such as a record the key could not sign. */
|
|
335
508
|
warnings: string[];
|
|
509
|
+
/** What `--verbose` prints: such as a run left unsigned when nothing asks for a signature (#4156). */
|
|
510
|
+
notes: string[];
|
|
511
|
+
/** The exit code of the job's command when it failed with one, such as pr-apply's 3 for a moved plan. */
|
|
512
|
+
exitCode?: number;
|
|
336
513
|
/** The checkout the job ran in, removed by now. */
|
|
337
514
|
checkout: string;
|
|
515
|
+
/** The job ran with `local` (#4161): its record stays on this machine. */
|
|
516
|
+
local?: true;
|
|
338
517
|
}
|
|
339
518
|
|
|
340
519
|
const message = (err: unknown): string => (err instanceof Error ? err.message : String(err));
|
|
@@ -393,6 +572,62 @@ function freshCheckout(top: string, commit: string): { dir: string; remove(): vo
|
|
|
393
572
|
};
|
|
394
573
|
}
|
|
395
574
|
|
|
575
|
+
/**
|
|
576
|
+
* Add the settled job to the run index at its report URL (#4098). The pull
|
|
577
|
+
* request is the one the job's own report names, since a pr-apply finds it
|
|
578
|
+
* itself, else `--pr`. A warning, never a failure, when the index can't be
|
|
579
|
+
* updated.
|
|
580
|
+
*/
|
|
581
|
+
async function recordInIndex(input: {
|
|
582
|
+
url: string;
|
|
583
|
+
job: CiJobName;
|
|
584
|
+
commit: string;
|
|
585
|
+
member?: string;
|
|
586
|
+
pr?: number;
|
|
587
|
+
record: OpRunRecord;
|
|
588
|
+
runOrigin: RunOrigin;
|
|
589
|
+
/** The runner that signed the record, or null (#4156). */
|
|
590
|
+
signer: string | null;
|
|
591
|
+
/** The code `chant ci run` exits with for the job (#4165). */
|
|
592
|
+
exitCode: number;
|
|
593
|
+
reportDir: string;
|
|
594
|
+
limit?: number;
|
|
595
|
+
/** The run's redactor (#4096): every string written to the index goes through it, as the copied report does. */
|
|
596
|
+
redact?: Redact;
|
|
597
|
+
warn: (message: string) => void;
|
|
598
|
+
}): Promise<void> {
|
|
599
|
+
const target = parseReportUrl(input.url);
|
|
600
|
+
if ("error" in target) return input.warn(`the run index is not updated: ${target.error}`);
|
|
601
|
+
let pr: number | null = input.pr ?? null;
|
|
602
|
+
try {
|
|
603
|
+
const file = join(input.reportDir, `${CI_JOB_OPS[input.job]}.json`);
|
|
604
|
+
if (existsSync(file)) {
|
|
605
|
+
const said = (JSON.parse(readFileSync(file, "utf-8")) as { pr?: unknown }).pr;
|
|
606
|
+
if (Number.isInteger(said) && (said as number) > 0) pr = said as number;
|
|
607
|
+
}
|
|
608
|
+
} catch {
|
|
609
|
+
// The report is not readable; the flag stands.
|
|
610
|
+
}
|
|
611
|
+
const failure = input.record.failure;
|
|
612
|
+
const clean = (text: string): string => (input.redact ? input.redact(text) : text);
|
|
613
|
+
const entry: CiRunIndexEntry = {
|
|
614
|
+
job: input.job,
|
|
615
|
+
pr,
|
|
616
|
+
commit: input.commit,
|
|
617
|
+
status: input.record.status === "ok" ? "ok" : "fail",
|
|
618
|
+
...(input.record.status !== "ok" ? { failure: { code: clean(failure?.code ?? "command-failed") } } : {}),
|
|
619
|
+
...(input.member ? { member: clean(input.member) } : {}),
|
|
620
|
+
started: input.record.started,
|
|
621
|
+
ended: input.record.ended,
|
|
622
|
+
runId: clean(input.record.id),
|
|
623
|
+
key: clean(reportKey(pr, input.commit, input.member)),
|
|
624
|
+
runOrigin: input.runOrigin,
|
|
625
|
+
signer: input.signer,
|
|
626
|
+
exitCode: input.exitCode,
|
|
627
|
+
};
|
|
628
|
+
await updateCiRunIndex({ target, entry, ...(input.limit ? { limit: input.limit } : {}), warn: input.warn });
|
|
629
|
+
}
|
|
630
|
+
|
|
396
631
|
const ACTIVITIES = new Map<string, ActivityFn>([["shellCmd", shellCmd as unknown as ActivityFn]]);
|
|
397
632
|
|
|
398
633
|
/**
|
|
@@ -406,25 +641,108 @@ export async function ciRun(req: CiRunRequest): Promise<CiRunResult> {
|
|
|
406
641
|
const top = gitTop(cwd);
|
|
407
642
|
if (!top) throw new CiRunError("a job runs in a fresh checkout of a commit, and this directory is not in a git repository");
|
|
408
643
|
const commit = resolveJobCommit(top, req.commit ?? "HEAD");
|
|
644
|
+
if (req.inPlace && commit !== resolveJobCommit(top, "HEAD")) throw new CiRunError(`--in-place runs the checkout's HEAD, and --commit names ${commit.slice(0, 12)}`);
|
|
645
|
+
if (req.local && req.inPlace) throw new CiRunError("--in-place runs a forge job in the forge's checkout, and --local runs one on this machine; pass one of them");
|
|
409
646
|
const place = ciJobPlace(cwd, top);
|
|
410
|
-
const jobs = prLoopJobs(req.forge, req.env, req.loop ?? {}, place.member);
|
|
411
|
-
const
|
|
412
|
-
if (!
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
const
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
647
|
+
const jobs = prLoopJobs(req.forge, req.env, req.local ? localLoop(req.loop) : (req.loop ?? {}), place.member);
|
|
648
|
+
const found = jobs.find((j) => j.jobName === req.job);
|
|
649
|
+
if (!found) throw new CiRunError(`the pull-request pipeline has no ${req.job} job; it has ${jobs.map((j) => j.jobName).join(" and ")}`);
|
|
650
|
+
if (req.wave !== undefined && (req.job !== "apply" || !req.loop?.waveGate)) throw new CiRunError("--wave names the one wave an apply runs, and takes --wave-gate: ci run apply --wave-gate <gate> --wave <k>");
|
|
651
|
+
// A generated wave job's command, as prLoopWaveJobs writes it. #4161: a local job's command posts no status or note.
|
|
652
|
+
const command = `${found.command}${req.wave !== undefined ? ` --wave ${req.wave}` : ""}${req.local ? " --no-status" : ""}`;
|
|
653
|
+
const job = { ...found, command, ...(req.wave !== undefined ? { wave: req.wave } : {}) };
|
|
654
|
+
const own = ciJobEnv(job, {
|
|
655
|
+
forge: req.forge,
|
|
656
|
+
env: req.env,
|
|
657
|
+
values: { base: req.base, pr: req.pr, commit },
|
|
658
|
+
hostEnv,
|
|
659
|
+
passEnv: req.passEnv,
|
|
660
|
+
secrets: ciSecretsFor(req.secrets, req.job),
|
|
661
|
+
});
|
|
662
|
+
// #4156: in place, the forge job's environment is the job's, less the runner key.
|
|
663
|
+
const jobEnv = req.inPlace ? { ...definedEnv(hostEnv), ...own } : own;
|
|
664
|
+
delete jobEnv[RUNNER_KEY_ENV];
|
|
665
|
+
// #4096: the secrets and the job's secret-looking variables, redacted from everything the run writes and returns.
|
|
666
|
+
// In place the forge's own variables come too; one whose value is short, such as a flag, is left out, so it can't blank common words.
|
|
667
|
+
const redact = ciRedactor(req.inPlace ? { ...longValues(jobEnv), ...own } : jobEnv, req.secrets);
|
|
668
|
+
try {
|
|
669
|
+
const result = await runCiJob(req, { cwd, top, commit, place, job, jobEnv, ...(redact ? { redact } : {}) });
|
|
670
|
+
return redact ? { ...result, warnings: result.warnings.map(redact), notes: result.notes.map(redact), ...(result.pushWarning ? { pushWarning: redact(result.pushWarning) } : {}) } : result;
|
|
671
|
+
} catch (err) {
|
|
672
|
+
if (redact && err instanceof Error) err.message = redact(err.message);
|
|
673
|
+
throw err;
|
|
425
674
|
}
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/** The generator's options for a `--local` job (#4161): the same, with no report URL. */
|
|
678
|
+
function localLoop(loop: PrLoopPipelineOptions | undefined): PrLoopPipelineOptions {
|
|
679
|
+
const { reportUrl: _dropped, ...rest } = loop ?? {};
|
|
680
|
+
return rest;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/** The variables of `env` whose value is long enough to be a credential, 8 characters or more. */
|
|
684
|
+
function longValues(env: Record<string, string>): Record<string, string> {
|
|
685
|
+
return Object.fromEntries(Object.entries(env).filter(([, value]) => value.length >= 8));
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/** The variables of `env` that have a value. */
|
|
689
|
+
function definedEnv(env: Record<string, string | undefined>): Record<string, string> {
|
|
690
|
+
const out: Record<string, string> = {};
|
|
691
|
+
for (const [name, value] of Object.entries(env)) if (value !== undefined) out[name] = value;
|
|
692
|
+
return out;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/**
|
|
696
|
+
* Who signs the run (#4156): the runner key, checked against
|
|
697
|
+
* `.chant/trust.json` at the commit. A key that isn't an Ed25519 PEM is an
|
|
698
|
+
* error, and so is a `--key` the file does not list. A key from the
|
|
699
|
+
* environment that it does not list, and no key at all, leave the run
|
|
700
|
+
* unsigned: a warning when the declaration's `ci.green.signedSince` asks
|
|
701
|
+
* for signed runs, a note for `--verbose` otherwise.
|
|
702
|
+
*/
|
|
703
|
+
function ciRunSigner(req: CiRunRequest, at: { cwd: string; top: string; commit: string }): { signer?: ReturnType<typeof ciTagSigner>; warnings: string[]; notes: string[] } {
|
|
704
|
+
// #4161: a local run never counts, signed or not, so nothing about its signature warns.
|
|
705
|
+
const required = req.local ? null : signedRunsRequired(at.cwd);
|
|
706
|
+
const say = (text: string) => (required ? { warnings: [`${text}, and ci.green.signedSince is ${required}, so it will not count`], notes: [] } : { warnings: [], notes: [text] });
|
|
707
|
+
if (req.runnerKey === undefined) return say("there is no runner key, so the run's record is unsigned");
|
|
708
|
+
try {
|
|
709
|
+
loadRunnerKey(req.runnerKey);
|
|
710
|
+
} catch (err) {
|
|
711
|
+
throw new CiRunError(`the runner key${req.runnerKeyFromEnv ? ` in ${RUNNER_KEY_ENV}` : ""} is not an Ed25519 private key in PEM: ${message(err)}`);
|
|
712
|
+
}
|
|
713
|
+
try {
|
|
714
|
+
return { signer: ciTagSigner(at.top, at.commit, req.runnerKey, "the runs it signed"), warnings: [], notes: [] };
|
|
715
|
+
} catch (err) {
|
|
716
|
+
if (!req.runnerKeyFromEnv) throw new CiRunError(`the run would not be signed: ${message(err)}`);
|
|
717
|
+
return say(`${RUNNER_KEY_ENV} is set, and the run will not be signed: ${message(err)}`);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
426
720
|
|
|
427
|
-
|
|
721
|
+
/** Where the job runs: a fresh worktree of the commit, or with `inPlace` the checkout itself, which is left as it is. */
|
|
722
|
+
function jobCheckout(top: string, commit: string, inPlace?: boolean): { dir: string; remove(): void } {
|
|
723
|
+
return inPlace ? { dir: top, remove() {} } : freshCheckout(top, commit);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
/** {@link ciRun} once the job and its environment are known. */
|
|
727
|
+
async function runCiJob(
|
|
728
|
+
req: CiRunRequest,
|
|
729
|
+
at: { cwd: string; top: string; commit: string; place: { member?: string; dir: string }; job: PrLoopJob; jobEnv: Record<string, string>; redact?: Redact },
|
|
730
|
+
): Promise<CiRunResult> {
|
|
731
|
+
const { cwd, top, commit, place, job, jobEnv, redact } = at;
|
|
732
|
+
const local = req.local === true;
|
|
733
|
+
const status = local ? undefined : req.status;
|
|
734
|
+
const reportUrl = local ? undefined : req.loop?.reportUrl;
|
|
735
|
+
|
|
736
|
+
// The record is signed against .chant/trust.json at the commit the job runs, so a --key that commit does not list is refused before anything runs.
|
|
737
|
+
const signing = ciRunSigner(req, { cwd, top, commit });
|
|
738
|
+
const signer = signing.signer;
|
|
739
|
+
const early = signing.warnings;
|
|
740
|
+
const notes = signing.notes;
|
|
741
|
+
const origin: RunOrigin = local ? CHANT_LOCAL_ORIGIN : req.inPlace ? (resolveCiJob(req.hostEnv ?? process.env)?.runOrigin ?? CHANT_RUNNER_ORIGIN) : CHANT_RUNNER_ORIGIN;
|
|
742
|
+
// In place, the job writes its record on the checkout's chant/lifecycle, which a forge checkout may not have fetched.
|
|
743
|
+
if (req.inPlace && req.push !== false) await fetchLifecycleStatus({ cwd: top }).catch(() => undefined);
|
|
744
|
+
|
|
745
|
+
const checkout = jobCheckout(top, commit, req.inPlace);
|
|
428
746
|
try {
|
|
429
747
|
const jobDir = place.dir === "." ? checkout.dir : join(checkout.dir, place.dir);
|
|
430
748
|
const op = ciJobOp({
|
|
@@ -432,7 +750,7 @@ export async function ciRun(req: CiRunRequest): Promise<CiRunResult> {
|
|
|
432
750
|
env: req.env,
|
|
433
751
|
cwd: jobDir,
|
|
434
752
|
jobEnv,
|
|
435
|
-
setup: prLoopSetup(req.image ?? PR_LOOP_IMAGE),
|
|
753
|
+
setup: req.inPlace ? [] : prLoopSetup(req.image ?? PR_LOOP_IMAGE),
|
|
436
754
|
build: [...(req.beforeScript ?? []), ...(job.build ?? [])],
|
|
437
755
|
...(req.chant ? { chant: req.chant } : {}),
|
|
438
756
|
});
|
|
@@ -443,6 +761,20 @@ export async function ciRun(req: CiRunRequest): Promise<CiRunResult> {
|
|
|
443
761
|
mkdirSync(dirname(resume.job), { recursive: true });
|
|
444
762
|
copyFileSync(resume.host, resume.job);
|
|
445
763
|
}
|
|
764
|
+
const statusContext = ciStatusContext(req.job, req.loop?.gate);
|
|
765
|
+
const statusUrl = status
|
|
766
|
+
? ciStatusUrl({ ...(status.urlTemplate ? { urlTemplate: status.urlTemplate } : {}), ...(reportUrl ? { reportUrl } : {}), ...(req.pr !== undefined ? { pr: req.pr } : {}), commit, ...(place.member ? { member: place.member } : {}) })
|
|
767
|
+
: undefined;
|
|
768
|
+
const postStatus = async (state: CommitStatus["state"], description: string): Promise<void> => {
|
|
769
|
+
if (!status) return;
|
|
770
|
+
try {
|
|
771
|
+
await status.client.setCommitStatus(commit, { context: statusContext, state, description: cut(redact ? redact(description) : description), ...(statusUrl ? { targetUrl: statusUrl } : {}) });
|
|
772
|
+
} catch (err) {
|
|
773
|
+
warnings.push(`could not set the ${statusContext} status: ${message(err)}`);
|
|
774
|
+
}
|
|
775
|
+
};
|
|
776
|
+
const startedAt = Date.now();
|
|
777
|
+
await postStatus("pending", `${op.name} running`);
|
|
446
778
|
let ledgerError: string | undefined;
|
|
447
779
|
let result: OpRunResult;
|
|
448
780
|
try {
|
|
@@ -452,20 +784,31 @@ export async function ciRun(req: CiRunRequest): Promise<CiRunResult> {
|
|
|
452
784
|
cwd: jobDir,
|
|
453
785
|
commit,
|
|
454
786
|
ledger: { cwd: jobDir },
|
|
455
|
-
runner: { kind: "pipeline", runtime: "ci", runOrigin:
|
|
787
|
+
runner: { kind: "pipeline", runtime: local ? "local" : "ci", runOrigin: origin },
|
|
456
788
|
describeFailure: (records) => ciJobFailure({ job: req.job, op, records, reportDir: join(jobDir, PR_LOOP_REPORT_DIR) }),
|
|
457
789
|
...(req.runEvents ? { runEvents: { write: req.runEvents } } : {}),
|
|
458
790
|
...(req.stepOutput ? { stepOutput: req.stepOutput } : {}),
|
|
459
791
|
...(signer ? { signRecord: (record: OpRunRecord) => signOpRunRecord(record, signer!) } : {}),
|
|
460
792
|
onSignError: (err) => warnings.push(`the run was not signed: ${message(err)}`),
|
|
793
|
+
...(redact ? { redact } : {}),
|
|
461
794
|
onLedgerError: (err) => {
|
|
462
795
|
ledgerError = `the job ran, and its record could not be written to chant/lifecycle: ${message(err)}`;
|
|
463
796
|
},
|
|
464
797
|
});
|
|
465
798
|
} catch (err) {
|
|
466
|
-
if (!(err instanceof OpRunFailure))
|
|
799
|
+
if (!(err instanceof OpRunFailure)) {
|
|
800
|
+
await postStatus("error", `${op.name} stopped: ${firstLine(message(err))}`);
|
|
801
|
+
throw err;
|
|
802
|
+
}
|
|
467
803
|
result = err.result;
|
|
468
804
|
}
|
|
805
|
+
const failedStep = result.records.find((r) => r.status === "fail");
|
|
806
|
+
const settled = ciStatusSettled(
|
|
807
|
+
op.name,
|
|
808
|
+
{ status: result.status, record: result.record, ...(failedStep ? { failure: { phase: failedStep.phase, error: failedStep.error ?? "the step failed" } } : {}) },
|
|
809
|
+
Math.round((Date.now() - startedAt) / 1000),
|
|
810
|
+
);
|
|
811
|
+
await postStatus(settled.state, settled.description);
|
|
469
812
|
|
|
470
813
|
if (resume && existsSync(resume.job)) {
|
|
471
814
|
try {
|
|
@@ -479,54 +822,131 @@ export async function ciRun(req: CiRunRequest): Promise<CiRunResult> {
|
|
|
479
822
|
let report: string | null = null;
|
|
480
823
|
if (existsSync(written)) {
|
|
481
824
|
report = resolve(cwd, req.output ?? PR_LOOP_REPORT_DIR);
|
|
482
|
-
|
|
825
|
+
// In place the report is already where the forge keeps it, and stays as the command wrote it.
|
|
826
|
+
if (!(existsSync(report) && realpathSync(report) === realpathSync(written))) {
|
|
827
|
+
cpSync(written, report, { recursive: true });
|
|
828
|
+
if (redact) redactTree(report, redact);
|
|
829
|
+
}
|
|
483
830
|
}
|
|
484
|
-
if (ledgerError) throw new CiRunError(ledgerError);
|
|
485
|
-
const pushed: { pushed: boolean; pushWarning?: string } = req.push === false ? { pushed: false, pushWarning: "chant/lifecycle was not pushed" } : await pushLifecycleReport({ cwd: top });
|
|
486
831
|
const failed = result.records.find((r) => r.status === "fail");
|
|
832
|
+
const exitCode = failed?.phase === req.job ? exitCodeOf(failed.error) : undefined;
|
|
833
|
+
if (reportUrl) {
|
|
834
|
+
await recordInIndex({ url: reportUrl, job: req.job, commit, member: place.member, pr: req.pr, record: result.record, runOrigin: origin, signer: result.record.statement && signer ? signer.principal : null, exitCode: ciJobExitCode({ status: result.status, ...(exitCode !== undefined ? { exitCode } : {}) }), reportDir: written, limit: req.runIndexLimit, ...(redact ? { redact } : {}), warn: (w) => warnings.push(w) });
|
|
835
|
+
}
|
|
836
|
+
// In place the forge job's outcome is the command's, so a record that could not be written is a warning there.
|
|
837
|
+
if (ledgerError && req.inPlace) warnings.push(ledgerError);
|
|
838
|
+
else if (ledgerError) throw new CiRunError(ledgerError);
|
|
839
|
+
const pushed: { pushed: boolean; pushWarning?: string } = local
|
|
840
|
+
? { pushed: false }
|
|
841
|
+
: req.push === false ? { pushed: false, pushWarning: "chant/lifecycle was not pushed" } : await pushLifecycleReport({ cwd: top });
|
|
487
842
|
return {
|
|
488
843
|
op: op.name,
|
|
489
844
|
status: result.status,
|
|
490
845
|
record: result.record,
|
|
491
846
|
...(failed ? { failure: { phase: failed.phase, error: failed.error ?? "the step failed" } } : {}),
|
|
847
|
+
...(exitCode !== undefined ? { exitCode } : {}),
|
|
492
848
|
report,
|
|
493
|
-
signer: signer
|
|
849
|
+
signer: result.record.statement && signer ? signer.principal : null,
|
|
494
850
|
pushed: pushed.pushed,
|
|
495
851
|
...(pushed.pushWarning ? { pushWarning: pushed.pushWarning } : {}),
|
|
496
852
|
warnings,
|
|
853
|
+
notes,
|
|
497
854
|
checkout: checkout.dir,
|
|
855
|
+
...(local ? { local: true as const } : {}),
|
|
498
856
|
};
|
|
499
857
|
} finally {
|
|
500
858
|
checkout.remove();
|
|
501
859
|
}
|
|
502
860
|
}
|
|
503
861
|
|
|
862
|
+
/**
|
|
863
|
+
* Redact every text file under `dir` in place (#4096): the job's report, its
|
|
864
|
+
* change set and its component events, as `ci run` copied them out. A
|
|
865
|
+
* JSON file stays JSON. A file with a NUL byte is taken as binary and left.
|
|
866
|
+
*/
|
|
867
|
+
export function redactTree(dir: string, redact: Redact): void {
|
|
868
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
869
|
+
const path = join(dir, entry.name);
|
|
870
|
+
if (entry.isDirectory()) {
|
|
871
|
+
redactTree(path, redact);
|
|
872
|
+
continue;
|
|
873
|
+
}
|
|
874
|
+
if (!entry.isFile()) continue;
|
|
875
|
+
const bytes = readFileSync(path);
|
|
876
|
+
if (bytes.includes(0)) continue;
|
|
877
|
+
const text = bytes.toString("utf-8");
|
|
878
|
+
const clean = redactText(text, redact);
|
|
879
|
+
if (clean !== text) writeFileSync(path, clean);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
|
|
504
883
|
/** The text `chant ci run` prints when the run settles. */
|
|
505
884
|
export function formatCiRun(r: CiRunResult): string {
|
|
506
885
|
const lines = [`${r.op} ${r.status === "ok" ? "passed" : r.status === "fail" ? "failed" : r.status} at ${r.record.gitSha?.slice(0, 12) ?? "an unknown commit"}, run ${r.record.id} in ${r.record.env}`];
|
|
507
886
|
const why = r.record.failure;
|
|
508
887
|
if (why) lines.push(` failure: ${why.code}${why.phase ? ` in ${why.phase}` : ""}${why.exitCode !== undefined ? `, exit ${why.exitCode}` : ""}: ${why.reason}`);
|
|
509
888
|
else if (r.failure) lines.push(` ${r.failure.phase}: ${r.failure.error}`);
|
|
510
|
-
lines.push(` record: ${r.record.env}/runs__${r.op}.jsonl on chant/lifecycle, ${r.signer ? `signed by ${r.signer}` : "unsigned
|
|
889
|
+
lines.push(` record: ${r.record.env}/runs__${r.op}.jsonl on chant/lifecycle, ${r.signer ? `signed by ${r.signer}` : "unsigned"}`);
|
|
511
890
|
if (r.report) lines.push(` report: ${r.report}`);
|
|
512
|
-
if (
|
|
891
|
+
if (r.local) lines.push(" local run: the record stays on this machine, with no push, commit status or report upload");
|
|
892
|
+
else if (!r.pushed) lines.push(` ${r.pushWarning ?? "chant/lifecycle was not pushed"}`);
|
|
513
893
|
return lines.join("\n");
|
|
514
894
|
}
|
|
515
895
|
|
|
516
896
|
// ── The command ──────────────────────────────────────────────────────────────
|
|
517
897
|
|
|
898
|
+
/**
|
|
899
|
+
* The generator's options from `ci run`'s and `ci serve`'s flags, as `chant
|
|
900
|
+
* build --components --generate <lexicon> --pr-loop` takes them: `--gate`,
|
|
901
|
+
* `--branch`, `--events-json`, `--report-url`, `--pr-build`,
|
|
902
|
+
* `--pr-build-command`, `--wave-gate` (#4003) and `--no-require-review`
|
|
903
|
+
* (#4156).
|
|
904
|
+
*/
|
|
905
|
+
export function ciLoopOptions(args: CommandContext["args"]): PrLoopPipelineOptions {
|
|
906
|
+
return {
|
|
907
|
+
...(args.gate ? { gate: args.gate } : {}),
|
|
908
|
+
...(args.branch ? { branch: args.branch } : {}),
|
|
909
|
+
...(args.eventsJson ? { events: true } : {}),
|
|
910
|
+
...(args.reportUrl ? { reportUrl: args.reportUrl } : {}),
|
|
911
|
+
...(args.prBuild ? { build: true } : {}),
|
|
912
|
+
...(args.prBuildCommand ? { buildCommand: args.prBuildCommand } : {}),
|
|
913
|
+
...(args.waveGate ? { waveGate: args.waveGate } : {}),
|
|
914
|
+
...(args.requireReview === false ? { requireReview: false } : {}),
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
|
|
518
918
|
const USAGE =
|
|
519
919
|
"chant ci run plan|apply --env <env> --base <sha> [--pr <n>] [--commit <sha>] [--forge github|gitlab|forgejo] " +
|
|
520
|
-
"[--gate <name>] [--branch <name>] [--events-json] [--report-url <url>] [--image <image>] [--before-script <line>]... " +
|
|
521
|
-
"[--pr-build] [--pr-build-command <line>] " +
|
|
522
|
-
"[--chant <command>] [--pass-env <NAME>]... [--key <runner key.pem>] [-o <dir>]";
|
|
920
|
+
"[--gate <name>] [--wave-gate <name>] [--wave <k>] [--branch <name>] [--events-json] [--report-url <url>] [--run-index-limit <n>] [--image <image>] [--before-script <line>]... " +
|
|
921
|
+
"[--pr-build] [--pr-build-command <line>] [--no-require-review] [--no-status] [--status-url <url template>] " +
|
|
922
|
+
"[--chant <command>] [--pass-env <NAME>]... [--secrets <file>] [--key <runner key.pem>] [-o <dir>] [--in-place | --local] [--no-push] [--verbose] | chant ci run --setup-key [--trust [--principal <name>]] [--state <dir>] [--branch <name>]";
|
|
523
923
|
|
|
524
924
|
const ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
525
925
|
|
|
926
|
+
/** The host's runner key in `--state`, or in ci serve's default state directory, when `--setup-key` made one there (#4099). */
|
|
927
|
+
function hostRunnerKeyAt(cwd: string, statePath: string | undefined): { pem: string } | undefined {
|
|
928
|
+
const common = statePath ? null : (() => {
|
|
929
|
+
try {
|
|
930
|
+
return execFileSync("git", ["rev-parse", "--path-format=absolute", "--git-common-dir"], { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
931
|
+
} catch {
|
|
932
|
+
return null;
|
|
933
|
+
}
|
|
934
|
+
})();
|
|
935
|
+
const dir = statePath ? resolve(statePath) : common ? join(common, "chant", "ci-serve") : null;
|
|
936
|
+
return dir ? hostRunnerKey(dir) : undefined;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
/** The secrets file `--secrets` names, else the one `CHANT_CI_SECRETS` names, read; undefined when neither names one (#4096). */
|
|
940
|
+
export function ciSecretsOf(flag: string | undefined, env: Record<string, string | undefined> = process.env): CiSecrets | undefined {
|
|
941
|
+
const file = flag ?? (env[CI_SECRETS_ENV] || undefined);
|
|
942
|
+
return file === undefined ? undefined : readCiSecrets(resolve(file));
|
|
943
|
+
}
|
|
944
|
+
|
|
526
945
|
/** `chant ci run plan|apply` (#4094). */
|
|
527
946
|
export async function runCiRun(ctx: CommandContext): Promise<number> {
|
|
528
947
|
const { args } = ctx;
|
|
529
948
|
try {
|
|
949
|
+
if (args.setupKey) return (await import("./ci-serve")).runnerKeySetupCommand(ctx);
|
|
530
950
|
const job = args.extraPositional;
|
|
531
951
|
if (job !== "plan" && job !== "apply") throw new CiRunError(`ci run takes the job to run, plan or apply${job ? `, not ${JSON.stringify(job)}` : ""}`);
|
|
532
952
|
if (!args.env) throw new CiRunError("--env names the environment the pipeline is for, as chant build --components --generate <lexicon> --pr-loop took it: --env <env>");
|
|
@@ -535,35 +955,47 @@ export async function runCiRun(ctx: CommandContext): Promise<number> {
|
|
|
535
955
|
if (!(FORGE_KINDS as readonly string[]).includes(forge)) throw new CiRunError(`--forge takes github, gitlab or forgejo, not ${JSON.stringify(forge)}`);
|
|
536
956
|
for (const name of args.passEnv ?? []) if (!ENV_NAME.test(name)) throw new CiRunError(`--pass-env takes a variable's name, not ${JSON.stringify(name)}`);
|
|
537
957
|
const fromFlag = args.key !== undefined;
|
|
538
|
-
|
|
958
|
+
// #4099: without --key or CHANT_RUNNER_KEY, the host's key that --setup-key made, if there is one. Like the variable, an unlisted one only warns.
|
|
959
|
+
const hostKey = fromFlag || process.env[RUNNER_KEY_ENV] ? undefined : hostRunnerKeyAt(process.cwd(), args.statePath);
|
|
960
|
+
const runnerKey = fromFlag ? readFileSync(resolve(args.key!), "utf-8") : process.env[RUNNER_KEY_ENV] || hostKey?.pem;
|
|
961
|
+
const hostEnv = process.env;
|
|
962
|
+
// #4156: in place the forge shows the job, so ci run sets no status of its own.
|
|
963
|
+
// #4161: a local run sets no status, so it builds no client and warns about none.
|
|
964
|
+
const { status, warning } = ciStatusFromEnv({ forge: forge as ForgeKind, hostEnv, noStatus: args.noStatus || args.ciInPlace || args.local, statusUrl: args.statusUrl });
|
|
965
|
+
if (warning) console.error(formatWarning({ message: warning }));
|
|
966
|
+
const secrets = ciSecretsOf(args.ciSecrets);
|
|
967
|
+
// #4160: without --report-url, the declaration's ci.reportUrl.
|
|
968
|
+
const reportUrl = await reportUrlOf(args.reportUrl, process.cwd());
|
|
539
969
|
const result = await ciRun({
|
|
540
970
|
cwd: process.cwd(),
|
|
971
|
+
...(status ? { status } : {}),
|
|
541
972
|
job,
|
|
542
973
|
env: args.env,
|
|
543
974
|
forge: forge as ForgeKind,
|
|
544
975
|
...(args.base ? { base: args.base } : {}),
|
|
545
976
|
...(args.pr !== undefined ? { pr: args.pr } : {}),
|
|
546
977
|
...(args.commit ? { commit: args.commit } : {}),
|
|
547
|
-
loop: {
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
...(args.prBuildCommand ? { buildCommand: args.prBuildCommand } : {}),
|
|
554
|
-
},
|
|
978
|
+
loop: ciLoopOptions(reportUrl ? { ...args, reportUrl } : args),
|
|
979
|
+
...(args.ciInPlace ? { inPlace: true } : {}),
|
|
980
|
+
...(args.local ? { local: true } : {}),
|
|
981
|
+
...(args.noPush ? { push: false } : {}),
|
|
982
|
+
...(args.wave !== undefined ? { wave: args.wave } : {}),
|
|
983
|
+
...(args.runIndexLimit ? { runIndexLimit: args.runIndexLimit } : {}),
|
|
555
984
|
...(args.image ? { image: args.image } : {}),
|
|
556
985
|
...(args.beforeScript?.length ? { beforeScript: args.beforeScript } : {}),
|
|
557
986
|
...(args.ciChant ? { chant: args.ciChant } : {}),
|
|
558
987
|
...(args.passEnv?.length ? { passEnv: args.passEnv } : {}),
|
|
988
|
+
...(secrets ? { secrets } : {}),
|
|
559
989
|
...(args.output ? { output: args.output } : {}),
|
|
560
990
|
...(runnerKey !== undefined ? { runnerKey, runnerKeyFromEnv: !fromFlag } : {}),
|
|
561
991
|
...(args.eventsJson ? { runEvents: (line: string) => void process.stdout.write(line) } : {}),
|
|
562
992
|
stepOutput: (line) => void process.stderr.write(`${line}\n`),
|
|
563
993
|
});
|
|
564
994
|
for (const w of result.warnings) console.error(formatWarning({ message: w }));
|
|
995
|
+
if (args.verbose) for (const n of result.notes) console.error(`ci run: ${n}`);
|
|
565
996
|
console.error(formatCiRun(result));
|
|
566
|
-
|
|
997
|
+
// #4156: a failed command's own exit code, such as 3 for a moved plan, is the job's, as it was before the job ran through ci run.
|
|
998
|
+
return ciJobExitCode(result);
|
|
567
999
|
} catch (err) {
|
|
568
1000
|
console.error(formatError({ message: `chant ci run: ${message(err)}`, hint: USAGE }));
|
|
569
1001
|
return 1;
|