@intentius/chant 0.124.31 → 0.124.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (509) hide show
  1. package/bin/chant +7 -2
  2. package/dist/ci-jobs.d.ts +4 -0
  3. package/dist/ci-jobs.d.ts.map +1 -1
  4. package/dist/ci-jobs.js +9 -2
  5. package/dist/cli/commands/check-lexicon-plugin.d.ts +7 -4
  6. package/dist/cli/commands/check-lexicon-plugin.d.ts.map +1 -1
  7. package/dist/cli/commands/check-lexicon-plugin.js +10 -7
  8. package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
  9. package/dist/cli/commands/check-lexicon.js +18 -12
  10. package/dist/cli/commands/import.d.ts +10 -0
  11. package/dist/cli/commands/import.d.ts.map +1 -1
  12. package/dist/cli/commands/import.js +37 -0
  13. package/dist/cli/commands/init-lexicon/templates/project.d.ts.map +1 -1
  14. package/dist/cli/commands/init-lexicon/templates/project.js +4 -3
  15. package/dist/cli/commands/onboard.d.ts.map +1 -1
  16. package/dist/cli/commands/onboard.js +8 -4
  17. package/dist/cli/commands/update.d.ts.map +1 -1
  18. package/dist/cli/commands/update.js +2 -1
  19. package/dist/cli/dependencies.d.ts +18 -0
  20. package/dist/cli/dependencies.d.ts.map +1 -0
  21. package/dist/cli/dependencies.js +120 -0
  22. package/dist/cli/handlers/build.d.ts.map +1 -1
  23. package/dist/cli/handlers/build.js +38 -4
  24. package/dist/cli/handlers/fan-out.d.ts.map +1 -1
  25. package/dist/cli/handlers/fan-out.js +12 -4
  26. package/dist/cli/handlers/graph.d.ts.map +1 -1
  27. package/dist/cli/handlers/graph.js +16 -6
  28. package/dist/cli/handlers/lifecycle-health.d.ts +16 -0
  29. package/dist/cli/handlers/lifecycle-health.d.ts.map +1 -0
  30. package/dist/cli/handlers/lifecycle-health.js +150 -0
  31. package/dist/cli/handlers/lifecycle.d.ts +26 -0
  32. package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
  33. package/dist/cli/handlers/lifecycle.js +1 -1
  34. package/dist/cli/handlers/lint.d.ts.map +1 -1
  35. package/dist/cli/handlers/lint.js +6 -0
  36. package/dist/cli/handlers/operator.d.ts +2 -2
  37. package/dist/cli/handlers/operator.js +3 -3
  38. package/dist/cli/handlers/pr.d.ts +16 -0
  39. package/dist/cli/handlers/pr.d.ts.map +1 -1
  40. package/dist/cli/handlers/pr.js +610 -32
  41. package/dist/cli/handlers/promote.d.ts.map +1 -1
  42. package/dist/cli/handlers/promote.js +19 -2
  43. package/dist/cli/handlers/release.d.ts +16 -1
  44. package/dist/cli/handlers/release.d.ts.map +1 -1
  45. package/dist/cli/handlers/release.js +83 -11
  46. package/dist/cli/handlers/run.d.ts +13 -1
  47. package/dist/cli/handlers/run.d.ts.map +1 -1
  48. package/dist/cli/handlers/run.js +38 -15
  49. package/dist/cli/main.d.ts.map +1 -1
  50. package/dist/cli/main.js +121 -33
  51. package/dist/cli/parse-args.d.ts.map +1 -1
  52. package/dist/cli/parse-args.js +37 -0
  53. package/dist/cli/registry.d.ts +29 -6
  54. package/dist/cli/registry.d.ts.map +1 -1
  55. package/dist/cli/registry.js +6 -2
  56. package/dist/codegen/generate-post-synth-barrel.js +2 -2
  57. package/dist/codegen/lexicon-regen.d.ts +4 -0
  58. package/dist/codegen/lexicon-regen.d.ts.map +1 -1
  59. package/dist/codegen/lexicon-regen.js +1 -1
  60. package/dist/codegen/pinned-upgrade.js +2 -2
  61. package/dist/components/capability.d.ts +44 -3
  62. package/dist/components/capability.d.ts.map +1 -1
  63. package/dist/components/capability.js +21 -0
  64. package/dist/components/changed-files.d.ts +7 -1
  65. package/dist/components/changed-files.d.ts.map +1 -1
  66. package/dist/components/changed-files.js +9 -1
  67. package/dist/components/deploy-inputs.d.ts +91 -0
  68. package/dist/components/deploy-inputs.d.ts.map +1 -0
  69. package/dist/components/deploy-inputs.js +184 -0
  70. package/dist/components/driver.d.ts +38 -0
  71. package/dist/components/driver.d.ts.map +1 -1
  72. package/dist/components/driver.js +56 -6
  73. package/dist/components/fan-out-record.d.ts +6 -1
  74. package/dist/components/fan-out-record.d.ts.map +1 -1
  75. package/dist/components/fan-out-record.js +24 -0
  76. package/dist/components/fan-out-run.d.ts +20 -0
  77. package/dist/components/fan-out-run.d.ts.map +1 -1
  78. package/dist/components/fan-out-run.js +21 -1
  79. package/dist/components/gate-plan.d.ts +8 -0
  80. package/dist/components/gate-plan.d.ts.map +1 -1
  81. package/dist/components/gate-plan.js +6 -0
  82. package/dist/components/pr-pipeline.d.ts +98 -12
  83. package/dist/components/pr-pipeline.d.ts.map +1 -1
  84. package/dist/components/pr-pipeline.js +146 -23
  85. package/dist/components/pr-run.d.ts +128 -30
  86. package/dist/components/pr-run.d.ts.map +1 -1
  87. package/dist/components/pr-run.js +290 -70
  88. package/dist/components/release-compile.d.ts +37 -2
  89. package/dist/components/release-compile.d.ts.map +1 -1
  90. package/dist/components/release-compile.js +64 -8
  91. package/dist/components/release-promote.d.ts +17 -3
  92. package/dist/components/release-promote.d.ts.map +1 -1
  93. package/dist/components/release-promote.js +79 -18
  94. package/dist/components/verbs/bom-writer.d.ts +1 -1
  95. package/dist/components/verbs/bom-writer.d.ts.map +1 -1
  96. package/dist/components/verbs/lockfile-sbom-generator.d.ts +20 -0
  97. package/dist/components/verbs/lockfile-sbom-generator.d.ts.map +1 -1
  98. package/dist/components/verbs/lockfile-sbom-generator.js +41 -4
  99. package/dist/components/wave-plan.d.ts +15 -2
  100. package/dist/components/wave-plan.d.ts.map +1 -1
  101. package/dist/components/wave-plan.js +44 -5
  102. package/dist/discovery/fold-import.d.ts.map +1 -1
  103. package/dist/discovery/fold-import.js +16 -6
  104. package/dist/discovery/sandbox/bundle.d.ts.map +1 -1
  105. package/dist/discovery/sandbox/bundle.js +6 -0
  106. package/dist/discovery/specifier-resolve.d.ts.map +1 -1
  107. package/dist/discovery/specifier-resolve.js +8 -2
  108. package/dist/fold/fold.d.ts.map +1 -1
  109. package/dist/fold/fold.js +68 -1
  110. package/dist/fold/subset.d.ts +37 -0
  111. package/dist/fold/subset.d.ts.map +1 -1
  112. package/dist/fold/subset.js +88 -0
  113. package/dist/graph-detail.d.ts.map +1 -1
  114. package/dist/graph-detail.js +32 -7
  115. package/dist/health.d.ts +110 -0
  116. package/dist/health.d.ts.map +1 -0
  117. package/dist/health.js +105 -0
  118. package/dist/health.schema.json +191 -0
  119. package/dist/lexicon-module.d.ts +10 -0
  120. package/dist/lexicon-module.d.ts.map +1 -1
  121. package/dist/lexicon-module.js +53 -8
  122. package/dist/lexicon-plugin-helpers.d.ts.map +1 -1
  123. package/dist/lexicon-plugin-helpers.js +6 -4
  124. package/dist/lexicon.d.ts +29 -0
  125. package/dist/lexicon.d.ts.map +1 -1
  126. package/dist/lifecycle/affected.d.ts.map +1 -1
  127. package/dist/lifecycle/affected.js +7 -1
  128. package/dist/lifecycle/merge-record.d.ts +104 -0
  129. package/dist/lifecycle/merge-record.d.ts.map +1 -0
  130. package/dist/lifecycle/merge-record.js +102 -0
  131. package/dist/lifecycle/publish-attachments.d.ts +8 -5
  132. package/dist/lifecycle/publish-attachments.d.ts.map +1 -1
  133. package/dist/lifecycle/publish-attachments.js +27 -13
  134. package/dist/lifecycle/publish-npm.d.ts +3 -2
  135. package/dist/lifecycle/publish-npm.d.ts.map +1 -1
  136. package/dist/lifecycle/publish-npm.js +4 -3
  137. package/dist/lifecycle/release-at.d.ts +40 -0
  138. package/dist/lifecycle/release-at.d.ts.map +1 -0
  139. package/dist/lifecycle/release-at.js +83 -0
  140. package/dist/lifecycle/release-cut.d.ts.map +1 -1
  141. package/dist/lifecycle/release-cut.js +23 -12
  142. package/dist/lifecycle/release-ledger.d.ts +21 -0
  143. package/dist/lifecycle/release-ledger.d.ts.map +1 -1
  144. package/dist/lifecycle/release-ledger.js +24 -0
  145. package/dist/lifecycle/release-publish.d.ts +28 -12
  146. package/dist/lifecycle/release-publish.d.ts.map +1 -1
  147. package/dist/lifecycle/release-publish.js +29 -17
  148. package/dist/lifecycle/release-store.d.ts +25 -2
  149. package/dist/lifecycle/release-store.d.ts.map +1 -1
  150. package/dist/lifecycle/release-store.js +40 -13
  151. package/dist/lifecycle/run-ledger.d.ts.map +1 -1
  152. package/dist/lifecycle/run-ledger.js +1 -0
  153. package/dist/lifecycle/work-dir.d.ts +45 -0
  154. package/dist/lifecycle/work-dir.d.ts.map +1 -0
  155. package/dist/lifecycle/work-dir.js +78 -0
  156. package/dist/lint/discover.d.ts +17 -9
  157. package/dist/lint/discover.d.ts.map +1 -1
  158. package/dist/lint/discover.js +62 -54
  159. package/dist/lint/rules/op/index.d.ts +2 -1
  160. package/dist/lint/rules/op/index.d.ts.map +1 -1
  161. package/dist/lint/rules/op/index.js +4 -2
  162. package/dist/lint/rules/op/ops018-shell-image-digest.d.ts +10 -0
  163. package/dist/lint/rules/op/ops018-shell-image-digest.d.ts.map +1 -0
  164. package/dist/lint/rules/op/ops018-shell-image-digest.js +38 -0
  165. package/dist/module-source.d.ts +57 -0
  166. package/dist/module-source.d.ts.map +1 -0
  167. package/dist/module-source.js +104 -0
  168. package/dist/op/activities/activity-contracts.d.ts +5 -0
  169. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  170. package/dist/op/activities/activity-contracts.js +3 -0
  171. package/dist/op/activities/factory.js +1 -1
  172. package/dist/op/activities/shell.d.ts +38 -0
  173. package/dist/op/activities/shell.d.ts.map +1 -1
  174. package/dist/op/activities/shell.js +89 -3
  175. package/dist/op/builders.d.ts +7 -0
  176. package/dist/op/builders.d.ts.map +1 -1
  177. package/dist/op/builders.js +7 -0
  178. package/dist/op/discover.d.ts.map +1 -1
  179. package/dist/op/discover.js +13 -3
  180. package/dist/op/gate.d.ts.map +1 -1
  181. package/dist/op/gate.js +3 -0
  182. package/dist/op/op-waves-run.d.ts.map +1 -1
  183. package/dist/op/op-waves-run.js +20 -5
  184. package/dist/op/operator.d.ts +2 -2
  185. package/dist/op/operator.js +2 -2
  186. package/dist/op/runtime.d.ts +2 -0
  187. package/dist/op/runtime.d.ts.map +1 -1
  188. package/dist/op/steward.d.ts +6 -4
  189. package/dist/op/steward.d.ts.map +1 -1
  190. package/dist/op/steward.js +5 -3
  191. package/dist/op/work-lease-run.d.ts +20 -7
  192. package/dist/op/work-lease-run.d.ts.map +1 -1
  193. package/dist/op/work-lease-run.js +56 -12
  194. package/dist/pr-forge.d.ts +10 -0
  195. package/dist/pr-forge.d.ts.map +1 -1
  196. package/dist/pr-forge.js +39 -0
  197. package/dist/pr-loop.d.ts +155 -7
  198. package/dist/pr-loop.d.ts.map +1 -1
  199. package/dist/pr-loop.js +137 -11
  200. package/dist/pr-report-url.d.ts +4 -4
  201. package/dist/pr-report-url.d.ts.map +1 -1
  202. package/dist/pr-report-url.js +5 -5
  203. package/dist/runner-start.d.ts +179 -0
  204. package/dist/runner-start.d.ts.map +1 -0
  205. package/dist/runner-start.js +277 -0
  206. package/dist/ts-loader.d.ts +38 -4
  207. package/dist/ts-loader.d.ts.map +1 -1
  208. package/dist/ts-loader.js +190 -16
  209. package/dist/wave-runtime.mjs +238 -231
  210. package/dist/workspace/chant-migrations/fountain-retired-kinds.d.ts +34 -0
  211. package/dist/workspace/chant-migrations/fountain-retired-kinds.d.ts.map +1 -0
  212. package/dist/workspace/chant-migrations/fountain-retired-kinds.js +176 -0
  213. package/dist/workspace/chant-migrations.d.ts.map +1 -1
  214. package/dist/workspace/chant-migrations.js +2 -0
  215. package/dist/workspace/check.schema.json +70 -2
  216. package/dist/workspace/checks/boxes.d.ts +6 -6
  217. package/dist/workspace/checks/boxes.js +9 -9
  218. package/dist/workspace/checks/security.d.ts +35 -0
  219. package/dist/workspace/checks/security.d.ts.map +1 -0
  220. package/dist/workspace/checks/security.js +305 -0
  221. package/dist/workspace/checks.d.ts +2 -0
  222. package/dist/workspace/checks.d.ts.map +1 -1
  223. package/dist/workspace/checks.js +5 -0
  224. package/dist/workspace/ci-builds.d.ts +163 -0
  225. package/dist/workspace/ci-builds.d.ts.map +1 -0
  226. package/dist/workspace/ci-builds.js +315 -0
  227. package/dist/workspace/ci-green-cli.d.ts +2 -1
  228. package/dist/workspace/ci-green-cli.d.ts.map +1 -1
  229. package/dist/workspace/ci-green-cli.js +25 -5
  230. package/dist/workspace/ci-green-forge.d.ts +18 -4
  231. package/dist/workspace/ci-green-forge.d.ts.map +1 -1
  232. package/dist/workspace/ci-green-forge.js +12 -0
  233. package/dist/workspace/ci-green-workflow.d.ts +55 -1
  234. package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
  235. package/dist/workspace/ci-green-workflow.js +128 -1
  236. package/dist/workspace/ci-green.d.ts +117 -16
  237. package/dist/workspace/ci-green.d.ts.map +1 -1
  238. package/dist/workspace/ci-green.js +358 -43
  239. package/dist/workspace/ci-last-green.schema.json +49 -0
  240. package/dist/workspace/ci-list.schema.json +47 -7
  241. package/dist/workspace/ci-report-url.d.ts +18 -0
  242. package/dist/workspace/ci-report-url.d.ts.map +1 -0
  243. package/dist/workspace/ci-report-url.js +33 -0
  244. package/dist/workspace/ci-run-index.d.ts +13 -1
  245. package/dist/workspace/ci-run-index.d.ts.map +1 -1
  246. package/dist/workspace/ci-run-index.js +6 -4
  247. package/dist/workspace/ci-run-index.schema.json +6 -4
  248. package/dist/workspace/ci-run.d.ts +85 -6
  249. package/dist/workspace/ci-run.d.ts.map +1 -1
  250. package/dist/workspace/ci-run.js +262 -64
  251. package/dist/workspace/ci-runner-key.d.ts +16 -3
  252. package/dist/workspace/ci-runner-key.d.ts.map +1 -1
  253. package/dist/workspace/ci-runner-key.js +30 -10
  254. package/dist/workspace/ci-runner-start.d.ts +77 -0
  255. package/dist/workspace/ci-runner-start.d.ts.map +1 -0
  256. package/dist/workspace/ci-runner-start.js +185 -0
  257. package/dist/workspace/ci-serve.d.ts +130 -11
  258. package/dist/workspace/ci-serve.d.ts.map +1 -1
  259. package/dist/workspace/ci-serve.js +323 -40
  260. package/dist/workspace/declaration.d.ts +25 -0
  261. package/dist/workspace/declaration.d.ts.map +1 -1
  262. package/dist/workspace/declaration.js +14 -2
  263. package/dist/workspace/declaration.schema.json +30 -2
  264. package/dist/workspace/export.d.ts +1 -1
  265. package/dist/workspace/export.d.ts.map +1 -1
  266. package/dist/workspace/export.js +7 -3
  267. package/dist/workspace/ls.d.ts +8 -1
  268. package/dist/workspace/ls.d.ts.map +1 -1
  269. package/dist/workspace/ls.js +2 -1
  270. package/dist/workspace/ls.schema.json +9 -2
  271. package/dist/workspace/member-pipeline.d.ts +1 -0
  272. package/dist/workspace/member-pipeline.d.ts.map +1 -1
  273. package/dist/workspace/member-pipeline.js +2 -0
  274. package/dist/workspace/merge-record.schema.json +49 -0
  275. package/dist/workspace/pin-integrity.d.ts.map +1 -1
  276. package/dist/workspace/pin-integrity.js +3 -2
  277. package/dist/workspace/pr-report.schema.json +75 -8
  278. package/dist/workspace/reason-codes.d.ts +18 -1
  279. package/dist/workspace/reason-codes.d.ts.map +1 -1
  280. package/dist/workspace/reason-codes.js +20 -2
  281. package/dist/workspace/release-candidates.d.ts +129 -0
  282. package/dist/workspace/release-candidates.d.ts.map +1 -0
  283. package/dist/workspace/release-candidates.js +233 -0
  284. package/dist/workspace/release-candidates.schema.json +353 -0
  285. package/dist/workspace/run-event.schema.json +3 -1
  286. package/dist/workspace/security.d.ts +40 -0
  287. package/dist/workspace/security.d.ts.map +1 -0
  288. package/dist/workspace/security.js +99 -0
  289. package/dist/workspace/status.schema.json +3 -1
  290. package/dist/workspace/write-scope.d.ts +9 -0
  291. package/dist/workspace/write-scope.d.ts.map +1 -1
  292. package/dist/workspace/write-scope.js +11 -1
  293. package/package.json +8 -1
  294. package/src/__fixtures__/pr-loop/pr-apply-wave-waiting.note.golden.md +48 -0
  295. package/src/__fixtures__/pr-loop/pr-plan.note.golden.md +1 -1
  296. package/src/ci-jobs.ts +11 -2
  297. package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +3 -3
  298. package/src/cli/commands/audit.test.ts +2 -2
  299. package/src/cli/commands/check-lexicon-plugin.test.ts +9 -0
  300. package/src/cli/commands/check-lexicon-plugin.ts +11 -8
  301. package/src/cli/commands/check-lexicon.test.ts +1 -1
  302. package/src/cli/commands/check-lexicon.ts +16 -11
  303. package/src/cli/commands/import.ts +40 -0
  304. package/src/cli/commands/init-lexicon/templates/project.ts +4 -3
  305. package/src/cli/commands/init-lexicon.test.ts +8 -5
  306. package/src/cli/commands/onboard.test.ts +45 -0
  307. package/src/cli/commands/onboard.ts +8 -4
  308. package/src/cli/commands/update.test.ts +6 -0
  309. package/src/cli/commands/update.ts +4 -1
  310. package/src/cli/compiled-cli.e2e.test.ts +76 -11
  311. package/src/cli/dependencies.e2e.test.ts +101 -0
  312. package/src/cli/dependencies.test.ts +93 -0
  313. package/src/cli/dependencies.ts +122 -0
  314. package/src/cli/handlers/build.test.ts +105 -0
  315. package/src/cli/handlers/build.ts +35 -4
  316. package/src/cli/handlers/fan-out.test.ts +73 -0
  317. package/src/cli/handlers/fan-out.ts +11 -4
  318. package/src/cli/handlers/graph.test.ts +76 -0
  319. package/src/cli/handlers/graph.ts +19 -12
  320. package/src/cli/handlers/lifecycle-health.test.ts +123 -0
  321. package/src/cli/handlers/lifecycle-health.ts +157 -0
  322. package/src/cli/handlers/lifecycle.ts +1 -1
  323. package/src/cli/handlers/lint.test.ts +42 -0
  324. package/src/cli/handlers/lint.ts +6 -0
  325. package/src/cli/handlers/operator.ts +3 -3
  326. package/src/cli/handlers/pr-deploy-inputs.test.ts +209 -0
  327. package/src/cli/handlers/pr-record.test.ts +201 -0
  328. package/src/cli/handlers/pr-waves.test.ts +449 -0
  329. package/src/cli/handlers/pr.test.ts +156 -0
  330. package/src/cli/handlers/pr.ts +648 -33
  331. package/src/cli/handlers/promote.test.ts +43 -1
  332. package/src/cli/handlers/promote.ts +19 -3
  333. package/src/cli/handlers/release-cut-at.e2e.test.ts +115 -0
  334. package/src/cli/handlers/release-publish-targets.e2e.test.ts +2 -2
  335. package/src/cli/handlers/release-verify.e2e.test.ts +12 -10
  336. package/src/cli/handlers/release.e2e.test.ts +19 -28
  337. package/src/cli/handlers/release.ts +81 -11
  338. package/src/cli/handlers/run-components-exit.test.ts +99 -0
  339. package/src/cli/handlers/run-signer.test.ts +79 -0
  340. package/src/cli/handlers/run.ts +34 -16
  341. package/src/cli/main.test.ts +18 -0
  342. package/src/cli/main.ts +120 -33
  343. package/src/cli/parse-args.ts +28 -0
  344. package/src/cli/registry.ts +35 -8
  345. package/src/codegen/generate-post-synth-barrel.ts +2 -2
  346. package/src/codegen/lexicon-regen.ts +1 -1
  347. package/src/codegen/pinned-upgrade.ts +2 -2
  348. package/src/codegen/post-synth-barrel-guards.test.ts +6 -11
  349. package/src/components/capability.ts +51 -3
  350. package/src/components/changed-files.test.ts +16 -1
  351. package/src/components/changed-files.ts +9 -1
  352. package/src/components/deploy-inputs.test.ts +108 -0
  353. package/src/components/deploy-inputs.ts +225 -0
  354. package/src/components/driver.test.ts +36 -1
  355. package/src/components/driver.ts +87 -4
  356. package/src/components/fan-out-record.ts +29 -1
  357. package/src/components/fan-out-run.test.ts +35 -0
  358. package/src/components/fan-out-run.ts +40 -1
  359. package/src/components/gate-plan.ts +9 -0
  360. package/src/components/pr-pipeline.test.ts +100 -0
  361. package/src/components/pr-pipeline.ts +184 -28
  362. package/src/components/pr-run.test.ts +323 -18
  363. package/src/components/pr-run.ts +355 -76
  364. package/src/components/release-compile.test.ts +34 -0
  365. package/src/components/release-compile.ts +89 -8
  366. package/src/components/release-promote.test.ts +55 -2
  367. package/src/components/release-promote.ts +93 -23
  368. package/src/components/verbs/bom-writer.ts +1 -1
  369. package/src/components/verbs/lockfile-sbom-generator.test.ts +36 -0
  370. package/src/components/verbs/lockfile-sbom-generator.ts +43 -5
  371. package/src/components/wave-plan.ts +48 -8
  372. package/src/discovery/fold-host-method.test.ts +87 -0
  373. package/src/discovery/fold-import.ts +15 -5
  374. package/src/discovery/fold-package-composite.test.ts +27 -0
  375. package/src/discovery/fold-specifiers.test.ts +8 -0
  376. package/src/discovery/sandbox/bundle.ts +6 -0
  377. package/src/discovery/specifier-resolve.ts +8 -2
  378. package/src/fold/fold.test.ts +72 -0
  379. package/src/fold/fold.ts +71 -0
  380. package/src/fold/subset.test.ts +31 -0
  381. package/src/fold/subset.ts +99 -0
  382. package/src/graph-detail.test.ts +57 -0
  383. package/src/graph-detail.ts +33 -7
  384. package/src/health.schema.json +191 -0
  385. package/src/health.test.ts +62 -0
  386. package/src/health.ts +178 -0
  387. package/src/lexicon-dist.e2e.test.ts +254 -0
  388. package/src/lexicon-module.test.ts +47 -0
  389. package/src/lexicon-module.ts +49 -7
  390. package/src/lexicon-plugin-helpers.ts +6 -4
  391. package/src/lexicon.ts +43 -1
  392. package/src/lifecycle/affected.test.ts +7 -0
  393. package/src/lifecycle/affected.ts +6 -1
  394. package/src/lifecycle/merge-record.test.ts +111 -0
  395. package/src/lifecycle/merge-record.ts +154 -0
  396. package/src/lifecycle/publish-attachments.ts +25 -13
  397. package/src/lifecycle/publish-npm.ts +4 -3
  398. package/src/lifecycle/publish-targets.test.ts +34 -4
  399. package/src/lifecycle/release-at.test.ts +48 -0
  400. package/src/lifecycle/release-at.ts +100 -0
  401. package/src/lifecycle/release-cut.test.ts +41 -0
  402. package/src/lifecycle/release-cut.ts +22 -11
  403. package/src/lifecycle/release-ledger.ts +34 -0
  404. package/src/lifecycle/release-publish.test.ts +43 -13
  405. package/src/lifecycle/release-publish.ts +38 -18
  406. package/src/lifecycle/release-store.ts +57 -13
  407. package/src/lifecycle/run-ledger.test.ts +13 -0
  408. package/src/lifecycle/run-ledger.ts +1 -0
  409. package/src/lifecycle/work-dir.test.ts +77 -0
  410. package/src/lifecycle/work-dir.ts +82 -0
  411. package/src/lint/discover.test.ts +46 -2
  412. package/src/lint/discover.ts +55 -49
  413. package/src/lint/rules/op/index.ts +4 -2
  414. package/src/lint/rules/op/ops018-shell-image-digest.test.ts +60 -0
  415. package/src/lint/rules/op/ops018-shell-image-digest.ts +48 -0
  416. package/src/module-source.test.ts +85 -0
  417. package/src/module-source.ts +112 -0
  418. package/src/op/activities/activity-contracts.ts +3 -0
  419. package/src/op/activities/factory.ts +1 -1
  420. package/src/op/activities/shell-container.e2e.test.ts +67 -0
  421. package/src/op/activities/shell.test.ts +44 -1
  422. package/src/op/activities/shell.ts +110 -3
  423. package/src/op/builders.ts +7 -0
  424. package/src/op/discover-root.test.ts +31 -2
  425. package/src/op/discover.ts +14 -4
  426. package/src/op/gate.test.ts +30 -0
  427. package/src/op/gate.ts +3 -0
  428. package/src/op/op-waves-run.ts +20 -5
  429. package/src/op/op-waves.test.ts +49 -0
  430. package/src/op/operator.ts +2 -2
  431. package/src/op/runtime.ts +2 -0
  432. package/src/op/step-console.test.ts +4 -2
  433. package/src/op/steward.ts +6 -4
  434. package/src/op/work-lease-run.test.ts +88 -2
  435. package/src/op/work-lease-run.ts +52 -12
  436. package/src/pr-forge-bundle.test.ts +2 -2
  437. package/src/pr-forge.test.ts +42 -0
  438. package/src/pr-forge.ts +40 -0
  439. package/src/pr-loop-bundle.test.ts +2 -1
  440. package/src/pr-loop.test.ts +146 -1
  441. package/src/pr-loop.ts +278 -17
  442. package/src/pr-report-url.ts +7 -7
  443. package/src/runner-start-bundle.test.ts +29 -0
  444. package/src/runner-start.test.ts +179 -0
  445. package/src/runner-start.ts +329 -0
  446. package/src/terraform/__fixtures__/advise.test.ts +21 -2
  447. package/src/ts-loader.test.ts +89 -1
  448. package/src/ts-loader.ts +180 -13
  449. package/src/workspace/changes.test.ts +1 -1
  450. package/src/workspace/chant-migrations/fountain-retired-kinds.test.ts +33 -0
  451. package/src/workspace/chant-migrations/fountain-retired-kinds.ts +190 -0
  452. package/src/workspace/chant-migrations.e2e.test.ts +5 -0
  453. package/src/workspace/chant-migrations.ts +2 -0
  454. package/src/workspace/check-contract.test.ts +3 -2
  455. package/src/workspace/check.schema.json +70 -2
  456. package/src/workspace/checks/boxes.ts +9 -9
  457. package/src/workspace/checks/security.test.ts +138 -0
  458. package/src/workspace/checks/security.ts +305 -0
  459. package/src/workspace/checks.ts +5 -0
  460. package/src/workspace/ci-builds.e2e.test.ts +208 -0
  461. package/src/workspace/ci-builds.test.ts +174 -0
  462. package/src/workspace/ci-builds.ts +385 -0
  463. package/src/workspace/ci-green-builds.test.ts +180 -0
  464. package/src/workspace/ci-green-cli.test.ts +54 -0
  465. package/src/workspace/ci-green-cli.ts +26 -5
  466. package/src/workspace/ci-green-forge.ts +30 -4
  467. package/src/workspace/ci-green-runs.test.ts +53 -11
  468. package/src/workspace/ci-green-workflow.test.ts +84 -1
  469. package/src/workspace/ci-green-workflow.ts +139 -2
  470. package/src/workspace/ci-green.test.ts +6 -3
  471. package/src/workspace/ci-green.ts +408 -48
  472. package/src/workspace/ci-last-green.schema.json +49 -0
  473. package/src/workspace/ci-list.schema.json +47 -7
  474. package/src/workspace/ci-report-url.test.ts +220 -0
  475. package/src/workspace/ci-report-url.ts +33 -0
  476. package/src/workspace/ci-run-index.schema.json +6 -4
  477. package/src/workspace/ci-run-index.test.ts +42 -3
  478. package/src/workspace/ci-run-index.ts +19 -5
  479. package/src/workspace/ci-run.test.ts +203 -8
  480. package/src/workspace/ci-run.ts +295 -63
  481. package/src/workspace/ci-runner-key.test.ts +55 -17
  482. package/src/workspace/ci-runner-key.ts +36 -11
  483. package/src/workspace/ci-runner-start.test.ts +151 -0
  484. package/src/workspace/ci-runner-start.ts +193 -0
  485. package/src/workspace/ci-serve-builds.test.ts +86 -0
  486. package/src/workspace/ci-serve.test.ts +209 -6
  487. package/src/workspace/ci-serve.ts +341 -42
  488. package/src/workspace/declaration.schema.json +30 -2
  489. package/src/workspace/declaration.test.ts +40 -0
  490. package/src/workspace/declaration.ts +40 -3
  491. package/src/workspace/export.ts +6 -3
  492. package/src/workspace/ls.schema.json +9 -2
  493. package/src/workspace/ls.ts +10 -4
  494. package/src/workspace/member-pipeline.ts +2 -1
  495. package/src/workspace/merge-record.schema.json +49 -0
  496. package/src/workspace/pin-integrity.test.ts +5 -0
  497. package/src/workspace/pin-integrity.ts +3 -2
  498. package/src/workspace/pr-report.schema.json +75 -8
  499. package/src/workspace/reason-codes.test.ts +2 -0
  500. package/src/workspace/reason-codes.ts +20 -2
  501. package/src/workspace/release-candidates.schema.json +353 -0
  502. package/src/workspace/release-candidates.test.ts +106 -0
  503. package/src/workspace/release-candidates.ts +315 -0
  504. package/src/workspace/run-event.schema.json +3 -1
  505. package/src/workspace/security.test.ts +70 -0
  506. package/src/workspace/security.ts +118 -0
  507. package/src/workspace/status.schema.json +3 -1
  508. package/src/workspace/write-scope.test.ts +29 -1
  509. package/src/workspace/write-scope.ts +12 -1
@@ -0,0 +1,101 @@
1
+ /**
2
+ * chant #4116, end to end through the real CLI: a project whose dependencies
3
+ * are not installed is told so, and a project without node_modules that builds
4
+ * today still builds.
5
+ *
6
+ * Both fixtures are temporary git repositories with no package.json install,
7
+ * run with this checkout's chant from source the way
8
+ * ../workspace/member-commands.e2e.test.ts runs it:
9
+ *
10
+ * - `bare` is a clone of a project like examples/fan-out-estate: its source
11
+ * imports `@intentius/chant-lexicon-k8s` and nothing installs it;
12
+ * - `linked` is member-commands' setup: a node_modules holding only the
13
+ * lexicon, linked to this checkout, and no `@intentius/chant`. chant loads
14
+ * the lexicon, and the lexicon resolves `@intentius/chant` from its own
15
+ * location, so it builds. The first attempt at #4116 (#4118) refused it.
16
+ */
17
+ import { execFileSync, spawnSync } from "node:child_process";
18
+ import { mkdirSync, mkdtempSync, realpathSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
19
+ import { tmpdir } from "node:os";
20
+ import { dirname, join } from "node:path";
21
+ import { afterAll, beforeAll, describe, expect, test } from "vitest";
22
+
23
+ const REPO = realpathSync(join(import.meta.dirname, "..", "..", "..", ".."));
24
+ const LOADER = join(REPO, "node_modules", "tsx", "dist", "loader.mjs");
25
+ const MAIN = join(REPO, "packages", "core", "src", "cli", "main.ts");
26
+ const TIMEOUT = 240_000;
27
+
28
+ let root: string;
29
+
30
+ function chant(args: string[], cwd: string): { status: number | null; stdout: string; stderr: string } {
31
+ const r = spawnSync(process.execPath, ["--import", `file://${LOADER}`, MAIN, ...args], {
32
+ cwd,
33
+ encoding: "utf-8",
34
+ timeout: TIMEOUT,
35
+ env: { ...process.env, TSX_DISABLE_CACHE: "1", NO_COLOR: "1" },
36
+ maxBuffer: 64 * 1024 * 1024,
37
+ });
38
+ return { status: r.status, stdout: r.stdout ?? "", stderr: r.stderr ?? "" };
39
+ }
40
+
41
+ function project(name: string): string {
42
+ const dir = join(root, name);
43
+ const write = (path: string, text: string) => {
44
+ mkdirSync(dirname(join(dir, path)), { recursive: true });
45
+ writeFileSync(join(dir, path), text);
46
+ };
47
+ write("chant.config.ts", 'export default { lexicons: ["k8s"] };\n');
48
+ write(
49
+ "src/infra.ts",
50
+ 'import { Namespace } from "@intentius/chant-lexicon-k8s";\nexport const appNs = new Namespace({ metadata: { name: "app" } });\n',
51
+ );
52
+ write(".gitignore", "node_modules\nout\n");
53
+ const git = (...args: string[]) =>
54
+ execFileSync("git", ["-c", "user.name=t", "-c", "user.email=t@example.com", "-c", "commit.gpgsign=false", ...args], { cwd: dir, stdio: "ignore" });
55
+ git("init", "-q");
56
+ git("add", "-A");
57
+ git("commit", "-qm", "fixture");
58
+ return dir;
59
+ }
60
+
61
+ let bare: string;
62
+ let linked: string;
63
+
64
+ beforeAll(() => {
65
+ root = realpathSync(mkdtempSync(join(tmpdir(), "chant-4116-e2e-")));
66
+ bare = project("bare");
67
+ linked = project("linked");
68
+ mkdirSync(join(linked, "node_modules", "@intentius"), { recursive: true });
69
+ symlinkSync(join(REPO, "lexicons", "k8s"), join(linked, "node_modules", "@intentius", "chant-lexicon-k8s"), "dir");
70
+ }, TIMEOUT);
71
+
72
+ afterAll(() => {
73
+ if (root) rmSync(root, { recursive: true, force: true });
74
+ });
75
+
76
+ describe("dependencies not installed (#4116)", () => {
77
+ test("graph says the dependencies are not installed, not that the source has lint errors", () => {
78
+ const r = chant(["graph", "--format", "ir"], bare);
79
+ expect(r.status, r.stderr).toBe(1);
80
+ expect(r.stderr).toContain(`dependencies not installed: @intentius/chant-lexicon-k8s does not resolve from ${bare}`);
81
+ expect(r.stderr).toContain("run npm install (or the project's package manager)");
82
+ expect(r.stderr).not.toMatch(/lint errors/i);
83
+ expect(r.stdout).toBe("");
84
+ }, TIMEOUT);
85
+
86
+ test("build and lint lead with the dependencies message and exit 1", () => {
87
+ for (const verb of ["build", "lint"]) {
88
+ const r = chant([verb], bare);
89
+ expect(r.status, `${verb}: ${r.stderr}`).toBe(1);
90
+ expect(r.stderr, verb).toContain("dependencies not installed: @intentius/chant-lexicon-k8s does not resolve from");
91
+ }
92
+ }, TIMEOUT);
93
+
94
+ test("a project without @intentius/chant in node_modules that builds today still builds", () => {
95
+ for (const args of [["build"], ["lint"], ["graph", "--format", "ir"]]) {
96
+ const r = chant(args, linked);
97
+ expect(r.status, `${args.join(" ")}: ${r.stderr}`).toBe(0);
98
+ expect(r.stderr, args.join(" ")).not.toContain("dependencies not installed");
99
+ }
100
+ }, TIMEOUT);
101
+ });
@@ -0,0 +1,93 @@
1
+ /**
2
+ * chant #4116: which failure messages are imports of packages the project has
3
+ * not installed. The command paths that print the message have their own tests
4
+ * (handlers/graph.test.ts, handlers/lint.test.ts, dependencies.e2e.test.ts).
5
+ */
6
+ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
7
+ import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
8
+ import { tmpdir } from "node:os";
9
+ import { join } from "node:path";
10
+ import { reportUninstalledDependencies, uninstalledDependencyIn } from "./dependencies";
11
+
12
+ let root: string;
13
+ let project: string;
14
+
15
+ const notFound = (pkg: string, file = "src/a.ts") => `Cannot find package '${pkg}' imported from ${join(project, file)}`;
16
+
17
+ beforeEach(() => {
18
+ root = realpathSync(mkdtempSync(join(tmpdir(), "chant-4116-")));
19
+ project = join(root, "proj");
20
+ mkdirSync(join(project, "src"), { recursive: true });
21
+ });
22
+
23
+ afterEach(() => {
24
+ vi.restoreAllMocks();
25
+ rmSync(root, { recursive: true, force: true });
26
+ });
27
+
28
+ describe("uninstalledDependencyIn", () => {
29
+ test("an @intentius package no node_modules holds, as lint and build report it", () => {
30
+ expect(uninstalledDependencyIn(notFound("@intentius/chant-lexicon-aws"), project)).toEqual({
31
+ package: "@intentius/chant-lexicon-aws",
32
+ dir: project,
33
+ });
34
+ expect(uninstalledDependencyIn(`Component discovery error: ${notFound("@intentius/chant", "a.component.ts")}`, project)).toEqual({
35
+ package: "@intentius/chant",
36
+ dir: project,
37
+ });
38
+ });
39
+
40
+ test("a subpath import names its package", () => {
41
+ expect(uninstalledDependencyIn("Cannot find module '@intentius/chant/params' imported from /x/a.ts", project)?.package).toBe("@intentius/chant");
42
+ });
43
+
44
+ test("a package found in a parent node_modules is installed", () => {
45
+ mkdirSync(join(root, "node_modules", "@intentius", "chant-lexicon-aws"), { recursive: true });
46
+ expect(uninstalledDependencyIn(notFound("@intentius/chant-lexicon-aws"), project)).toBeUndefined();
47
+ });
48
+
49
+ test("a package found from the importing file's directory is installed", () => {
50
+ const lexicon = join(root, "elsewhere", "lexicon");
51
+ mkdirSync(join(root, "elsewhere", "node_modules", "@intentius", "chant"), { recursive: true });
52
+ mkdirSync(lexicon, { recursive: true });
53
+ expect(uninstalledDependencyIn(`Cannot find package '@intentius/chant' imported from ${join(lexicon, "index.ts")}`, project)).toBeUndefined();
54
+ });
55
+
56
+ test("another package counts only when the project's package.json declares it", () => {
57
+ expect(uninstalledDependencyIn(notFound("lodahs"), project)).toBeUndefined();
58
+ writeFileSync(join(project, "package.json"), JSON.stringify({ dependencies: { yaml: "*" } }));
59
+ expect(uninstalledDependencyIn(notFound("yaml"), project)).toEqual({ package: "yaml", dir: project });
60
+ expect(uninstalledDependencyIn(notFound("lodahs"), project)).toBeUndefined();
61
+ });
62
+
63
+ test("relative imports and other messages are not dependencies", () => {
64
+ expect(uninstalledDependencyIn("Cannot find module './missing' imported from /x/a.ts", project)).toBeUndefined();
65
+ expect(uninstalledDependencyIn("Exported declarable 'x' is never referenced", project)).toBeUndefined();
66
+ });
67
+ });
68
+
69
+ describe("reportUninstalledDependencies", () => {
70
+ let stderr: string[];
71
+ beforeEach(() => {
72
+ stderr = [];
73
+ vi.spyOn(console, "error").mockImplementation((s: string) => void stderr.push(s));
74
+ });
75
+
76
+ test("names the first uninstalled package with the install hint; all when every message is one", () => {
77
+ expect(reportUninstalledDependencies([notFound("@intentius/chant-lexicon-aws"), notFound("@intentius/chant")], project)).toBe("all");
78
+ expect(stderr).toHaveLength(1);
79
+ expect(stderr[0]).toContain(`dependencies not installed: @intentius/chant-lexicon-aws does not resolve from ${project}`);
80
+ expect(stderr[0]).toContain("run npm install (or the project's package manager)");
81
+ });
82
+
83
+ test("some when other errors are beside them", () => {
84
+ expect(reportUninstalledDependencies([notFound("@intentius/chant"), "a real problem"], project)).toBe("some");
85
+ expect(stderr).toHaveLength(1);
86
+ });
87
+
88
+ test("prints nothing when no message is one", () => {
89
+ expect(reportUninstalledDependencies(["a real problem"], project)).toBeUndefined();
90
+ expect(reportUninstalledDependencies([], project)).toBeUndefined();
91
+ expect(stderr).toEqual([]);
92
+ });
93
+ });
@@ -0,0 +1,122 @@
1
+ /**
2
+ * chant #4116: a failure caused by a project whose dependencies are not installed.
3
+ *
4
+ * In a clone with no node_modules, every project file that imports
5
+ * `@intentius/chant` or a lexicon package fails to load. Lint reports each one
6
+ * (LEX002, COMP000), the graph's lint gate then says the source has lint
7
+ * errors, and build prints node's own "Cannot find package" per file. The
8
+ * source is fine; the install is missing.
9
+ *
10
+ * This runs only after something has already failed, on that failure's
11
+ * messages. A project chant builds today never reaches it, whatever its
12
+ * node_modules looks like: chant resolves a lexicon from its own install first,
13
+ * and a project that imports only what it has installed loads fine. That is why
14
+ * the check is not made up front (#4118, reverted in #4125).
15
+ *
16
+ * A message counts when it is node's "Cannot find package|module '<spec>'",
17
+ * `<spec>` names a package that is `@intentius/*` or declared in the project's
18
+ * package.json, and no node_modules from the project directory up holds it. A
19
+ * typo in an import of an undeclared package keeps its message as it is.
20
+ */
21
+ import { existsSync, readFileSync } from "node:fs";
22
+ import { dirname, isAbsolute, join, resolve } from "node:path";
23
+ import { fileURLToPath } from "node:url";
24
+ import { packageNameOf } from "../lexicon-module";
25
+ import { formatError } from "./format";
26
+
27
+ /** A package that a failure could not import and that is not installed for the project. */
28
+ export interface UninstalledDependency {
29
+ package: string;
30
+ /** The project directory it does not resolve from. */
31
+ dir: string;
32
+ }
33
+
34
+ const NOT_FOUND = /Cannot find (?:package|module) '([^']+)'(?: imported from (\S+))?/g;
35
+
36
+ /** True when a node_modules at `dir` or above holds `pkg`. */
37
+ function installedFrom(pkg: string, dir: string): boolean {
38
+ for (let d = resolve(dir); ; d = dirname(d)) {
39
+ if (existsSync(join(d, "node_modules", ...pkg.split("/")))) return true;
40
+ if (dirname(d) === d) return false;
41
+ }
42
+ }
43
+
44
+ /** The packages the nearest package.json at `dir` or above declares, in any dependency field. */
45
+ function declaredPackages(dir: string): Set<string> {
46
+ for (let d = resolve(dir); ; d = dirname(d)) {
47
+ const file = join(d, "package.json");
48
+ if (existsSync(file)) {
49
+ try {
50
+ const json = JSON.parse(readFileSync(file, "utf-8")) as Record<string, unknown>;
51
+ const names = new Set<string>();
52
+ for (const field of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
53
+ const deps = json[field];
54
+ if (deps && typeof deps === "object") for (const name of Object.keys(deps)) names.add(name);
55
+ }
56
+ return names;
57
+ } catch {
58
+ return new Set();
59
+ }
60
+ }
61
+ if (dirname(d) === d) return new Set();
62
+ }
63
+ }
64
+
65
+ function importerDir(importer: string | undefined): string | undefined {
66
+ if (!importer) return undefined;
67
+ try {
68
+ const path = importer.startsWith("file:") ? fileURLToPath(importer) : importer;
69
+ return isAbsolute(path) ? dirname(path) : undefined;
70
+ } catch {
71
+ return undefined;
72
+ }
73
+ }
74
+
75
+ /** The uninstalled package `message` reports, or `undefined` when it reports none. */
76
+ export function uninstalledDependencyIn(message: string, projectDir: string): UninstalledDependency | undefined {
77
+ const dir = resolve(projectDir);
78
+ let declared: Set<string> | undefined;
79
+ for (const match of message.matchAll(NOT_FOUND)) {
80
+ const pkg = packageNameOf(match[1]);
81
+ if (pkg === undefined) continue;
82
+ if (!pkg.startsWith("@intentius/")) {
83
+ declared ??= declaredPackages(dir);
84
+ if (!declared.has(pkg)) continue;
85
+ }
86
+ if (installedFrom(pkg, dir)) continue;
87
+ const from = importerDir(match[2]);
88
+ if (from !== undefined && installedFrom(pkg, from)) continue;
89
+ return { package: pkg, dir };
90
+ }
91
+ return undefined;
92
+ }
93
+
94
+ /** The dependencies message, formatted as an error with the install hint. */
95
+ export function formatDependenciesNotInstalled(dep: UninstalledDependency): string {
96
+ return formatError({
97
+ message: `dependencies not installed: ${dep.package} does not resolve from ${dep.dir}`,
98
+ hint: "run npm install (or the project's package manager)",
99
+ });
100
+ }
101
+
102
+ /**
103
+ * Print the dependencies message, naming the first uninstalled package, when
104
+ * any of a failure's `messages` reports one. Prints nothing and returns
105
+ * `undefined` when none does. Returns `"all"` when every message reports one,
106
+ * so a caller can drop its own wording when that wording blames the source.
107
+ */
108
+ export function reportUninstalledDependencies(
109
+ messages: readonly string[],
110
+ projectDir: string,
111
+ ): "all" | "some" | undefined {
112
+ let first: UninstalledDependency | undefined;
113
+ let all = messages.length > 0;
114
+ for (const message of messages) {
115
+ const dep = uninstalledDependencyIn(message, projectDir);
116
+ if (dep) first ??= dep;
117
+ else all = false;
118
+ }
119
+ if (!first) return undefined;
120
+ console.error(formatDependenciesNotInstalled(first));
121
+ return all ? "all" : "some";
122
+ }
@@ -148,6 +148,111 @@ describe("runBuild --components --generate (chant #1108 build-time parameters)",
148
148
  vi.restoreAllMocks();
149
149
  });
150
150
 
151
+ test("--pr-loop asks for --require-review only under --require-review or the review-rules security group (#4168)", async () => {
152
+ const { mkdtempSync, mkdirSync, writeFileSync, rmSync } = await import("node:fs");
153
+ const { tmpdir } = await import("node:os");
154
+ const { join } = await import("node:path");
155
+ const { execFileSync } = await import("node:child_process");
156
+ const root = mkdtempSync(join(tmpdir(), "chant-build-review-rules-"));
157
+ try {
158
+ execFileSync("git", ["init", "-q"], { cwd: root });
159
+ mkdirSync(join(root, "app"));
160
+ const declare = (extra: Record<string, unknown>) =>
161
+ writeFileSync(join(root, "chant.workspace.json"), JSON.stringify({ name: "acme", schema: 1, ...extra, members: [{ name: "app", dir: "app", kind: "chant", because: "the app" }] }));
162
+ generateComponentsPipelineMock.mockResolvedValue({ success: true, yaml: "jobs: {}", stages: [], jobs: [] });
163
+ vi.spyOn(console, "log").mockImplementation(() => {});
164
+ const path = join(root, "app");
165
+ const output = join(root, "out.yml");
166
+
167
+ declare({});
168
+ expect(await runBuild({ args: makeArgs({ path, output, generate: "github", prLoop: true }), plugins: [], serializers: [] })).toBe(0);
169
+ expect(generateComponentsPipelineMock.mock.calls[0][2].prLoop).toEqual({});
170
+ expect(await runBuild({ args: makeArgs({ path, output, generate: "github", prLoop: true, requireReview: true }), plugins: [], serializers: [] })).toBe(0);
171
+ expect(generateComponentsPipelineMock.mock.calls[1][2].prLoop).toEqual({ requireReview: true });
172
+
173
+ declare({ security: ["review-rules"] });
174
+ expect(await runBuild({ args: makeArgs({ path, output, generate: "github", prLoop: true }), plugins: [], serializers: [] })).toBe(0);
175
+ expect(generateComponentsPipelineMock.mock.calls[2][2].prLoop).toEqual({ requireReview: true });
176
+
177
+ // #4168 step 7: a member's own group counts, and isolated-runners asks for --require-runner-start.
178
+ writeFileSync(join(root, "chant.workspace.json"), JSON.stringify({ name: "acme", schema: 1, members: [{ name: "app", dir: "app", kind: "chant", because: "the app", security: ["isolated-runners"] }] }));
179
+ expect(await runBuild({ args: makeArgs({ path, output, generate: "github", prLoop: true }), plugins: [], serializers: [] })).toBe(0);
180
+ expect(generateComponentsPipelineMock.mock.calls[3][2].prLoop).toEqual({ requireRunnerStart: true });
181
+ } finally {
182
+ rmSync(root, { recursive: true, force: true });
183
+ vi.restoreAllMocks();
184
+ }
185
+ });
186
+
187
+ test("--pr-loop without --report-url bakes the workspace's ci.reportUrl into the jobs, and the flag wins (#4160)", async () => {
188
+ const { mkdtempSync, mkdirSync, writeFileSync, rmSync } = await import("node:fs");
189
+ const { tmpdir } = await import("node:os");
190
+ const { join } = await import("node:path");
191
+ const { execFileSync } = await import("node:child_process");
192
+ const root = mkdtempSync(join(tmpdir(), "chant-build-report-url-"));
193
+ try {
194
+ execFileSync("git", ["init", "-q"], { cwd: root });
195
+ mkdirSync(join(root, "app"));
196
+ writeFileSync(
197
+ join(root, "chant.workspace.json"),
198
+ JSON.stringify({ name: "acme", schema: 1, ci: { reportUrl: "s3://acme-reports/prod/" }, members: [{ name: "app", dir: "app", kind: "chant", because: "the app" }] }),
199
+ );
200
+ generateComponentsPipelineMock.mockResolvedValue({ success: true, yaml: "jobs: {}", stages: [], jobs: [] });
201
+ vi.spyOn(console, "log").mockImplementation(() => {});
202
+ const path = join(root, "app");
203
+ const output = join(root, "out.yml");
204
+
205
+ expect(await runBuild({ args: makeArgs({ path, output, generate: "github", prLoop: true }), plugins: [], serializers: [] })).toBe(0);
206
+ expect(generateComponentsPipelineMock.mock.calls[0][2].prLoop).toEqual({ reportUrl: "s3://acme-reports/prod" });
207
+
208
+ expect(await runBuild({ args: makeArgs({ path, output, generate: "github", prLoop: true, reportUrl: "file:///srv/reports" }), plugins: [], serializers: [] })).toBe(0);
209
+ expect(generateComponentsPipelineMock.mock.calls[1][2].prLoop).toEqual({ reportUrl: "file:///srv/reports" });
210
+
211
+ // Without --pr-loop the declaration's URL is not read and nothing changes.
212
+ expect(await runBuild({ args: makeArgs({ path, output, generate: "github" }), plugins: [], serializers: [] })).toBe(0);
213
+ expect(generateComponentsPipelineMock.mock.calls[2][2]).not.toHaveProperty("prLoop");
214
+ } finally {
215
+ rmSync(root, { recursive: true, force: true });
216
+ vi.restoreAllMocks();
217
+ }
218
+ });
219
+
220
+ test("--pr-loop outside any workspace has no report URL unless the flag gives one (#4160)", async () => {
221
+ const { mkdtempSync, rmSync } = await import("node:fs");
222
+ const { tmpdir } = await import("node:os");
223
+ const { join } = await import("node:path");
224
+ const plain = mkdtempSync(join(tmpdir(), "chant-build-plain-"));
225
+ try {
226
+ generateComponentsPipelineMock.mockResolvedValue({ success: true, yaml: "jobs: {}", stages: [], jobs: [] });
227
+ vi.spyOn(console, "log").mockImplementation(() => {});
228
+ expect(await runBuild({ args: makeArgs({ path: plain, generate: "github", prLoop: true }), plugins: [], serializers: [] })).toBe(0);
229
+ expect(generateComponentsPipelineMock).toHaveBeenCalledWith(plain, "github", { env: undefined, prLoop: {} }, undefined, []);
230
+ } finally {
231
+ rmSync(plain, { recursive: true, force: true });
232
+ vi.restoreAllMocks();
233
+ }
234
+ });
235
+
236
+ test("an invalid ci.reportUrl stops --pr-loop with its reason code, and generates nothing (#4160)", async () => {
237
+ const { mkdtempSync, mkdirSync, writeFileSync, rmSync } = await import("node:fs");
238
+ const { tmpdir } = await import("node:os");
239
+ const { join } = await import("node:path");
240
+ const { execFileSync } = await import("node:child_process");
241
+ const root = mkdtempSync(join(tmpdir(), "chant-build-report-url-bad-"));
242
+ try {
243
+ execFileSync("git", ["init", "-q"], { cwd: root });
244
+ mkdirSync(join(root, "app"));
245
+ writeFileSync(join(root, "chant.workspace.json"), JSON.stringify({ name: "acme", schema: 1, ci: { reportUrl: "https://reports.example" }, members: [{ name: "app", dir: "app", kind: "chant", because: "the app" }] }));
246
+ const stderr = makeStderrSpy();
247
+ expect(await runBuild({ args: makeArgs({ path: join(root, "app"), generate: "github", prLoop: true }), plugins: [], serializers: [] })).toBe(1);
248
+ expect(generateComponentsPipelineMock).not.toHaveBeenCalled();
249
+ expect(stderr.join("\n")).toMatch(/ci\.reportUrl https:\/\/reports\.example: .*\[declaration-invalid\]/);
250
+ } finally {
251
+ rmSync(root, { recursive: true, force: true });
252
+ vi.restoreAllMocks();
253
+ }
254
+ });
255
+
151
256
  test("--pr-build and --pr-build-command become the prLoop build options (#4088)", async () => {
152
257
  generateComponentsPipelineMock.mockResolvedValue({ success: true, yaml: "jobs: {}", stages: [], jobs: [] });
153
258
  vi.spyOn(console, "log").mockImplementation(() => {});
@@ -2,6 +2,7 @@ import { writeFileSync, mkdirSync } from "node:fs";
2
2
  import { dirname, resolve } from "node:path";
3
3
  import { buildCommand, buildCommandWatch, printErrors, printWarnings, resolveBuildFormat } from "../commands/build";
4
4
  import { formatError, formatInfo, formatSuccess, formatBold } from "../format";
5
+ import { reportUninstalledDependencies } from "../dependencies";
5
6
  import type { CommandContext } from "../registry";
6
7
  import { generateComponentsPipeline } from "../../components/cli-support";
7
8
  import { loadChantConfigUpward, type ChantConfig } from "../../config";
@@ -15,15 +16,23 @@ import type { PrLoopPipelineOptions } from "../../components/pr-pipeline";
15
16
  * and the branch, and with `--events-json` and `--report-url` (#4007) the
16
17
  * flags the generated jobs pass to pr-plan and pr-apply. `--pr-build` and
17
18
  * `--pr-build-command` (#4088) add the build and lint steps before both.
19
+ * `reportUrl` is `--report-url`, else the workspace's `ci.reportUrl` (#4160).
20
+ * `requireReview` is `--require-review`, or a workspace that opts into the
21
+ * `review-rules` security group (#4168); off otherwise. `requireRunnerStart`
22
+ * is `--require-runner-start`, or the `isolated-runners` group. `--apply-on-merge`
23
+ * (#4168) writes the apply job on merge in place of the record job.
18
24
  */
19
- function prLoopOption(args: CommandContext["args"]): PrLoopPipelineOptions {
25
+ function prLoopOption(args: CommandContext["args"], reportUrl: string | undefined, requireReview = false, requireRunnerStart = false): PrLoopPipelineOptions {
20
26
  return {
21
27
  ...(args.gate ? { gate: args.gate } : {}),
22
28
  ...(args.branch ? { branch: args.branch } : {}),
23
29
  ...(args.eventsJson ? { events: true } : {}),
24
- ...(args.reportUrl ? { reportUrl: args.reportUrl } : {}),
30
+ ...(reportUrl ? { reportUrl } : {}),
25
31
  ...(args.prBuild ? { build: true } : {}),
26
32
  ...(args.prBuildCommand ? { buildCommand: args.prBuildCommand } : {}),
33
+ ...(args.requireReview === true || requireReview ? { requireReview: true } : {}),
34
+ ...(args.requireRunnerStart === true || requireRunnerStart ? { requireRunnerStart: true } : {}),
35
+ ...(args.applyOnMerge ? { applyOnMerge: true } : {}),
27
36
  };
28
37
  }
29
38
 
@@ -90,6 +99,24 @@ async function runGenerateComponents(ctx: CommandContext): Promise<number> {
90
99
  const found = findWorkspaceRoot(resolve(args.path));
91
100
  let plan: import("../../workspace/member-pipeline").MemberComponentPlan | undefined;
92
101
  let memberPipeline: typeof import("../../workspace/member-pipeline") | undefined;
102
+ // #4160: without --report-url, the declaration's ci.reportUrl. Read only inside a workspace.
103
+ let reportUrl = args.reportUrl;
104
+ if (found && args.prLoop && !reportUrl) {
105
+ try {
106
+ reportUrl = await (await import("../../workspace/ci-report-url")).declaredReportUrl(resolve(args.path));
107
+ } catch (err) {
108
+ console.error(formatError({ message: err instanceof Error ? err.message : String(err) }));
109
+ return 1;
110
+ }
111
+ }
112
+ // #4168: the review-rules group turns on --require-review in the generated apply. Read only inside a workspace.
113
+ let reviewRules = false;
114
+ let isolatedRunners = false;
115
+ if (found && args.prLoop) {
116
+ const groups = (await import("../../workspace/security")).securityGroupsAt(resolve(args.path));
117
+ reviewRules = groups.has("review-rules");
118
+ isolatedRunners = groups.has("isolated-runners");
119
+ }
93
120
  if (found) {
94
121
  memberPipeline = await import("../../workspace/member-pipeline");
95
122
  try {
@@ -104,7 +131,7 @@ async function runGenerateComponents(ctx: CommandContext): Promise<number> {
104
131
  params: args.param,
105
132
  paramsFile: args.paramsFile,
106
133
  ...(args.waveGate ? { waveGate: { gate: args.waveGate, ...(args.canary?.length ? { canary: args.canary } : {}), ...(args.base ? { base: args.base } : {}) } } : {}),
107
- ...(args.prLoop ? { prLoop: prLoopOption(args) } : {}),
134
+ ...(args.prLoop ? { prLoop: prLoopOption(args, reportUrl, reviewRules, isolatedRunners) } : {}),
108
135
  ...(args.image ? { image: args.image } : {}),
109
136
  ...(args.beforeScript?.length ? { beforeScript: args.beforeScript } : {}),
110
137
  });
@@ -134,7 +161,7 @@ async function runGenerateComponents(ctx: CommandContext): Promise<number> {
134
161
  ? { gatedWaves: { gate: args.waveGate, ...(args.canary?.length ? { canary: args.canary } : {}), ...(args.base ? { base: args.base } : {}) } }
135
162
  : {}),
136
163
  // #3183: a plan job per pull request and an apply job per merge.
137
- ...(args.prLoop ? { prLoop: prLoopOption(args) } : {}),
164
+ ...(args.prLoop ? { prLoop: prLoopOption(args, reportUrl, reviewRules, isolatedRunners) } : {}),
138
165
  // #4002: the job image and the lines that install what it lacks, chant itself included.
139
166
  ...(args.image ? { image: args.image } : {}),
140
167
  ...(args.beforeScript?.length ? { beforeScript: args.beforeScript } : {}),
@@ -284,6 +311,8 @@ export async function runBuild(ctx: CommandContext): Promise<number> {
284
311
  const own = result.warnings.filter((w) => !w.includes("No serializer found for lexicon") && !seenWarnings.has(w));
285
312
  for (const w of own) seenWarnings.add(w);
286
313
  printWarnings(own);
314
+ // #4116: once, before the first failing group's errors.
315
+ if (!result.success && !failed) reportUninstalledDependencies(result.errors, resolve(args.path));
287
316
  printErrors(result.errors);
288
317
  if (!result.success) failed = true;
289
318
  }
@@ -333,6 +362,8 @@ export async function runBuild(ctx: CommandContext): Promise<number> {
333
362
  warnings = warnings.filter((w) => !w.includes('No serializer found for lexicon'));
334
363
  }
335
364
  printWarnings(warnings);
365
+ // #4116: errors that are imports of uninstalled packages are led by the dependencies message.
366
+ if (!result.success) reportUninstalledDependencies(result.errors, resolve(args.path));
336
367
  printErrors(result.errors);
337
368
 
338
369
  return result.success ? 0 : 1;
@@ -78,6 +78,18 @@ const ESTATE = [
78
78
 
79
79
  let ran: string[] = [];
80
80
  let failing: string[] = [];
81
+ /** The error a component's step throws, in place of applying (#3997). */
82
+ let refusal: Record<string, Error> = {};
83
+
84
+ /** A step's refusal of a plan that moved since it was approved, marked `planMoved` as cfn-deploy marks it. */
85
+ function planMovedError(component: string): Error {
86
+ return Object.assign(new Error(`${component}: refusing to apply, the plan moved`), { planMoved: true });
87
+ }
88
+
89
+ /** A step error carrying an `exitCode`, as kubectl-apply's carried one before #3997's follow-up. */
90
+ function exitCodeError(component: string, exitCode: number): Error {
91
+ return Object.assign(new Error(`${component}: refused`), { exitCode });
92
+ }
81
93
  /** What each component's step was handed, after wiring resolved. */
82
94
  let inputs: Record<string, Record<string, unknown>> = {};
83
95
  /** Stack outputs a component's step returns, as cfn-deploy's `outputs` does. */
@@ -118,6 +130,7 @@ beforeEach(() => {
118
130
  vi.clearAllMocks();
119
131
  ran = [];
120
132
  failing = [];
133
+ refusal = {};
121
134
  inputs = {};
122
135
  stepOutputs = {};
123
136
  onStart = () => {};
@@ -155,6 +168,7 @@ beforeEach(() => {
155
168
  onStart(runCtx.component);
156
169
  ran.push(runCtx.component);
157
170
  inputs[runCtx.component] = input;
171
+ if (refusal[runCtx.component]) throw refusal[runCtx.component];
158
172
  if (failing.includes(runCtx.component)) throw new Error(`${runCtx.component} failed`);
159
173
  return { ok: true, ...(stepOutputs[runCtx.component] ? { outputs: stepOutputs[runCtx.component] } : {}) };
160
174
  },
@@ -310,6 +324,31 @@ describe("proof: a mid-run failure blocks its subtree and leaves the independent
310
324
  });
311
325
  });
312
326
 
327
+ describe("a step that refuses its plan because the plan moved exits 3 (#3997)", () => {
328
+ test("marked planMoved: exit 3, and its subtree is blocked as for any failure", async () => {
329
+ refusal = { "cluster-a": planMovedError("cluster-a") };
330
+ expect(await runComponentsFanOut(ctx({ base: "main", env: "test" }))).toBe(3);
331
+ expect(ran).not.toContain("app-one");
332
+ expect(ran).toContain("app-three");
333
+ });
334
+
335
+ test("an exitCode of 3 on the step's error is the same mark", async () => {
336
+ refusal = { "cluster-a": exitCodeError("cluster-a", 3) };
337
+ expect(await runComponentsFanOut(ctx({ base: "main", env: "test" }))).toBe(3);
338
+ });
339
+
340
+ test("any other exitCode is ignored, and the failure exits 1", async () => {
341
+ refusal = { "cluster-a": exitCodeError("cluster-a", 7) };
342
+ expect(await runComponentsFanOut(ctx({ base: "main", env: "test" }))).toBe(1);
343
+ });
344
+
345
+ test("one moved plan beside another failure still exits 3", async () => {
346
+ refusal = { "cluster-a": planMovedError("cluster-a") };
347
+ failing = ["cluster-b"];
348
+ expect(await runComponentsFanOut(ctx({ base: "main", env: "test" }))).toBe(3);
349
+ });
350
+ });
351
+
313
352
  describe("proof: the re-run prints already-applied for what it skips, and finishes", () => {
314
353
  test("repeating the identical command finishes the fan-out, and the approval is not re-asked", async () => {
315
354
  const dir = await mkdtemp(join(tmpdir(), "fan-out-"));
@@ -523,6 +562,7 @@ describe("gated waves (#3049): one gate per wave, bound to the wave's set digest
523
562
  },
524
563
  async run(runCtx: { component: string }) {
525
564
  ran.push(runCtx.component);
565
+ if (refusal[runCtx.component]) throw refusal[runCtx.component];
526
566
  return { ok: true };
527
567
  },
528
568
  } as never);
@@ -530,6 +570,39 @@ describe("gated waves (#3049): one gate per wave, bound to the wave's set digest
530
570
  });
531
571
  });
532
572
 
573
+ /** Run the gated fan-out once to record wave 1's pending fact, then approve that digest. */
574
+ async function approveWave1(run: () => Promise<number>): Promise<void> {
575
+ await run();
576
+ const first = gateLedger.appended[0];
577
+ gateLedger = memoryGateLedgerPort({
578
+ pending: [first],
579
+ resolutions: [
580
+ {
581
+ version: 1,
582
+ kind: "resolution",
583
+ op: "fan-out",
584
+ gate: "release-wave-1",
585
+ resolvedBy: "operator",
586
+ timestamp: new Date(Date.parse(first.timestamp) + 1000).toISOString(),
587
+ planDigest: first.planDigest,
588
+ },
589
+ ],
590
+ });
591
+ }
592
+
593
+ test.each([
594
+ ["marked planMoved", 3, () => planMovedError("net")],
595
+ ["with exitCode 3", 3, () => exitCodeError("net", 3)],
596
+ ["with exitCode 7", 1, () => exitCodeError("net", 7)],
597
+ ["unmarked", 1, () => new Error("net failed")],
598
+ ])("an approved wave whose step fails with an error %s exits %i (#3997)", async (_label, code, error) => {
599
+ const run = () => runComponentsFanOut(ctx({ base: "main", env: "test", waveGate: "release" }));
600
+ await approveWave1(run);
601
+ refusal = { net: error() };
602
+ expect(await run()).toBe(code);
603
+ expect(ran).toEqual(["net"]);
604
+ });
605
+
533
606
  test("wave 1 stops at its own gate, and the record names its digest", async () => {
534
607
  const dir = await mkdtemp(join(tmpdir(), "fan-out-waves-"));
535
608
  try {