@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
@@ -20,8 +20,8 @@
20
20
  * environment: every component of the release at the digest the environment
21
21
  * ran from it, through the release gate `promote --release` binds (#4045).
22
22
  */
23
- import { resolve } from "node:path";
24
- import { readFileSync } from "node:fs";
23
+ import { dirname, join, resolve } from "node:path";
24
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
25
25
  import { loadChantConfig } from "../../config.js";
26
26
  import { promotionOrder } from "../../workspace/promotion-order.js";
27
27
  import { resolvePromotionSource } from "../../components/promote-order.js";
@@ -41,7 +41,7 @@ import { resolveCliBuildParams, parseParamFlags } from "../build-params-cli.js";
41
41
  import { formatError, formatWarning, formatSuccess, formatBold, formatInfo } from "../format.js";
42
42
  import { GATED_EXIT_CODE } from "./run.js";
43
43
  import { redeployLine, releaseRollbackLine, rollbackTarget, unsettledComponents } from "../../lifecycle/mixed-environment.js";
44
- const PROMOTE_USAGE = "chant components promote --to <env> [--from <env>] [--allow-skip] [--release <label|digest>] [--component <name> [--digest <sha256:...>] | --digest <component>=<sha256:...> ...] [--dry-run] [--json]";
44
+ const PROMOTE_USAGE = "chant components promote --to <env> [--from <env>] [--allow-skip] [--release <label|digest>] [--component <name> [--digest <sha256:...>] | --digest <component>=<sha256:...> ...] [--wave-gate <gate>] [--resume <file>] [--dry-run] [--json]";
45
45
  const ROLLBACK_USAGE = "chant components rollback <env> (--component <name> [--digest <sha256:...>] | --release <label|digest>) [--dry-run] [--json]";
46
46
  const REDEPLOY_USAGE = "chant components redeploy <env> --component <name> [--digest <sha256:...>] [--dry-run] [--json]";
47
47
  function renderPlan(verb, plan, removed) {
@@ -115,6 +115,11 @@ async function readLedger(env) {
115
115
  * a refusal already printed: a provisional member, or a member that failed
116
116
  * to plan. On an empty change set, each gate gets an `empty: true`
117
117
  * resolution the run then counts once it reads the stored document back.
118
+ *
119
+ * A dependent is planned against what a promoted upstream's plan says it will
120
+ * expose (#4168 step 4). With a resume record, only what is left is planned,
121
+ * against the outputs the record kept, and the approval the record names
122
+ * carries on when it covers that plan.
118
123
  */
119
124
  async function bindReleaseGate(input) {
120
125
  const { plan, under, port } = input;
@@ -145,71 +150,157 @@ async function bindReleaseGate(input) {
145
150
  for (const [component, u] of unsettledComponents(envRecords, await readDeployOutcomes(env).catch(() => []))) {
146
151
  unsettled.set(component, `the ${u.outcome.verb} run ${u.outcome.runId} left ${component} ${u.state} in "${env}" and nothing has been recorded for it since, so what it runs is not known`);
147
152
  }
148
- const doc = await gate.planReleaseChangeSet({
153
+ const digests = new Map(plan.items.map((i) => [i.component, i.digest]));
154
+ const planSet = (only, outputs) => gate.planReleaseSet({
149
155
  release: under.release,
150
156
  env,
151
157
  components: input.prepared,
152
- digests: new Map(plan.items.map((i) => [i.component, i.digest])),
158
+ digests,
153
159
  registry: input.registry,
154
160
  current,
155
161
  unsettled,
156
- seededOutputs: input.seededOutputs,
162
+ seededOutputs: outputs,
163
+ ...(only ? { only } : {}),
157
164
  });
158
- const provisional = gate.provisionalMembers(doc);
159
- if (provisional.length > 0) {
160
- console.error(formatError({
161
- message: `the change set of release ${under.label ?? releaseDigest} for "${env}" has provisional members (${provisional.join(", ")}), which no approval covers; nothing was deployed`,
162
- hint: "Deploy what they read first, so they plan against real values.",
163
- }));
164
- return 1;
165
- }
166
- const failed = doc.members.filter((m) => m.status === "failed");
167
- if (failed.length > 0) {
168
- for (const m of failed)
169
- console.error(formatError({ message: `${m.member} failed to plan for "${env}"${m.error ? `: ${m.error}` : ""}` }));
170
- console.error(formatError({ message: "nothing was deployed" }));
171
- return 1;
172
- }
173
- // The document is stored before the gate is decided, so the plan a pending
174
- // gate waits on can be read, and an empty pass can be checked.
175
165
  const { storeGateChangeSet } = await import("../../lifecycle/gate-change-set.js");
176
- try {
177
- const stored = await storeGateChangeSet({ doc, cwd: input.projectPath, at: input.head ?? null });
178
- if (stored.warning)
179
- console.error(formatWarning({ message: stored.warning }));
180
- }
181
- catch (err) {
182
- console.error(formatWarning({ message: `could not store the change set ${doc.digest}: ${err instanceof Error ? err.message : String(err)}` }));
183
- }
166
+ const { readStoredChangeSet } = await import("../../lifecycle/change-set-store.js");
184
167
  const { isEmptyChangeSet } = await import("../../change-set.js");
185
- const empty = isEmptyChangeSet(doc);
186
- console.error(formatInfo(`release gate: change set ${doc.digest} for "${env}"` +
187
- (empty ? ", empty: its gates pass without an approval" : `, ${doc.summary.entries} entr${doc.summary.entries === 1 ? "y" : "ies"}, ${doc.summary.holes} hole(s)`) +
188
- ` (chant change-set show ${doc.digest})`));
189
- if (empty) {
190
- if (!port.appendResolution)
191
- throw new Error("the gate ledger can't record a resolution, so an empty change set can't pass its gates");
192
- const { resolutions } = await port.read(gate.RELEASE_GATE_OP);
193
- const timestamp = new Date().toISOString();
194
- for (const name of gates) {
195
- const standing = resolutions.some((r) => r.empty === true && r.gate === name && r.environment === env && r.releaseDigest === releaseDigest && r.planDigest === doc.digest);
196
- if (standing)
197
- continue;
198
- // Pushed with the resolution; the deploy records the promote writes push the same branch.
199
- await port.appendResolution(gate.emptyResolution({ gate: name, doc, actor: input.actor, timestamp, ...(input.head ? { gitSha: input.head } : {}) }));
168
+ const verifyEmpty = gate.emptyVerifier((digest) => readStoredChangeSet(digest, { cwd: input.projectPath }), { releaseDigest, environment: env });
169
+ /** Refuse, store, and pass the gates of an empty plan; the binding, or the exit code of a refusal already printed. */
170
+ const bindDoc = async (doc, names, what) => {
171
+ const provisional = gate.provisionalMembers(doc);
172
+ if (provisional.length > 0) {
173
+ console.error(formatError({
174
+ message: `${what} of release ${under.label ?? releaseDigest} for "${env}" has provisional members (${provisional.join(", ")}), which no approval covers; nothing more was deployed`,
175
+ hint: "Deploy what they read first, so they plan against real values.",
176
+ }));
177
+ return 1;
200
178
  }
201
- }
202
- const { readStoredChangeSet } = await import("../../lifecycle/change-set-store.js");
203
- return {
204
- doc,
205
- empty,
206
- binding: {
207
- op: gate.RELEASE_GATE_OP,
208
- planDigest: doc.digest,
209
- releaseDigest,
210
- verifyEmpty: gate.emptyVerifier((digest) => readStoredChangeSet(digest, { cwd: input.projectPath }), { releaseDigest, environment: env }),
179
+ const failed = doc.members.filter((m) => m.status === "failed");
180
+ if (failed.length > 0) {
181
+ for (const m of failed)
182
+ console.error(formatError({ message: `${m.member} failed to plan for "${env}"${m.error ? `: ${m.error}` : ""}` }));
183
+ console.error(formatError({ message: "nothing more was deployed" }));
184
+ return 1;
185
+ }
186
+ // The document is stored before the gate is decided, so the plan a pending
187
+ // gate waits on can be read, and an empty pass can be checked.
188
+ try {
189
+ const stored = await storeGateChangeSet({ doc, cwd: input.projectPath, at: input.head ?? null });
190
+ if (stored.warning)
191
+ console.error(formatWarning({ message: stored.warning }));
192
+ }
193
+ catch (err) {
194
+ console.error(formatWarning({ message: `could not store the change set ${doc.digest}: ${err instanceof Error ? err.message : String(err)}` }));
195
+ }
196
+ const empty = isEmptyChangeSet(doc);
197
+ console.error(formatInfo(`release gate: ${what} ${doc.digest} for "${env}"` +
198
+ (empty ? ", empty: its gates pass without an approval" : `, ${doc.summary.entries} entr${doc.summary.entries === 1 ? "y" : "ies"}, ${doc.summary.holes} hole(s)`) +
199
+ ` (chant change-set show ${doc.digest})`));
200
+ if (empty) {
201
+ if (!port.appendResolution)
202
+ throw new Error("the gate ledger can't record a resolution, so an empty change set can't pass its gates");
203
+ const { resolutions } = await port.read(gate.RELEASE_GATE_OP);
204
+ const timestamp = new Date().toISOString();
205
+ for (const name of names) {
206
+ const standing = resolutions.some((r) => r.empty === true && r.gate === name && r.environment === env && r.releaseDigest === releaseDigest && r.planDigest === doc.digest);
207
+ if (standing)
208
+ continue;
209
+ // Pushed with the resolution; the deploy records the promote writes push the same branch.
210
+ await port.appendResolution(gate.emptyResolution({ gate: name, doc, actor: input.actor, timestamp, ...(input.head ? { gitSha: input.head } : {}) }));
211
+ }
212
+ }
213
+ return { binding: { op: gate.RELEASE_GATE_OP, planDigest: doc.digest, releaseDigest, verifyEmpty }, empty };
214
+ };
215
+ const later = {
216
+ replan: async (only, outputs) => (await planSet(only, outputs)).doc,
217
+ bind: async (doc, names) => {
218
+ const bound = await bindDoc(doc, names, "a later plan");
219
+ if (typeof bound === "number")
220
+ throw new PromoteStageRefused();
221
+ return bound.binding;
211
222
  },
212
223
  };
224
+ // #4168 step 4: a resume plans what is left against the outputs it kept.
225
+ if (input.resume) {
226
+ const done = new Set(input.resume.completed);
227
+ const left = new Set(plan.items.map((i) => i.component).filter((c) => !done.has(c)));
228
+ const fresh = await planSet(left, { ...input.seededOutputs, ...input.resume.outputs });
229
+ const stored = await readStoredChangeSet(input.resume.approvedDigest, { cwd: input.projectPath });
230
+ if (stored.status === "found") {
231
+ const { waveNotCovered } = await import("../../components/pr-run.js");
232
+ const notCovered = waveNotCovered(stored.document, fresh.doc);
233
+ if (notCovered.length === 0) {
234
+ console.error(formatInfo(`resuming: ${done.size} component(s) applied earlier; what is left is covered by change set ${stored.document.digest}`));
235
+ return {
236
+ doc: stored.document,
237
+ empty: isEmptyChangeSet(stored.document),
238
+ binding: { op: gate.RELEASE_GATE_OP, planDigest: stored.document.digest, releaseDigest, verifyEmpty },
239
+ reads: fresh.reads,
240
+ resumedFrom: stored.document.digest,
241
+ ...(fresh.plannedOutputs ? { plannedOutputs: fresh.plannedOutputs } : {}),
242
+ ...(fresh.unknownInputs ? { unknownInputs: fresh.unknownInputs } : {}),
243
+ ...later,
244
+ };
245
+ }
246
+ for (const line of notCovered)
247
+ console.error(formatWarning({ message: `resume: ${line}` }));
248
+ }
249
+ const bound = await bindDoc(fresh.doc, gates, "change set");
250
+ if (typeof bound === "number")
251
+ return bound;
252
+ return { doc: fresh.doc, ...bound, reads: fresh.reads, ...(fresh.plannedOutputs ? { plannedOutputs: fresh.plannedOutputs } : {}), ...(fresh.unknownInputs ? { unknownInputs: fresh.unknownInputs } : {}), ...later };
253
+ }
254
+ const first = await planSet(undefined, input.seededOutputs);
255
+ const bound = await bindDoc(first.doc, gates, "change set");
256
+ if (typeof bound === "number")
257
+ return bound;
258
+ for (const [component, refs] of Object.entries(first.unknownInputs ?? {})) {
259
+ console.error(formatInfo(`${component} reads ${refs.join(", ")}, known only once its upstream applies; it is planned again then, and needs its own approval if its plan moves`));
260
+ }
261
+ return { doc: first.doc, ...bound, reads: first.reads, ...(first.plannedOutputs ? { plannedOutputs: first.plannedOutputs } : {}), ...(first.unknownInputs ? { unknownInputs: first.unknownInputs } : {}), ...later };
262
+ }
263
+ /** A later round's or wave's plan was refused (provisional or failed members); the refusal is already printed. */
264
+ class PromoteStageRefused extends Error {
265
+ constructor() {
266
+ super("a later round's plan was refused");
267
+ this.name = "PromoteStageRefused";
268
+ }
269
+ }
270
+ /**
271
+ * The pull requests a release carries into `env` and how each was approved
272
+ * on merge (#4168 decision 2): shown beside the release gate, never counted
273
+ * by it. The commits are the release commit's first-parent history since
274
+ * the commits `env` runs, at most 200, read from the merge records.
275
+ */
276
+ async function pullRequestApprovals(release, since, cwd) {
277
+ const { execFileSync } = await import("node:child_process");
278
+ const { readMergeRecord } = await import("../../lifecycle/merge-record.js");
279
+ let commits = [];
280
+ try {
281
+ const out = execFileSync("git", ["rev-list", "--first-parent", "--max-count=200", release.commit, ...since.map((c) => `^${c}`)], { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
282
+ commits = out.split("\n").filter(Boolean);
283
+ }
284
+ catch {
285
+ return [];
286
+ }
287
+ const records = [];
288
+ for (const commit of commits) {
289
+ const record = await readMergeRecord(commit, { cwd }).catch(() => null);
290
+ if (record)
291
+ records.push(record);
292
+ }
293
+ return records;
294
+ }
295
+ /** One line per merge record: where it came from and how its plan stood against its approval. */
296
+ function approvalLine(r) {
297
+ const from = r.source.kind === "pull-request" ? `#${r.source.pr}` : r.source.kind === "work-item" ? r.source.records.map((x) => `${x.kind}:${x.id}`).join(", ") : "direct push";
298
+ const how = r.plan === "approved"
299
+ ? `approved by ${r.approvedBy.join(", ") || "nobody named"}${r.authorApproved?.length ? ` (by its author: ${r.authorApproved.join(", ")})` : ""}`
300
+ : r.plan === "changed"
301
+ ? `its plan moved after the approval of ${r.approvedDigest ?? "another digest"}`
302
+ : r.plan;
303
+ return `${r.commit.slice(0, 12)} ${from}: ${how}`;
213
304
  }
214
305
  /**
215
306
  * Prepare, run and record a plan. Shared by promote, rollback and redeploy,
@@ -247,7 +338,7 @@ async function deployPlan(ctx, verb, plan, project, actor, record, under) {
247
338
  if (under) {
248
339
  const { pinReleaseTemplates } = await import("../../components/release-promote.js");
249
340
  const { readReleaseTemplate } = await import("../../lifecycle/release-store.js");
250
- const pinned = await pinReleaseTemplates(prepared, under.release, under.target, (digest) => readReleaseTemplate(digest));
341
+ const pinned = await pinReleaseTemplates(prepared, under.release, under.target, (digest, encoding) => readReleaseTemplate(digest, undefined, encoding));
251
342
  if ("errors" in pinned) {
252
343
  for (const message of pinned.errors)
253
344
  console.error(formatError({ message }));
@@ -319,16 +410,43 @@ async function deployPlan(ctx, verb, plan, project, actor, record, under) {
319
410
  // #4045: under a release, every gate binds the release's change set for this environment.
320
411
  let releaseGate;
321
412
  let changeSetJson;
413
+ let bound;
414
+ // #4168 step 4: a promote that stopped at a round's or a wave's gate left a resume record.
415
+ const rounds = await import("../../components/promote-rounds.js");
416
+ const resumePath = resolve(project.projectPath, args.resume ?? join(".chant", "promote", `${plan.to}.resume.json`));
417
+ let resume;
418
+ if (under && existsSync(resumePath)) {
419
+ try {
420
+ const record = JSON.parse(readFileSync(resumePath, "utf8"));
421
+ if (record.schema === rounds.PROMOTE_RESUME_SCHEMA && record.env === plan.to && record.releaseDigest === under.release.releaseDigest)
422
+ resume = record;
423
+ else if (args.resume)
424
+ console.error(formatWarning({ message: `--resume ${args.resume} is for another release or environment; planning from the start` }));
425
+ }
426
+ catch (err) {
427
+ console.error(formatWarning({ message: `could not read the resume record ${resumePath}: ${err instanceof Error ? err.message : String(err)}` }));
428
+ }
429
+ }
430
+ let pullRequests = [];
322
431
  if (under) {
323
432
  try {
324
- const bound = await bindReleaseGate({ plan, prepared, under, registry, port, seededOutputs, actor: actor, projectPath: project.projectPath, head });
325
- if (typeof bound === "number") {
433
+ const result = await bindReleaseGate({ plan, prepared, under, registry, port, seededOutputs, actor: actor, projectPath: project.projectPath, head, ...(resume ? { resume } : {}) });
434
+ if (typeof result === "number") {
326
435
  tidy();
327
- return bound;
436
+ return result;
328
437
  }
329
- if (bound) {
330
- releaseGate = bound.binding;
331
- changeSetJson = { changeSet: bound.doc.digest, empty: bound.empty };
438
+ if (result) {
439
+ bound = result;
440
+ releaseGate = result.binding;
441
+ changeSetJson = { changeSet: result.doc.digest, empty: result.empty };
442
+ // #4168 decision 2: the pull requests' own approvals, shown beside the gate.
443
+ const since = [...new Set((await readLedger(plan.to)).filter((r) => r.env === plan.to).map((r) => r.gitSha))];
444
+ pullRequests = await pullRequestApprovals(under.release, since, project.projectPath);
445
+ if (pullRequests.length > 0 && !args.json) {
446
+ console.error(formatInfo(`pull requests this release carries into "${plan.to}" (shown, not counted by the release gate):`));
447
+ for (const r of pullRequests)
448
+ console.error(` ${approvalLine(r)}`);
449
+ }
332
450
  }
333
451
  }
334
452
  catch (err) {
@@ -336,6 +454,11 @@ async function deployPlan(ctx, verb, plan, project, actor, record, under) {
336
454
  throw err;
337
455
  }
338
456
  }
457
+ if (args.waveGate && !bound) {
458
+ console.error(formatError({ message: "--wave-gate needs a promote under --release whose compositions declare a gate", hint: PROMOTE_USAGE }));
459
+ tidy();
460
+ return 1;
461
+ }
339
462
  // The run id the release records carry, chosen before the run so that
340
463
  // `--events-json` (#4041) names the run by it too.
341
464
  const { runId, runOrigin } = resolveRunId(args.runId);
@@ -346,25 +469,88 @@ async function deployPlan(ctx, verb, plan, project, actor, record, under) {
346
469
  cwd: project.projectPath,
347
470
  });
348
471
  let run;
472
+ let staged;
473
+ // #4168 step 4: a wave at a time when a dependent read an upstream's plan,
474
+ // under --wave-gate, or on a resume; one run otherwise.
475
+ const inPlan = new Set(plan.items.map((i) => i.component));
476
+ const readsPlanned = bound?.plannedOutputs !== undefined && Object.keys(bound.plannedOutputs).some((d) => inPlan.has(d));
477
+ const inRounds = bound !== undefined && (readsPlanned || args.waveGate !== undefined || resume !== undefined);
349
478
  try {
350
- run = await runPromotion({
351
- plan,
352
- components: prepared,
353
- registry,
354
- componentOutputs: seededOutputs,
355
- gates: port,
356
- ...(releaseGate ? { releaseGate } : {}),
357
- ...(args.progressJson ? { onProgress: ndjsonProgressSink() } : {}),
358
- ...(events ? { onRunEvent: events.emit } : {}),
359
- });
479
+ if (inRounds && bound) {
480
+ staged = await rounds.runPromotionInRounds({
481
+ components: prepared,
482
+ outputs: { ...seededOutputs, ...(resume?.outputs ?? {}) },
483
+ binding: bound.binding,
484
+ approved: bound.doc,
485
+ reads: bound.reads,
486
+ ...(resume ? { completed: resume.completed } : {}),
487
+ ...(args.waveGate ? { waveGate: args.waveGate } : {}),
488
+ replan: bound.replan,
489
+ bind: bound.bind,
490
+ run: (components, outputs, binding) => runPromotion({
491
+ plan: { ...plan, items: plan.items.filter((i) => components.some((c) => c.name === i.component)) },
492
+ components,
493
+ registry,
494
+ componentOutputs: outputs,
495
+ gates: port,
496
+ releaseGate: binding,
497
+ ...(args.progressJson ? { onProgress: ndjsonProgressSink() } : {}),
498
+ ...(events ? { onRunEvent: events.emit } : {}),
499
+ }),
500
+ });
501
+ run = staged.run;
502
+ }
503
+ else {
504
+ run = await runPromotion({
505
+ plan,
506
+ components: prepared,
507
+ registry,
508
+ componentOutputs: seededOutputs,
509
+ gates: port,
510
+ ...(releaseGate ? { releaseGate } : {}),
511
+ ...(args.progressJson ? { onProgress: ndjsonProgressSink() } : {}),
512
+ ...(events ? { onRunEvent: events.emit } : {}),
513
+ });
514
+ }
360
515
  }
361
516
  catch (err) {
517
+ if (err instanceof PromoteStageRefused) {
518
+ events?.settle("failed", err.message);
519
+ return 1;
520
+ }
362
521
  events?.settle("failed", err instanceof Error ? err.message : String(err));
363
522
  throw err;
364
523
  }
365
524
  finally {
366
525
  tidy();
367
526
  }
527
+ if (staged && bound) {
528
+ for (const stage of staged.stages) {
529
+ const label = stage.kind === "round" ? `round ${stage.number}` : `wave ${stage.number}`;
530
+ console.error(formatInfo(`${label} (${stage.components.join(", ")}): planned again as ${stage.digest}, ${stage.status}`));
531
+ for (const line of stage.notCovered)
532
+ console.error(` not covered by ${bound.doc.digest}: ${line}`);
533
+ if (stage.message)
534
+ console.error(formatError({ message: stage.message }));
535
+ }
536
+ if (run.status === "ok") {
537
+ rmSync(resumePath, { force: true });
538
+ }
539
+ else if (staged.completed.length > 0) {
540
+ const record = {
541
+ schema: rounds.PROMOTE_RESUME_SCHEMA,
542
+ env: plan.to,
543
+ releaseDigest: under.release.releaseDigest,
544
+ approvedDigest: resume?.approvedDigest ?? bound.resumedFrom ?? bound.doc.digest,
545
+ completed: staged.completed,
546
+ outputs: staged.outputs,
547
+ at: new Date().toISOString(),
548
+ };
549
+ mkdirSync(dirname(resumePath), { recursive: true });
550
+ writeFileSync(resumePath, `${JSON.stringify(record, null, 2)}\n`);
551
+ console.error(formatInfo(`resume record: ${resumePath}; the same promote carries on from here`));
552
+ }
553
+ }
368
554
  // The driver settles the stream; this only ends one it left open.
369
555
  events?.settle(run.status === "fail" ? "failed" : run.status);
370
556
  if (!args.json)
@@ -400,7 +586,9 @@ async function deployPlan(ctx, verb, plan, project, actor, record, under) {
400
586
  }
401
587
  if (args.json) {
402
588
  const withChangeSet = under && changeSetJson ? { release: { ...releaseJson.release, ...changeSetJson } } : releaseJson;
403
- console.log(JSON.stringify({ ...planJson(plan, removed), ...withChangeSet, run, recorded, ...(outcome ? { outcome } : {}) }, null, 2));
589
+ const stagedJson = staged ? { stages: staged.stages, ...(bound?.plannedOutputs ? { plannedOutputs: bound.plannedOutputs } : {}), ...(bound?.unknownInputs ? { unknownInputs: bound.unknownInputs } : {}) } : {};
590
+ const prJson = pullRequests.length > 0 ? { pullRequests: pullRequests.map((r) => ({ commit: r.commit, source: r.source, plan: r.plan, digest: r.digest, approvedBy: r.approvedBy, ...(r.authorApproved ? { authorApproved: r.authorApproved } : {}) })) } : {};
591
+ console.log(JSON.stringify({ ...planJson(plan, removed), ...withChangeSet, ...stagedJson, ...prJson, run, recorded, ...(outcome ? { outcome } : {}) }, null, 2));
404
592
  }
405
593
  else {
406
594
  for (const r of recorded) {
@@ -526,6 +714,21 @@ async function resolveUnderRelease(ref, env, verb, fix) {
526
714
  console.error(formatError(revokedRefusal({ releaseDigest: found.release.releaseDigest, label: found.label }, revocation, await fix(found.release))));
527
715
  return 1;
528
716
  }
717
+ // #4168: under verified-releases a release deploys only once `chant release verify` passes for it.
718
+ const { securityGroupsAt } = await import("../../workspace/security.js");
719
+ if (securityGroupsAt(process.cwd()).has("verified-releases")) {
720
+ const { verifyRelease, verificationCode } = await import("../../lifecycle/release-verify.js");
721
+ const name = found.label ?? found.release.releaseDigest;
722
+ const v = await verifyRelease({ cwd: process.cwd(), ref: found.release.releaseDigest });
723
+ const code = "error" in v ? v.error.code : verificationCode(v);
724
+ if (code) {
725
+ console.error(formatError({
726
+ message: `${code}: release ${name} does not verify (${"error" in v ? v.error.message : v.reason}), and the workspace opts into verified-releases, so ${verb} deploys only a verified release`,
727
+ hint: `Check it with: chant release verify ${name}`,
728
+ }));
729
+ return 1;
730
+ }
731
+ }
529
732
  return { release: found.release, label: found.label, target: target.target };
530
733
  }
531
734
  /**
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * `chant release` (#4026, ws-104): the release is what graduates.
3
3
  *
4
- * release cut [<label>] [--yes] [--build <command>] [--param k=v]... [--params-file <file>] [--src <dir>] [-o <file>] [--lexicon <name>] [--actor <name>] [--sign [<key file>]] [--json]
4
+ * release cut [<label>] [--at <sha>] [--yes] [--build <command>] [--param k=v]... [--params-file <file>] [--src <dir>] [-o <file>] [--lexicon <name>] [--actor <name>] [--sign [<key file>]] [--json]
5
5
  * a dry run of a cut of this member from the checkout's HEAD, which must be
6
6
  * clean, on trunk and pushed, and green (#4027); `--yes` cuts, records,
7
7
  * and tags the commit `release/<label>` locally;
@@ -17,6 +17,9 @@
17
17
  * release verify <label|digest> [--json]
18
18
  * check the release's signature offline against .chant/allowed_signers
19
19
  * at the release's commit (#4049)
20
+ * release candidates [--env <env>] [--json]
21
+ * the green commits of trunk a release can be cut from, each with what it
22
+ * would ship, and what each environment runs (#4168 4.2)
20
23
  * release list [--json]
21
24
  * every release of this member with its targets, its cuts and where it runs;
22
25
  * `--json` prints the read contract's `releases` document
@@ -49,6 +52,11 @@ export declare function sameWithYes(argv: readonly string[]): string;
49
52
  * would record, build and tag, and what publishes where. With `--yes` it
50
53
  * cuts, signs with `--sign`, records the cut with its actor and run id,
51
54
  * tags the commit locally and prints the next command.
55
+ *
56
+ * `--at <sha>` (#4168 4.2) cuts that commit instead of `HEAD`, from a
57
+ * temporary worktree (../../lifecycle/release-at.ts), so the checkout need
58
+ * not be clean or at the commit. The label reaches every compile and
59
+ * artifact build as `CHANT_RELEASE_LABEL` (decision 5).
52
60
  */
53
61
  export declare function runReleaseCut(ctx: CommandContext): Promise<number>;
54
62
  /**
@@ -76,6 +84,13 @@ export declare function runReleasePublish(ctx: CommandContext): Promise<number>;
76
84
  * verifies and 1 when it does not or can't be checked.
77
85
  */
78
86
  export declare function runReleaseVerify(ctx: CommandContext): Promise<number>;
87
+ /**
88
+ * `release candidates [--env <env>] [--json]` (#4168 4.2): the green commits
89
+ * of trunk a release can be cut from, newest first, each with what it would
90
+ * ship since the previous one, and what each environment runs. `--json`
91
+ * prints the read contract's `release-candidates` document.
92
+ */
93
+ export declare function runReleaseCandidates(ctx: CommandContext): Promise<number>;
79
94
  export declare function runReleaseList(ctx: CommandContext): Promise<number>;
80
95
  /**
81
96
  * `release export <label|digest> --env <env> [-o <dir>]` (#4046): write each
@@ -1 +1 @@
1
- {"version":3,"file":"release.d.ts","sourceRoot":"","sources":["../../../src/cli/handlers/release.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAOH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAmBlD,0HAA0H;AAC1H,wBAAgB,YAAY,CAAC,GAAG,EAAE;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,uFAAuF;IACvF,YAAY,EAAE,OAAO,CAAC;CACvB,GAAG,MAAM,EAAE,CASX;AAED,wGAAwG;AACxG,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAG3D;AAiBD;;;;;;;GAOG;AACH,wBAAsB,aAAa,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAyMxE;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CA2G5E;AAQD;;;;;GAKG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAkC3E;AAED,wBAAsB,cAAc,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CA8BzE;AAED;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAyC3E;AAED,gHAAgH;AAChH,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAsB5E;AAED,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAG5E"}
1
+ {"version":3,"file":"release.d.ts","sourceRoot":"","sources":["../../../src/cli/handlers/release.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAOH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAyBlD,0HAA0H;AAC1H,wBAAgB,YAAY,CAAC,GAAG,EAAE;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,uFAAuF;IACvF,YAAY,EAAE,OAAO,CAAC;CACvB,GAAG,MAAM,EAAE,CASX;AAED,wGAAwG;AACxG,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAG3D;AAiBD;;;;;;;;;;;;GAYG;AACH,wBAAsB,aAAa,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAwNxE;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CA2G5E;AAQD;;;;;GAKG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAkC3E;AAED;;;;;GAKG;AACH,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAiC/E;AAED,wBAAsB,cAAc,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CA8BzE;AAED;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAyC3E;AAED,gHAAgH;AAChH,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAsB5E;AAED,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAG5E"}