@intentius/chant 0.124.32 → 0.124.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +16 -1
- 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 +81 -21
- package/dist/cli/parse-args.d.ts.map +1 -1
- package/dist/cli/parse-args.js +23 -1
- package/dist/cli/registry.d.ts +18 -4
- 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/release-compile.d.ts +37 -2
- package/dist/components/release-compile.d.ts.map +1 -1
- package/dist/components/release-compile.js +64 -8
- package/dist/components/release-promote.d.ts +17 -3
- package/dist/components/release-promote.d.ts.map +1 -1
- package/dist/components/release-promote.js +79 -18
- package/dist/components/verbs/bom-writer.d.ts +1 -1
- package/dist/components/verbs/bom-writer.d.ts.map +1 -1
- package/dist/components/verbs/lockfile-sbom-generator.d.ts +20 -0
- package/dist/components/verbs/lockfile-sbom-generator.d.ts.map +1 -1
- package/dist/components/verbs/lockfile-sbom-generator.js +41 -4
- package/dist/components/wave-plan.d.ts +10 -0
- package/dist/components/wave-plan.d.ts.map +1 -1
- package/dist/components/wave-plan.js +32 -4
- 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 +235 -228
- package/dist/workspace/check.schema.json +70 -2
- package/dist/workspace/checks/security.d.ts +35 -0
- package/dist/workspace/checks/security.d.ts.map +1 -0
- package/dist/workspace/checks/security.js +305 -0
- package/dist/workspace/checks.d.ts +2 -0
- package/dist/workspace/checks.d.ts.map +1 -1
- package/dist/workspace/checks.js +5 -0
- package/dist/workspace/ci-builds.d.ts +163 -0
- package/dist/workspace/ci-builds.d.ts.map +1 -0
- package/dist/workspace/ci-builds.js +315 -0
- package/dist/workspace/ci-green-cli.d.ts +2 -1
- package/dist/workspace/ci-green-cli.d.ts.map +1 -1
- package/dist/workspace/ci-green-cli.js +25 -5
- package/dist/workspace/ci-green-forge.d.ts +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-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/declaration.d.ts +17 -0
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.js +3 -0
- package/dist/workspace/declaration.schema.json +23 -0
- 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 +17 -0
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/reason-codes.js +18 -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/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 +16 -1
- 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 +81 -21
- package/src/cli/parse-args.ts +17 -1
- package/src/cli/registry.ts +18 -4
- 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/release-compile.test.ts +34 -0
- package/src/components/release-compile.ts +89 -8
- package/src/components/release-promote.test.ts +55 -2
- package/src/components/release-promote.ts +93 -23
- package/src/components/verbs/bom-writer.ts +1 -1
- package/src/components/verbs/lockfile-sbom-generator.test.ts +36 -0
- package/src/components/verbs/lockfile-sbom-generator.ts +43 -5
- package/src/components/wave-plan.ts +32 -4
- 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/check-contract.test.ts +3 -2
- package/src/workspace/check.schema.json +70 -2
- package/src/workspace/checks/security.test.ts +138 -0
- package/src/workspace/checks/security.ts +305 -0
- package/src/workspace/checks.ts +5 -0
- package/src/workspace/ci-builds.e2e.test.ts +208 -0
- package/src/workspace/ci-builds.test.ts +174 -0
- package/src/workspace/ci-builds.ts +385 -0
- package/src/workspace/ci-green-builds.test.ts +180 -0
- package/src/workspace/ci-green-cli.test.ts +54 -0
- package/src/workspace/ci-green-cli.ts +26 -5
- package/src/workspace/ci-green-forge.ts +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-last-green.schema.json +49 -0
- package/src/workspace/ci-list.schema.json +35 -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/declaration.schema.json +23 -0
- package/src/workspace/declaration.test.ts +17 -0
- package/src/workspace/declaration.ts +22 -1
- 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 +2 -0
- package/src/workspace/reason-codes.ts +18 -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/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 ?? [])
|
|
@@ -104,6 +104,55 @@
|
|
|
104
104
|
}
|
|
105
105
|
]
|
|
106
106
|
},
|
|
107
|
+
"newer": {
|
|
108
|
+
"type": "array",
|
|
109
|
+
"description": "Added within contract version 1 by #4169, written by chant 0.124.33 and newer. The first-parent commits walked past, newest first and above the one found, each with what its _ci/<sha>.json record on the local chant/lifecycle says (null when it has none), so a reader can say why the newest commits are not green. At most 50.",
|
|
110
|
+
"items": {
|
|
111
|
+
"type": "object",
|
|
112
|
+
"required": [
|
|
113
|
+
"sha",
|
|
114
|
+
"record"
|
|
115
|
+
],
|
|
116
|
+
"properties": {
|
|
117
|
+
"sha": {
|
|
118
|
+
"type": "string",
|
|
119
|
+
"pattern": "^[0-9a-f]{40,64}$"
|
|
120
|
+
},
|
|
121
|
+
"record": {
|
|
122
|
+
"oneOf": [
|
|
123
|
+
{
|
|
124
|
+
"type": "null"
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"type": "object",
|
|
128
|
+
"required": [
|
|
129
|
+
"state"
|
|
130
|
+
],
|
|
131
|
+
"properties": {
|
|
132
|
+
"state": {
|
|
133
|
+
"enum": [
|
|
134
|
+
"green",
|
|
135
|
+
"red",
|
|
136
|
+
"unbuilt",
|
|
137
|
+
"pending"
|
|
138
|
+
]
|
|
139
|
+
},
|
|
140
|
+
"reason": {
|
|
141
|
+
"type": "string"
|
|
142
|
+
},
|
|
143
|
+
"coveredBy": {
|
|
144
|
+
"type": "string"
|
|
145
|
+
},
|
|
146
|
+
"culprit": {
|
|
147
|
+
"type": "string"
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
]
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
},
|
|
107
156
|
"ignored": {
|
|
108
157
|
"type": "array",
|
|
109
158
|
"description": "Added within contract version 1 by #4031, written by chant 0.124.28 and newer. The tags on the commits walked, newest first and down to the one found, that do not count for want of a valid statement. Empty when ci.green.signedSince is not set.",
|
|
@@ -284,9 +284,10 @@
|
|
|
284
284
|
"pass",
|
|
285
285
|
"fail",
|
|
286
286
|
"pending",
|
|
287
|
+
"unbuilt",
|
|
287
288
|
"unrecorded"
|
|
288
289
|
],
|
|
289
|
-
"description": "pass and fail come from the tags (a green tag records the phases that passed, a revoked tag the one that failed) or, for an undecided commit, from the forge under --forge, or from the signed Op runs on the local chant/lifecycle when the source is chant runs (#4044). pending is such a read with runs still going or not yet reported. unrecorded means no tag holds the result and nothing was read."
|
|
290
|
+
"description": "pass and fail come from the tags (a green tag records the phases that passed, a revoked tag the one that failed) or, for an undecided commit, from the forge under --forge, or from the signed Op runs on the local chant/lifecycle when the source is chant runs (#4044). pending is such a read with runs still going or not yet reported. unbuilt (#4169, chant 0.124.33 and newer) is such a read whose runs were cancelled. unrecorded means no tag holds the result and nothing was read."
|
|
290
291
|
},
|
|
291
292
|
"runs": {
|
|
292
293
|
"type": "array",
|
|
@@ -406,6 +407,39 @@
|
|
|
406
407
|
}
|
|
407
408
|
]
|
|
408
409
|
},
|
|
410
|
+
"record": {
|
|
411
|
+
"description": "Added within contract version 1 by #4169, written by chant 0.124.33 and newer. What the commit's _ci/<sha>.json record on the local chant/lifecycle says, the tick's last word on it, or null when it has none. state is green, red, unbuilt or pending. reason, coveredBy and culprit come from the required phases: why a phase is unbuilt or still pending (cancelled, superseded, retrying, requested, request-unserved or aged-out), the newer commit whose run covers a superseded phase, and the commit a bisect found broke a phase. Each is absent when no phase has one.",
|
|
412
|
+
"oneOf": [
|
|
413
|
+
{
|
|
414
|
+
"type": "null"
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
"type": "object",
|
|
418
|
+
"required": [
|
|
419
|
+
"state"
|
|
420
|
+
],
|
|
421
|
+
"properties": {
|
|
422
|
+
"state": {
|
|
423
|
+
"enum": [
|
|
424
|
+
"green",
|
|
425
|
+
"red",
|
|
426
|
+
"unbuilt",
|
|
427
|
+
"pending"
|
|
428
|
+
]
|
|
429
|
+
},
|
|
430
|
+
"reason": {
|
|
431
|
+
"type": "string"
|
|
432
|
+
},
|
|
433
|
+
"coveredBy": {
|
|
434
|
+
"type": "string"
|
|
435
|
+
},
|
|
436
|
+
"culprit": {
|
|
437
|
+
"type": "string"
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
]
|
|
442
|
+
},
|
|
409
443
|
"ignored": {
|
|
410
444
|
"type": "array",
|
|
411
445
|
"description": "Added within contract version 1 by #4031, written by chant 0.124.28 and newer. The commit's ci tags that do not count for want of a valid statement signed by a runner key. A tag on any commit that is not a strict first-parent ancestor of ci.green.signedSince needs one; empty when signedSince is not set. state, green and revoked count only the tags that are not ignored.",
|
|
@@ -38,7 +38,7 @@ export declare const CI_RUN_INDEX_KEY = "runs/index.json";
|
|
|
38
38
|
export declare const CI_RUN_INDEX_LIMIT = 200;
|
|
39
39
|
/** One job the runner ran. */
|
|
40
40
|
export interface CiRunIndexEntry {
|
|
41
|
-
job: "plan" | "apply";
|
|
41
|
+
job: "plan" | "apply" | "record";
|
|
42
42
|
/** The pull request, or null when the job found none. */
|
|
43
43
|
pr: number | null;
|
|
44
44
|
commit: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ci-run-index.d.ts","sourceRoot":"","sources":["../../src/workspace/ci-run-index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGtD,OAAO,EAAwC,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAG3H,eAAO,MAAM,sBAAsB,0FAA0F,CAAC;AAC9H,eAAO,MAAM,qBAAqB,IAAI,CAAC;AACvC,6CAA6C;AAC7C,eAAO,MAAM,kBAAkB,aAAa,CAAC;AAC7C,qDAAqD;AACrD,eAAO,MAAM,gBAAgB,oBAAoB,CAAC;AAClD,8DAA8D;AAC9D,eAAO,MAAM,kBAAkB,MAAM,CAAC;AAEtC,8BAA8B;AAC9B,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"ci-run-index.d.ts","sourceRoot":"","sources":["../../src/workspace/ci-run-index.ts"],"names":[],"mappings":"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"}
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"if": { "properties": { "status": { "const": "fail" } } },
|
|
24
24
|
"then": { "required": ["failure"], "properties": { "failure": { "type": "object" } } },
|
|
25
25
|
"properties": {
|
|
26
|
-
"job": { "enum": ["plan", "apply"] },
|
|
26
|
+
"job": { "enum": ["plan", "apply", "record"], "description": "record (#4168, chant 0.124.33 and newer): the merge record job." },
|
|
27
27
|
"pr": { "type": ["integer", "null"], "minimum": 1, "description": "The pull or merge request number: the one the job's report names, else --pr. null when neither has one." },
|
|
28
28
|
"commit": { "type": "string", "pattern": "^[0-9a-f]{40,64}$", "description": "The full commit the job ran at." },
|
|
29
29
|
"status": { "enum": ["ok", "fail"] },
|