@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
@@ -0,0 +1,233 @@
1
+ /**
2
+ * The `release-candidates` read (#4168 4.2): which green commits of trunk a
3
+ * person can cut a release from, and what each one would ship.
4
+ *
5
+ * `chant release candidates [--env <env>] [--json]` prints this document.
6
+ * It walks the first-parent line of the trunk (`ci.green.branch`, else what
7
+ * `origin/HEAD` names, as `release cut` resolves it) from its tip back to the
8
+ * oldest commit an environment of the member runs, or `limit` commits when an
9
+ * environment runs no release yet. An environment runs the releases its
10
+ * latest deploy records name in `releaseDigest`, as the `releases` read
11
+ * (./releases.ts) says.
12
+ *
13
+ * Every green commit that is not revoked is a candidate. Its `changes` are
14
+ * the first-parent commits after the previous candidate up to and including
15
+ * it, so the candidates partition the walk. A revoked commit is listed under
16
+ * `skipped`, and its changes carry on into the next candidate. Commits after
17
+ * the newest candidate are listed under `pending`: they are not green yet, or
18
+ * went red.
19
+ *
20
+ * For each change it names the work items and other records its
21
+ * `Chant-Record` trailers name, the pull request a forge's squash subject
22
+ * names (`... (#123)`), and the merge record `_merges/<sha>.json` on
23
+ * `chant/lifecycle` when one exists (#4168 4.1). Each candidate lists the
24
+ * flags whose record files changed in its range, from the declared flag kinds
25
+ * (#4024).
26
+ *
27
+ * It reads local refs and tags only and never fetches. Git is read through a
28
+ * local `git` subprocess.
29
+ */
30
+ import { execFileSync } from "node:child_process";
31
+ import { relative, resolve, sep } from "node:path";
32
+ import { latestPerComponent, readReleaseLedger } from "../lifecycle/release-ledger.js";
33
+ import { readAllReleases, readCuts } from "../lifecycle/release-store.js";
34
+ import { resolveMemberLedger } from "../lifecycle/member-ledger.js";
35
+ import { readBlobFromPath } from "../lifecycle/git.js";
36
+ import { readerVersion } from "./declaration.js";
37
+ export const RELEASE_CANDIDATES_CONTRACT_VERSION = 1;
38
+ export const RELEASE_CANDIDATES_SCHEMA_ID = "https://intentius.io/chant/schemas/workspace/release-candidates/v1/release-candidates.schema.json";
39
+ /** Why the read stopped. Closed; each code is in `reason-codes.ts`. */
40
+ export const RELEASE_CANDIDATES_ERROR_CODES = ["not-a-git-repository", "ledger-unreadable"];
41
+ /** How many first-parent commits the walk reads when no environment bounds it. */
42
+ export const DEFAULT_CANDIDATE_LIMIT = 50;
43
+ function git(top, args) {
44
+ return execFileSync("git", args, { cwd: top, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"], maxBuffer: 64 * 1024 * 1024 }).trim();
45
+ }
46
+ function tryGit(top, args) {
47
+ try {
48
+ return git(top, args);
49
+ }
50
+ catch {
51
+ return null;
52
+ }
53
+ }
54
+ const PR_IN_SUBJECT = /\(#(\d+)\)\s*$/;
55
+ /** The work ids and `<kind>:<id>` records a commit's `Chant-Record` trailers name. */
56
+ export function recordTrailers(trailers) {
57
+ const values = Object.entries(trailers)
58
+ .filter(([k]) => k.toLowerCase() === "chant-record")
59
+ .flatMap(([, v]) => v);
60
+ const records = values.map((v) => v.trim()).filter((v) => /^[^:\s]+:\S+$/.test(v));
61
+ const work = records.filter((r) => r.startsWith("work:")).map((r) => r.slice("work:".length));
62
+ return { work, records };
63
+ }
64
+ /** The pull request number at the end of a squash subject, or null. */
65
+ export function prFromSubject(subject) {
66
+ const m = PR_IN_SUBJECT.exec(subject);
67
+ return m ? Number(m[1]) : null;
68
+ }
69
+ /** Read the release candidates of the member at `query.cwd`. */
70
+ export async function releaseCandidatesDocument(query) {
71
+ const head = { $schema: RELEASE_CANDIDATES_SCHEMA_ID, contract: RELEASE_CANDIDATES_CONTRACT_VERSION, chant: readerVersion() };
72
+ const cwd = resolve(query.cwd);
73
+ const top = tryGit(cwd, ["rev-parse", "--show-toplevel"]);
74
+ if (!top)
75
+ return { ...head, error: { code: "not-a-git-repository", message: "candidates are read from trunk's commits and tags, and this directory is not in a git repository" } };
76
+ try {
77
+ const { scopeSource } = await import("./write-scope.js");
78
+ const { resolveTrunk } = await import("../lifecycle/release-workstation.js");
79
+ const { judgedCiTags } = await import("../lifecycle/release-revocation.js");
80
+ const { commitDetails } = await import("./intent.js");
81
+ const source = scopeSource(cwd);
82
+ const green = source.declaration?.ci?.green ?? null;
83
+ const trunk = resolveTrunk(top, green?.branch ?? null);
84
+ const tip = tryGit(top, ["rev-parse", "--verify", "--quiet", `refs/remotes/${trunk.remote}/${trunk.branch}^{commit}`]) ??
85
+ tryGit(top, ["rev-parse", "--verify", "--quiet", `refs/heads/${trunk.branch}^{commit}`]);
86
+ const ledger = await resolveMemberLedger(cwd);
87
+ const loc = { cwd };
88
+ // What each environment runs.
89
+ const { readMemberPromotionOrder } = await import("./promotion-order.js");
90
+ const order = ((await readMemberPromotionOrder(cwd)) ?? []).map((s) => s.name);
91
+ const envNames = query.env !== undefined ? [query.env] : order;
92
+ const { releases } = await readAllReleases(loc);
93
+ const { cuts } = await readCuts(loc);
94
+ const commitOf = new Map(releases.map((r) => [r.releaseDigest, r.commit]));
95
+ const labelOf = new Map();
96
+ const cutAt = new Map();
97
+ for (const c of cuts) {
98
+ if (c.label !== null && !labelOf.has(c.releaseDigest))
99
+ labelOf.set(c.releaseDigest, c.label);
100
+ if (!cutAt.has(c.releaseDigest) || c.timestamp > cutAt.get(c.releaseDigest))
101
+ cutAt.set(c.releaseDigest, c.timestamp);
102
+ }
103
+ const environments = [];
104
+ for (const env of envNames) {
105
+ const { records } = await readReleaseLedger(env, loc);
106
+ const byDigest = new Map();
107
+ for (const record of latestPerComponent(records).values()) {
108
+ const digest = record.releaseDigest;
109
+ if (typeof digest !== "string")
110
+ continue;
111
+ byDigest.set(digest, [...(byDigest.get(digest) ?? []), record.component].sort());
112
+ }
113
+ const running = [...byDigest]
114
+ .map(([releaseDigest, components]) => ({ releaseDigest, label: labelOf.get(releaseDigest) ?? null, commit: commitOf.get(releaseDigest) ?? null, components }))
115
+ .sort((a, b) => ((cutAt.get(b.releaseDigest) ?? "") < (cutAt.get(a.releaseDigest) ?? "") ? -1 : 1));
116
+ environments.push({ env, running });
117
+ }
118
+ const empty = { ...head, member: ledger.members.length > 0 ? ledger.members.join("/") : null, branch: trunk.branch, tip, environments, since: null, candidates: [], skipped: [], pending: [] };
119
+ if (!tip)
120
+ return empty;
121
+ // The oldest commit any environment runs bounds the walk, when every environment runs one.
122
+ const limit = query.limit ?? DEFAULT_CANDIDATE_LIMIT;
123
+ const line = git(top, ["rev-list", "--first-parent", `--max-count=${Math.max(limit, 1) * 20}`, tip]).split("\n").filter(Boolean);
124
+ const position = new Map(line.map((sha, i) => [sha, i]));
125
+ const runningCommits = (e) => e.running.map((r) => r.commit).filter((c) => c !== null && position.has(c));
126
+ const bounded = environments.length > 0 && environments.every((e) => runningCommits(e).length > 0);
127
+ let since = null;
128
+ let walk = line.slice(0, limit);
129
+ if (bounded) {
130
+ const oldest = Math.max(...environments.flatMap((e) => runningCommits(e).map((c) => position.get(c))));
131
+ since = line[oldest];
132
+ walk = line.slice(0, oldest);
133
+ }
134
+ // An environment's newest running commit on the line, by position (smaller is newer).
135
+ const newestRun = new Map(environments.map((e) => [e.env, Math.min(...runningCommits(e).map((c) => position.get(c)), Number.POSITIVE_INFINITY)]));
136
+ const tags = await judgedCiTags(top, green);
137
+ const details = commitDetails(top, walk);
138
+ const merges = new Map();
139
+ for (const sha of walk) {
140
+ const text = (await readBlobFromPath("_merges", `${sha}.json`, loc)) ?? (ledger.prefix !== "" ? await readBlobFromPath("_merges", `${sha}.json`, { cwd, ledgerPrefix: "" }) : null);
141
+ if (text === null)
142
+ continue;
143
+ try {
144
+ const parsed = JSON.parse(text);
145
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
146
+ merges.set(sha, parsed);
147
+ }
148
+ catch {
149
+ // A merge record that is not JSON is left out.
150
+ }
151
+ }
152
+ const change = (sha) => {
153
+ const d = details.get(sha);
154
+ const { work, records } = recordTrailers(d?.trailers ?? {});
155
+ const merge = merges.get(sha) ?? null;
156
+ const mergePr = merge && typeof merge.pr === "number" ? merge.pr : null;
157
+ const subject = d?.subject ?? "";
158
+ return { commit: sha, subject, date: d?.date ?? "", work, records, pr: mergePr ?? prFromSubject(subject), merge };
159
+ };
160
+ const flagReader = await flagsReader(top, source.rootOnDisk);
161
+ const cutLabels = new Map();
162
+ for (const r of releases) {
163
+ const label = labelOf.get(r.releaseDigest);
164
+ if (label !== undefined)
165
+ cutLabels.set(r.commit, [...(cutLabels.get(r.commit) ?? []), label].sort());
166
+ }
167
+ // Oldest first, so each candidate closes the commits since the previous one.
168
+ const candidates = [];
169
+ const skipped = [];
170
+ let open = [];
171
+ for (let i = walk.length - 1; i >= 0; i--) {
172
+ const sha = walk[i];
173
+ open.push(sha);
174
+ if (tags.revoked.has(sha)) {
175
+ skipped.push({ commit: sha, subject: details.get(sha)?.subject ?? "", reason: "revoked" });
176
+ continue;
177
+ }
178
+ if (!tags.green.has(sha))
179
+ continue;
180
+ const own = change(sha);
181
+ candidates.push({
182
+ commit: sha,
183
+ subject: own.subject,
184
+ date: own.date,
185
+ newFor: environments.filter((e) => i < newestRun.get(e.env)).map((e) => e.env),
186
+ cut: cutLabels.get(sha) ?? [],
187
+ changes: open.map(change).reverse(),
188
+ flags: flagReader(open),
189
+ });
190
+ open = [];
191
+ }
192
+ return {
193
+ ...empty,
194
+ since,
195
+ candidates: candidates.reverse(),
196
+ skipped: skipped.reverse(),
197
+ pending: open.map(change).reverse(),
198
+ };
199
+ }
200
+ catch (err) {
201
+ return { ...head, error: { code: "ledger-unreadable", message: `reading the candidates failed: ${err instanceof Error ? err.message : String(err)}` } };
202
+ }
203
+ }
204
+ /** The flags whose record files the given commits change, or a reader that finds none when no flag kind can be read. */
205
+ async function flagsReader(top, root) {
206
+ if (!root)
207
+ return () => [];
208
+ let flags;
209
+ try {
210
+ const { readWorkspaceFlags } = await import("./flags-read.js");
211
+ flags = (await readWorkspaceFlags(root)).flags;
212
+ }
213
+ catch {
214
+ return () => [];
215
+ }
216
+ if (flags.length === 0)
217
+ return () => [];
218
+ const fromTop = (p) => relative(top, resolve(root, p)).split(sep).join("/");
219
+ const byPath = new Map(flags.map((f) => [fromTop(f.path), f]));
220
+ return (shas) => {
221
+ const touched = new Set();
222
+ for (const sha of shas) {
223
+ const names = tryGit(top, ["diff-tree", "--no-commit-id", "--name-only", "-r", "--root", sha]) ?? "";
224
+ for (const n of names.split("\n"))
225
+ if (byPath.has(n))
226
+ touched.add(n);
227
+ }
228
+ return [...touched]
229
+ .sort()
230
+ .map((p) => byPath.get(p))
231
+ .map((f) => ({ id: f.id, member: f.member, param: f.param, path: f.path, values: f.values }));
232
+ };
233
+ }
@@ -0,0 +1,353 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://intentius.io/chant/schemas/workspace/release-candidates/v1/release-candidates.schema.json",
4
+ "title": "chant release candidates output",
5
+ "description": "What `chant release candidates --json` prints (#4168 4.2): the green commits of trunk a release can be cut from, newest first, each with the commits, work items, pull requests, merge records and flags it would ship since the previous candidate, and what each environment of the member runs. It reads local refs and tags and never fetches. Version 1 of the read contract. Readers ignore fields they do not know; a field is only ever added within a version. The codes are a closed list, each in the one closed list of `reason-codes.ts`. Contract version 1 of this document is written by chant 0.124.33 and newer.",
6
+ "oneOf": [
7
+ {
8
+ "$ref": "#/$defs/result"
9
+ },
10
+ {
11
+ "$ref": "#/$defs/failure"
12
+ }
13
+ ],
14
+ "$defs": {
15
+ "result": {
16
+ "type": "object",
17
+ "required": [
18
+ "$schema",
19
+ "contract",
20
+ "chant",
21
+ "member",
22
+ "branch",
23
+ "tip",
24
+ "environments",
25
+ "since",
26
+ "candidates",
27
+ "skipped",
28
+ "pending"
29
+ ],
30
+ "properties": {
31
+ "$schema": {
32
+ "const": "https://intentius.io/chant/schemas/workspace/release-candidates/v1/release-candidates.schema.json"
33
+ },
34
+ "contract": {
35
+ "const": 1
36
+ },
37
+ "chant": {
38
+ "type": "string"
39
+ },
40
+ "error": false,
41
+ "member": {
42
+ "type": [
43
+ "string",
44
+ "null"
45
+ ],
46
+ "description": "The workspace member read, or null for a project outside a workspace."
47
+ },
48
+ "branch": {
49
+ "type": "string",
50
+ "description": "The trunk walked: ci.green.branch, else what origin/HEAD names."
51
+ },
52
+ "tip": {
53
+ "type": [
54
+ "string",
55
+ "null"
56
+ ],
57
+ "description": "The branch's tip as this clone knows it, or null when it knows none."
58
+ },
59
+ "environments": {
60
+ "type": "array",
61
+ "items": {
62
+ "$ref": "#/$defs/environment"
63
+ },
64
+ "description": "The member's environments in promotion order, or the one --env names."
65
+ },
66
+ "since": {
67
+ "type": [
68
+ "string",
69
+ "null"
70
+ ],
71
+ "description": "The oldest commit an environment runs, where the walk stopped, or null when it read its limit of commits."
72
+ },
73
+ "candidates": {
74
+ "type": "array",
75
+ "items": {
76
+ "$ref": "#/$defs/candidate"
77
+ },
78
+ "description": "Green commits that are not revoked, newest first."
79
+ },
80
+ "skipped": {
81
+ "type": "array",
82
+ "items": {
83
+ "type": "object",
84
+ "required": [
85
+ "commit",
86
+ "subject",
87
+ "reason"
88
+ ],
89
+ "properties": {
90
+ "commit": {
91
+ "type": "string"
92
+ },
93
+ "subject": {
94
+ "type": "string"
95
+ },
96
+ "reason": {
97
+ "enum": [
98
+ "revoked"
99
+ ]
100
+ }
101
+ }
102
+ },
103
+ "description": "Revoked green commits, newest first. Their changes are in the next candidate."
104
+ },
105
+ "pending": {
106
+ "type": "array",
107
+ "items": {
108
+ "$ref": "#/$defs/change"
109
+ },
110
+ "description": "Commits after the newest candidate, newest first: not green yet, or red."
111
+ }
112
+ },
113
+ "description": "Exit code 0, with or without candidates."
114
+ },
115
+ "failure": {
116
+ "type": "object",
117
+ "required": [
118
+ "$schema",
119
+ "contract",
120
+ "chant",
121
+ "error"
122
+ ],
123
+ "properties": {
124
+ "$schema": {
125
+ "const": "https://intentius.io/chant/schemas/workspace/release-candidates/v1/release-candidates.schema.json"
126
+ },
127
+ "contract": {
128
+ "const": 1
129
+ },
130
+ "chant": {
131
+ "type": "string"
132
+ },
133
+ "error": {
134
+ "type": "object",
135
+ "required": [
136
+ "code",
137
+ "message"
138
+ ],
139
+ "properties": {
140
+ "code": {
141
+ "enum": [
142
+ "not-a-git-repository",
143
+ "ledger-unreadable"
144
+ ]
145
+ },
146
+ "message": {
147
+ "type": "string"
148
+ }
149
+ }
150
+ }
151
+ },
152
+ "description": "Exit code 1."
153
+ },
154
+ "environment": {
155
+ "type": "object",
156
+ "required": [
157
+ "env",
158
+ "running"
159
+ ],
160
+ "properties": {
161
+ "env": {
162
+ "type": "string"
163
+ },
164
+ "running": {
165
+ "type": "array",
166
+ "items": {
167
+ "$ref": "#/$defs/running"
168
+ },
169
+ "description": "The releases the environment's latest deploys name, newest cut first."
170
+ }
171
+ }
172
+ },
173
+ "running": {
174
+ "type": "object",
175
+ "required": [
176
+ "releaseDigest",
177
+ "label",
178
+ "commit",
179
+ "components"
180
+ ],
181
+ "properties": {
182
+ "releaseDigest": {
183
+ "type": "string"
184
+ },
185
+ "label": {
186
+ "type": [
187
+ "string",
188
+ "null"
189
+ ]
190
+ },
191
+ "commit": {
192
+ "type": [
193
+ "string",
194
+ "null"
195
+ ],
196
+ "description": "The commit it was cut from, or null when its release file is not on this clone's branch."
197
+ },
198
+ "components": {
199
+ "type": "array",
200
+ "items": {
201
+ "type": "string"
202
+ }
203
+ }
204
+ }
205
+ },
206
+ "candidate": {
207
+ "type": "object",
208
+ "required": [
209
+ "commit",
210
+ "subject",
211
+ "date",
212
+ "newFor",
213
+ "cut",
214
+ "changes",
215
+ "flags"
216
+ ],
217
+ "properties": {
218
+ "commit": {
219
+ "type": "string"
220
+ },
221
+ "subject": {
222
+ "type": "string"
223
+ },
224
+ "date": {
225
+ "type": "string"
226
+ },
227
+ "newFor": {
228
+ "type": "array",
229
+ "items": {
230
+ "type": "string"
231
+ },
232
+ "description": "The environments that run neither this commit nor a later one."
233
+ },
234
+ "cut": {
235
+ "type": "array",
236
+ "items": {
237
+ "type": "string"
238
+ },
239
+ "description": "The labels of releases already cut from it."
240
+ },
241
+ "changes": {
242
+ "type": "array",
243
+ "items": {
244
+ "$ref": "#/$defs/change"
245
+ },
246
+ "description": "The first-parent commits after the previous candidate, up to and including this one, newest first."
247
+ },
248
+ "flags": {
249
+ "type": "array",
250
+ "items": {
251
+ "$ref": "#/$defs/flag"
252
+ },
253
+ "description": "The flags of the declared flag kinds whose record files those commits change."
254
+ }
255
+ }
256
+ },
257
+ "change": {
258
+ "type": "object",
259
+ "required": [
260
+ "commit",
261
+ "subject",
262
+ "date",
263
+ "work",
264
+ "records",
265
+ "pr",
266
+ "merge"
267
+ ],
268
+ "properties": {
269
+ "commit": {
270
+ "type": "string"
271
+ },
272
+ "subject": {
273
+ "type": "string"
274
+ },
275
+ "date": {
276
+ "type": "string",
277
+ "description": "Author date, ISO 8601."
278
+ },
279
+ "work": {
280
+ "type": "array",
281
+ "items": {
282
+ "type": "string"
283
+ },
284
+ "description": "The ids its `Chant-Record: work:<id>` trailers name."
285
+ },
286
+ "records": {
287
+ "type": "array",
288
+ "items": {
289
+ "type": "string"
290
+ },
291
+ "description": "Every `Chant-Record` trailer, as `<kind>:<id>`."
292
+ },
293
+ "pr": {
294
+ "type": [
295
+ "integer",
296
+ "null"
297
+ ],
298
+ "description": "The pull request the merge record or the squash subject (`(#123)` at its end) names, or null."
299
+ },
300
+ "merge": {
301
+ "type": [
302
+ "object",
303
+ "null"
304
+ ],
305
+ "description": "The merge record `_merges/<sha>.json` on chant/lifecycle (#4168 4.1), as stored, or null when there is none."
306
+ }
307
+ },
308
+ "description": "One first-parent commit."
309
+ },
310
+ "flag": {
311
+ "type": "object",
312
+ "required": [
313
+ "id",
314
+ "member",
315
+ "param",
316
+ "path",
317
+ "values"
318
+ ],
319
+ "properties": {
320
+ "id": {
321
+ "type": "string"
322
+ },
323
+ "member": {
324
+ "type": "string"
325
+ },
326
+ "param": {
327
+ "type": "string"
328
+ },
329
+ "path": {
330
+ "type": "string",
331
+ "description": "The record file from the workspace root."
332
+ },
333
+ "values": {
334
+ "type": "object",
335
+ "additionalProperties": {
336
+ "anyOf": [
337
+ {
338
+ "type": "string"
339
+ },
340
+ {
341
+ "type": "number"
342
+ },
343
+ {
344
+ "type": "boolean"
345
+ }
346
+ ]
347
+ },
348
+ "description": "The value per environment, as the working tree's record writes it."
349
+ }
350
+ }
351
+ }
352
+ }
353
+ }
@@ -0,0 +1,31 @@
1
+ import type { SecurityGroup } from "./security.js";
2
+ export declare const RELEASE_WORKFLOW_FORGES: readonly ["github", "forgejo", "gitlab"];
3
+ export type ReleaseWorkflowForge = (typeof RELEASE_WORKFLOW_FORGES)[number];
4
+ /** Where each forge's publish workflow is written, relative to the repository root. */
5
+ export declare const RELEASE_WORKFLOW_FILES: Record<ReleaseWorkflowForge, string>;
6
+ export interface ReleaseWorkflowInput {
7
+ forge: ReleaseWorkflowForge;
8
+ /** The security groups the workspace opts into at its root. */
9
+ groups: ReadonlySet<SecurityGroup>;
10
+ /** The command that installs what the build needs. Default `npm ci`. */
11
+ install?: string;
12
+ /** The command that builds what the targets upload, or none. Default `npm run build`. */
13
+ build?: string | null;
14
+ /** What runs chant. Default `npx chant`. */
15
+ chant?: string;
16
+ /** The workspace root relative to the repository root, `.` at the top. */
17
+ workspaceRoot?: string;
18
+ }
19
+ /** The flags `chant release publish` gets under the groups. */
20
+ export declare function publishFlags(groups: ReadonlySet<SecurityGroup>): string[];
21
+ /** The workflow file's text, without the generated-by header. */
22
+ export declare function renderReleaseWorkflow(input: ReleaseWorkflowInput): string;
23
+ /** The header that marks the file generated, naming the command that regenerates it. */
24
+ export declare function releaseWorkflowHeader(command: string): string;
25
+ /**
26
+ * `chant release workflow [--forge github|gitlab|forgejo] [--install <command>]
27
+ * [--build <command>|--no-build] [--chant <command>] [--output <file>]`: write
28
+ * the publish workflow for the workspace's forge, under its security groups.
29
+ */
30
+ export declare function runReleaseWorkflow(ctx: import("../cli/registry.js").CommandContext): Promise<number>;
31
+ //# sourceMappingURL=release-workflow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"release-workflow.d.ts","sourceRoot":"","sources":["../../src/workspace/release-workflow.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,eAAO,MAAM,uBAAuB,YAAI,QAAQ,EAAE,SAAS,EAAE,QAAQ,CAAU,CAAC;AAChF,MAAM,MAAM,oBAAoB,GAAG,CAAC,OAAO,uBAAuB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE5E,uFAAuF;AACvF,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC,oBAAoB,EAAE,MAAM,CAKvE,CAAC;AAEF,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,oBAAoB,CAAC;IAC5B,+DAA+D;IAC/D,MAAM,EAAE,WAAW,CAAC,aAAa,CAAC,CAAC;IACnC,wEAAwE;IACxE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yFAAyF;IACzF,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,4CAA4C;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0EAA0E;IAC1E,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,+DAA+D;AAC/D,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW,CAAC,aAAa,CAAC,GAAG,MAAM,EAAE,CAEzE;AAED,iEAAiE;AACjE,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,oBAAoB,GAAG,MAAM,CAwDzE;AAED,wFAAwF;AACxF,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,OAAO,iBAAiB,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CA8CvG"}