@intentius/chant 0.124.46 → 0.124.49

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 (726) hide show
  1. package/dist/__golden__/wave-loop/op-by-apply-after-fails.json +10 -0
  2. package/dist/__golden__/wave-loop/op-by-apply-hold.json +10 -0
  3. package/dist/__golden__/wave-loop/op-by-apply-ignored.json +10 -0
  4. package/dist/__golden__/wave-loop/op-by-apply-pass.json +10 -0
  5. package/dist/__golden__/wave-loop/op-by-apply-skip.json +10 -0
  6. package/dist/__golden__/wave-loop/op-cost-member-clash.json +6 -0
  7. package/dist/__golden__/wave-loop/op-cost-member.json +13 -0
  8. package/dist/__golden__/wave-loop/op-denied-source.json +16 -0
  9. package/dist/__golden__/wave-loop/op-denied.json +16 -0
  10. package/dist/__golden__/wave-loop/op-discover-rest-short.json +6 -0
  11. package/dist/__golden__/wave-loop/op-discover-rest-stops.json +13 -0
  12. package/dist/__golden__/wave-loop/op-discover-rest.json +26 -0
  13. package/dist/__golden__/wave-loop/op-discover-wide.json +26 -0
  14. package/dist/__golden__/wave-loop/op-held-on-again.json +16 -0
  15. package/dist/__golden__/wave-loop/op-held-on-before-apply.json +16 -0
  16. package/dist/__golden__/wave-loop/op-held-on.json +16 -0
  17. package/dist/__golden__/wave-loop/op-hooks-apply-fails.json +13 -0
  18. package/dist/__golden__/wave-loop/op-hooks-fail-after-apply.json +13 -0
  19. package/dist/__golden__/wave-loop/op-hooks-fail-before-apply.json +13 -0
  20. package/dist/__golden__/wave-loop/op-hooks-fail-before-plan.json +13 -0
  21. package/dist/__golden__/wave-loop/op-hooks-held.json +13 -0
  22. package/dist/__golden__/wave-loop/op-hooks-hold-before-apply.json +13 -0
  23. package/dist/__golden__/wave-loop/op-hooks-pass.json +13 -0
  24. package/dist/__golden__/wave-loop/op-hooks-refused-at-base.json +13 -0
  25. package/dist/__golden__/wave-loop/op-hooks-share.json +33 -0
  26. package/dist/__golden__/wave-loop/op-outcome-held.json +10 -0
  27. package/dist/__golden__/wave-loop/op-outcome-replan-approved.json +10 -0
  28. package/dist/__golden__/wave-loop/op-outcome-replan-forever.json +10 -0
  29. package/dist/__golden__/wave-loop/op-outcome-replan.json +10 -0
  30. package/dist/__golden__/wave-loop/op-outcome-stood-down.json +10 -0
  31. package/dist/__golden__/wave-loop/op-outcome-unreadable.json +10 -0
  32. package/dist/__golden__/wave-loop/op-overridden.json +16 -0
  33. package/dist/__golden__/wave-loop/op-per-run.json +20 -0
  34. package/dist/__golden__/wave-loop/op-point.json +13 -0
  35. package/dist/__golden__/wave-loop/op-pr-review.json +10 -0
  36. package/dist/__golden__/wave-loop/op-sealed.json +10 -0
  37. package/dist/__golden__/wave-loop/op-spend-change-only.json +10 -0
  38. package/dist/__golden__/wave-loop/op-spend.json +10 -0
  39. package/dist/__golden__/wave-loop/op-wave-commands.json +55 -0
  40. package/dist/__golden__/wave-loop/op-wide-refused.json +26 -0
  41. package/dist/__golden__/wave-loop/op-wide-waiting.json +29 -0
  42. package/dist/__golden__/wave-loop/op-wide.json +39 -0
  43. package/dist/action-pins.d.ts +61 -0
  44. package/dist/action-pins.d.ts.map +1 -0
  45. package/dist/action-pins.js +77 -0
  46. package/dist/cli/commands/carve.d.ts +3 -0
  47. package/dist/cli/commands/carve.d.ts.map +1 -1
  48. package/dist/cli/commands/carve.js +36 -7
  49. package/dist/cli/commands/import-report.d.ts +26 -0
  50. package/dist/cli/commands/import-report.d.ts.map +1 -0
  51. package/dist/cli/commands/import-report.js +65 -0
  52. package/dist/cli/commands/import.d.ts +11 -1
  53. package/dist/cli/commands/import.d.ts.map +1 -1
  54. package/dist/cli/commands/import.js +22 -3
  55. package/dist/cli/commands/migrate.d.ts +13 -1
  56. package/dist/cli/commands/migrate.d.ts.map +1 -1
  57. package/dist/cli/commands/migrate.js +134 -10
  58. package/dist/cli/handlers/approve.d.ts.map +1 -1
  59. package/dist/cli/handlers/approve.js +41 -14
  60. package/dist/cli/handlers/build.d.ts.map +1 -1
  61. package/dist/cli/handlers/build.js +6 -1
  62. package/dist/cli/handlers/graph-reader.d.ts +26 -0
  63. package/dist/cli/handlers/graph-reader.d.ts.map +1 -0
  64. package/dist/cli/handlers/graph-reader.js +41 -0
  65. package/dist/cli/handlers/graph.d.ts.map +1 -1
  66. package/dist/cli/handlers/graph.js +14 -0
  67. package/dist/cli/handlers/ledger.d.ts +3 -0
  68. package/dist/cli/handlers/ledger.d.ts.map +1 -0
  69. package/dist/cli/handlers/ledger.js +55 -0
  70. package/dist/cli/handlers/migrate.d.ts.map +1 -1
  71. package/dist/cli/handlers/migrate.js +18 -11
  72. package/dist/cli/handlers/misc.d.ts.map +1 -1
  73. package/dist/cli/handlers/misc.js +12 -1
  74. package/dist/cli/handlers/pr-env.js +2 -2
  75. package/dist/cli/handlers/pr.d.ts +15 -1
  76. package/dist/cli/handlers/pr.d.ts.map +1 -1
  77. package/dist/cli/handlers/pr.js +183 -40
  78. package/dist/cli/handlers/query.d.ts +7 -2
  79. package/dist/cli/handlers/query.d.ts.map +1 -1
  80. package/dist/cli/handlers/query.js +16 -4
  81. package/dist/cli/handlers/release.d.ts.map +1 -1
  82. package/dist/cli/handlers/release.js +6 -3
  83. package/dist/cli/handlers/search.d.ts +2 -4
  84. package/dist/cli/handlers/search.d.ts.map +1 -1
  85. package/dist/cli/handlers/search.js +13 -39
  86. package/dist/cli/main.d.ts.map +1 -1
  87. package/dist/cli/main.js +87 -14
  88. package/dist/cli/parse-args.d.ts.map +1 -1
  89. package/dist/cli/parse-args.js +47 -2
  90. package/dist/cli/registry.d.ts +22 -4
  91. package/dist/cli/registry.d.ts.map +1 -1
  92. package/dist/components/builders.d.ts +5 -1
  93. package/dist/components/builders.d.ts.map +1 -1
  94. package/dist/components/builders.js +5 -0
  95. package/dist/components/capability-plugin-loader.d.ts.map +1 -1
  96. package/dist/components/capability-plugin-loader.js +16 -1
  97. package/dist/components/capability-plugin.d.ts +14 -0
  98. package/dist/components/capability-plugin.d.ts.map +1 -1
  99. package/dist/components/pr-hooks.d.ts +21 -1
  100. package/dist/components/pr-hooks.d.ts.map +1 -1
  101. package/dist/components/pr-hooks.js +72 -4
  102. package/dist/components/pr-pipeline.d.ts +45 -10
  103. package/dist/components/pr-pipeline.d.ts.map +1 -1
  104. package/dist/components/pr-pipeline.js +47 -14
  105. package/dist/components/pr-policy.d.ts +36 -0
  106. package/dist/components/pr-policy.d.ts.map +1 -1
  107. package/dist/components/pr-policy.js +36 -0
  108. package/dist/components/pr-run.d.ts +3 -0
  109. package/dist/components/pr-run.d.ts.map +1 -1
  110. package/dist/components/pr-run.js +1 -0
  111. package/dist/components/starter-plugin.d.ts +1 -0
  112. package/dist/components/starter-plugin.d.ts.map +1 -1
  113. package/dist/components/starter-plugin.js +5 -1
  114. package/dist/components/verbs/index.d.ts +2 -0
  115. package/dist/components/verbs/index.d.ts.map +1 -1
  116. package/dist/components/verbs/index.js +2 -0
  117. package/dist/components/verbs/publish-artifact.d.ts +65 -0
  118. package/dist/components/verbs/publish-artifact.d.ts.map +1 -0
  119. package/dist/components/verbs/publish-artifact.js +74 -0
  120. package/dist/components/verbs/publish-image.d.ts +129 -0
  121. package/dist/components/verbs/publish-image.d.ts.map +1 -0
  122. package/dist/components/verbs/publish-image.js +145 -0
  123. package/dist/config-environments.d.ts +20 -0
  124. package/dist/config-environments.d.ts.map +1 -1
  125. package/dist/config-environments.js +12 -0
  126. package/dist/config.d.ts +25 -1
  127. package/dist/config.d.ts.map +1 -1
  128. package/dist/config.js +10 -2
  129. package/dist/gate-decision.d.ts +20 -2
  130. package/dist/gate-decision.d.ts.map +1 -1
  131. package/dist/gate-decision.js +30 -3
  132. package/dist/graph-ir.d.ts +25 -0
  133. package/dist/graph-ir.d.ts.map +1 -1
  134. package/dist/graph-walk.d.ts +66 -0
  135. package/dist/graph-walk.d.ts.map +1 -0
  136. package/dist/graph-walk.js +70 -0
  137. package/dist/import/parser.d.ts +19 -0
  138. package/dist/import/parser.d.ts.map +1 -1
  139. package/dist/lexicon.d.ts +94 -6
  140. package/dist/lexicon.d.ts.map +1 -1
  141. package/dist/lifecycle/gate-ledger.d.ts +11 -0
  142. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  143. package/dist/lifecycle/git.d.ts +2 -5
  144. package/dist/lifecycle/git.d.ts.map +1 -1
  145. package/dist/lifecycle/ledger-read.d.ts +65 -0
  146. package/dist/lifecycle/ledger-read.d.ts.map +1 -0
  147. package/dist/lifecycle/ledger-read.js +160 -0
  148. package/dist/lifecycle/release-workstation.d.ts +7 -0
  149. package/dist/lifecycle/release-workstation.d.ts.map +1 -1
  150. package/dist/lifecycle/release-workstation.js +25 -0
  151. package/dist/lifecycle/run-ledger.d.ts +4 -3
  152. package/dist/lifecycle/run-ledger.d.ts.map +1 -1
  153. package/dist/lifecycle/run-ledger.js +1 -0
  154. package/dist/lint/discover.js +3 -1
  155. package/dist/lint/post-synth.d.ts +127 -1
  156. package/dist/lint/post-synth.d.ts.map +1 -1
  157. package/dist/lint/post-synth.js +84 -2
  158. package/dist/lint/rules/op/index.d.ts +1 -0
  159. package/dist/lint/rules/op/index.d.ts.map +1 -1
  160. package/dist/lint/rules/op/index.js +2 -0
  161. package/dist/lint/rules/op/ops019-workspace-op-target.d.ts +32 -0
  162. package/dist/lint/rules/op/ops019-workspace-op-target.d.ts.map +1 -0
  163. package/dist/lint/rules/op/ops019-workspace-op-target.js +63 -0
  164. package/dist/op/activities/activity-contracts.d.ts +9 -6
  165. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  166. package/dist/op/activities/activity-contracts.js +5 -2
  167. package/dist/op/activities/index.d.ts +2 -2
  168. package/dist/op/activities/index.d.ts.map +1 -1
  169. package/dist/op/activities/index.js +1 -1
  170. package/dist/op/activities/review-agent.d.ts +90 -4
  171. package/dist/op/activities/review-agent.d.ts.map +1 -1
  172. package/dist/op/activities/review-agent.js +217 -19
  173. package/dist/op/activities/work-evidence.d.ts +8 -1
  174. package/dist/op/activities/work-evidence.d.ts.map +1 -1
  175. package/dist/op/activities/work-evidence.js +1 -0
  176. package/dist/op/builders.d.ts +9 -5
  177. package/dist/op/builders.d.ts.map +1 -1
  178. package/dist/op/builders.js +9 -5
  179. package/dist/op/composites/apply-op.d.ts +29 -2
  180. package/dist/op/composites/apply-op.d.ts.map +1 -1
  181. package/dist/op/composites/apply-op.js +20 -1
  182. package/dist/op/gate-approval-base.d.ts +25 -0
  183. package/dist/op/gate-approval-base.d.ts.map +1 -0
  184. package/dist/op/gate-approval-base.js +71 -0
  185. package/dist/op/gate-approval.d.ts +18 -9
  186. package/dist/op/gate-approval.d.ts.map +1 -1
  187. package/dist/op/gate-approval.js +13 -31
  188. package/dist/op/gate-name.d.ts +13 -0
  189. package/dist/op/gate-name.d.ts.map +1 -1
  190. package/dist/op/gate-name.js +14 -1
  191. package/dist/op/gate-quorum.d.ts +100 -3
  192. package/dist/op/gate-quorum.d.ts.map +1 -1
  193. package/dist/op/gate-quorum.js +108 -6
  194. package/dist/op/gate-review.d.ts +9 -0
  195. package/dist/op/gate-review.d.ts.map +1 -1
  196. package/dist/op/gate-review.js +13 -0
  197. package/dist/op/gate.d.ts +47 -5
  198. package/dist/op/gate.d.ts.map +1 -1
  199. package/dist/op/gate.js +25 -4
  200. package/dist/op/index.d.ts +3 -3
  201. package/dist/op/index.d.ts.map +1 -1
  202. package/dist/op/index.js +1 -1
  203. package/dist/op/local-executor.d.ts +45 -3
  204. package/dist/op/local-executor.d.ts.map +1 -1
  205. package/dist/op/local-executor.js +68 -7
  206. package/dist/op/op-waves-run.d.ts +26 -1
  207. package/dist/op/op-waves-run.d.ts.map +1 -1
  208. package/dist/op/op-waves-run.js +259 -13
  209. package/dist/op/op-waves.d.ts +72 -5
  210. package/dist/op/op-waves.d.ts.map +1 -1
  211. package/dist/op/op-waves.js +110 -15
  212. package/dist/op/runtime.d.ts +11 -0
  213. package/dist/op/runtime.d.ts.map +1 -1
  214. package/dist/op/schedule-overdue.d.ts +16 -0
  215. package/dist/op/schedule-overdue.d.ts.map +1 -1
  216. package/dist/op/schedule-overdue.js +36 -0
  217. package/dist/op/stage-hooks.d.ts +9 -2
  218. package/dist/op/stage-hooks.d.ts.map +1 -1
  219. package/dist/op/stage-hooks.js +7 -5
  220. package/dist/op/workspace-run-env.d.ts +22 -0
  221. package/dist/op/workspace-run-env.d.ts.map +1 -0
  222. package/dist/op/workspace-run-env.js +21 -0
  223. package/dist/pr-forge.d.ts +24 -0
  224. package/dist/pr-forge.d.ts.map +1 -1
  225. package/dist/pr-forge.js +47 -0
  226. package/dist/pr-loop.d.ts +10 -0
  227. package/dist/pr-loop.d.ts.map +1 -1
  228. package/dist/pr-loop.js +4 -0
  229. package/dist/pr-migrations.d.ts +62 -0
  230. package/dist/pr-migrations.d.ts.map +1 -0
  231. package/dist/pr-migrations.js +50 -0
  232. package/dist/pr-post.d.ts +42 -0
  233. package/dist/pr-post.d.ts.map +1 -1
  234. package/dist/pr-post.js +68 -2
  235. package/dist/read-filter.d.ts +65 -0
  236. package/dist/read-filter.d.ts.map +1 -0
  237. package/dist/read-filter.js +179 -0
  238. package/dist/run-event.d.ts +1 -1
  239. package/dist/run-event.d.ts.map +1 -1
  240. package/dist/terraform/carve.d.ts +24 -3
  241. package/dist/terraform/carve.d.ts.map +1 -1
  242. package/dist/terraform/carve.js +32 -14
  243. package/dist/terraform/cuts.d.ts +97 -0
  244. package/dist/terraform/cuts.d.ts.map +1 -0
  245. package/dist/terraform/cuts.js +138 -0
  246. package/dist/terraform/folds.d.ts +26 -0
  247. package/dist/terraform/folds.d.ts.map +1 -0
  248. package/dist/terraform/folds.js +38 -0
  249. package/dist/terraform/graph.d.ts.map +1 -1
  250. package/dist/terraform/graph.js +10 -0
  251. package/dist/terraform/reach.d.ts +140 -0
  252. package/dist/terraform/reach.d.ts.map +1 -0
  253. package/dist/terraform/reach.js +170 -0
  254. package/dist/terraform/score.d.ts +14 -0
  255. package/dist/terraform/score.d.ts.map +1 -1
  256. package/dist/terraform/score.js +14 -31
  257. package/dist/terraform/state.d.ts.map +1 -1
  258. package/dist/terraform/state.js +4 -2
  259. package/dist/terraform/types.d.ts +8 -0
  260. package/dist/terraform/types.d.ts.map +1 -1
  261. package/dist/wave-blocked.d.ts +10 -1
  262. package/dist/wave-blocked.d.ts.map +1 -1
  263. package/dist/wave-blocked.js +31 -3
  264. package/dist/wave-runtime.mjs +264 -254
  265. package/dist/workspace/check.schema.json +13 -2
  266. package/dist/workspace/checks/ci-branch.d.ts +44 -0
  267. package/dist/workspace/checks/ci-branch.d.ts.map +1 -0
  268. package/dist/workspace/checks/ci-branch.js +133 -0
  269. package/dist/workspace/checks/gate-approval.d.ts +58 -0
  270. package/dist/workspace/checks/gate-approval.d.ts.map +1 -0
  271. package/dist/workspace/checks/gate-approval.js +103 -0
  272. package/dist/workspace/checks/links.d.ts +3 -0
  273. package/dist/workspace/checks/links.d.ts.map +1 -1
  274. package/dist/workspace/checks/links.js +12 -1
  275. package/dist/workspace/checks/org.d.ts +48 -0
  276. package/dist/workspace/checks/org.d.ts.map +1 -0
  277. package/dist/workspace/checks/org.js +156 -0
  278. package/dist/workspace/checks.d.ts +12 -1
  279. package/dist/workspace/checks.d.ts.map +1 -1
  280. package/dist/workspace/checks.js +63 -7
  281. package/dist/workspace/ci-branches.d.ts +16 -0
  282. package/dist/workspace/ci-branches.d.ts.map +1 -1
  283. package/dist/workspace/ci-branches.js +27 -0
  284. package/dist/workspace/ci-green-cli.js +7 -7
  285. package/dist/workspace/ci-green-workflow.d.ts +1 -1
  286. package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
  287. package/dist/workspace/ci-green-workflow.js +13 -8
  288. package/dist/workspace/ci-green.d.ts +14 -0
  289. package/dist/workspace/ci-green.d.ts.map +1 -1
  290. package/dist/workspace/ci-green.js +44 -11
  291. package/dist/workspace/ci-run.d.ts +9 -1
  292. package/dist/workspace/ci-run.d.ts.map +1 -1
  293. package/dist/workspace/ci-run.js +67 -6
  294. package/dist/workspace/ci-serve.d.ts +9 -0
  295. package/dist/workspace/ci-serve.d.ts.map +1 -1
  296. package/dist/workspace/ci-serve.js +114 -76
  297. package/dist/workspace/compose-graph.d.ts +14 -0
  298. package/dist/workspace/compose-graph.d.ts.map +1 -1
  299. package/dist/workspace/composites.d.ts +4 -1
  300. package/dist/workspace/composites.d.ts.map +1 -1
  301. package/dist/workspace/composites.js +5 -2
  302. package/dist/workspace/composites.schema.json +22 -0
  303. package/dist/workspace/conformance/__writer_fixture__/work/work.schema.json +105 -1
  304. package/dist/workspace/cross-root-lint.d.ts +56 -0
  305. package/dist/workspace/cross-root-lint.d.ts.map +1 -0
  306. package/dist/workspace/cross-root-lint.js +111 -0
  307. package/dist/workspace/dbt-models-reader.d.ts +44 -0
  308. package/dist/workspace/dbt-models-reader.d.ts.map +1 -0
  309. package/dist/workspace/dbt-models-reader.js +356 -0
  310. package/dist/workspace/dbt-project-common.d.ts +40 -0
  311. package/dist/workspace/dbt-project-common.d.ts.map +1 -0
  312. package/dist/workspace/dbt-project-common.js +106 -0
  313. package/dist/workspace/dbt-project-reader.d.ts +4 -3
  314. package/dist/workspace/dbt-project-reader.d.ts.map +1 -1
  315. package/dist/workspace/dbt-project-reader.js +14 -108
  316. package/dist/workspace/decision-rank.d.ts +26 -0
  317. package/dist/workspace/decision-rank.d.ts.map +1 -0
  318. package/dist/workspace/decision-rank.js +16 -0
  319. package/dist/workspace/declaration.d.ts +50 -2
  320. package/dist/workspace/declaration.d.ts.map +1 -1
  321. package/dist/workspace/declaration.js +57 -11
  322. package/dist/workspace/declaration.schema.json +166 -24
  323. package/dist/workspace/environments.d.ts +26 -2
  324. package/dist/workspace/environments.d.ts.map +1 -1
  325. package/dist/workspace/environments.js +26 -2
  326. package/dist/workspace/exit-cli.d.ts +44 -0
  327. package/dist/workspace/exit-cli.d.ts.map +1 -0
  328. package/dist/workspace/exit-cli.js +198 -0
  329. package/dist/workspace/exit.d.ts +159 -0
  330. package/dist/workspace/exit.d.ts.map +1 -0
  331. package/dist/workspace/exit.js +480 -0
  332. package/dist/workspace/exit.schema.json +257 -0
  333. package/dist/workspace/flyway-dir.d.ts +39 -0
  334. package/dist/workspace/flyway-dir.d.ts.map +1 -0
  335. package/dist/workspace/flyway-dir.js +104 -0
  336. package/dist/workspace/flyway-layout.d.ts +96 -0
  337. package/dist/workspace/flyway-layout.d.ts.map +1 -0
  338. package/dist/workspace/flyway-layout.js +308 -0
  339. package/dist/workspace/graph-cache.d.ts +3 -1
  340. package/dist/workspace/graph-cache.d.ts.map +1 -1
  341. package/dist/workspace/graph-cache.js +12 -1
  342. package/dist/workspace/graph-cli.d.ts.map +1 -1
  343. package/dist/workspace/graph-cli.js +26 -8
  344. package/dist/workspace/graph.schema.json +34 -0
  345. package/dist/workspace/import-fetch.d.ts +38 -0
  346. package/dist/workspace/import-fetch.d.ts.map +1 -0
  347. package/dist/workspace/import-fetch.js +99 -0
  348. package/dist/workspace/import-init.d.ts +26 -0
  349. package/dist/workspace/import-init.d.ts.map +1 -0
  350. package/dist/workspace/import-init.js +130 -0
  351. package/dist/workspace/import-source.d.ts +147 -0
  352. package/dist/workspace/import-source.d.ts.map +1 -0
  353. package/dist/workspace/import-source.js +370 -0
  354. package/dist/workspace/init.d.ts +4 -0
  355. package/dist/workspace/init.d.ts.map +1 -1
  356. package/dist/workspace/init.js +70 -3
  357. package/dist/workspace/intent.d.ts +2 -3
  358. package/dist/workspace/intent.d.ts.map +1 -1
  359. package/dist/workspace/intent.js +5 -9
  360. package/dist/workspace/kind-link-readers.d.ts +59 -2
  361. package/dist/workspace/kind-link-readers.d.ts.map +1 -1
  362. package/dist/workspace/kind-link-readers.js +17 -1
  363. package/dist/workspace/kind-readers.d.ts +6 -0
  364. package/dist/workspace/kind-readers.d.ts.map +1 -1
  365. package/dist/workspace/kind-readers.js +18 -0
  366. package/dist/workspace/kinds.d.ts +90 -3
  367. package/dist/workspace/kinds.d.ts.map +1 -1
  368. package/dist/workspace/kinds.js +161 -7
  369. package/dist/workspace/ledger.schema.json +158 -0
  370. package/dist/workspace/lexicon-outputs.d.ts +20 -1
  371. package/dist/workspace/lexicon-outputs.d.ts.map +1 -1
  372. package/dist/workspace/lexicon-outputs.js +102 -10
  373. package/dist/workspace/lineage-check.d.ts +3 -0
  374. package/dist/workspace/lineage-check.d.ts.map +1 -1
  375. package/dist/workspace/lineage-check.js +23 -4
  376. package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
  377. package/dist/workspace/lineage-upgrade.js +13 -4
  378. package/dist/workspace/links.d.ts +8 -1
  379. package/dist/workspace/links.d.ts.map +1 -1
  380. package/dist/workspace/links.js +25 -1
  381. package/dist/workspace/ls.d.ts +8 -0
  382. package/dist/workspace/ls.d.ts.map +1 -1
  383. package/dist/workspace/ls.js +7 -1
  384. package/dist/workspace/ls.schema.json +7 -0
  385. package/dist/workspace/member-commands.d.ts.map +1 -1
  386. package/dist/workspace/member-commands.js +54 -5
  387. package/dist/workspace/migrate-links.d.ts +45 -0
  388. package/dist/workspace/migrate-links.d.ts.map +1 -0
  389. package/dist/workspace/migrate-links.js +100 -0
  390. package/dist/workspace/migration-dirs.d.ts +74 -0
  391. package/dist/workspace/migration-dirs.d.ts.map +1 -0
  392. package/dist/workspace/migration-dirs.js +254 -0
  393. package/dist/workspace/new-migrations.d.ts +47 -0
  394. package/dist/workspace/new-migrations.d.ts.map +1 -0
  395. package/dist/workspace/new-migrations.js +91 -0
  396. package/dist/workspace/pr-report.schema.json +46 -0
  397. package/dist/workspace/promote.d.ts +96 -0
  398. package/dist/workspace/promote.d.ts.map +1 -0
  399. package/dist/workspace/promote.js +263 -0
  400. package/dist/workspace/reason-codes.d.ts +21 -0
  401. package/dist/workspace/reason-codes.d.ts.map +1 -1
  402. package/dist/workspace/reason-codes.js +25 -0
  403. package/dist/workspace/records-cli.d.ts +11 -0
  404. package/dist/workspace/records-cli.d.ts.map +1 -1
  405. package/dist/workspace/records-cli.js +57 -5
  406. package/dist/workspace/records.schema.json +5 -0
  407. package/dist/workspace/release-candidates.d.ts +2 -0
  408. package/dist/workspace/release-candidates.d.ts.map +1 -1
  409. package/dist/workspace/release-candidates.js +5 -2
  410. package/dist/workspace/release-train.d.ts +53 -0
  411. package/dist/workspace/release-train.d.ts.map +1 -0
  412. package/dist/workspace/release-train.js +134 -0
  413. package/dist/workspace/release-workflow.d.ts.map +1 -1
  414. package/dist/workspace/release-workflow.js +4 -3
  415. package/dist/workspace/repo-ref.d.ts +45 -0
  416. package/dist/workspace/repo-ref.d.ts.map +1 -0
  417. package/dist/workspace/repo-ref.js +114 -0
  418. package/dist/workspace/run-event.schema.json +2 -2
  419. package/dist/workspace/runs-cli.d.ts +9 -0
  420. package/dist/workspace/runs-cli.d.ts.map +1 -1
  421. package/dist/workspace/runs-cli.js +48 -3
  422. package/dist/workspace/runs.schema.json +7 -0
  423. package/dist/workspace/search-contract.d.ts +1 -1
  424. package/dist/workspace/search-contract.d.ts.map +1 -1
  425. package/dist/workspace/status-decisions.d.ts +37 -0
  426. package/dist/workspace/status-decisions.d.ts.map +1 -0
  427. package/dist/workspace/status-decisions.js +147 -0
  428. package/dist/workspace/status-gates.d.ts +9 -0
  429. package/dist/workspace/status-gates.d.ts.map +1 -1
  430. package/dist/workspace/status.d.ts +4 -0
  431. package/dist/workspace/status.d.ts.map +1 -1
  432. package/dist/workspace/status.js +14 -2
  433. package/dist/workspace/status.schema.json +6 -0
  434. package/dist/workspace/work-cli.d.ts.map +1 -1
  435. package/dist/workspace/work-cli.js +5 -3
  436. package/dist/workspace/work-evidence-ops.d.ts +83 -0
  437. package/dist/workspace/work-evidence-ops.d.ts.map +1 -0
  438. package/dist/workspace/work-evidence-ops.js +169 -0
  439. package/dist/workspace/work-evidence.d.ts +12 -2
  440. package/dist/workspace/work-evidence.d.ts.map +1 -1
  441. package/dist/workspace/work-evidence.js +21 -3
  442. package/dist/workspace/work-evidence.schema.json +49 -2
  443. package/dist/workspace/workspace-gates.d.ts +110 -0
  444. package/dist/workspace/workspace-gates.d.ts.map +1 -0
  445. package/dist/workspace/workspace-gates.js +140 -0
  446. package/dist/workspace/workspace-kinds.schema.json +108 -10
  447. package/dist/workspace/workspace-ledger.d.ts +76 -0
  448. package/dist/workspace/workspace-ledger.d.ts.map +1 -0
  449. package/dist/workspace/workspace-ledger.js +91 -0
  450. package/dist/workspace/workspace-run.d.ts +112 -0
  451. package/dist/workspace/workspace-run.d.ts.map +1 -0
  452. package/dist/workspace/workspace-run.js +432 -0
  453. package/package.json +1 -1
  454. package/src/__golden__/wave-loop/op-by-apply-after-fails.json +10 -0
  455. package/src/__golden__/wave-loop/op-by-apply-hold.json +10 -0
  456. package/src/__golden__/wave-loop/op-by-apply-ignored.json +10 -0
  457. package/src/__golden__/wave-loop/op-by-apply-pass.json +10 -0
  458. package/src/__golden__/wave-loop/op-by-apply-skip.json +10 -0
  459. package/src/__golden__/wave-loop/op-cost-member-clash.json +6 -0
  460. package/src/__golden__/wave-loop/op-cost-member.json +13 -0
  461. package/src/__golden__/wave-loop/op-denied-source.json +16 -0
  462. package/src/__golden__/wave-loop/op-denied.json +16 -0
  463. package/src/__golden__/wave-loop/op-discover-rest-short.json +6 -0
  464. package/src/__golden__/wave-loop/op-discover-rest-stops.json +13 -0
  465. package/src/__golden__/wave-loop/op-discover-rest.json +26 -0
  466. package/src/__golden__/wave-loop/op-discover-wide.json +26 -0
  467. package/src/__golden__/wave-loop/op-held-on-again.json +16 -0
  468. package/src/__golden__/wave-loop/op-held-on-before-apply.json +16 -0
  469. package/src/__golden__/wave-loop/op-held-on.json +16 -0
  470. package/src/__golden__/wave-loop/op-hooks-apply-fails.json +13 -0
  471. package/src/__golden__/wave-loop/op-hooks-fail-after-apply.json +13 -0
  472. package/src/__golden__/wave-loop/op-hooks-fail-before-apply.json +13 -0
  473. package/src/__golden__/wave-loop/op-hooks-fail-before-plan.json +13 -0
  474. package/src/__golden__/wave-loop/op-hooks-held.json +13 -0
  475. package/src/__golden__/wave-loop/op-hooks-hold-before-apply.json +13 -0
  476. package/src/__golden__/wave-loop/op-hooks-pass.json +13 -0
  477. package/src/__golden__/wave-loop/op-hooks-refused-at-base.json +13 -0
  478. package/src/__golden__/wave-loop/op-hooks-share.json +33 -0
  479. package/src/__golden__/wave-loop/op-outcome-held.json +10 -0
  480. package/src/__golden__/wave-loop/op-outcome-replan-approved.json +10 -0
  481. package/src/__golden__/wave-loop/op-outcome-replan-forever.json +10 -0
  482. package/src/__golden__/wave-loop/op-outcome-replan.json +10 -0
  483. package/src/__golden__/wave-loop/op-outcome-stood-down.json +10 -0
  484. package/src/__golden__/wave-loop/op-outcome-unreadable.json +10 -0
  485. package/src/__golden__/wave-loop/op-overridden.json +16 -0
  486. package/src/__golden__/wave-loop/op-per-run.json +20 -0
  487. package/src/__golden__/wave-loop/op-point.json +13 -0
  488. package/src/__golden__/wave-loop/op-pr-review.json +10 -0
  489. package/src/__golden__/wave-loop/op-sealed.json +10 -0
  490. package/src/__golden__/wave-loop/op-spend-change-only.json +10 -0
  491. package/src/__golden__/wave-loop/op-spend.json +10 -0
  492. package/src/__golden__/wave-loop/op-wave-commands.json +55 -0
  493. package/src/__golden__/wave-loop/op-wide-refused.json +26 -0
  494. package/src/__golden__/wave-loop/op-wide-waiting.json +29 -0
  495. package/src/__golden__/wave-loop/op-wide.json +39 -0
  496. package/src/action-pins.test.ts +78 -0
  497. package/src/action-pins.ts +92 -0
  498. package/src/cli/commands/carve-score-once.test.ts +99 -0
  499. package/src/cli/commands/carve.ts +37 -6
  500. package/src/cli/commands/check-lexicon.test.ts +2 -2
  501. package/src/cli/commands/import-report.ts +71 -0
  502. package/src/cli/commands/import.ts +33 -3
  503. package/src/cli/commands/migrate-dialect.test.ts +145 -0
  504. package/src/cli/commands/migrate.ts +132 -11
  505. package/src/cli/handlers/__snapshots__/build-member.test.ts.snap +4 -4
  506. package/src/cli/handlers/approve.ts +33 -4
  507. package/src/cli/handlers/build.ts +7 -1
  508. package/src/cli/handlers/graph-reader.test.ts +31 -0
  509. package/src/cli/handlers/graph-reader.ts +51 -0
  510. package/src/cli/handlers/graph.ts +14 -0
  511. package/src/cli/handlers/ledger.ts +54 -0
  512. package/src/cli/handlers/migrate.ts +21 -12
  513. package/src/cli/handlers/misc.ts +10 -1
  514. package/src/cli/handlers/pr-env.ts +2 -2
  515. package/src/cli/handlers/pr.ts +176 -37
  516. package/src/cli/handlers/query.ts +16 -4
  517. package/src/cli/handlers/release.ts +6 -3
  518. package/src/cli/handlers/search.ts +13 -34
  519. package/src/cli/main.test.ts +17 -0
  520. package/src/cli/main.ts +87 -14
  521. package/src/cli/parse-args.ts +35 -2
  522. package/src/cli/registry.ts +22 -4
  523. package/src/components/builders.ts +8 -0
  524. package/src/components/capability-plugin-loader.ts +13 -1
  525. package/src/components/capability-plugin.ts +16 -0
  526. package/src/components/pr-env-run-bundle.test.ts +1 -0
  527. package/src/components/pr-hooks.test.ts +73 -5
  528. package/src/components/pr-hooks.ts +95 -5
  529. package/src/components/pr-pipeline.ts +64 -16
  530. package/src/components/pr-policy.test.ts +64 -1
  531. package/src/components/pr-policy.ts +53 -0
  532. package/src/components/pr-run.ts +4 -0
  533. package/src/components/registry.test.ts +1 -1
  534. package/src/components/starter-plugin.ts +6 -0
  535. package/src/components/verbs/index.ts +2 -0
  536. package/src/components/verbs/publish-artifact.test.ts +60 -0
  537. package/src/components/verbs/publish-artifact.ts +122 -0
  538. package/src/components/verbs/publish-image.test.ts +101 -0
  539. package/src/components/verbs/publish-image.ts +265 -0
  540. package/src/config-environments.ts +28 -1
  541. package/src/config.ts +24 -1
  542. package/src/gate-decision-bundle.test.ts +5 -2
  543. package/src/gate-decision-identity.test.ts +20 -0
  544. package/src/gate-decision.ts +46 -3
  545. package/src/graph-ir.ts +27 -0
  546. package/src/graph-walk.test.ts +44 -0
  547. package/src/graph-walk.ts +102 -0
  548. package/src/import/parser.ts +20 -0
  549. package/src/lexicon.ts +98 -6
  550. package/src/lifecycle/gate-ledger.ts +8 -0
  551. package/src/lifecycle/git.ts +2 -2
  552. package/src/lifecycle/ledger-read.test.ts +107 -0
  553. package/src/lifecycle/ledger-read.ts +195 -0
  554. package/src/lifecycle/release-workstation.ts +24 -0
  555. package/src/lifecycle/run-ledger.ts +5 -2
  556. package/src/lint/cross-root.test.ts +122 -0
  557. package/src/lint/discover.ts +3 -1
  558. package/src/lint/post-synth.ts +212 -2
  559. package/src/lint/rules/op/index.ts +2 -0
  560. package/src/lint/rules/op/ops019-workspace-op-target.ts +89 -0
  561. package/src/op/activities/activity-contracts.ts +5 -2
  562. package/src/op/activities/index.ts +2 -2
  563. package/src/op/activities/review-agent.test.ts +221 -4
  564. package/src/op/activities/review-agent.ts +265 -22
  565. package/src/op/activities/work-evidence.ts +9 -1
  566. package/src/op/builders.ts +9 -5
  567. package/src/op/composites/apply-op.test.ts +108 -0
  568. package/src/op/composites/apply-op.ts +37 -2
  569. package/src/op/gate-approval-at-base.test.ts +97 -0
  570. package/src/op/gate-approval-base.ts +72 -0
  571. package/src/op/gate-approval.test.ts +6 -1
  572. package/src/op/gate-approval.ts +22 -27
  573. package/src/op/gate-name.ts +16 -1
  574. package/src/op/gate-quorum-at-base.test.ts +211 -0
  575. package/src/op/gate-quorum.ts +156 -8
  576. package/src/op/gate-review.ts +14 -0
  577. package/src/op/gate.ts +69 -9
  578. package/src/op/index.ts +3 -2
  579. package/src/op/local-executor.ts +91 -11
  580. package/src/op/op-waves-depends-on.test.ts +81 -0
  581. package/src/op/op-waves-hook-targets.test.ts +4 -2
  582. package/src/op/op-waves-quorum-product.test.ts +158 -0
  583. package/src/op/op-waves-run.ts +288 -14
  584. package/src/op/op-waves.ts +143 -14
  585. package/src/op/runtime.ts +8 -0
  586. package/src/op/schedule-overdue.ts +33 -0
  587. package/src/op/stage-hooks.ts +13 -5
  588. package/src/op/workspace-run-env.ts +29 -0
  589. package/src/pr-forge-bundle.test.ts +2 -2
  590. package/src/pr-forge.test.ts +47 -0
  591. package/src/pr-forge.ts +68 -0
  592. package/src/pr-loop-bundle.test.ts +3 -2
  593. package/src/pr-loop.test.ts +52 -0
  594. package/src/pr-loop.ts +12 -0
  595. package/src/pr-migrations.ts +97 -0
  596. package/src/pr-post-open.e2e.test.ts +36 -0
  597. package/src/pr-post.test.ts +102 -3
  598. package/src/pr-post.ts +90 -2
  599. package/src/read-filter.test.ts +54 -0
  600. package/src/read-filter.ts +193 -0
  601. package/src/run-event.ts +1 -1
  602. package/src/stage-hooks-bundle.test.ts +1 -0
  603. package/src/terraform/carve.ts +52 -15
  604. package/src/terraform/cuts.test.ts +173 -0
  605. package/src/terraform/cuts.ts +188 -0
  606. package/src/terraform/folds.ts +44 -0
  607. package/src/terraform/graph.ts +9 -0
  608. package/src/terraform/radius.test.ts +90 -0
  609. package/src/terraform/reach.test.ts +146 -0
  610. package/src/terraform/reach.ts +247 -0
  611. package/src/terraform/score.ts +27 -29
  612. package/src/terraform/state.ts +3 -1
  613. package/src/terraform/types.ts +8 -0
  614. package/src/wave-blocked.ts +28 -3
  615. package/src/wave-loop-golden.test.ts +4 -3
  616. package/src/workspace/__fixtures__/import/spacelift.json +9 -0
  617. package/src/workspace/__fixtures__/import/tfc.json +13 -0
  618. package/src/workspace/behold-kinds.test.ts +2 -1
  619. package/src/workspace/check-contract.test.ts +8 -6
  620. package/src/workspace/check.schema.json +13 -2
  621. package/src/workspace/checks/ci-branch.test.ts +128 -0
  622. package/src/workspace/checks/ci-branch.ts +141 -0
  623. package/src/workspace/checks/gate-approval.test.ts +77 -0
  624. package/src/workspace/checks/gate-approval.ts +121 -0
  625. package/src/workspace/checks/links.ts +12 -1
  626. package/src/workspace/checks/org.ts +169 -0
  627. package/src/workspace/checks.ts +68 -7
  628. package/src/workspace/ci-branches.ts +31 -0
  629. package/src/workspace/ci-green-branches.test.ts +153 -0
  630. package/src/workspace/ci-green-cli.ts +7 -7
  631. package/src/workspace/ci-green-workflow.test.ts +4 -3
  632. package/src/workspace/ci-green-workflow.ts +13 -9
  633. package/src/workspace/ci-green.ts +49 -12
  634. package/src/workspace/ci-run.test.ts +72 -1
  635. package/src/workspace/ci-run.ts +68 -8
  636. package/src/workspace/ci-serve.test.ts +95 -0
  637. package/src/workspace/ci-serve.ts +106 -65
  638. package/src/workspace/component-environments.test.ts +25 -2
  639. package/src/workspace/compose-graph.ts +9 -2
  640. package/src/workspace/composites.schema.json +22 -0
  641. package/src/workspace/composites.test.ts +39 -8
  642. package/src/workspace/composites.ts +6 -2
  643. package/src/workspace/conformance/__writer_fixture__/work/work.schema.json +105 -1
  644. package/src/workspace/cross-root-lint.test.ts +90 -0
  645. package/src/workspace/cross-root-lint.ts +126 -0
  646. package/src/workspace/dbt-kind.test.ts +3 -2
  647. package/src/workspace/dbt-models-reader.ts +362 -0
  648. package/src/workspace/dbt-models.test.ts +248 -0
  649. package/src/workspace/dbt-project-common.ts +113 -0
  650. package/src/workspace/dbt-project-reader.ts +29 -112
  651. package/src/workspace/decision-rank.ts +40 -0
  652. package/src/workspace/declaration.schema.json +166 -24
  653. package/src/workspace/declaration.ts +101 -15
  654. package/src/workspace/environments.ts +40 -4
  655. package/src/workspace/exit-cli.ts +219 -0
  656. package/src/workspace/exit.schema.json +257 -0
  657. package/src/workspace/exit.test.ts +185 -0
  658. package/src/workspace/exit.ts +560 -0
  659. package/src/workspace/flyway-dir.ts +113 -0
  660. package/src/workspace/flyway-layout.ts +337 -0
  661. package/src/workspace/graph-cache.ts +12 -2
  662. package/src/workspace/graph-cli.ts +25 -10
  663. package/src/workspace/graph-contract.test.ts +4 -2
  664. package/src/workspace/graph.schema.json +34 -0
  665. package/src/workspace/identity.test.ts +29 -2
  666. package/src/workspace/import-fetch.ts +151 -0
  667. package/src/workspace/import-init.ts +147 -0
  668. package/src/workspace/import-source.test.ts +183 -0
  669. package/src/workspace/import-source.ts +462 -0
  670. package/src/workspace/init.test.ts +55 -1
  671. package/src/workspace/init.ts +66 -4
  672. package/src/workspace/intent.ts +6 -14
  673. package/src/workspace/kind-link-readers.ts +68 -3
  674. package/src/workspace/kind-readers.test.ts +36 -0
  675. package/src/workspace/kind-readers.ts +18 -1
  676. package/src/workspace/kinds.test.ts +35 -7
  677. package/src/workspace/kinds.ts +212 -11
  678. package/src/workspace/ledger.schema.json +158 -0
  679. package/src/workspace/lexicon-outputs.ts +132 -13
  680. package/src/workspace/lineage-check.ts +30 -7
  681. package/src/workspace/lineage-upgrade.test.ts +20 -0
  682. package/src/workspace/lineage-upgrade.ts +16 -7
  683. package/src/workspace/links.ts +24 -1
  684. package/src/workspace/ls-contract.test.ts +1 -1
  685. package/src/workspace/ls.schema.json +7 -0
  686. package/src/workspace/ls.ts +10 -1
  687. package/src/workspace/member-commands.ts +60 -2
  688. package/src/workspace/member-file-schema.test.ts +124 -0
  689. package/src/workspace/migrate-links.test.ts +99 -0
  690. package/src/workspace/migrate-links.ts +108 -0
  691. package/src/workspace/migration-dirs.test.ts +272 -0
  692. package/src/workspace/migration-dirs.ts +267 -0
  693. package/src/workspace/new-migrations.test.ts +98 -0
  694. package/src/workspace/new-migrations.ts +109 -0
  695. package/src/workspace/org-scope.test.ts +238 -0
  696. package/src/workspace/pr-report.schema.json +46 -0
  697. package/src/workspace/promote.test.ts +114 -0
  698. package/src/workspace/promote.ts +302 -0
  699. package/src/workspace/reason-codes.test.ts +10 -1
  700. package/src/workspace/reason-codes.ts +25 -0
  701. package/src/workspace/records-cli.ts +58 -5
  702. package/src/workspace/records.schema.json +5 -0
  703. package/src/workspace/release-candidates.ts +7 -2
  704. package/src/workspace/release-train.ts +157 -0
  705. package/src/workspace/release-workflow.ts +4 -3
  706. package/src/workspace/repo-ref.ts +113 -0
  707. package/src/workspace/run-event.schema.json +2 -2
  708. package/src/workspace/runs-cli.ts +48 -4
  709. package/src/workspace/runs.schema.json +7 -0
  710. package/src/workspace/status-contract.test.ts +46 -1
  711. package/src/workspace/status-decisions.test.ts +100 -0
  712. package/src/workspace/status-decisions.ts +151 -0
  713. package/src/workspace/status-gates.ts +9 -0
  714. package/src/workspace/status.schema.json +6 -0
  715. package/src/workspace/status.ts +17 -3
  716. package/src/workspace/where-filters.test.ts +39 -0
  717. package/src/workspace/work-cli.ts +5 -3
  718. package/src/workspace/work-evidence-ops.test.ts +176 -0
  719. package/src/workspace/work-evidence-ops.ts +189 -0
  720. package/src/workspace/work-evidence.schema.json +49 -2
  721. package/src/workspace/work-evidence.ts +22 -3
  722. package/src/workspace/workspace-gates.ts +200 -0
  723. package/src/workspace/workspace-kinds.schema.json +108 -10
  724. package/src/workspace/workspace-ledger.ts +130 -0
  725. package/src/workspace/workspace-run.test.ts +322 -0
  726. package/src/workspace/workspace-run.ts +461 -0
@@ -0,0 +1,560 @@
1
+ /**
2
+ * `chant workspace exit` (#3177): leave a member, or the workspace, in one
3
+ * step, and prove on every commit that leaving still works.
4
+ *
5
+ * Per member kind:
6
+ *
7
+ * - `chant`: build once per lexicon and commit the native output under
8
+ * `native/`, rewrite the member's CI files to call the platform's own CLI,
9
+ * drop the chant dependencies and `chant.config.*`, and set the member to
10
+ * `other` with its outputs listed. Where an Op's `op.json` sits in the
11
+ * member and the Op is linear (shell steps only), a plain CI job is written
12
+ * beside it. The `op.json` stays.
13
+ * - `choudoufu`: the roundtrip in choudoufu's `live/MIGRATE.md`. It needs
14
+ * stock OpenTofu and the estate's state, so the exit lists its steps and
15
+ * changes no file.
16
+ * - every other kind (`other`, records, ledgers, `workspace`, `repo`): kept
17
+ * as files.
18
+ *
19
+ * The functions here work on a plain directory, so the same code runs in the
20
+ * staging worktree (`exit-cli.ts`) and in the drill's scratch copy.
21
+ * {@link runExitDrill} runs the exit on a scratch copy of a workspace and
22
+ * checks the result statically: it proves each member holds what a stock
23
+ * tool needs and nothing that needs chant. It never installs chant or calls
24
+ * a cloud, so it is a unit-level check CI can run on every commit.
25
+ */
26
+
27
+ import { copyFileSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
28
+ import { tmpdir } from "node:os";
29
+ import { dirname, join, relative, resolve } from "node:path";
30
+ import { readDeclaration, type Member } from "./declaration";
31
+ import { setProperty } from "./json-edit";
32
+ import type { ReasonCode } from "./reason-codes";
33
+ import { workingTree } from "./tree";
34
+
35
+ /** Runs `chant build` in a directory. The same shape as `ChantRunner` in lineage-upgrade.ts. */
36
+ export type ExitBuildRunner = (command: "build", cwd: string, args: readonly string[]) => Promise<{ exitCode: number; output: string }>;
37
+
38
+ // ── Native targets ───────────────────────────────────────────────────────────
39
+
40
+ export interface NativeTarget {
41
+ /** What the output is. */
42
+ describes: string;
43
+ /** The file under the member's `native/` directory, or a path from the member's directory when `inMember`. */
44
+ file: string;
45
+ /** The file is written at this path from the member's directory rather than under `native/`. */
46
+ inMember?: boolean;
47
+ /** The stock command that deploys it, given the committed file's path from the member's directory and the member name. Null when there is nothing to deploy (CI files, collector config). */
48
+ deploy: ((file: string, member: string) => string) | null;
49
+ }
50
+
51
+ /** The lexicons `workspace exit` knows a native target for. */
52
+ export const NATIVE_TARGETS: Record<string, NativeTarget> = {
53
+ aws: {
54
+ describes: "CloudFormation template",
55
+ file: "template.json",
56
+ deploy: (f, m) => `aws cloudformation deploy --template-file ${f} --stack-name ${m} --capabilities CAPABILITY_NAMED_IAM`,
57
+ },
58
+ azure: {
59
+ describes: "ARM template",
60
+ file: "azuredeploy.json",
61
+ deploy: (f, m) => `az deployment group create --resource-group ${m} --template-file ${f}`,
62
+ },
63
+ gcp: { describes: "Config Connector objects", file: "config-connector.yaml", deploy: (f) => `kubectl apply -f ${f}` },
64
+ k8s: { describes: "Kubernetes YAML", file: "k8s.yaml", deploy: (f) => `kubectl apply -f ${f}` },
65
+ docker: { describes: "Compose file", file: "docker-compose.yml", deploy: (f) => `docker compose -f ${f} up -d` },
66
+ otel: { describes: "OpenTelemetry collector config", file: "otel-collector.yaml", deploy: null },
67
+ github: { describes: "GitHub Actions workflow", file: ".github/workflows/chant-exit.yml", inMember: true, deploy: null },
68
+ gitlab: { describes: "GitLab CI file", file: ".gitlab-ci.yml", inMember: true, deploy: null },
69
+ };
70
+
71
+ /** The lexicons a member's `chant.config.*` or `package.json` names, in order. */
72
+ export function memberLexicons(dir: string): string[] {
73
+ for (const name of ["chant.config.ts", "chant.config.js", "chant.config.mjs"]) {
74
+ const file = join(dir, name);
75
+ if (!existsSync(file)) continue;
76
+ const m = /lexicons\s*:\s*\[([^\]]*)\]/.exec(readFileSync(file, "utf-8"));
77
+ if (m) return [...m[1]!.matchAll(/["'`]([\w-]+)["'`]/g)].map((x) => x[1]!);
78
+ }
79
+ const pkg = readJson(join(dir, "package.json"));
80
+ const found: string[] = [];
81
+ for (const section of ["dependencies", "devDependencies"]) {
82
+ for (const dep of Object.keys((pkg?.[section] as Record<string, string> | undefined) ?? {})) {
83
+ const m = /^@intentius\/chant-lexicon-([\w-]+)$/.exec(dep);
84
+ if (m && !found.includes(m[1]!)) found.push(m[1]!);
85
+ }
86
+ }
87
+ return found;
88
+ }
89
+
90
+ // ── The plan ─────────────────────────────────────────────────────────────────
91
+
92
+ export type ExitAction = "native-build" | "choudoufu-roundtrip" | "keep";
93
+
94
+ /** Why a chant member cannot be exited. Closed; each is in `reason-codes.ts`. */
95
+ export const EXIT_REFUSAL_CODES = ["missing-dir", "no-lexicon", "lexicon-unsupported"] as const satisfies readonly ReasonCode[];
96
+ export type ExitRefusalCode = (typeof EXIT_REFUSAL_CODES)[number];
97
+
98
+ export interface MemberPlan {
99
+ name: string;
100
+ dir: string;
101
+ kind: string;
102
+ action: ExitAction;
103
+ lexicons: string[];
104
+ /** Set when the member cannot be exited, with the reason code. */
105
+ refused?: { code: ExitRefusalCode; message: string };
106
+ }
107
+
108
+ export interface ExitPlan {
109
+ root: string;
110
+ members: MemberPlan[];
111
+ }
112
+
113
+ export class ExitError extends Error {}
114
+
115
+ /** What exiting `names` (every exitable member when none are named) does, read from the declaration. */
116
+ export function planExit(root: string, names: readonly string[] = []): ExitPlan {
117
+ const dec = readDeclaration(workingTree(root));
118
+ const real = dec.members.filter((m) => m.type === "member");
119
+ for (const n of names) {
120
+ if (!real.some((m) => m.name === n)) throw new ExitError(`no member named "${n}" (members: ${real.map((m) => m.name).join(", ") || "none"})`);
121
+ }
122
+ const chosen = names.length > 0 ? real.filter((m) => names.includes(m.name)) : real;
123
+ return { root, members: chosen.map((m) => planMember(root, m)) };
124
+ }
125
+
126
+ function planMember(root: string, m: Member): MemberPlan {
127
+ const base = { name: m.name, dir: m.dir, kind: m.kind };
128
+ if (m.kind === "choudoufu") return { ...base, action: "choudoufu-roundtrip", lexicons: [] };
129
+ if (m.kind !== "chant") return { ...base, action: "keep", lexicons: [] };
130
+ const abs = resolve(root, m.dir);
131
+ if (!existsSync(abs)) return { ...base, action: "native-build", lexicons: [], refused: { code: "missing-dir", message: `${m.dir} does not exist` } };
132
+ const lexicons = memberLexicons(abs);
133
+ if (lexicons.length === 0) {
134
+ return { ...base, action: "native-build", lexicons, refused: { code: "no-lexicon", message: "no lexicon is named in chant.config.* or package.json, so there is nothing to build" } };
135
+ }
136
+ const unknown = lexicons.filter((l) => !NATIVE_TARGETS[l]);
137
+ if (unknown.length > 0) {
138
+ return {
139
+ ...base,
140
+ action: "native-build",
141
+ lexicons,
142
+ refused: { code: "lexicon-unsupported", message: `no native target for lexicon ${unknown.join(", ")} (supported: ${Object.keys(NATIVE_TARGETS).join(", ")})` },
143
+ };
144
+ }
145
+ return { ...base, action: "native-build", lexicons };
146
+ }
147
+
148
+ // ── Doing the exit in a directory ────────────────────────────────────────────
149
+
150
+ export interface MemberResult {
151
+ name: string;
152
+ dir: string;
153
+ kind: string;
154
+ status: "exited" | "kept" | "manual" | "refused" | "failed";
155
+ /** Why the member was refused; set only with status `refused`. */
156
+ code?: ExitRefusalCode;
157
+ /** Native files written, relative to the workspace root. */
158
+ native: string[];
159
+ /** The stock command for each native file, relative to the member's directory. */
160
+ deploy: string[];
161
+ /** CI files rewritten or written, relative to the workspace root. */
162
+ ci: string[];
163
+ /** `op.json` files left in place, relative to the workspace root. */
164
+ ops: string[];
165
+ /** Steps a person runs, for a member the exit cannot do alone. */
166
+ steps: string[];
167
+ detail?: string;
168
+ }
169
+
170
+ export interface ExitResult {
171
+ members: MemberResult[];
172
+ }
173
+
174
+ const CHANT_FILES = ["chant.config.ts", "chant.config.js", "chant.config.mjs", "chant.config.json"];
175
+ const CI_GLOBS = [".github/workflows", "ci", ".circleci"];
176
+ const CI_ROOT_FILES = [".gitlab-ci.yml", ".gitlab-ci.yaml"];
177
+
178
+ /** The choudoufu roundtrip, in order (`live/MIGRATE.md`). Run with stock OpenTofu. */
179
+ export function choudoufuSteps(member: string, dir: string): string[] {
180
+ return [
181
+ `cd ${dir}`,
182
+ "copy the cache to terraform.tfstate",
183
+ "remove the live block from the estate",
184
+ `CHOUDOUFU_UNMIGRATE=${member} tofu plan # stock's first plan removes the two marker tags`,
185
+ "tofu apply",
186
+ ];
187
+ }
188
+
189
+ /**
190
+ * Exit the planned members in the tree at `plan.root`, editing it in place.
191
+ * Builds through `runBuild`, so a test can supply output without chant.
192
+ */
193
+ export async function exitInDir(plan: ExitPlan, runBuild: ExitBuildRunner): Promise<ExitResult> {
194
+ const results: MemberResult[] = [];
195
+ for (const m of plan.members) {
196
+ const result: MemberResult = { name: m.name, dir: m.dir, kind: m.kind, status: "kept", native: [], deploy: [], ci: [], ops: [], steps: [] };
197
+ results.push(result);
198
+ if (m.refused) {
199
+ result.status = "refused";
200
+ result.code = m.refused.code;
201
+ result.detail = `${m.refused.code}: ${m.refused.message}`;
202
+ continue;
203
+ }
204
+ if (m.action === "choudoufu-roundtrip") {
205
+ result.status = "manual";
206
+ result.steps = choudoufuSteps(m.name, m.dir);
207
+ result.detail = "needs stock OpenTofu and the estate's state; the exit changes no file";
208
+ continue;
209
+ }
210
+ if (m.action === "keep") continue;
211
+ const abs = resolve(plan.root, m.dir);
212
+ const outputs: string[] = [];
213
+ for (const lexicon of m.lexicons) {
214
+ const target = NATIVE_TARGETS[lexicon]!;
215
+ const rel = target.inMember ? target.file : `native/${target.file}`;
216
+ const out = join(abs, rel);
217
+ mkdirSync(dirname(out), { recursive: true });
218
+ const run = await runBuild("build", abs, [".", "--lexicon", lexicon, "--output", out]);
219
+ if (run.exitCode !== 0 || !existsSync(out) || statSync(out).size === 0) {
220
+ result.status = "failed";
221
+ result.detail = `chant build --lexicon ${lexicon} failed${run.output ? `: ${run.output.trim().split("\n").slice(-5).join("\n")}` : ""}`;
222
+ break;
223
+ }
224
+ result.native.push(relPath(plan.root, out));
225
+ if (target.deploy) result.deploy.push(target.deploy(rel, m.name));
226
+ outputs.push(rel);
227
+ }
228
+ if (result.status === "failed") continue;
229
+ result.ci = rewriteCi(plan.root, abs, result.deploy);
230
+ result.ops = collectOps(plan.root, abs);
231
+ for (const op of linearOpJobs(plan.root, abs)) result.ci.push(op);
232
+ dropChant(abs);
233
+ result.status = "exited";
234
+ }
235
+ setKindOther(plan.root, results.filter((r) => r.status === "exited"));
236
+ return { members: results };
237
+ }
238
+
239
+ function relPath(root: string, p: string): string {
240
+ return relative(root, p).split("\\").join("/");
241
+ }
242
+
243
+ function readJson(file: string): Record<string, unknown> | undefined {
244
+ try {
245
+ return JSON.parse(readFileSync(file, "utf-8")) as Record<string, unknown>;
246
+ } catch {
247
+ return undefined;
248
+ }
249
+ }
250
+
251
+ function walk(dir: string, visit: (file: string) => void): void {
252
+ if (!existsSync(dir)) return;
253
+ for (const e of readdirSync(dir, { withFileTypes: true })) {
254
+ if (e.name === "node_modules" || e.name === ".git" || e.name === "native") continue;
255
+ const p = join(dir, e.name);
256
+ if (e.isDirectory()) walk(p, visit);
257
+ else visit(p);
258
+ }
259
+ }
260
+
261
+ function ciFiles(abs: string): string[] {
262
+ const out: string[] = [];
263
+ for (const d of CI_GLOBS) walk(join(abs, d), (f) => /\.ya?ml$/.test(f) && out.push(f));
264
+ for (const f of CI_ROOT_FILES) if (existsSync(join(abs, f))) out.push(join(abs, f));
265
+ return out;
266
+ }
267
+
268
+ const CHANT_CALL = /^(\s*(?:-\s+)?(?:run:\s*)?)(?:npx\s+|pnpm\s+exec\s+|npm\s+exec\s+(?:--\s+)?)?chant\s+(?:build|deploy|apply)\b.*$/;
269
+
270
+ /**
271
+ * Replace each `chant build|deploy|apply` line of the member's CI files with
272
+ * the stock commands that deploy its native output. A line with no stock
273
+ * command to replace it (CI output, a collector config) is removed.
274
+ */
275
+ function rewriteCi(root: string, abs: string, deploy: readonly string[]): string[] {
276
+ const changed: string[] = [];
277
+ for (const file of ciFiles(abs)) {
278
+ const lines = readFileSync(file, "utf-8").split("\n");
279
+ let touched = false;
280
+ const next: string[] = [];
281
+ for (const line of lines) {
282
+ const m = CHANT_CALL.exec(line);
283
+ if (!m) {
284
+ next.push(line);
285
+ continue;
286
+ }
287
+ touched = true;
288
+ const lead = m[1]!;
289
+ if (deploy.length === 0) continue;
290
+ const inline = /run:/.test(lead);
291
+ if (inline) {
292
+ next.push(`${lead}${deploy.join(" && ")}`);
293
+ } else {
294
+ const indent = lead.replace(/-\s+$/, "").replace(/\S.*$/, "");
295
+ deploy.forEach((d, i) => next.push(i === 0 ? `${lead}${d}` : `${indent}${d}`));
296
+ }
297
+ }
298
+ if (touched) {
299
+ writeFileSync(file, next.join("\n"));
300
+ changed.push(relPath(root, file));
301
+ }
302
+ }
303
+ return changed;
304
+ }
305
+
306
+ /** `op.json` files under the member's `dist/ops/<name>/` or anywhere beside generated Ops. They stay. */
307
+ function collectOps(root: string, abs: string): string[] {
308
+ const out: string[] = [];
309
+ walk(abs, (f) => f.endsWith(`${"/"}op.json`) && out.push(relPath(root, f)));
310
+ return out.sort();
311
+ }
312
+
313
+ interface OpJson {
314
+ name?: string;
315
+ phases?: { parallel?: boolean; steps?: { kind?: string; fn?: string; args?: { cmd?: unknown } }[] }[];
316
+ onFailure?: unknown[];
317
+ }
318
+
319
+ /** The plain commands of a linear Op, or undefined when the Op is not linear. */
320
+ export function linearCommands(op: OpJson): string[] | undefined {
321
+ const cmds: string[] = [];
322
+ for (const phase of op.phases ?? []) {
323
+ if (phase.parallel) return undefined;
324
+ for (const step of phase.steps ?? []) {
325
+ if (step.kind !== "activity" || step.fn !== "shellCmd" || typeof step.args?.cmd !== "string") return undefined;
326
+ cmds.push(step.args.cmd);
327
+ }
328
+ }
329
+ if ((op.onFailure ?? []).length > 0) return undefined;
330
+ return cmds.length > 0 ? cmds : undefined;
331
+ }
332
+
333
+ /** For each linear Op, a plain GitHub Actions job beside its `op.json`. */
334
+ function linearOpJobs(root: string, abs: string): string[] {
335
+ const written: string[] = [];
336
+ walk(abs, (f) => {
337
+ if (!f.endsWith("/op.json")) return;
338
+ const op = readJson(f) as OpJson | undefined;
339
+ const cmds = op && linearCommands(op);
340
+ if (!op || !cmds) return;
341
+ const out = join(dirname(f), "job.yml");
342
+ const name = op.name ?? "op";
343
+ writeFileSync(
344
+ out,
345
+ [
346
+ `# Written by chant workspace exit from ${name}'s op.json. The op is linear, so it is one job.`,
347
+ `name: ${name}`,
348
+ "on: workflow_dispatch",
349
+ "jobs:",
350
+ ` ${name.replace(/[^\w-]/g, "-")}:`,
351
+ " runs-on: ubuntu-latest",
352
+ " steps:",
353
+ " - uses: actions/checkout@v4",
354
+ ...cmds.map((c, i) => ` - name: step ${i + 1}\n run: ${JSON.stringify(c)}`),
355
+ "",
356
+ ].join("\n"),
357
+ );
358
+ written.push(relPath(root, out));
359
+ });
360
+ return written;
361
+ }
362
+
363
+ /** Remove chant.config.* and the chant dependencies and scripts from the member's package.json. */
364
+ function dropChant(abs: string): void {
365
+ for (const f of CHANT_FILES) rmSync(join(abs, f), { force: true });
366
+ const file = join(abs, "package.json");
367
+ const pkg = readJson(file);
368
+ if (!pkg) return;
369
+ for (const section of ["dependencies", "devDependencies", "peerDependencies"]) {
370
+ const deps = pkg[section] as Record<string, string> | undefined;
371
+ if (!deps) continue;
372
+ for (const d of Object.keys(deps)) if (d === "@intentius/chant" || d.startsWith("@intentius/chant-")) delete deps[d];
373
+ if (Object.keys(deps).length === 0) delete pkg[section];
374
+ }
375
+ const scripts = pkg.scripts as Record<string, string> | undefined;
376
+ if (scripts) {
377
+ for (const [k, v] of Object.entries(scripts)) if (/\bchant\b/.test(v)) delete scripts[k];
378
+ if (Object.keys(scripts).length === 0) delete pkg.scripts;
379
+ }
380
+ writeFileSync(file, `${JSON.stringify(pkg, null, 2)}\n`);
381
+ }
382
+
383
+ /** Set each exited member to kind `other` in the declaration, with `because` and its outputs. */
384
+ function setKindOther(root: string, exited: MemberResult[]): void {
385
+ if (exited.length === 0) return;
386
+ const dec = readDeclaration(workingTree(root));
387
+ const file = ["chant.workspace.json", "chant.workspace.jsonc"].find((f) => existsSync(join(root, f)))!;
388
+ const jsonc = file.endsWith("c");
389
+ let text = readFileSync(join(root, file), "utf-8");
390
+ const raw = (JSON.parse(jsonc ? text.replace(/^\s*\/\/.*$/gm, "") : text) as { members: { name?: string }[] }).members;
391
+ for (const r of exited) {
392
+ const i = raw.findIndex((e) => e.name === r.name);
393
+ if (i < 0) continue;
394
+ const ptr = `/members/${i}`;
395
+ const member = dec.members.find((m) => m.type === "member" && m.name === r.name);
396
+ text = setProperty(text, jsonc, ptr, "kind", "other");
397
+ text = setProperty(text, jsonc, ptr, "because", `left chant with chant workspace exit; its native output is committed under ${r.native.map((n) => relPath(join(root, r.dir), join(root, n))).join(", ")}`);
398
+ const outputs = member?.outputs ?? [];
399
+ if (outputs.length > 0) text = setProperty(text, jsonc, ptr, "outputs", outputs);
400
+ }
401
+ writeFileSync(join(root, file), text);
402
+ }
403
+
404
+ // ── The drill ────────────────────────────────────────────────────────────────
405
+
406
+ /** What the exit drill finds wrong with an exited member. Closed; each is in `reason-codes.ts`. */
407
+ export const DRILL_CODES = [
408
+ "native-missing",
409
+ "native-unreadable",
410
+ "chant-dependency",
411
+ "chant-config-left",
412
+ "chant-in-ci",
413
+ "no-deploy-command",
414
+ "declaration-not-other",
415
+ "exit-failed",
416
+ "choudoufu-steps-missing",
417
+ ] as const satisfies readonly ReasonCode[];
418
+ export type DrillCode = (typeof DRILL_CODES)[number];
419
+
420
+ /** Every code `workspace exit` prints: why a member was refused, and what the drill found. */
421
+ export const EXIT_CODES = [...EXIT_REFUSAL_CODES, ...DRILL_CODES] as const;
422
+
423
+ export interface DrillFinding {
424
+ code: DrillCode;
425
+ message: string;
426
+ }
427
+
428
+ export interface DrillMember {
429
+ name: string;
430
+ kind: string;
431
+ status: "passed" | "failed" | "not-run" | "kept";
432
+ /** Why it is `not-run` or `kept`. */
433
+ note?: string;
434
+ findings: DrillFinding[];
435
+ native: string[];
436
+ deploy: string[];
437
+ }
438
+
439
+ export interface DrillReport {
440
+ /** True when no member failed. A `not-run` member does not fail the drill; it is listed. */
441
+ ok: boolean;
442
+ members: DrillMember[];
443
+ }
444
+
445
+ export interface DrillOptions {
446
+ /** The workspace root to drill. It is copied; the tree is not touched. */
447
+ root: string;
448
+ /** Limit to these members. */
449
+ members?: readonly string[];
450
+ /** Builds a member's native output. */
451
+ runBuild: ExitBuildRunner;
452
+ }
453
+
454
+ /** Copy a workspace, leaving out `node_modules`, `.git` and chant's own scratch. */
455
+ export function copyWorkspace(from: string, to: string): void {
456
+ const skip = new Set(["node_modules", ".git", ".chant"]);
457
+ const copy = (src: string, dst: string): void => {
458
+ mkdirSync(dst, { recursive: true });
459
+ for (const e of readdirSync(src, { withFileTypes: true })) {
460
+ if (skip.has(e.name)) continue;
461
+ const s = join(src, e.name);
462
+ const d = join(dst, e.name);
463
+ if (e.isDirectory()) copy(s, d);
464
+ else if (e.isFile()) copyFileSync(s, d);
465
+ }
466
+ };
467
+ copy(from, to);
468
+ }
469
+
470
+ const CHANT_IN_CI = /\bchant\s+(?:build|lint|deploy|apply|run|workspace|ci)\b|@intentius\/chant\b/;
471
+
472
+ /**
473
+ * Run the exit on a scratch copy and check each member for what a stock tool
474
+ * needs. Nothing here runs chant or a cloud; the build is the one injected
475
+ * step, and its output is read as text.
476
+ */
477
+ export async function runExitDrill(opts: DrillOptions): Promise<DrillReport> {
478
+ const scratch = mkdtempSync(join(tmpdir(), "chant-exit-drill-"));
479
+ try {
480
+ copyWorkspace(opts.root, scratch);
481
+ const plan = planExit(scratch, opts.members ?? []);
482
+ const result = await exitInDir(plan, opts.runBuild);
483
+ return verifyExit(scratch, result);
484
+ } finally {
485
+ rmSync(scratch, { recursive: true, force: true });
486
+ }
487
+ }
488
+
489
+ /** Check an exited tree at `root` against what {@link exitInDir} reported. */
490
+ export function verifyExit(root: string, result: ExitResult): DrillReport {
491
+ const declared = new Map(readDeclaration(workingTree(root)).members.filter((m) => m.type === "member").map((m) => [m.name, m]));
492
+ const members = result.members.map((r) => checkMember(root, r, declared.get(r.name)));
493
+ return { ok: members.every((m) => m.status !== "failed"), members };
494
+ }
495
+
496
+ function checkMember(root: string, r: MemberResult, declared: Member | undefined): DrillMember {
497
+ const base = { name: r.name, kind: r.kind, native: r.native, deploy: r.deploy };
498
+ const findings: DrillFinding[] = [];
499
+ if (r.status === "kept") return { ...base, status: "kept", note: `kind ${r.kind} stays as files`, findings };
500
+ if (r.status === "manual") {
501
+ if (r.steps.length === 0) findings.push({ code: "choudoufu-steps-missing", message: "the roundtrip steps were not listed" });
502
+ return findings.length > 0
503
+ ? { ...base, status: "failed", findings }
504
+ : { ...base, status: "not-run", note: "the stock OpenTofu roundtrip (copy the cache to terraform.tfstate, remove the live block, plan) is not run, needs the maintainer", findings };
505
+ }
506
+ if (r.status !== "exited") {
507
+ return { ...base, status: "failed", findings: [{ code: "exit-failed", message: r.detail ?? `exit ${r.status}` }] };
508
+ }
509
+ const abs = resolve(root, r.dir);
510
+ for (const n of r.native) {
511
+ const p = join(root, n);
512
+ if (!existsSync(p) || statSync(p).size === 0) {
513
+ findings.push({ code: "native-missing", message: `${n} is missing or empty` });
514
+ continue;
515
+ }
516
+ const text = readFileSync(p, "utf-8");
517
+ if (p.endsWith(".json")) {
518
+ try {
519
+ JSON.parse(text);
520
+ } catch (e) {
521
+ findings.push({ code: "native-unreadable", message: `${n} is not valid JSON: ${(e as Error).message}` });
522
+ }
523
+ } else if (/^\s*$/.test(text) || /^\s*[{[]?\s*[}\]]?\s*$/.test(text)) {
524
+ findings.push({ code: "native-unreadable", message: `${n} has no content` });
525
+ }
526
+ }
527
+ const deployable = r.native.filter((n) => !/\.github\/|\.gitlab-ci|otel-collector/.test(n));
528
+ if (deployable.length > 0 && r.deploy.length === 0) findings.push({ code: "no-deploy-command", message: "a native file has no stock deploy command" });
529
+
530
+ for (const f of CHANT_FILES) if (existsSync(join(abs, f))) findings.push({ code: "chant-config-left", message: `${join(r.dir, f)} is still there` });
531
+ const pkg = readJson(join(abs, "package.json"));
532
+ if (pkg) {
533
+ for (const section of ["dependencies", "devDependencies", "peerDependencies"]) {
534
+ for (const d of Object.keys((pkg[section] as Record<string, string> | undefined) ?? {})) {
535
+ if (d === "@intentius/chant" || d.startsWith("@intentius/chant-")) findings.push({ code: "chant-dependency", message: `package.json ${section} still has ${d}` });
536
+ }
537
+ }
538
+ for (const [k, v] of Object.entries((pkg.scripts as Record<string, string> | undefined) ?? {})) {
539
+ if (/\bchant\b/.test(v)) findings.push({ code: "chant-dependency", message: `package.json script "${k}" still calls chant` });
540
+ }
541
+ }
542
+ for (const f of ciFiles(abs)) {
543
+ const text = readFileSync(f, "utf-8");
544
+ const bad = text.split("\n").find((l) => CHANT_IN_CI.test(l) && !l.trim().startsWith("#"));
545
+ if (bad) findings.push({ code: "chant-in-ci", message: `${relPath(root, f)} still calls chant: ${bad.trim()}` });
546
+ }
547
+ if (declared && declared.kind !== "other") findings.push({ code: "declaration-not-other", message: `the declaration still has kind ${declared.kind}` });
548
+ return { ...base, status: findings.length > 0 ? "failed" : "passed", findings };
549
+ }
550
+
551
+ /** The drill report as lines for a terminal. */
552
+ export function describeDrill(report: DrillReport): string[] {
553
+ const lines: string[] = [];
554
+ for (const m of report.members) {
555
+ lines.push(`${m.name} ${m.kind} ${m.status}${m.note ? ` (${m.note})` : ""}`);
556
+ for (const f of m.findings) lines.push(` ${f.code}: ${f.message}`);
557
+ }
558
+ lines.push(report.ok ? "exit drill passed" : "exit drill failed");
559
+ return lines;
560
+ }
@@ -0,0 +1,113 @@
1
+ /**
2
+ * The `flyway-dir` reader (#4400): the migrations of a Flyway project, in the
3
+ * order `flyway migrate` applies them. It reads the layout with
4
+ * `./flyway-layout.ts`, the reading `yodel check` shares, and never runs
5
+ * Flyway or substitutes placeholders (a replay does that).
6
+ *
7
+ * File names follow Flyway's migration naming
8
+ * (https://documentation.red-gate.com/fd/migrations-271585107.html):
9
+ *
10
+ * - versioned `V<version>__<description>.sql`, the version digits joined by
11
+ * `.` or `_`;
12
+ * - repeatable `R__<description>.sql`, run after every versioned migration,
13
+ * in the order of their descriptions;
14
+ * - baseline `B<version>__<description>.sql`, listed with the versioned ones;
15
+ * - undo `U<version>__<description>.sql`, which is the `down` file of the
16
+ * versioned migration with that version and is not listed on its own.
17
+ *
18
+ * The prefixes, the separator and the suffixes come from the configuration.
19
+ * Versions compare part by part as numbers, so `V2` comes before `V10` and
20
+ * `V1.1` before `V1.2`. Callbacks are not migrations, and a Java or Kotlin
21
+ * migration is a gap: its code is not SQL.
22
+ */
23
+
24
+ import type { KindLinkRead, KindReadGap, KindReadMigration, KindReaderInput } from "./kind-link-readers";
25
+ import { codeMigrations, findFlywayConfig, isCallback, readFlywayConfig, walk, type FlywayConfig } from "./flyway-layout";
26
+ import { fieldString, withReplay } from "./migration-dirs";
27
+
28
+ const escape = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
29
+
30
+ /** Compare dotted versions part by part as numbers; a missing part is 0. */
31
+ export function compareVersions(a: string, b: string): number {
32
+ const pa = a.split(/[._]/).map(BigInt);
33
+ const pb = b.split(/[._]/).map(BigInt);
34
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
35
+ const x = pa[i] ?? 0n;
36
+ const y = pb[i] ?? 0n;
37
+ if (x !== y) return x < y ? -1 : 1;
38
+ }
39
+ return 0;
40
+ }
41
+
42
+ export interface FlywayFile {
43
+ type: KindReadMigration["type"];
44
+ /** The version written with `.` between its parts; empty for a repeatable migration. */
45
+ version: string;
46
+ description: string;
47
+ file: string;
48
+ }
49
+
50
+ /** The file's migration role under `config`'s naming, or undefined when its name is not a migration's. */
51
+ export function classifyFlywayFile(file: string, config: FlywayConfig): FlywayFile | undefined {
52
+ const name = file.split("/").pop() ?? file;
53
+ if (isCallback(name)) return undefined;
54
+ const suffix = config.sqlMigrationSuffixes.find((s) => name.endsWith(s));
55
+ if (!suffix) return undefined;
56
+ const stem = name.slice(0, -suffix.length);
57
+ const sep = escape(config.sqlMigrationSeparator);
58
+ const repeatable = new RegExp(`^${escape(config.repeatableSqlMigrationPrefix)}${sep}(.+)$`).exec(stem);
59
+ if (repeatable) return { type: "repeatable", version: "", description: repeatable[1]!.replace(/_/g, " "), file };
60
+ const roles: [KindReadMigration["type"], string][] = [
61
+ ["versioned", config.sqlMigrationPrefix],
62
+ ["baseline", config.baselineMigrationPrefix],
63
+ ["undo", config.undoSqlMigrationPrefix],
64
+ ];
65
+ for (const [type, prefix] of roles) {
66
+ if (!prefix) continue;
67
+ const m = new RegExp(`^${escape(prefix)}([0-9]+(?:[._][0-9]+)*)${sep}(.+)$`).exec(stem);
68
+ if (m) return { type, version: m[1]!.replace(/_/g, "."), description: m[2]!.replace(/_/g, " "), file };
69
+ }
70
+ return undefined;
71
+ }
72
+
73
+ /** The configuration file the replay depends on, besides the migrations: it holds the placeholders. */
74
+ export function flywayReplayFiles(input: Pick<KindReaderInput, "tree" | "dir">): string[] {
75
+ const f = findFlywayConfig(input.tree, input.dir);
76
+ return f === undefined ? [] : [f];
77
+ }
78
+
79
+ export const readFlywayDir = (input: KindReaderInput): KindLinkRead => withReplay(listFlywayDir(input), input.tree, input.dir, flywayReplayFiles(input));
80
+
81
+ function listFlywayDir(input: KindReaderInput): KindLinkRead {
82
+ const { tree, dir } = input;
83
+ const gaps: KindReadGap[] = [];
84
+ const result: KindLinkRead = { reader: "flyway-dir", links: [], gaps, migrations: [] };
85
+ const configFile = findFlywayConfig(tree, dir);
86
+ let config: FlywayConfig;
87
+ try {
88
+ config = readFlywayConfig(tree, configFile, dir);
89
+ } catch (err) {
90
+ gaps.push({ file: configFile ?? dir, line: 1, message: `the Flyway configuration could not be read (${(err as Error).message}), so no migration was listed` });
91
+ return result;
92
+ }
93
+ const dialect = fieldString(input.fields, "dialect", config.dialect ?? "postgres");
94
+ for (const l of config.outside) gaps.push({ file: configFile ?? dir, line: 1, message: `the location ${l} lies outside the workspace, so its migrations were not listed` });
95
+
96
+ const files = [...new Set(config.locations.flatMap((d) => walk(tree, d)))];
97
+ const classified = files.map((f) => classifyFlywayFile(f, config)).filter((f): f is FlywayFile => f !== undefined);
98
+ for (const code of codeMigrations(tree, config)) gaps.push({ file: code, line: 1, message: `${code.split("/").pop()!} is a code migration; its code is not SQL, so it is not listed` });
99
+
100
+ const undo = new Map(classified.filter((f) => f.type === "undo").map((f) => [f.version, f.file]));
101
+ const ordered = classified.filter((f) => f.type === "versioned" || f.type === "baseline").sort((a, b) => compareVersions(a.version, b.version) || (a.type === "baseline" ? -1 : 1) || (a.file < b.file ? -1 : 1));
102
+ const repeatable = classified.filter((f) => f.type === "repeatable").sort((a, b) => (a.description < b.description ? -1 : a.description > b.description ? 1 : a.file < b.file ? -1 : 1));
103
+ const seen = new Map<string, string>();
104
+ for (const f of ordered.filter((x) => x.type === "versioned")) {
105
+ const prior = [...seen.keys()].find((v) => compareVersions(v, f.version) === 0);
106
+ if (prior !== undefined) gaps.push({ file: f.file, line: 1, message: `version ${f.version} is also ${seen.get(prior)!}; Flyway refuses two migrations with one version` });
107
+ else seen.set(f.version, f.file);
108
+ }
109
+ [...ordered, ...repeatable].forEach((f, i) =>
110
+ result.migrations!.push({ position: i + 1, version: f.version, name: f.description, type: f.type, file: f.file, line: 1, down: f.type === "versioned" ? (undo.get(f.version) ?? false) : false, dialect }),
111
+ );
112
+ return result;
113
+ }