@intentius/chant 0.124.32 → 0.124.34
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/dist/ci-jobs.d.ts +4 -0
- package/dist/ci-jobs.d.ts.map +1 -1
- package/dist/ci-jobs.js +9 -2
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/build.js +18 -3
- package/dist/cli/handlers/lifecycle-health.d.ts +16 -0
- package/dist/cli/handlers/lifecycle-health.d.ts.map +1 -0
- package/dist/cli/handlers/lifecycle-health.js +150 -0
- package/dist/cli/handlers/lifecycle.d.ts +26 -0
- package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
- package/dist/cli/handlers/lifecycle.js +1 -1
- package/dist/cli/handlers/pr.d.ts +2 -0
- package/dist/cli/handlers/pr.d.ts.map +1 -1
- package/dist/cli/handlers/pr.js +124 -2
- package/dist/cli/handlers/promote.d.ts.map +1 -1
- package/dist/cli/handlers/promote.js +277 -74
- package/dist/cli/handlers/release.d.ts +16 -1
- package/dist/cli/handlers/release.d.ts.map +1 -1
- package/dist/cli/handlers/release.js +83 -11
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +99 -22
- package/dist/cli/parse-args.d.ts.map +1 -1
- package/dist/cli/parse-args.js +30 -1
- package/dist/cli/registry.d.ts +21 -5
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/components/deploy-inputs.d.ts +91 -0
- package/dist/components/deploy-inputs.d.ts.map +1 -0
- package/dist/components/deploy-inputs.js +184 -0
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/driver.js +9 -0
- package/dist/components/gate-plan.d.ts +8 -0
- package/dist/components/gate-plan.d.ts.map +1 -1
- package/dist/components/gate-plan.js +6 -0
- package/dist/components/pr-pipeline.d.ts +33 -10
- package/dist/components/pr-pipeline.d.ts.map +1 -1
- package/dist/components/pr-pipeline.js +42 -11
- package/dist/components/pr-run.d.ts +1 -1
- package/dist/components/pr-run.d.ts.map +1 -1
- package/dist/components/promote-rounds.d.ts +102 -0
- package/dist/components/promote-rounds.d.ts.map +1 -0
- package/dist/components/promote-rounds.js +174 -0
- package/dist/components/release-compile.d.ts +37 -2
- package/dist/components/release-compile.d.ts.map +1 -1
- package/dist/components/release-compile.js +64 -8
- package/dist/components/release-gate.d.ts +30 -0
- package/dist/components/release-gate.d.ts.map +1 -1
- package/dist/components/release-gate.js +92 -42
- package/dist/components/release-promote.d.ts +17 -3
- package/dist/components/release-promote.d.ts.map +1 -1
- package/dist/components/release-promote.js +79 -18
- package/dist/components/verbs/bom-writer.d.ts +1 -1
- package/dist/components/verbs/bom-writer.d.ts.map +1 -1
- package/dist/components/verbs/lockfile-sbom-generator.d.ts +20 -0
- package/dist/components/verbs/lockfile-sbom-generator.d.ts.map +1 -1
- package/dist/components/verbs/lockfile-sbom-generator.js +41 -4
- package/dist/components/wave-plan.d.ts +10 -0
- package/dist/components/wave-plan.d.ts.map +1 -1
- package/dist/components/wave-plan.js +32 -4
- package/dist/graph-declared.d.ts +4 -0
- package/dist/graph-declared.d.ts.map +1 -1
- package/dist/graph-declared.js +11 -1
- package/dist/graph-detail.d.ts.map +1 -1
- package/dist/graph-detail.js +32 -7
- package/dist/health.d.ts +110 -0
- package/dist/health.d.ts.map +1 -0
- package/dist/health.js +105 -0
- package/dist/health.schema.json +191 -0
- package/dist/lexicon.d.ts +27 -0
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/merge-record.d.ts +104 -0
- package/dist/lifecycle/merge-record.d.ts.map +1 -0
- package/dist/lifecycle/merge-record.js +102 -0
- package/dist/lifecycle/publish-attachments.d.ts +8 -5
- package/dist/lifecycle/publish-attachments.d.ts.map +1 -1
- package/dist/lifecycle/publish-attachments.js +27 -13
- package/dist/lifecycle/publish-npm.d.ts +3 -2
- package/dist/lifecycle/publish-npm.d.ts.map +1 -1
- package/dist/lifecycle/publish-npm.js +4 -3
- package/dist/lifecycle/release-at.d.ts +40 -0
- package/dist/lifecycle/release-at.d.ts.map +1 -0
- package/dist/lifecycle/release-at.js +83 -0
- package/dist/lifecycle/release-cut.d.ts.map +1 -1
- package/dist/lifecycle/release-cut.js +23 -12
- package/dist/lifecycle/release-publish.d.ts +28 -12
- package/dist/lifecycle/release-publish.d.ts.map +1 -1
- package/dist/lifecycle/release-publish.js +29 -17
- package/dist/lifecycle/release-store.d.ts +25 -2
- package/dist/lifecycle/release-store.d.ts.map +1 -1
- package/dist/lifecycle/release-store.js +40 -13
- package/dist/lifecycle/run-ledger.d.ts.map +1 -1
- package/dist/lifecycle/run-ledger.js +1 -0
- package/dist/lint/rules/op/index.d.ts +2 -1
- package/dist/lint/rules/op/index.d.ts.map +1 -1
- package/dist/lint/rules/op/index.js +4 -2
- package/dist/lint/rules/op/ops018-shell-image-digest.d.ts +10 -0
- package/dist/lint/rules/op/ops018-shell-image-digest.d.ts.map +1 -0
- package/dist/lint/rules/op/ops018-shell-image-digest.js +38 -0
- package/dist/op/activities/activity-contracts.d.ts +5 -0
- package/dist/op/activities/activity-contracts.d.ts.map +1 -1
- package/dist/op/activities/activity-contracts.js +3 -0
- package/dist/op/activities/shell.d.ts +38 -0
- package/dist/op/activities/shell.d.ts.map +1 -1
- package/dist/op/activities/shell.js +89 -3
- package/dist/op/builders.d.ts +7 -0
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/builders.js +7 -0
- package/dist/op/runtime.d.ts +2 -0
- package/dist/op/runtime.d.ts.map +1 -1
- package/dist/pr-loop.d.ts +14 -3
- package/dist/pr-loop.d.ts.map +1 -1
- package/dist/pr-loop.js +15 -5
- package/dist/pr-report-url.d.ts +2 -2
- package/dist/pr-report-url.d.ts.map +1 -1
- package/dist/runner-start.d.ts +179 -0
- package/dist/runner-start.d.ts.map +1 -0
- package/dist/runner-start.js +277 -0
- package/dist/wave-runtime.mjs +236 -229
- package/dist/workspace/__fixtures__/contract-repo.d.ts.map +1 -1
- package/dist/workspace/__fixtures__/contract-repo.js +13 -2
- package/dist/workspace/check.schema.json +88 -2
- package/dist/workspace/checks/ci-workflow.d.ts +17 -0
- package/dist/workspace/checks/ci-workflow.d.ts.map +1 -0
- package/dist/workspace/checks/ci-workflow.js +123 -0
- package/dist/workspace/checks/security.d.ts +50 -0
- package/dist/workspace/checks/security.d.ts.map +1 -0
- package/dist/workspace/checks/security.js +305 -0
- package/dist/workspace/checks.d.ts +2 -0
- package/dist/workspace/checks.d.ts.map +1 -1
- package/dist/workspace/checks.js +8 -0
- package/dist/workspace/ci-builds.d.ts +163 -0
- package/dist/workspace/ci-builds.d.ts.map +1 -0
- package/dist/workspace/ci-builds.js +315 -0
- package/dist/workspace/ci-green-cli.d.ts +2 -1
- package/dist/workspace/ci-green-cli.d.ts.map +1 -1
- package/dist/workspace/ci-green-cli.js +25 -5
- package/dist/workspace/ci-green-forge.d.ts +12 -0
- package/dist/workspace/ci-green-forge.d.ts.map +1 -1
- package/dist/workspace/ci-green-forge.js +12 -0
- package/dist/workspace/ci-green-workflow.d.ts +55 -1
- package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
- package/dist/workspace/ci-green-workflow.js +128 -1
- package/dist/workspace/ci-green.d.ts +89 -4
- package/dist/workspace/ci-green.d.ts.map +1 -1
- package/dist/workspace/ci-green.js +310 -23
- package/dist/workspace/ci-init.d.ts +100 -0
- package/dist/workspace/ci-init.d.ts.map +1 -0
- package/dist/workspace/ci-init.js +434 -0
- package/dist/workspace/ci-last-green.schema.json +49 -0
- package/dist/workspace/ci-list.schema.json +35 -1
- package/dist/workspace/ci-run-index.d.ts +1 -1
- package/dist/workspace/ci-run-index.d.ts.map +1 -1
- package/dist/workspace/ci-run-index.schema.json +1 -1
- package/dist/workspace/ci-run.d.ts +12 -6
- package/dist/workspace/ci-run.d.ts.map +1 -1
- package/dist/workspace/ci-run.js +72 -16
- package/dist/workspace/ci-runner-start.d.ts +77 -0
- package/dist/workspace/ci-runner-start.d.ts.map +1 -0
- package/dist/workspace/ci-runner-start.js +185 -0
- package/dist/workspace/ci-serve.d.ts +76 -7
- package/dist/workspace/ci-serve.d.ts.map +1 -1
- package/dist/workspace/ci-serve.js +153 -19
- package/dist/workspace/ci-workflow.d.ts +91 -0
- package/dist/workspace/ci-workflow.d.ts.map +1 -0
- package/dist/workspace/ci-workflow.js +33 -0
- package/dist/workspace/declaration.d.ts +35 -0
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.js +12 -2
- package/dist/workspace/declaration.schema.json +51 -0
- package/dist/workspace/init.js +2 -2
- package/dist/workspace/ls.d.ts +2 -0
- package/dist/workspace/ls.d.ts.map +1 -1
- package/dist/workspace/ls.js +1 -1
- package/dist/workspace/ls.schema.json +2 -1
- package/dist/workspace/member-pipeline.d.ts +1 -0
- package/dist/workspace/member-pipeline.d.ts.map +1 -1
- package/dist/workspace/member-pipeline.js +2 -0
- package/dist/workspace/merge-record.schema.json +49 -0
- package/dist/workspace/pr-report.schema.json +3 -3
- package/dist/workspace/reason-codes.d.ts +26 -0
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/reason-codes.js +28 -0
- package/dist/workspace/release-candidates.d.ts +129 -0
- package/dist/workspace/release-candidates.d.ts.map +1 -0
- package/dist/workspace/release-candidates.js +233 -0
- package/dist/workspace/release-candidates.schema.json +353 -0
- package/dist/workspace/release-workflow.d.ts +31 -0
- package/dist/workspace/release-workflow.d.ts.map +1 -0
- package/dist/workspace/release-workflow.js +150 -0
- package/dist/workspace/security.d.ts +40 -0
- package/dist/workspace/security.d.ts.map +1 -0
- package/dist/workspace/security.js +99 -0
- package/package.json +8 -1
- package/src/__fixtures__/pr-loop/pr-plan.note.golden.md +1 -1
- package/src/ci-jobs.ts +11 -2
- package/src/cli/handlers/build.test.ts +36 -0
- package/src/cli/handlers/build.ts +18 -3
- package/src/cli/handlers/lifecycle-health.test.ts +123 -0
- package/src/cli/handlers/lifecycle-health.ts +157 -0
- package/src/cli/handlers/lifecycle.ts +1 -1
- package/src/cli/handlers/pr-deploy-inputs.test.ts +209 -0
- package/src/cli/handlers/pr-record.test.ts +201 -0
- package/src/cli/handlers/pr.test.ts +22 -0
- package/src/cli/handlers/pr.ts +127 -4
- package/src/cli/handlers/promote.test.ts +17 -0
- package/src/cli/handlers/promote.ts +296 -84
- package/src/cli/handlers/release-cut-at.e2e.test.ts +115 -0
- package/src/cli/handlers/release-publish-targets.e2e.test.ts +2 -2
- package/src/cli/handlers/release-verify.e2e.test.ts +12 -10
- package/src/cli/handlers/release.e2e.test.ts +19 -28
- package/src/cli/handlers/release.ts +81 -11
- package/src/cli/main.ts +99 -22
- package/src/cli/parse-args.ts +22 -1
- package/src/cli/registry.ts +21 -5
- package/src/components/deploy-inputs.test.ts +108 -0
- package/src/components/deploy-inputs.ts +225 -0
- package/src/components/driver.ts +11 -0
- package/src/components/gate-plan.ts +9 -0
- package/src/components/pr-pipeline.ts +56 -15
- package/src/components/pr-run.ts +1 -1
- package/src/components/promote-rounds.test.ts +220 -0
- package/src/components/promote-rounds.ts +232 -0
- package/src/components/release-compile.test.ts +34 -0
- package/src/components/release-compile.ts +89 -8
- package/src/components/release-gate.test.ts +47 -0
- package/src/components/release-gate.ts +111 -46
- package/src/components/release-promote.test.ts +55 -2
- package/src/components/release-promote.ts +93 -23
- package/src/components/verbs/bom-writer.ts +1 -1
- package/src/components/verbs/lockfile-sbom-generator.test.ts +36 -0
- package/src/components/verbs/lockfile-sbom-generator.ts +43 -5
- package/src/components/wave-plan.ts +32 -4
- package/src/graph-declared-exports.test.ts +40 -0
- package/src/graph-declared.ts +9 -1
- package/src/graph-detail.test.ts +57 -0
- package/src/graph-detail.ts +33 -7
- package/src/health.schema.json +191 -0
- package/src/health.test.ts +62 -0
- package/src/health.ts +178 -0
- package/src/lexicon.ts +41 -1
- package/src/lifecycle/merge-record.test.ts +111 -0
- package/src/lifecycle/merge-record.ts +154 -0
- package/src/lifecycle/publish-attachments.ts +25 -13
- package/src/lifecycle/publish-npm.ts +4 -3
- package/src/lifecycle/publish-targets.test.ts +34 -4
- package/src/lifecycle/release-at.test.ts +48 -0
- package/src/lifecycle/release-at.ts +100 -0
- package/src/lifecycle/release-cut.test.ts +41 -0
- package/src/lifecycle/release-cut.ts +22 -11
- package/src/lifecycle/release-publish.test.ts +43 -13
- package/src/lifecycle/release-publish.ts +38 -18
- package/src/lifecycle/release-store.ts +57 -13
- package/src/lifecycle/run-ledger.test.ts +13 -0
- package/src/lifecycle/run-ledger.ts +1 -0
- package/src/lint/rules/op/index.ts +4 -2
- package/src/lint/rules/op/ops018-shell-image-digest.test.ts +60 -0
- package/src/lint/rules/op/ops018-shell-image-digest.ts +48 -0
- package/src/op/activities/activity-contracts.ts +3 -0
- package/src/op/activities/shell-container.e2e.test.ts +67 -0
- package/src/op/activities/shell.test.ts +44 -1
- package/src/op/activities/shell.ts +110 -3
- package/src/op/builders.ts +7 -0
- package/src/op/runtime.ts +2 -0
- package/src/op/step-console.test.ts +4 -2
- package/src/pr-loop.test.ts +29 -0
- package/src/pr-loop.ts +26 -7
- package/src/pr-report-url.ts +2 -2
- package/src/runner-start-bundle.test.ts +29 -0
- package/src/runner-start.test.ts +179 -0
- package/src/runner-start.ts +329 -0
- package/src/workspace/__fixtures__/contract-repo.ts +11 -2
- package/src/workspace/check-contract.test.ts +4 -2
- package/src/workspace/check.schema.json +88 -2
- package/src/workspace/checks/ci-workflow.test.ts +150 -0
- package/src/workspace/checks/ci-workflow.ts +129 -0
- package/src/workspace/checks/security.test.ts +138 -0
- package/src/workspace/checks/security.ts +305 -0
- package/src/workspace/checks.ts +8 -0
- package/src/workspace/ci-builds.e2e.test.ts +208 -0
- package/src/workspace/ci-builds.test.ts +174 -0
- package/src/workspace/ci-builds.ts +385 -0
- package/src/workspace/ci-green-builds.test.ts +180 -0
- package/src/workspace/ci-green-cli.test.ts +54 -0
- package/src/workspace/ci-green-cli.ts +26 -5
- package/src/workspace/ci-green-forge.ts +24 -0
- package/src/workspace/ci-green-workflow.test.ts +84 -1
- package/src/workspace/ci-green-workflow.ts +139 -2
- package/src/workspace/ci-green.test.ts +5 -2
- package/src/workspace/ci-green.ts +359 -27
- package/src/workspace/ci-init.test.ts +159 -0
- package/src/workspace/ci-init.ts +445 -0
- package/src/workspace/ci-last-green.schema.json +49 -0
- package/src/workspace/ci-list.schema.json +35 -1
- package/src/workspace/ci-report-url.test.ts +6 -1
- package/src/workspace/ci-run-index.schema.json +1 -1
- package/src/workspace/ci-run-index.ts +1 -1
- package/src/workspace/ci-run.test.ts +24 -7
- package/src/workspace/ci-run.ts +78 -16
- package/src/workspace/ci-runner-start.test.ts +151 -0
- package/src/workspace/ci-runner-start.ts +193 -0
- package/src/workspace/ci-serve-builds.test.ts +86 -0
- package/src/workspace/ci-serve.test.ts +59 -0
- package/src/workspace/ci-serve.ts +177 -22
- package/src/workspace/ci-workflow.ts +71 -0
- package/src/workspace/declaration.schema.json +51 -0
- package/src/workspace/declaration.test.ts +17 -0
- package/src/workspace/declaration.ts +53 -3
- package/src/workspace/init.ts +2 -2
- package/src/workspace/ls.schema.json +2 -1
- package/src/workspace/ls.ts +3 -2
- package/src/workspace/member-pipeline.ts +2 -1
- package/src/workspace/merge-record.schema.json +49 -0
- package/src/workspace/pr-report.schema.json +3 -3
- package/src/workspace/reason-codes.test.ts +4 -0
- package/src/workspace/reason-codes.ts +28 -0
- package/src/workspace/release-candidates.schema.json +353 -0
- package/src/workspace/release-candidates.test.ts +106 -0
- package/src/workspace/release-candidates.ts +315 -0
- package/src/workspace/release-workflow.ts +167 -0
- package/src/workspace/security.test.ts +70 -0
- package/src/workspace/security.ts +118 -0
|
@@ -56,7 +56,10 @@ import { judgeCommits, mergeCommits, readWorkIndex } from "./commit-links.js";
|
|
|
56
56
|
import { execFileSync } from "node:child_process";
|
|
57
57
|
import { readDeclaration, readerVersion, WORKSPACE_ERROR_CODES, WorkspaceReadError } from "./declaration.js";
|
|
58
58
|
import { readRunLedger } from "../lifecycle/run-ledger.js";
|
|
59
|
-
import { fetchLifecycleStatus, listFilesInDir, listLedgerTrees } from "../lifecycle/git.js";
|
|
59
|
+
import { fetchLifecycleStatus, listFilesInDir, listLedgerTrees, pushLifecycleStatus, writeLedgerFiles } from "../lifecycle/git.js";
|
|
60
|
+
import { ledgerRecordingOff } from "../lifecycle/no-record.js";
|
|
61
|
+
import { agedOut, buildRequestAnnotation, CI_BUILD_TAG_PREFIX, planBuilds, readBuildRequests, readCiRecords, recordOf, recordPath, requestedBuild, sameRecord } from "./ci-builds.js";
|
|
62
|
+
import { dispatchableWorkflows, readWorkflowSources } from "./ci-green-workflow.js";
|
|
60
63
|
import { signOpRunRecord, verifyOpRunStatement } from "./trust/op-run-statement.js";
|
|
61
64
|
import { gitTop } from "./tree.js";
|
|
62
65
|
import { locateWorkspace } from "./which-chant.js";
|
|
@@ -79,6 +82,8 @@ export class CiGreenError extends Error {
|
|
|
79
82
|
this.name = "CiGreenError";
|
|
80
83
|
}
|
|
81
84
|
}
|
|
85
|
+
/** Conclusions that say a run was stopped, or never started, rather than that it failed (#4169). */
|
|
86
|
+
const UNBUILT_CONCLUSIONS = new Set(["cancelled", "stale"]);
|
|
82
87
|
/** Whether a check run's name matches a pattern: the whole name, `*` standing for any run of characters. */
|
|
83
88
|
export function matchesCheckRun(pattern, name) {
|
|
84
89
|
const re = new RegExp(`^${pattern.split("*").map((p) => p.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join(".*")}$`, "s");
|
|
@@ -101,6 +106,8 @@ function runVerdict(phase, r) {
|
|
|
101
106
|
return "pass";
|
|
102
107
|
if (r.conclusion === "skipped")
|
|
103
108
|
return phase.skipped === "pass" ? "pass" : "fail";
|
|
109
|
+
if (r.conclusion !== null && UNBUILT_CONCLUSIONS.has(r.conclusion))
|
|
110
|
+
return "unbuilt";
|
|
104
111
|
return "fail";
|
|
105
112
|
}
|
|
106
113
|
/** Judge one phase against a commit's latest attempts. */
|
|
@@ -117,21 +124,34 @@ export function evaluatePhase(phase, latest) {
|
|
|
117
124
|
runs.push({ name: r.name, id: r.id, status: r.status, conclusion: r.conclusion, url: r.url, verdict: runVerdict(phase, r), ...(r.opRun ? { opRun: r.opRun } : {}), ...(r.failureCode ? { failureCode: r.failureCode } : {}) });
|
|
118
125
|
}
|
|
119
126
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
127
|
+
return { phase: phase.name, ...phaseVerdictOf(runs, unmatched), runs, unmatched };
|
|
128
|
+
}
|
|
129
|
+
/** A phase's verdict from its runs: a failure wins, then a run still going or a pattern no run matches, then a cancelled run. */
|
|
130
|
+
export function phaseVerdictOf(runs, unmatched) {
|
|
131
|
+
if (runs.some((r) => r.verdict === "fail"))
|
|
132
|
+
return { verdict: "fail" };
|
|
133
|
+
if (runs.some((r) => r.verdict === "pending") || unmatched.length > 0)
|
|
134
|
+
return { verdict: "pending" };
|
|
135
|
+
if (runs.some((r) => r.verdict === "unbuilt"))
|
|
136
|
+
return { verdict: "unbuilt", reason: "cancelled" };
|
|
137
|
+
return { verdict: "pass" };
|
|
126
138
|
}
|
|
127
139
|
/** Judge a commit: it fails when a required phase fails, and passes when every one passes. */
|
|
128
140
|
export function evaluateCommit(green, runs) {
|
|
129
141
|
const latest = latestAttempts(runs);
|
|
130
|
-
|
|
142
|
+
return commitVerdictOf(green.require.map((name) => evaluatePhase(green.phases.find((p) => p.name === name), latest)));
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* A commit's verdict from its required phases: it fails when one fails, is
|
|
146
|
+
* pending while one is, is unbuilt when one is unbuilt (#4169), and passes
|
|
147
|
+
* when every one passes.
|
|
148
|
+
*/
|
|
149
|
+
export function commitVerdictOf(phases) {
|
|
131
150
|
const failed = phases.find((p) => p.verdict === "fail");
|
|
132
151
|
if (failed)
|
|
133
152
|
return { verdict: "fail", phases, failure: { phase: failed.phase, run: failed.runs.find((r) => r.verdict === "fail") } };
|
|
134
|
-
|
|
153
|
+
const verdict = phases.some((p) => p.verdict === "pending") ? "pending" : phases.some((p) => p.verdict === "unbuilt") ? "unbuilt" : "pass";
|
|
154
|
+
return { verdict, phases, failure: null };
|
|
135
155
|
}
|
|
136
156
|
/** A time as an annotation and a tick report write it: UTC, to the second. */
|
|
137
157
|
const iso = (d) => d.toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
@@ -398,22 +418,31 @@ export async function ciTick(req) {
|
|
|
398
418
|
const signer = req.runnerKey !== undefined ? ciTagSigner(top, base, req.runnerKey) : undefined;
|
|
399
419
|
const links = await commitLinks(req.cwd, top, tracking, commits.map((c) => c.sha));
|
|
400
420
|
const tags = readCiTags(top, green, base);
|
|
421
|
+
const warnings = [];
|
|
401
422
|
let runs;
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
423
|
+
// The runs and the records (#4169) live on chant/lifecycle, which a CI checkout may not have yet.
|
|
424
|
+
const fetched = await fetchLifecycleStatus({ cwd: top });
|
|
425
|
+
const ledgerOk = fetched.status !== "diverged" && fetched.status !== "unreachable";
|
|
426
|
+
if (!ledgerOk) {
|
|
427
|
+
const why = `could not fetch chant/lifecycle from ${fetched.remote}: ${fetched.status === "diverged" ? "the local branch and the remote's have diverged" : fetched.stderr}`;
|
|
428
|
+
if (green.source === "runs")
|
|
429
|
+
throw new Error(why.replace("chant/lifecycle", "chant/lifecycle, where the Op runs are recorded,"));
|
|
430
|
+
warnings.push(`${why}; no _ci records were read or written`);
|
|
409
431
|
}
|
|
432
|
+
if (green.source === "runs")
|
|
433
|
+
runs = await readOpRunSource(req.cwd, top, base, green.signedSince);
|
|
434
|
+
const runsBy = new Map();
|
|
410
435
|
const planned = await planTick({
|
|
411
436
|
green,
|
|
412
437
|
commits,
|
|
413
438
|
greenTags: new Set(tags.green.keys()),
|
|
414
439
|
revokedTags: new Set(tags.revoked.keys()),
|
|
415
440
|
ignored: tags.ignored,
|
|
416
|
-
checkRuns: async (sha) =>
|
|
441
|
+
checkRuns: async (sha) => {
|
|
442
|
+
const got = runs ? runs.checkRuns(sha) : await forge.checkRuns(sha);
|
|
443
|
+
runsBy.set(sha, got);
|
|
444
|
+
return got;
|
|
445
|
+
},
|
|
417
446
|
now,
|
|
418
447
|
signer,
|
|
419
448
|
});
|
|
@@ -427,6 +456,10 @@ export async function ciTick(req) {
|
|
|
427
456
|
c.ignoredRuns = ignored;
|
|
428
457
|
}
|
|
429
458
|
}
|
|
459
|
+
const records = ledgerOk ? readCiRecords(top) : new Map();
|
|
460
|
+
const requests = readBuildRequests(top);
|
|
461
|
+
const retryVia = green.source === "runs" ? "request" : forge?.rerun ? "forge" : null;
|
|
462
|
+
const builds = planBuilds({ green, commits: planned, runs: runsBy, requests, records, now, retryVia });
|
|
430
463
|
const due = planned.filter((c) => c.made !== null);
|
|
431
464
|
if (!req.dryRun && due.length > 0) {
|
|
432
465
|
const ident = taggerConfig(top);
|
|
@@ -442,7 +475,217 @@ export async function ciTick(req) {
|
|
|
442
475
|
throw new Error(`could not push ${due.map((c) => c.made.tag).join(", ")} to ${remote}: ${cause ? `${cause}\n` : ""}${stderr}`);
|
|
443
476
|
}
|
|
444
477
|
}
|
|
445
|
-
|
|
478
|
+
const retried = builds.retries.map((r) => ({ sha: r.sha, run: r.run.name }));
|
|
479
|
+
let recorded = [];
|
|
480
|
+
if (!req.dryRun) {
|
|
481
|
+
const asked = await askForBuilds({ top, remote, green, forge, builds, requests, records, planned, warnings });
|
|
482
|
+
recorded = ledgerOk && !ledgerRecordingOff() ? await writeRecords({ top, green, planned, records, builds, requests: asked.live, dispatched: asked.dispatched, now, warnings }) : [];
|
|
483
|
+
}
|
|
484
|
+
return {
|
|
485
|
+
branch: green.branch,
|
|
486
|
+
window: green.window,
|
|
487
|
+
ref: tracking,
|
|
488
|
+
commits: planned,
|
|
489
|
+
made: due.map((c) => c.made.tag),
|
|
490
|
+
requested: builds.requests,
|
|
491
|
+
retried,
|
|
492
|
+
recorded,
|
|
493
|
+
warnings,
|
|
494
|
+
signer: signer?.principal ?? null,
|
|
495
|
+
source: green.source,
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Make what the plan asks for (#4169): push each new request tag and start
|
|
500
|
+
* the workflows holding its phases at it on GitHub, start any older request
|
|
501
|
+
* no tick has started yet, run the failed jobs again, and delete the tags of
|
|
502
|
+
* requests that are done. Each step that fails is a warning, never the
|
|
503
|
+
* tick's failure: the green tags are already pushed.
|
|
504
|
+
*/
|
|
505
|
+
async function askForBuilds(input) {
|
|
506
|
+
const { top, remote, green, forge, builds, warnings } = input;
|
|
507
|
+
const live = new Map(input.requests);
|
|
508
|
+
for (const tag of builds.resolved)
|
|
509
|
+
live.delete(tag.slice(CI_BUILD_TAG_PREFIX.length));
|
|
510
|
+
if (builds.requests.length > 0) {
|
|
511
|
+
const ident = taggerConfig(top);
|
|
512
|
+
const made = [];
|
|
513
|
+
for (const r of builds.requests) {
|
|
514
|
+
if (tryGit(top, [...ident, "tag", "-a", "-m", buildRequestAnnotation(r), r.tag, r.commit]) === null)
|
|
515
|
+
warnings.push(`could not make ${r.tag}`);
|
|
516
|
+
else
|
|
517
|
+
made.push(r);
|
|
518
|
+
}
|
|
519
|
+
if (made.length > 0) {
|
|
520
|
+
try {
|
|
521
|
+
git(top, ["push", "--quiet", remote, ...made.map((r) => `refs/tags/${r.tag}`)]);
|
|
522
|
+
for (const r of made)
|
|
523
|
+
live.set(r.commit, r);
|
|
524
|
+
}
|
|
525
|
+
catch (err) {
|
|
526
|
+
for (const r of made)
|
|
527
|
+
tryGit(top, ["tag", "-d", r.tag]);
|
|
528
|
+
const stderr = stderrOf(err);
|
|
529
|
+
warnings.push(`could not push ${made.map((r) => r.tag).join(", ")} to ${remote}: ${pushRefusalCause(stderr) ?? stderr}`);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
const dispatched = new Set();
|
|
534
|
+
if (green.source === "checks" && forge?.dispatch) {
|
|
535
|
+
const sources = readWorkflowSources(top, "");
|
|
536
|
+
for (const r of live.values()) {
|
|
537
|
+
if (input.records.get(r.commit)?.request?.dispatched && input.records.get(r.commit)?.request?.tag === r.tag)
|
|
538
|
+
continue;
|
|
539
|
+
// A run already going at the commit was started for it, by the tick before or by `ci run --at`.
|
|
540
|
+
const c = input.planned.find((x) => x.sha === r.commit);
|
|
541
|
+
if (c?.verdict?.phases.some((p) => r.phases.includes(p.phase) && p.runs.some((x) => x.status !== "completed"))) {
|
|
542
|
+
dispatched.add(r.commit);
|
|
543
|
+
continue;
|
|
544
|
+
}
|
|
545
|
+
const { dispatchable, without } = dispatchableWorkflows(green, r.phases, sources);
|
|
546
|
+
for (const w of without)
|
|
547
|
+
warnings.push(`${w} holds a run of ${r.phases.join(", ")} and has no workflow_dispatch trigger, so ${r.tag} can't start it; regenerate it with chant ci workflow`);
|
|
548
|
+
let ok = dispatchable.length > 0;
|
|
549
|
+
for (const w of dispatchable) {
|
|
550
|
+
try {
|
|
551
|
+
await forge.dispatch(w, r.tag);
|
|
552
|
+
}
|
|
553
|
+
catch (err) {
|
|
554
|
+
ok = false;
|
|
555
|
+
warnings.push(`could not start ${w} at ${r.tag}: ${err instanceof Error ? err.message : String(err)}`);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
if (ok)
|
|
559
|
+
dispatched.add(r.commit);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
if (forge?.rerun) {
|
|
563
|
+
for (const r of builds.retries) {
|
|
564
|
+
try {
|
|
565
|
+
await forge.rerun(r.run);
|
|
566
|
+
}
|
|
567
|
+
catch (err) {
|
|
568
|
+
warnings.push(`could not run ${r.run.name} at ${r.sha.slice(0, 12)} again, so its failure counts once the retry's hour is up: ${err instanceof Error ? err.message : String(err)}`);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
if (builds.resolved.length > 0) {
|
|
573
|
+
for (const tag of builds.resolved)
|
|
574
|
+
tryGit(top, ["tag", "-d", tag]);
|
|
575
|
+
if (tryGit(top, ["push", "--quiet", remote, ...builds.resolved.map((t) => `:refs/tags/${t}`)]) === null) {
|
|
576
|
+
warnings.push(`could not delete ${builds.resolved.join(", ")} on ${remote}; a done request asks for nothing, so they can stay`);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
return { live, dispatched };
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* Write each judged commit's record, and rewrite a record whose commit left
|
|
583
|
+
* the window still pending as unbuilt (#4169), in one commit on
|
|
584
|
+
* `chant/lifecycle`, and push it. Only records whose content changed are
|
|
585
|
+
* written. A push another writer beat is tried once more on the new tip.
|
|
586
|
+
*/
|
|
587
|
+
async function writeRecords(input) {
|
|
588
|
+
const { top, green, planned, records, builds, requests, now, warnings } = input;
|
|
589
|
+
const writes = [];
|
|
590
|
+
const inWindow = new Set(planned.map((c) => c.sha));
|
|
591
|
+
for (const c of planned) {
|
|
592
|
+
const previous = records.get(c.sha);
|
|
593
|
+
let record = recordOf({ green, commit: c, previous, retries: builds.retries, requests, now });
|
|
594
|
+
if (record.request) {
|
|
595
|
+
const started = input.dispatched.has(c.sha) || (previous?.request?.tag === record.request.tag && previous.request.dispatched === true);
|
|
596
|
+
if (started)
|
|
597
|
+
record = { ...record, request: { ...record.request, dispatched: true } };
|
|
598
|
+
}
|
|
599
|
+
if (!sameRecord(previous, record))
|
|
600
|
+
writes.push({ path: recordPath(c.sha), content: `${JSON.stringify(record, null, 2)}\n` });
|
|
601
|
+
}
|
|
602
|
+
for (const [sha, r] of records) {
|
|
603
|
+
if (inWindow.has(sha) || r.state !== "pending")
|
|
604
|
+
continue;
|
|
605
|
+
writes.push({ path: recordPath(sha), content: `${JSON.stringify(agedOut(r, now), null, 2)}\n` });
|
|
606
|
+
}
|
|
607
|
+
if (writes.length === 0)
|
|
608
|
+
return [];
|
|
609
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
610
|
+
try {
|
|
611
|
+
await writeLedgerFiles(writes, `ci: ${writes.length} commit record${writes.length === 1 ? "" : "s"}`, { cwd: top });
|
|
612
|
+
}
|
|
613
|
+
catch (err) {
|
|
614
|
+
warnings.push(`could not write the _ci records: ${err instanceof Error ? err.message : String(err)}`);
|
|
615
|
+
return [];
|
|
616
|
+
}
|
|
617
|
+
const pushed = await pushLifecycleStatus({ cwd: top });
|
|
618
|
+
if (pushed.status === "pushed" || pushed.status === "no-remote" || pushed.status === "not-recorded")
|
|
619
|
+
return writes.map((w) => w.path);
|
|
620
|
+
if (pushed.status !== "stale" || attempt === 1) {
|
|
621
|
+
warnings.push(`could not push the _ci records: ${pushed.stderr}`);
|
|
622
|
+
return [];
|
|
623
|
+
}
|
|
624
|
+
// Another writer pushed first: drop this commit, follow the remote, and write again on its tip.
|
|
625
|
+
tryGit(top, ["update-ref", "refs/heads/chant/lifecycle", "refs/heads/chant/lifecycle^"]);
|
|
626
|
+
const again = await fetchLifecycleStatus({ cwd: top });
|
|
627
|
+
if (again.status === "diverged" || again.status === "unreachable") {
|
|
628
|
+
warnings.push(`could not push the _ci records: chant/lifecycle moved on the remote and could not be fetched again`);
|
|
629
|
+
return [];
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
return [];
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* `chant ci run --at <sha>`: ask for every required phase to be built at a
|
|
636
|
+
* commit of the branch, by pushing `ci/build/<sha>`. Given a forge that can
|
|
637
|
+
* start workflows, and with the source check runs, it starts the workflows
|
|
638
|
+
* holding the phases at the tag now; otherwise the next tick does, or `ci
|
|
639
|
+
* serve --build-command` for chant runs. Once the phases pass, the tick tags
|
|
640
|
+
* the commit green like any other, so it can be cut.
|
|
641
|
+
*/
|
|
642
|
+
export async function requestBuild(req) {
|
|
643
|
+
const read = readGreen(req.cwd);
|
|
644
|
+
const { green, top } = read;
|
|
645
|
+
const remote = req.remote ?? "origin";
|
|
646
|
+
const tracking = `refs/remotes/${remote}/${green.branch}`;
|
|
647
|
+
try {
|
|
648
|
+
git(top, ["fetch", "--quiet", "--no-tags", remote, `+refs/heads/${green.branch}:${tracking}`, `+refs/tags/${CI_BUILD_TAG_PREFIX}*:refs/tags/${CI_BUILD_TAG_PREFIX}*`]);
|
|
649
|
+
}
|
|
650
|
+
catch (err) {
|
|
651
|
+
throw new Error(`could not fetch ${green.branch} and the build requests from ${remote}: ${stderrOf(err)}`);
|
|
652
|
+
}
|
|
653
|
+
if (req.at.startsWith("-") || tryGit(top, ["rev-parse", "--verify", "--quiet", `${req.at}^{commit}`]) === null)
|
|
654
|
+
throw new WorkspaceReadError("revision-unknown", `--at ${req.at} names no commit in this repository`);
|
|
655
|
+
const sha = git(top, ["rev-parse", `${req.at}^{commit}`]);
|
|
656
|
+
const onBranch = git(top, ["rev-list", "--first-parent", tracking]).split("\n").includes(sha);
|
|
657
|
+
if (!onBranch)
|
|
658
|
+
throw new Error(`${sha.slice(0, 12)} is not a first-parent commit of ${remote}/${green.branch}, and only those are judged green`);
|
|
659
|
+
const existing = readBuildRequests(top).get(sha);
|
|
660
|
+
const request = existing ?? requestedBuild(green, sha, green.require, req.now ?? new Date());
|
|
661
|
+
const warnings = [];
|
|
662
|
+
if (!existing) {
|
|
663
|
+
git(top, [...taggerConfig(top), "tag", "-a", "-m", buildRequestAnnotation(request), request.tag, sha]);
|
|
664
|
+
try {
|
|
665
|
+
git(top, ["push", "--quiet", remote, `refs/tags/${request.tag}`]);
|
|
666
|
+
}
|
|
667
|
+
catch (err) {
|
|
668
|
+
tryGit(top, ["tag", "-d", request.tag]);
|
|
669
|
+
const stderr = stderrOf(err);
|
|
670
|
+
throw new Error(`could not push ${request.tag} to ${remote}: ${pushRefusalCause(stderr) ?? stderr}`);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
const dispatched = [];
|
|
674
|
+
if (green.source === "checks" && req.forge?.dispatch) {
|
|
675
|
+
const { dispatchable, without } = dispatchableWorkflows(green, request.phases, readWorkflowSources(top, ""));
|
|
676
|
+
for (const w of without)
|
|
677
|
+
warnings.push(`${w} holds a run of a required phase and has no workflow_dispatch trigger, so it can't be started at ${request.tag}`);
|
|
678
|
+
for (const w of dispatchable) {
|
|
679
|
+
try {
|
|
680
|
+
await req.forge.dispatch(w, request.tag);
|
|
681
|
+
dispatched.push(w);
|
|
682
|
+
}
|
|
683
|
+
catch (err) {
|
|
684
|
+
warnings.push(`could not start ${w} at ${request.tag}: ${err instanceof Error ? err.message : String(err)}; the next tick tries again`);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
return { request, existed: existing !== undefined, dispatched, warnings };
|
|
446
689
|
}
|
|
447
690
|
const RUN_LEDGER_FILE = /^runs__(.+)\.jsonl$/;
|
|
448
691
|
/**
|
|
@@ -589,8 +832,17 @@ export function formatTick(r, dryRun = false) {
|
|
|
589
832
|
}
|
|
590
833
|
else {
|
|
591
834
|
const v = c.verdict;
|
|
592
|
-
const
|
|
593
|
-
const
|
|
835
|
+
const why = (p) => `${p.phase}${p.reason ? ` (${p.reason}${p.coveredBy ? `, covered by ${p.coveredBy.slice(0, 12)}` : ""})` : ""}`;
|
|
836
|
+
const waiting = v.phases.filter((p) => p.verdict === "pending").map(why);
|
|
837
|
+
const unbuilt = v.phases.filter((p) => p.verdict === "unbuilt").map(why);
|
|
838
|
+
const culprit = v.phases.find((p) => p.culprit)?.culprit;
|
|
839
|
+
const what = c.before === "green"
|
|
840
|
+
? "green"
|
|
841
|
+
: v.verdict === "fail"
|
|
842
|
+
? `failed: phase ${v.failure.phase}, ${v.failure.run.name} ${v.failure.run.conclusion}${culprit ? `; culprit ${culprit.slice(0, 12)}` : ""}`
|
|
843
|
+
: v.verdict === "unbuilt"
|
|
844
|
+
? `unbuilt: ${unbuilt.join(", ")}`
|
|
845
|
+
: `undecided: ${waiting.join(", ")}`;
|
|
594
846
|
lines.push(` ${short} ${what}`);
|
|
595
847
|
}
|
|
596
848
|
const why = whyWorkUnknown(c.work);
|
|
@@ -605,6 +857,14 @@ export function formatTick(r, dryRun = false) {
|
|
|
605
857
|
}
|
|
606
858
|
if (r.signer && r.made.length > 0)
|
|
607
859
|
lines.push(`signed as ${r.signer}`);
|
|
860
|
+
for (const q of r.requested)
|
|
861
|
+
lines.push(`${dryRun ? "would ask" : "asked"} for ${q.phases.join(", ")} at ${q.commit.slice(0, 12)} (${q.reason}): ${q.tag}`);
|
|
862
|
+
for (const x of r.retried)
|
|
863
|
+
lines.push(`${dryRun ? "would run" : "ran"} ${x.run} at ${x.sha.slice(0, 12)} again`);
|
|
864
|
+
if (r.recorded.length > 0)
|
|
865
|
+
lines.push(`recorded ${r.recorded.length} commit${r.recorded.length === 1 ? "" : "s"} on chant/lifecycle`);
|
|
866
|
+
for (const w of r.warnings)
|
|
867
|
+
lines.push(`warning: ${w}`);
|
|
608
868
|
return lines.join("\n");
|
|
609
869
|
}
|
|
610
870
|
// ── last-green ───────────────────────────────────────────────────────────────
|
|
@@ -616,6 +876,8 @@ function branchRefOf(top, green, remote = "origin") {
|
|
|
616
876
|
throw new CiGreenError("ci-branch-unknown", `this repository has neither ${candidates.join(" nor ")}`);
|
|
617
877
|
return ref;
|
|
618
878
|
}
|
|
879
|
+
/** How many commits above the green one `ci last-green` reports with their records. */
|
|
880
|
+
export const LAST_GREEN_NEWER = 50;
|
|
619
881
|
/**
|
|
620
882
|
* `chant ci last-green`: the newest first-parent commit of the branch with a
|
|
621
883
|
* green tag and no revoked tag. It reads local refs and tags and never
|
|
@@ -637,16 +899,21 @@ export function lastGreen(req) {
|
|
|
637
899
|
const tags = readCiTags(top, green, git(top, ["rev-parse", `${branchRef}^{commit}`]));
|
|
638
900
|
let commit = null;
|
|
639
901
|
const ignored = [];
|
|
640
|
-
|
|
902
|
+
const newer = [];
|
|
903
|
+
const records = readCiRecords(top);
|
|
904
|
+
if (tags.green.size > 0 || tags.ignored.size > 0 || records.size > 0) {
|
|
641
905
|
for (const sha of git(top, ["rev-list", "--first-parent", ref]).split("\n")) {
|
|
642
906
|
ignored.push(...(tags.ignored.get(sha) ?? []));
|
|
643
|
-
if (!tags.green.has(sha) || tags.revoked.has(sha))
|
|
907
|
+
if (!tags.green.has(sha) || tags.revoked.has(sha)) {
|
|
908
|
+
if (newer.length < LAST_GREEN_NEWER)
|
|
909
|
+
newer.push({ sha, record: recordSummary(green, records.get(sha)) });
|
|
644
910
|
continue;
|
|
911
|
+
}
|
|
645
912
|
commit = { sha, tag: `${CI_GREEN_TAG_PREFIX}${sha}`, tagged: tags.green.get(sha).tagged, committed: git(top, ["show", "-s", "--format=%cI", sha]) };
|
|
646
913
|
break;
|
|
647
914
|
}
|
|
648
915
|
}
|
|
649
|
-
return { ...head, workspace, branch: green.branch, ref, commit, ignored };
|
|
916
|
+
return { ...head, workspace, branch: green.branch, ref, commit, ignored, newer: commit ? newer : [] };
|
|
650
917
|
}
|
|
651
918
|
catch (err) {
|
|
652
919
|
if (err instanceof CiGreenError || err instanceof WorkspaceReadError)
|
|
@@ -657,6 +924,17 @@ export function lastGreen(req) {
|
|
|
657
924
|
// ── list ─────────────────────────────────────────────────────────────────────
|
|
658
925
|
export const CI_LIST_CONTRACT_VERSION = 1;
|
|
659
926
|
export const CI_LIST_SCHEMA_ID = "https://intentius.io/chant/schemas/workspace/ci-list/v1/ci-list.schema.json";
|
|
927
|
+
/** The summary of a record, from its required phases in `require` order. */
|
|
928
|
+
export function recordSummary(green, r) {
|
|
929
|
+
if (!r)
|
|
930
|
+
return null;
|
|
931
|
+
const phases = green.require.map((name) => r.phases[name]).filter((p) => p !== undefined);
|
|
932
|
+
const first = (k) => phases.find((p) => p[k] !== undefined)?.[k];
|
|
933
|
+
const reason = first("reason");
|
|
934
|
+
const coveredBy = first("coveredBy");
|
|
935
|
+
const culprit = first("culprit");
|
|
936
|
+
return { state: r.state, ...(reason ? { reason } : {}), ...(coveredBy ? { coveredBy } : {}), ...(culprit ? { culprit } : {}) };
|
|
937
|
+
}
|
|
660
938
|
const asRun = (r) => {
|
|
661
939
|
if (!r || typeof r !== "object")
|
|
662
940
|
return null;
|
|
@@ -722,6 +1000,7 @@ export async function ciList(req) {
|
|
|
722
1000
|
if (source === "runs" && req.forge)
|
|
723
1001
|
throw new Error("--forge reads check runs, and the source is chant runs (ci.green.source or --source runs); drop one");
|
|
724
1002
|
const runs = source === "runs" ? await readOpRunSource(req.cwd, top, base, green.signedSince) : undefined;
|
|
1003
|
+
const records = readCiRecords(top);
|
|
725
1004
|
const out = [];
|
|
726
1005
|
for (const c of commits) {
|
|
727
1006
|
const g = tags.green.get(c.sha);
|
|
@@ -747,6 +1026,7 @@ export async function ciList(req) {
|
|
|
747
1026
|
work: links?.get(c.sha) ?? null,
|
|
748
1027
|
ignored: tags.ignored.get(c.sha) ?? [],
|
|
749
1028
|
...(runs ? { ignoredRuns: runs.ignored.get(c.sha) ?? [] } : {}),
|
|
1029
|
+
record: recordSummary(green, records.get(c.sha)),
|
|
750
1030
|
});
|
|
751
1031
|
}
|
|
752
1032
|
return { ...head, workspace, branch: green.branch, ref, window: req.since === undefined ? green.window : null, since: req.since ?? null, require: [...green.require], commits: out };
|
|
@@ -757,6 +1037,11 @@ export async function ciList(req) {
|
|
|
757
1037
|
throw err;
|
|
758
1038
|
}
|
|
759
1039
|
}
|
|
1040
|
+
/** A record summary in words, for `ci list` and the tick. */
|
|
1041
|
+
export function describeRecord(r) {
|
|
1042
|
+
const parts = [r.state, ...(r.reason ? [r.reason] : []), ...(r.coveredBy ? [`covered by ${r.coveredBy.slice(0, 12)}`] : []), ...(r.culprit ? [`culprit ${r.culprit.slice(0, 12)}`] : [])];
|
|
1043
|
+
return parts.join(", ");
|
|
1044
|
+
}
|
|
760
1045
|
/** The text `chant ci list` prints: one line per commit. */
|
|
761
1046
|
export function formatList(doc) {
|
|
762
1047
|
const lines = [`${doc.branch}, ${doc.since ? `commits after ${doc.since}` : `commits within ${doc.window}`}: ${doc.commits.length}`];
|
|
@@ -764,6 +1049,8 @@ export function formatList(doc) {
|
|
|
764
1049
|
const phases = c.phases.map((p) => `${p.phase}=${p.result}`).join(" ");
|
|
765
1050
|
const why = whyWorkUnknown(c.work);
|
|
766
1051
|
lines.push(` ${c.sha.slice(0, 12)} ${c.state.padEnd(9)} ${phases}${why ? `; why unknown: ${why}` : ""}`);
|
|
1052
|
+
if (c.record && c.state === "undecided")
|
|
1053
|
+
lines.push(` recorded ${describeRecord(c.record)}`);
|
|
767
1054
|
for (const t of c.ignored)
|
|
768
1055
|
lines.push(` ignored ${t.tag} (${t.status}): ${t.reason}`);
|
|
769
1056
|
for (const r of c.ignoredRuns ?? [])
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `chant ci init --workflow <name> [--forge <forge>] [--security <group>]...
|
|
3
|
+
* [--yes]` (#4168 step 6, section 9): set a workspace up for a prescribed
|
|
4
|
+
* delivery workflow (../ci-workflow.ts).
|
|
5
|
+
*
|
|
6
|
+
* It proposes, prints the proposal as a diff, and writes only on `--yes` or a
|
|
7
|
+
* yes at the prompt. What it proposes comes from what `workspace check`'s
|
|
8
|
+
* WSP151 reports for the workspace as it would be with `ci.workflow` set, so
|
|
9
|
+
* a second run proposes only what is still missing, and nothing once the
|
|
10
|
+
* workspace has it all.
|
|
11
|
+
*
|
|
12
|
+
* - The declaration gains `ci.workflow`, a `ci.green` block when it has none
|
|
13
|
+
* (one phase per GitHub workflow, its jobs as the check runs, and the
|
|
14
|
+
* workflows a pull request runs as `preMerge`), and `preMerge` when the
|
|
15
|
+
* block has none. `--security` adds groups to the top-level `security`;
|
|
16
|
+
* without it no group is turned on, and the groups are listed.
|
|
17
|
+
* - A member that builds with chant and declares no component gets a
|
|
18
|
+
* `<member>.component.ts` stub with a gate and a TODO line, which WSP151
|
|
19
|
+
* keeps reporting until someone fills the stub in. A `chant.config.ts`
|
|
20
|
+
* that declares no environments gains `environments: ["staging", "prod"]`
|
|
21
|
+
* when its default export is a plain object literal.
|
|
22
|
+
* - The generated files are written by the generators that own them, run
|
|
23
|
+
* after the edits: `chant build --components --generate <forge> --pr-loop`
|
|
24
|
+
* in each member, `chant ci workflow` for the tick, and `chant release
|
|
25
|
+
* workflow` for the publish job. Each is named in the proposal before it
|
|
26
|
+
* runs.
|
|
27
|
+
*
|
|
28
|
+
* It never edits a file the diff did not show, and never overwrites a file
|
|
29
|
+
* it proposed to create that appeared in the meantime.
|
|
30
|
+
*/
|
|
31
|
+
import type { CommandContext } from "../cli/registry.js";
|
|
32
|
+
import type { WorkspaceDiagnostic } from "./checks.js";
|
|
33
|
+
import { STUB_MARKER } from "./ci-workflow.js";
|
|
34
|
+
export { STUB_MARKER };
|
|
35
|
+
import { type CiWorkflow } from "./declaration.js";
|
|
36
|
+
import { type SecurityGroup } from "./security.js";
|
|
37
|
+
import { type ReleaseWorkflowForge } from "./release-workflow.js";
|
|
38
|
+
/** The environments a config with none is given. */
|
|
39
|
+
export declare const DEFAULT_ENVIRONMENTS: readonly ["staging", "prod"];
|
|
40
|
+
/** One file the proposal writes: created when `before` is null. */
|
|
41
|
+
export interface ProposedFile {
|
|
42
|
+
path: string;
|
|
43
|
+
before: string | null;
|
|
44
|
+
after: string;
|
|
45
|
+
}
|
|
46
|
+
/** A generator the proposal runs after the edits, in `cwd` (relative to the workspace root). */
|
|
47
|
+
export interface ProposedCommand {
|
|
48
|
+
cwd: string;
|
|
49
|
+
args: string[];
|
|
50
|
+
why: string;
|
|
51
|
+
}
|
|
52
|
+
export interface CiInitProposal {
|
|
53
|
+
files: ProposedFile[];
|
|
54
|
+
commands: ProposedCommand[];
|
|
55
|
+
/** What the proposal leaves to a person, such as a component with no gate. */
|
|
56
|
+
notes: string[];
|
|
57
|
+
/** The WSP151 findings the proposal answers. */
|
|
58
|
+
findings: WorkspaceDiagnostic[];
|
|
59
|
+
}
|
|
60
|
+
export interface CiInitOptions {
|
|
61
|
+
workflow: CiWorkflow;
|
|
62
|
+
forge?: ReleaseWorkflowForge;
|
|
63
|
+
security?: readonly SecurityGroup[];
|
|
64
|
+
}
|
|
65
|
+
/** The forge whose pipeline files the repository has, or null. */
|
|
66
|
+
export declare function inferForge(repoRoot: string): ReleaseWorkflowForge | null;
|
|
67
|
+
/** The branch `origin/HEAD` names, else `main`. */
|
|
68
|
+
export declare function inferTrunk(repoRoot: string): string;
|
|
69
|
+
/**
|
|
70
|
+
* A `ci.green` block from the repository's GitHub workflows: a phase per
|
|
71
|
+
* workflow holding its jobs' check runs, all required, and those a pull
|
|
72
|
+
* request runs as `preMerge`. chant's own generated workflows are left out.
|
|
73
|
+
*/
|
|
74
|
+
export declare function inferGreen(repoRoot: string): {
|
|
75
|
+
branch: string;
|
|
76
|
+
phases: Record<string, string[]>;
|
|
77
|
+
require: string[];
|
|
78
|
+
preMerge: string[];
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* The declaration's text with `next`'s top-level keys. A key the file lacks
|
|
82
|
+
* is added as text before the closing brace, so the rest keeps its layout; a
|
|
83
|
+
* key it has whose value changes rewrites the file, two-space indented.
|
|
84
|
+
*/
|
|
85
|
+
export declare function editDeclarationText(text: string, raw: Record<string, unknown>, next: Record<string, unknown>): string;
|
|
86
|
+
/** The component stub for a member that declares none: a gate and a TODO, so WSP151 keeps asking until it is filled in. */
|
|
87
|
+
export declare function componentStub(member: string, lastEnvironment: string): string;
|
|
88
|
+
/** The config with `environments` added after `export default {`, or null when its default export is not a plain object literal. */
|
|
89
|
+
export declare function withEnvironments(config: string, environments?: readonly string[]): string | null;
|
|
90
|
+
/**
|
|
91
|
+
* What `chant ci init` would write for the workspace at `root`, and the
|
|
92
|
+
* generators it would run. Reads only; {@link applyCiInit} writes.
|
|
93
|
+
*/
|
|
94
|
+
export declare function proposeCiInit(root: string, options: CiInitOptions): CiInitProposal;
|
|
95
|
+
/** A unified-style diff of two texts, for the proposal. Line-based, from their longest common subsequence. */
|
|
96
|
+
export declare function lineDiff(path: string, before: string | null, after: string): string;
|
|
97
|
+
/** Write the proposal's files. A file it would create that now exists is refused, and nothing is written. */
|
|
98
|
+
export declare function applyCiInit(root: string, proposal: CiInitProposal): string[];
|
|
99
|
+
export declare function runCiInit(ctx: CommandContext): Promise<number>;
|
|
100
|
+
//# sourceMappingURL=ci-init.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ci-init.d.ts","sourceRoot":"","sources":["../../src/workspace/ci-init.ts"],"names":[],"mappings":"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"}
|