@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.
Files changed (320) hide show
  1. package/dist/ci-jobs.d.ts +4 -0
  2. package/dist/ci-jobs.d.ts.map +1 -1
  3. package/dist/ci-jobs.js +9 -2
  4. package/dist/cli/handlers/build.d.ts.map +1 -1
  5. package/dist/cli/handlers/build.js +18 -3
  6. package/dist/cli/handlers/lifecycle-health.d.ts +16 -0
  7. package/dist/cli/handlers/lifecycle-health.d.ts.map +1 -0
  8. package/dist/cli/handlers/lifecycle-health.js +150 -0
  9. package/dist/cli/handlers/lifecycle.d.ts +26 -0
  10. package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
  11. package/dist/cli/handlers/lifecycle.js +1 -1
  12. package/dist/cli/handlers/pr.d.ts +2 -0
  13. package/dist/cli/handlers/pr.d.ts.map +1 -1
  14. package/dist/cli/handlers/pr.js +124 -2
  15. package/dist/cli/handlers/promote.d.ts.map +1 -1
  16. package/dist/cli/handlers/promote.js +277 -74
  17. package/dist/cli/handlers/release.d.ts +16 -1
  18. package/dist/cli/handlers/release.d.ts.map +1 -1
  19. package/dist/cli/handlers/release.js +83 -11
  20. package/dist/cli/main.d.ts.map +1 -1
  21. package/dist/cli/main.js +99 -22
  22. package/dist/cli/parse-args.d.ts.map +1 -1
  23. package/dist/cli/parse-args.js +30 -1
  24. package/dist/cli/registry.d.ts +21 -5
  25. package/dist/cli/registry.d.ts.map +1 -1
  26. package/dist/components/deploy-inputs.d.ts +91 -0
  27. package/dist/components/deploy-inputs.d.ts.map +1 -0
  28. package/dist/components/deploy-inputs.js +184 -0
  29. package/dist/components/driver.d.ts.map +1 -1
  30. package/dist/components/driver.js +9 -0
  31. package/dist/components/gate-plan.d.ts +8 -0
  32. package/dist/components/gate-plan.d.ts.map +1 -1
  33. package/dist/components/gate-plan.js +6 -0
  34. package/dist/components/pr-pipeline.d.ts +33 -10
  35. package/dist/components/pr-pipeline.d.ts.map +1 -1
  36. package/dist/components/pr-pipeline.js +42 -11
  37. package/dist/components/pr-run.d.ts +1 -1
  38. package/dist/components/pr-run.d.ts.map +1 -1
  39. package/dist/components/promote-rounds.d.ts +102 -0
  40. package/dist/components/promote-rounds.d.ts.map +1 -0
  41. package/dist/components/promote-rounds.js +174 -0
  42. package/dist/components/release-compile.d.ts +37 -2
  43. package/dist/components/release-compile.d.ts.map +1 -1
  44. package/dist/components/release-compile.js +64 -8
  45. package/dist/components/release-gate.d.ts +30 -0
  46. package/dist/components/release-gate.d.ts.map +1 -1
  47. package/dist/components/release-gate.js +92 -42
  48. package/dist/components/release-promote.d.ts +17 -3
  49. package/dist/components/release-promote.d.ts.map +1 -1
  50. package/dist/components/release-promote.js +79 -18
  51. package/dist/components/verbs/bom-writer.d.ts +1 -1
  52. package/dist/components/verbs/bom-writer.d.ts.map +1 -1
  53. package/dist/components/verbs/lockfile-sbom-generator.d.ts +20 -0
  54. package/dist/components/verbs/lockfile-sbom-generator.d.ts.map +1 -1
  55. package/dist/components/verbs/lockfile-sbom-generator.js +41 -4
  56. package/dist/components/wave-plan.d.ts +10 -0
  57. package/dist/components/wave-plan.d.ts.map +1 -1
  58. package/dist/components/wave-plan.js +32 -4
  59. package/dist/graph-declared.d.ts +4 -0
  60. package/dist/graph-declared.d.ts.map +1 -1
  61. package/dist/graph-declared.js +11 -1
  62. package/dist/graph-detail.d.ts.map +1 -1
  63. package/dist/graph-detail.js +32 -7
  64. package/dist/health.d.ts +110 -0
  65. package/dist/health.d.ts.map +1 -0
  66. package/dist/health.js +105 -0
  67. package/dist/health.schema.json +191 -0
  68. package/dist/lexicon.d.ts +27 -0
  69. package/dist/lexicon.d.ts.map +1 -1
  70. package/dist/lifecycle/merge-record.d.ts +104 -0
  71. package/dist/lifecycle/merge-record.d.ts.map +1 -0
  72. package/dist/lifecycle/merge-record.js +102 -0
  73. package/dist/lifecycle/publish-attachments.d.ts +8 -5
  74. package/dist/lifecycle/publish-attachments.d.ts.map +1 -1
  75. package/dist/lifecycle/publish-attachments.js +27 -13
  76. package/dist/lifecycle/publish-npm.d.ts +3 -2
  77. package/dist/lifecycle/publish-npm.d.ts.map +1 -1
  78. package/dist/lifecycle/publish-npm.js +4 -3
  79. package/dist/lifecycle/release-at.d.ts +40 -0
  80. package/dist/lifecycle/release-at.d.ts.map +1 -0
  81. package/dist/lifecycle/release-at.js +83 -0
  82. package/dist/lifecycle/release-cut.d.ts.map +1 -1
  83. package/dist/lifecycle/release-cut.js +23 -12
  84. package/dist/lifecycle/release-publish.d.ts +28 -12
  85. package/dist/lifecycle/release-publish.d.ts.map +1 -1
  86. package/dist/lifecycle/release-publish.js +29 -17
  87. package/dist/lifecycle/release-store.d.ts +25 -2
  88. package/dist/lifecycle/release-store.d.ts.map +1 -1
  89. package/dist/lifecycle/release-store.js +40 -13
  90. package/dist/lifecycle/run-ledger.d.ts.map +1 -1
  91. package/dist/lifecycle/run-ledger.js +1 -0
  92. package/dist/lint/rules/op/index.d.ts +2 -1
  93. package/dist/lint/rules/op/index.d.ts.map +1 -1
  94. package/dist/lint/rules/op/index.js +4 -2
  95. package/dist/lint/rules/op/ops018-shell-image-digest.d.ts +10 -0
  96. package/dist/lint/rules/op/ops018-shell-image-digest.d.ts.map +1 -0
  97. package/dist/lint/rules/op/ops018-shell-image-digest.js +38 -0
  98. package/dist/op/activities/activity-contracts.d.ts +5 -0
  99. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  100. package/dist/op/activities/activity-contracts.js +3 -0
  101. package/dist/op/activities/shell.d.ts +38 -0
  102. package/dist/op/activities/shell.d.ts.map +1 -1
  103. package/dist/op/activities/shell.js +89 -3
  104. package/dist/op/builders.d.ts +7 -0
  105. package/dist/op/builders.d.ts.map +1 -1
  106. package/dist/op/builders.js +7 -0
  107. package/dist/op/runtime.d.ts +2 -0
  108. package/dist/op/runtime.d.ts.map +1 -1
  109. package/dist/pr-loop.d.ts +14 -3
  110. package/dist/pr-loop.d.ts.map +1 -1
  111. package/dist/pr-loop.js +15 -5
  112. package/dist/pr-report-url.d.ts +2 -2
  113. package/dist/pr-report-url.d.ts.map +1 -1
  114. package/dist/runner-start.d.ts +179 -0
  115. package/dist/runner-start.d.ts.map +1 -0
  116. package/dist/runner-start.js +277 -0
  117. package/dist/wave-runtime.mjs +236 -229
  118. package/dist/workspace/__fixtures__/contract-repo.d.ts.map +1 -1
  119. package/dist/workspace/__fixtures__/contract-repo.js +13 -2
  120. package/dist/workspace/check.schema.json +88 -2
  121. package/dist/workspace/checks/ci-workflow.d.ts +17 -0
  122. package/dist/workspace/checks/ci-workflow.d.ts.map +1 -0
  123. package/dist/workspace/checks/ci-workflow.js +123 -0
  124. package/dist/workspace/checks/security.d.ts +50 -0
  125. package/dist/workspace/checks/security.d.ts.map +1 -0
  126. package/dist/workspace/checks/security.js +305 -0
  127. package/dist/workspace/checks.d.ts +2 -0
  128. package/dist/workspace/checks.d.ts.map +1 -1
  129. package/dist/workspace/checks.js +8 -0
  130. package/dist/workspace/ci-builds.d.ts +163 -0
  131. package/dist/workspace/ci-builds.d.ts.map +1 -0
  132. package/dist/workspace/ci-builds.js +315 -0
  133. package/dist/workspace/ci-green-cli.d.ts +2 -1
  134. package/dist/workspace/ci-green-cli.d.ts.map +1 -1
  135. package/dist/workspace/ci-green-cli.js +25 -5
  136. package/dist/workspace/ci-green-forge.d.ts +12 -0
  137. package/dist/workspace/ci-green-forge.d.ts.map +1 -1
  138. package/dist/workspace/ci-green-forge.js +12 -0
  139. package/dist/workspace/ci-green-workflow.d.ts +55 -1
  140. package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
  141. package/dist/workspace/ci-green-workflow.js +128 -1
  142. package/dist/workspace/ci-green.d.ts +89 -4
  143. package/dist/workspace/ci-green.d.ts.map +1 -1
  144. package/dist/workspace/ci-green.js +310 -23
  145. package/dist/workspace/ci-init.d.ts +100 -0
  146. package/dist/workspace/ci-init.d.ts.map +1 -0
  147. package/dist/workspace/ci-init.js +434 -0
  148. package/dist/workspace/ci-last-green.schema.json +49 -0
  149. package/dist/workspace/ci-list.schema.json +35 -1
  150. package/dist/workspace/ci-run-index.d.ts +1 -1
  151. package/dist/workspace/ci-run-index.d.ts.map +1 -1
  152. package/dist/workspace/ci-run-index.schema.json +1 -1
  153. package/dist/workspace/ci-run.d.ts +12 -6
  154. package/dist/workspace/ci-run.d.ts.map +1 -1
  155. package/dist/workspace/ci-run.js +72 -16
  156. package/dist/workspace/ci-runner-start.d.ts +77 -0
  157. package/dist/workspace/ci-runner-start.d.ts.map +1 -0
  158. package/dist/workspace/ci-runner-start.js +185 -0
  159. package/dist/workspace/ci-serve.d.ts +76 -7
  160. package/dist/workspace/ci-serve.d.ts.map +1 -1
  161. package/dist/workspace/ci-serve.js +153 -19
  162. package/dist/workspace/ci-workflow.d.ts +91 -0
  163. package/dist/workspace/ci-workflow.d.ts.map +1 -0
  164. package/dist/workspace/ci-workflow.js +33 -0
  165. package/dist/workspace/declaration.d.ts +35 -0
  166. package/dist/workspace/declaration.d.ts.map +1 -1
  167. package/dist/workspace/declaration.js +12 -2
  168. package/dist/workspace/declaration.schema.json +51 -0
  169. package/dist/workspace/init.js +2 -2
  170. package/dist/workspace/ls.d.ts +2 -0
  171. package/dist/workspace/ls.d.ts.map +1 -1
  172. package/dist/workspace/ls.js +1 -1
  173. package/dist/workspace/ls.schema.json +2 -1
  174. package/dist/workspace/member-pipeline.d.ts +1 -0
  175. package/dist/workspace/member-pipeline.d.ts.map +1 -1
  176. package/dist/workspace/member-pipeline.js +2 -0
  177. package/dist/workspace/merge-record.schema.json +49 -0
  178. package/dist/workspace/pr-report.schema.json +3 -3
  179. package/dist/workspace/reason-codes.d.ts +26 -0
  180. package/dist/workspace/reason-codes.d.ts.map +1 -1
  181. package/dist/workspace/reason-codes.js +28 -0
  182. package/dist/workspace/release-candidates.d.ts +129 -0
  183. package/dist/workspace/release-candidates.d.ts.map +1 -0
  184. package/dist/workspace/release-candidates.js +233 -0
  185. package/dist/workspace/release-candidates.schema.json +353 -0
  186. package/dist/workspace/release-workflow.d.ts +31 -0
  187. package/dist/workspace/release-workflow.d.ts.map +1 -0
  188. package/dist/workspace/release-workflow.js +150 -0
  189. package/dist/workspace/security.d.ts +40 -0
  190. package/dist/workspace/security.d.ts.map +1 -0
  191. package/dist/workspace/security.js +99 -0
  192. package/package.json +8 -1
  193. package/src/__fixtures__/pr-loop/pr-plan.note.golden.md +1 -1
  194. package/src/ci-jobs.ts +11 -2
  195. package/src/cli/handlers/build.test.ts +36 -0
  196. package/src/cli/handlers/build.ts +18 -3
  197. package/src/cli/handlers/lifecycle-health.test.ts +123 -0
  198. package/src/cli/handlers/lifecycle-health.ts +157 -0
  199. package/src/cli/handlers/lifecycle.ts +1 -1
  200. package/src/cli/handlers/pr-deploy-inputs.test.ts +209 -0
  201. package/src/cli/handlers/pr-record.test.ts +201 -0
  202. package/src/cli/handlers/pr.test.ts +22 -0
  203. package/src/cli/handlers/pr.ts +127 -4
  204. package/src/cli/handlers/promote.test.ts +17 -0
  205. package/src/cli/handlers/promote.ts +296 -84
  206. package/src/cli/handlers/release-cut-at.e2e.test.ts +115 -0
  207. package/src/cli/handlers/release-publish-targets.e2e.test.ts +2 -2
  208. package/src/cli/handlers/release-verify.e2e.test.ts +12 -10
  209. package/src/cli/handlers/release.e2e.test.ts +19 -28
  210. package/src/cli/handlers/release.ts +81 -11
  211. package/src/cli/main.ts +99 -22
  212. package/src/cli/parse-args.ts +22 -1
  213. package/src/cli/registry.ts +21 -5
  214. package/src/components/deploy-inputs.test.ts +108 -0
  215. package/src/components/deploy-inputs.ts +225 -0
  216. package/src/components/driver.ts +11 -0
  217. package/src/components/gate-plan.ts +9 -0
  218. package/src/components/pr-pipeline.ts +56 -15
  219. package/src/components/pr-run.ts +1 -1
  220. package/src/components/promote-rounds.test.ts +220 -0
  221. package/src/components/promote-rounds.ts +232 -0
  222. package/src/components/release-compile.test.ts +34 -0
  223. package/src/components/release-compile.ts +89 -8
  224. package/src/components/release-gate.test.ts +47 -0
  225. package/src/components/release-gate.ts +111 -46
  226. package/src/components/release-promote.test.ts +55 -2
  227. package/src/components/release-promote.ts +93 -23
  228. package/src/components/verbs/bom-writer.ts +1 -1
  229. package/src/components/verbs/lockfile-sbom-generator.test.ts +36 -0
  230. package/src/components/verbs/lockfile-sbom-generator.ts +43 -5
  231. package/src/components/wave-plan.ts +32 -4
  232. package/src/graph-declared-exports.test.ts +40 -0
  233. package/src/graph-declared.ts +9 -1
  234. package/src/graph-detail.test.ts +57 -0
  235. package/src/graph-detail.ts +33 -7
  236. package/src/health.schema.json +191 -0
  237. package/src/health.test.ts +62 -0
  238. package/src/health.ts +178 -0
  239. package/src/lexicon.ts +41 -1
  240. package/src/lifecycle/merge-record.test.ts +111 -0
  241. package/src/lifecycle/merge-record.ts +154 -0
  242. package/src/lifecycle/publish-attachments.ts +25 -13
  243. package/src/lifecycle/publish-npm.ts +4 -3
  244. package/src/lifecycle/publish-targets.test.ts +34 -4
  245. package/src/lifecycle/release-at.test.ts +48 -0
  246. package/src/lifecycle/release-at.ts +100 -0
  247. package/src/lifecycle/release-cut.test.ts +41 -0
  248. package/src/lifecycle/release-cut.ts +22 -11
  249. package/src/lifecycle/release-publish.test.ts +43 -13
  250. package/src/lifecycle/release-publish.ts +38 -18
  251. package/src/lifecycle/release-store.ts +57 -13
  252. package/src/lifecycle/run-ledger.test.ts +13 -0
  253. package/src/lifecycle/run-ledger.ts +1 -0
  254. package/src/lint/rules/op/index.ts +4 -2
  255. package/src/lint/rules/op/ops018-shell-image-digest.test.ts +60 -0
  256. package/src/lint/rules/op/ops018-shell-image-digest.ts +48 -0
  257. package/src/op/activities/activity-contracts.ts +3 -0
  258. package/src/op/activities/shell-container.e2e.test.ts +67 -0
  259. package/src/op/activities/shell.test.ts +44 -1
  260. package/src/op/activities/shell.ts +110 -3
  261. package/src/op/builders.ts +7 -0
  262. package/src/op/runtime.ts +2 -0
  263. package/src/op/step-console.test.ts +4 -2
  264. package/src/pr-loop.test.ts +29 -0
  265. package/src/pr-loop.ts +26 -7
  266. package/src/pr-report-url.ts +2 -2
  267. package/src/runner-start-bundle.test.ts +29 -0
  268. package/src/runner-start.test.ts +179 -0
  269. package/src/runner-start.ts +329 -0
  270. package/src/workspace/__fixtures__/contract-repo.ts +11 -2
  271. package/src/workspace/check-contract.test.ts +4 -2
  272. package/src/workspace/check.schema.json +88 -2
  273. package/src/workspace/checks/ci-workflow.test.ts +150 -0
  274. package/src/workspace/checks/ci-workflow.ts +129 -0
  275. package/src/workspace/checks/security.test.ts +138 -0
  276. package/src/workspace/checks/security.ts +305 -0
  277. package/src/workspace/checks.ts +8 -0
  278. package/src/workspace/ci-builds.e2e.test.ts +208 -0
  279. package/src/workspace/ci-builds.test.ts +174 -0
  280. package/src/workspace/ci-builds.ts +385 -0
  281. package/src/workspace/ci-green-builds.test.ts +180 -0
  282. package/src/workspace/ci-green-cli.test.ts +54 -0
  283. package/src/workspace/ci-green-cli.ts +26 -5
  284. package/src/workspace/ci-green-forge.ts +24 -0
  285. package/src/workspace/ci-green-workflow.test.ts +84 -1
  286. package/src/workspace/ci-green-workflow.ts +139 -2
  287. package/src/workspace/ci-green.test.ts +5 -2
  288. package/src/workspace/ci-green.ts +359 -27
  289. package/src/workspace/ci-init.test.ts +159 -0
  290. package/src/workspace/ci-init.ts +445 -0
  291. package/src/workspace/ci-last-green.schema.json +49 -0
  292. package/src/workspace/ci-list.schema.json +35 -1
  293. package/src/workspace/ci-report-url.test.ts +6 -1
  294. package/src/workspace/ci-run-index.schema.json +1 -1
  295. package/src/workspace/ci-run-index.ts +1 -1
  296. package/src/workspace/ci-run.test.ts +24 -7
  297. package/src/workspace/ci-run.ts +78 -16
  298. package/src/workspace/ci-runner-start.test.ts +151 -0
  299. package/src/workspace/ci-runner-start.ts +193 -0
  300. package/src/workspace/ci-serve-builds.test.ts +86 -0
  301. package/src/workspace/ci-serve.test.ts +59 -0
  302. package/src/workspace/ci-serve.ts +177 -22
  303. package/src/workspace/ci-workflow.ts +71 -0
  304. package/src/workspace/declaration.schema.json +51 -0
  305. package/src/workspace/declaration.test.ts +17 -0
  306. package/src/workspace/declaration.ts +53 -3
  307. package/src/workspace/init.ts +2 -2
  308. package/src/workspace/ls.schema.json +2 -1
  309. package/src/workspace/ls.ts +3 -2
  310. package/src/workspace/member-pipeline.ts +2 -1
  311. package/src/workspace/merge-record.schema.json +49 -0
  312. package/src/workspace/pr-report.schema.json +3 -3
  313. package/src/workspace/reason-codes.test.ts +4 -0
  314. package/src/workspace/reason-codes.ts +28 -0
  315. package/src/workspace/release-candidates.schema.json +353 -0
  316. package/src/workspace/release-candidates.test.ts +106 -0
  317. package/src/workspace/release-candidates.ts +315 -0
  318. package/src/workspace/release-workflow.ts +167 -0
  319. package/src/workspace/security.test.ts +70 -0
  320. 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
- const verdict = runs.some((r) => r.verdict === "fail")
121
- ? "fail"
122
- : runs.some((r) => r.verdict === "pending") || unmatched.length > 0
123
- ? "pending"
124
- : "pass";
125
- return { phase: phase.name, verdict, runs, unmatched };
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
- const phases = green.require.map((name) => evaluatePhase(green.phases.find((p) => p.name === name), latest));
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
- return { verdict: phases.some((p) => p.verdict === "pending") ? "pending" : "pass", phases, failure: null };
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
- if (green.source === "runs") {
403
- // The runs live on chant/lifecycle, which a CI checkout may not have yet.
404
- const fetched = await fetchLifecycleStatus({ cwd: top });
405
- if (fetched.status === "diverged" || fetched.status === "unreachable") {
406
- throw new Error(`could not fetch chant/lifecycle, where the Op runs are recorded, from ${fetched.remote}: ${fetched.status === "diverged" ? "the local branch and the remote's have diverged" : fetched.stderr}`);
407
- }
408
- runs = await readOpRunSource(req.cwd, top, base, green.signedSince);
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) => (runs ? runs.checkRuns(sha) : forge.checkRuns(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
- return { branch: green.branch, window: green.window, ref: tracking, commits: planned, made: due.map((c) => c.made.tag), signer: signer?.principal ?? null, source: green.source };
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 waiting = v.phases.filter((p) => p.verdict === "pending").map((p) => p.phase);
593
- const what = c.before === "green" ? "green" : v.verdict === "fail" ? `failed: phase ${v.failure.phase}, ${v.failure.run.name} ${v.failure.run.conclusion}` : `undecided: ${waiting.join(", ")}`;
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
- if (tags.green.size > 0 || tags.ignored.size > 0) {
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":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAKH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAItD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AACpD,OAAO,EAAgB,WAAW,EAAkB,MAAM,eAAe,CAAC;AAE1E,OAAO,EAAE,WAAW,EAAE,CAAC;AAGvB,OAAO,EAAyD,KAAK,UAAU,EAAoB,MAAM,eAAe,CAAC;AAEzH,OAAO,EAAmB,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AAEjE,OAAO,EAA2B,KAAK,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAKxF,oDAAoD;AACpD,eAAO,MAAM,oBAAoB,YAAI,SAAS,EAAE,MAAM,CAAU,CAAC;AAEjE,mEAAmE;AACnE,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,gGAAgG;AAChG,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,8EAA8E;IAC9E,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,gDAAgD;IAChD,QAAQ,EAAE,mBAAmB,EAAE,CAAC;CACjC;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,UAAU,CAAC;IACrB,KAAK,CAAC,EAAE,oBAAoB,CAAC;IAC7B,QAAQ,CAAC,EAAE,SAAS,aAAa,EAAE,CAAC;CACrC;AAMD,kEAAkE;AAClE,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,oBAAoB,GAAG,IAAI,CAKxE;AAED,mDAAmD;AACnD,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAInD;AAWD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,CAsBxI;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAerH;AASD,2HAA2H;AAC3H,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,MAAM,CAgB7E;AAED,oIAAoI;AACpI,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,GAAE,SAAS,MAAM,EAAyB,GAAG,MAAM,GAAG,IAAI,CAKtH;AAgBD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,cAAc,CAqFlF;AAED,8GAA8G;AAC9G,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAmCnF;AAED,6GAA6G;AAC7G,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,GAAG,MAAM,EAAE,CAO5E;AAoBD,wBAAsB,SAAS,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CA2EpE"}