@intentius/chant 0.124.46 → 0.124.48

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 (633) 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 +21 -2
  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/ledger.d.ts +3 -0
  63. package/dist/cli/handlers/ledger.d.ts.map +1 -0
  64. package/dist/cli/handlers/ledger.js +55 -0
  65. package/dist/cli/handlers/migrate.d.ts.map +1 -1
  66. package/dist/cli/handlers/migrate.js +18 -11
  67. package/dist/cli/handlers/misc.d.ts.map +1 -1
  68. package/dist/cli/handlers/misc.js +12 -1
  69. package/dist/cli/handlers/pr-env.js +2 -2
  70. package/dist/cli/handlers/pr.d.ts +15 -1
  71. package/dist/cli/handlers/pr.d.ts.map +1 -1
  72. package/dist/cli/handlers/pr.js +143 -39
  73. package/dist/cli/handlers/query.d.ts +7 -2
  74. package/dist/cli/handlers/query.d.ts.map +1 -1
  75. package/dist/cli/handlers/query.js +16 -4
  76. package/dist/cli/handlers/release.d.ts.map +1 -1
  77. package/dist/cli/handlers/release.js +6 -3
  78. package/dist/cli/handlers/search.d.ts +2 -4
  79. package/dist/cli/handlers/search.d.ts.map +1 -1
  80. package/dist/cli/handlers/search.js +13 -39
  81. package/dist/cli/main.d.ts.map +1 -1
  82. package/dist/cli/main.js +65 -14
  83. package/dist/cli/parse-args.d.ts.map +1 -1
  84. package/dist/cli/parse-args.js +36 -2
  85. package/dist/cli/registry.d.ts +14 -4
  86. package/dist/cli/registry.d.ts.map +1 -1
  87. package/dist/components/builders.d.ts +5 -1
  88. package/dist/components/builders.d.ts.map +1 -1
  89. package/dist/components/builders.js +5 -0
  90. package/dist/components/capability-plugin-loader.d.ts.map +1 -1
  91. package/dist/components/capability-plugin-loader.js +16 -1
  92. package/dist/components/capability-plugin.d.ts +14 -0
  93. package/dist/components/capability-plugin.d.ts.map +1 -1
  94. package/dist/components/pr-hooks.d.ts +21 -1
  95. package/dist/components/pr-hooks.d.ts.map +1 -1
  96. package/dist/components/pr-hooks.js +72 -4
  97. package/dist/components/pr-pipeline.d.ts +45 -10
  98. package/dist/components/pr-pipeline.d.ts.map +1 -1
  99. package/dist/components/pr-pipeline.js +47 -14
  100. package/dist/components/pr-policy.d.ts +36 -0
  101. package/dist/components/pr-policy.d.ts.map +1 -1
  102. package/dist/components/pr-policy.js +36 -0
  103. package/dist/components/starter-plugin.d.ts +1 -0
  104. package/dist/components/starter-plugin.d.ts.map +1 -1
  105. package/dist/components/starter-plugin.js +5 -1
  106. package/dist/components/verbs/index.d.ts +2 -0
  107. package/dist/components/verbs/index.d.ts.map +1 -1
  108. package/dist/components/verbs/index.js +2 -0
  109. package/dist/components/verbs/publish-artifact.d.ts +65 -0
  110. package/dist/components/verbs/publish-artifact.d.ts.map +1 -0
  111. package/dist/components/verbs/publish-artifact.js +74 -0
  112. package/dist/components/verbs/publish-image.d.ts +129 -0
  113. package/dist/components/verbs/publish-image.d.ts.map +1 -0
  114. package/dist/components/verbs/publish-image.js +145 -0
  115. package/dist/config-environments.d.ts +20 -0
  116. package/dist/config-environments.d.ts.map +1 -1
  117. package/dist/config-environments.js +12 -0
  118. package/dist/config.d.ts +9 -1
  119. package/dist/config.d.ts.map +1 -1
  120. package/dist/config.js +6 -2
  121. package/dist/gate-decision.d.ts +11 -2
  122. package/dist/gate-decision.d.ts.map +1 -1
  123. package/dist/gate-decision.js +15 -3
  124. package/dist/graph-ir.d.ts +25 -0
  125. package/dist/graph-ir.d.ts.map +1 -1
  126. package/dist/graph-walk.d.ts +66 -0
  127. package/dist/graph-walk.d.ts.map +1 -0
  128. package/dist/graph-walk.js +70 -0
  129. package/dist/import/parser.d.ts +19 -0
  130. package/dist/import/parser.d.ts.map +1 -1
  131. package/dist/lexicon.d.ts +71 -6
  132. package/dist/lexicon.d.ts.map +1 -1
  133. package/dist/lifecycle/gate-ledger.d.ts +11 -0
  134. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  135. package/dist/lifecycle/git.d.ts +2 -5
  136. package/dist/lifecycle/git.d.ts.map +1 -1
  137. package/dist/lifecycle/ledger-read.d.ts +65 -0
  138. package/dist/lifecycle/ledger-read.d.ts.map +1 -0
  139. package/dist/lifecycle/ledger-read.js +160 -0
  140. package/dist/lifecycle/release-workstation.d.ts +7 -0
  141. package/dist/lifecycle/release-workstation.d.ts.map +1 -1
  142. package/dist/lifecycle/release-workstation.js +25 -0
  143. package/dist/lifecycle/run-ledger.d.ts +4 -3
  144. package/dist/lifecycle/run-ledger.d.ts.map +1 -1
  145. package/dist/lifecycle/run-ledger.js +1 -0
  146. package/dist/lint/discover.js +3 -1
  147. package/dist/lint/post-synth.d.ts +127 -1
  148. package/dist/lint/post-synth.d.ts.map +1 -1
  149. package/dist/lint/post-synth.js +84 -2
  150. package/dist/lint/rules/op/index.d.ts +1 -0
  151. package/dist/lint/rules/op/index.d.ts.map +1 -1
  152. package/dist/lint/rules/op/index.js +2 -0
  153. package/dist/lint/rules/op/ops019-workspace-op-target.d.ts +32 -0
  154. package/dist/lint/rules/op/ops019-workspace-op-target.d.ts.map +1 -0
  155. package/dist/lint/rules/op/ops019-workspace-op-target.js +63 -0
  156. package/dist/op/activities/activity-contracts.d.ts +5 -2
  157. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  158. package/dist/op/activities/activity-contracts.js +5 -2
  159. package/dist/op/activities/index.d.ts +2 -2
  160. package/dist/op/activities/index.d.ts.map +1 -1
  161. package/dist/op/activities/index.js +1 -1
  162. package/dist/op/activities/review-agent.d.ts +90 -4
  163. package/dist/op/activities/review-agent.d.ts.map +1 -1
  164. package/dist/op/activities/review-agent.js +217 -19
  165. package/dist/op/activities/work-evidence.d.ts +8 -1
  166. package/dist/op/activities/work-evidence.d.ts.map +1 -1
  167. package/dist/op/activities/work-evidence.js +1 -0
  168. package/dist/op/builders.d.ts +9 -5
  169. package/dist/op/builders.d.ts.map +1 -1
  170. package/dist/op/builders.js +9 -5
  171. package/dist/op/composites/apply-op.d.ts +29 -2
  172. package/dist/op/composites/apply-op.d.ts.map +1 -1
  173. package/dist/op/composites/apply-op.js +20 -1
  174. package/dist/op/gate-approval.d.ts +11 -5
  175. package/dist/op/gate-approval.d.ts.map +1 -1
  176. package/dist/op/gate-approval.js +11 -6
  177. package/dist/op/gate-quorum.d.ts +100 -3
  178. package/dist/op/gate-quorum.d.ts.map +1 -1
  179. package/dist/op/gate-quorum.js +108 -6
  180. package/dist/op/gate-review.d.ts +9 -0
  181. package/dist/op/gate-review.d.ts.map +1 -1
  182. package/dist/op/gate-review.js +13 -0
  183. package/dist/op/gate.d.ts +44 -3
  184. package/dist/op/gate.d.ts.map +1 -1
  185. package/dist/op/gate.js +25 -4
  186. package/dist/op/index.d.ts +3 -3
  187. package/dist/op/index.d.ts.map +1 -1
  188. package/dist/op/index.js +1 -1
  189. package/dist/op/local-executor.d.ts +38 -3
  190. package/dist/op/local-executor.d.ts.map +1 -1
  191. package/dist/op/local-executor.js +48 -6
  192. package/dist/op/op-waves-run.d.ts +26 -1
  193. package/dist/op/op-waves-run.d.ts.map +1 -1
  194. package/dist/op/op-waves-run.js +259 -13
  195. package/dist/op/op-waves.d.ts +72 -5
  196. package/dist/op/op-waves.d.ts.map +1 -1
  197. package/dist/op/op-waves.js +110 -15
  198. package/dist/op/runtime.d.ts +11 -0
  199. package/dist/op/runtime.d.ts.map +1 -1
  200. package/dist/op/schedule-overdue.d.ts +16 -0
  201. package/dist/op/schedule-overdue.d.ts.map +1 -1
  202. package/dist/op/schedule-overdue.js +36 -0
  203. package/dist/op/stage-hooks.d.ts +9 -2
  204. package/dist/op/stage-hooks.d.ts.map +1 -1
  205. package/dist/op/stage-hooks.js +7 -5
  206. package/dist/op/workspace-run-env.d.ts +22 -0
  207. package/dist/op/workspace-run-env.d.ts.map +1 -0
  208. package/dist/op/workspace-run-env.js +21 -0
  209. package/dist/pr-forge.d.ts +24 -0
  210. package/dist/pr-forge.d.ts.map +1 -1
  211. package/dist/pr-forge.js +47 -0
  212. package/dist/pr-post.d.ts +42 -0
  213. package/dist/pr-post.d.ts.map +1 -1
  214. package/dist/pr-post.js +67 -1
  215. package/dist/read-filter.d.ts +65 -0
  216. package/dist/read-filter.d.ts.map +1 -0
  217. package/dist/read-filter.js +179 -0
  218. package/dist/run-event.d.ts +1 -1
  219. package/dist/run-event.d.ts.map +1 -1
  220. package/dist/terraform/carve.d.ts +24 -3
  221. package/dist/terraform/carve.d.ts.map +1 -1
  222. package/dist/terraform/carve.js +32 -14
  223. package/dist/terraform/cuts.d.ts +97 -0
  224. package/dist/terraform/cuts.d.ts.map +1 -0
  225. package/dist/terraform/cuts.js +138 -0
  226. package/dist/terraform/folds.d.ts +26 -0
  227. package/dist/terraform/folds.d.ts.map +1 -0
  228. package/dist/terraform/folds.js +38 -0
  229. package/dist/terraform/graph.d.ts.map +1 -1
  230. package/dist/terraform/graph.js +10 -0
  231. package/dist/terraform/reach.d.ts +140 -0
  232. package/dist/terraform/reach.d.ts.map +1 -0
  233. package/dist/terraform/reach.js +170 -0
  234. package/dist/terraform/score.d.ts +14 -0
  235. package/dist/terraform/score.d.ts.map +1 -1
  236. package/dist/terraform/score.js +14 -31
  237. package/dist/terraform/state.d.ts.map +1 -1
  238. package/dist/terraform/state.js +4 -2
  239. package/dist/terraform/types.d.ts +8 -0
  240. package/dist/terraform/types.d.ts.map +1 -1
  241. package/dist/wave-blocked.d.ts +10 -1
  242. package/dist/wave-blocked.d.ts.map +1 -1
  243. package/dist/wave-blocked.js +31 -3
  244. package/dist/wave-runtime.mjs +263 -254
  245. package/dist/workspace/check.schema.json +11 -2
  246. package/dist/workspace/checks/ci-branch.d.ts +44 -0
  247. package/dist/workspace/checks/ci-branch.d.ts.map +1 -0
  248. package/dist/workspace/checks/ci-branch.js +133 -0
  249. package/dist/workspace/checks/links.d.ts +3 -0
  250. package/dist/workspace/checks/links.d.ts.map +1 -1
  251. package/dist/workspace/checks/links.js +12 -1
  252. package/dist/workspace/checks/org.d.ts +48 -0
  253. package/dist/workspace/checks/org.d.ts.map +1 -0
  254. package/dist/workspace/checks/org.js +156 -0
  255. package/dist/workspace/checks.d.ts +11 -1
  256. package/dist/workspace/checks.d.ts.map +1 -1
  257. package/dist/workspace/checks.js +59 -7
  258. package/dist/workspace/ci-branches.d.ts +16 -0
  259. package/dist/workspace/ci-branches.d.ts.map +1 -1
  260. package/dist/workspace/ci-branches.js +27 -0
  261. package/dist/workspace/ci-green-cli.js +7 -7
  262. package/dist/workspace/ci-green-workflow.d.ts +1 -1
  263. package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
  264. package/dist/workspace/ci-green-workflow.js +13 -8
  265. package/dist/workspace/ci-green.d.ts +14 -0
  266. package/dist/workspace/ci-green.d.ts.map +1 -1
  267. package/dist/workspace/ci-green.js +44 -11
  268. package/dist/workspace/ci-run.d.ts +9 -1
  269. package/dist/workspace/ci-run.d.ts.map +1 -1
  270. package/dist/workspace/ci-run.js +67 -6
  271. package/dist/workspace/ci-serve.d.ts +9 -0
  272. package/dist/workspace/ci-serve.d.ts.map +1 -1
  273. package/dist/workspace/ci-serve.js +114 -76
  274. package/dist/workspace/compose-graph.d.ts +14 -0
  275. package/dist/workspace/compose-graph.d.ts.map +1 -1
  276. package/dist/workspace/composites.d.ts +4 -1
  277. package/dist/workspace/composites.d.ts.map +1 -1
  278. package/dist/workspace/composites.js +5 -2
  279. package/dist/workspace/composites.schema.json +22 -0
  280. package/dist/workspace/conformance/__writer_fixture__/work/work.schema.json +105 -1
  281. package/dist/workspace/cross-root-lint.d.ts +56 -0
  282. package/dist/workspace/cross-root-lint.d.ts.map +1 -0
  283. package/dist/workspace/cross-root-lint.js +111 -0
  284. package/dist/workspace/dbt-models-reader.d.ts +44 -0
  285. package/dist/workspace/dbt-models-reader.d.ts.map +1 -0
  286. package/dist/workspace/dbt-models-reader.js +356 -0
  287. package/dist/workspace/dbt-project-common.d.ts +40 -0
  288. package/dist/workspace/dbt-project-common.d.ts.map +1 -0
  289. package/dist/workspace/dbt-project-common.js +106 -0
  290. package/dist/workspace/dbt-project-reader.d.ts +4 -3
  291. package/dist/workspace/dbt-project-reader.d.ts.map +1 -1
  292. package/dist/workspace/dbt-project-reader.js +14 -108
  293. package/dist/workspace/decision-rank.d.ts +26 -0
  294. package/dist/workspace/decision-rank.d.ts.map +1 -0
  295. package/dist/workspace/decision-rank.js +16 -0
  296. package/dist/workspace/declaration.d.ts +43 -2
  297. package/dist/workspace/declaration.d.ts.map +1 -1
  298. package/dist/workspace/declaration.js +45 -11
  299. package/dist/workspace/declaration.schema.json +133 -24
  300. package/dist/workspace/environments.d.ts +26 -2
  301. package/dist/workspace/environments.d.ts.map +1 -1
  302. package/dist/workspace/environments.js +26 -2
  303. package/dist/workspace/flyway-dir.d.ts +39 -0
  304. package/dist/workspace/flyway-dir.d.ts.map +1 -0
  305. package/dist/workspace/flyway-dir.js +104 -0
  306. package/dist/workspace/flyway-layout.d.ts +96 -0
  307. package/dist/workspace/flyway-layout.d.ts.map +1 -0
  308. package/dist/workspace/flyway-layout.js +308 -0
  309. package/dist/workspace/graph-cli.d.ts.map +1 -1
  310. package/dist/workspace/graph-cli.js +26 -8
  311. package/dist/workspace/graph.schema.json +34 -0
  312. package/dist/workspace/intent.d.ts +2 -3
  313. package/dist/workspace/intent.d.ts.map +1 -1
  314. package/dist/workspace/intent.js +5 -9
  315. package/dist/workspace/kind-link-readers.d.ts +59 -2
  316. package/dist/workspace/kind-link-readers.d.ts.map +1 -1
  317. package/dist/workspace/kind-link-readers.js +17 -1
  318. package/dist/workspace/kinds.d.ts +66 -2
  319. package/dist/workspace/kinds.d.ts.map +1 -1
  320. package/dist/workspace/kinds.js +98 -4
  321. package/dist/workspace/ledger.schema.json +158 -0
  322. package/dist/workspace/lexicon-outputs.d.ts +20 -1
  323. package/dist/workspace/lexicon-outputs.d.ts.map +1 -1
  324. package/dist/workspace/lexicon-outputs.js +102 -10
  325. package/dist/workspace/lineage-check.d.ts +3 -0
  326. package/dist/workspace/lineage-check.d.ts.map +1 -1
  327. package/dist/workspace/lineage-check.js +23 -4
  328. package/dist/workspace/links.d.ts +8 -1
  329. package/dist/workspace/links.d.ts.map +1 -1
  330. package/dist/workspace/links.js +25 -1
  331. package/dist/workspace/ls.d.ts +8 -0
  332. package/dist/workspace/ls.d.ts.map +1 -1
  333. package/dist/workspace/ls.js +7 -1
  334. package/dist/workspace/ls.schema.json +7 -0
  335. package/dist/workspace/member-commands.d.ts.map +1 -1
  336. package/dist/workspace/member-commands.js +54 -5
  337. package/dist/workspace/migrate-links.d.ts +45 -0
  338. package/dist/workspace/migrate-links.d.ts.map +1 -0
  339. package/dist/workspace/migrate-links.js +100 -0
  340. package/dist/workspace/migration-dirs.d.ts +74 -0
  341. package/dist/workspace/migration-dirs.d.ts.map +1 -0
  342. package/dist/workspace/migration-dirs.js +254 -0
  343. package/dist/workspace/reason-codes.d.ts +8 -0
  344. package/dist/workspace/reason-codes.d.ts.map +1 -1
  345. package/dist/workspace/reason-codes.js +11 -0
  346. package/dist/workspace/records-cli.d.ts +11 -0
  347. package/dist/workspace/records-cli.d.ts.map +1 -1
  348. package/dist/workspace/records-cli.js +57 -5
  349. package/dist/workspace/records.schema.json +5 -0
  350. package/dist/workspace/release-candidates.d.ts +2 -0
  351. package/dist/workspace/release-candidates.d.ts.map +1 -1
  352. package/dist/workspace/release-candidates.js +5 -2
  353. package/dist/workspace/release-train.d.ts +53 -0
  354. package/dist/workspace/release-train.d.ts.map +1 -0
  355. package/dist/workspace/release-train.js +134 -0
  356. package/dist/workspace/release-workflow.d.ts.map +1 -1
  357. package/dist/workspace/release-workflow.js +4 -3
  358. package/dist/workspace/repo-ref.d.ts +45 -0
  359. package/dist/workspace/repo-ref.d.ts.map +1 -0
  360. package/dist/workspace/repo-ref.js +114 -0
  361. package/dist/workspace/run-event.schema.json +2 -2
  362. package/dist/workspace/runs-cli.d.ts +9 -0
  363. package/dist/workspace/runs-cli.d.ts.map +1 -1
  364. package/dist/workspace/runs-cli.js +48 -3
  365. package/dist/workspace/runs.schema.json +7 -0
  366. package/dist/workspace/search-contract.d.ts +1 -1
  367. package/dist/workspace/search-contract.d.ts.map +1 -1
  368. package/dist/workspace/status-decisions.d.ts +37 -0
  369. package/dist/workspace/status-decisions.d.ts.map +1 -0
  370. package/dist/workspace/status-decisions.js +147 -0
  371. package/dist/workspace/status-gates.d.ts +9 -0
  372. package/dist/workspace/status-gates.d.ts.map +1 -1
  373. package/dist/workspace/status.d.ts +4 -0
  374. package/dist/workspace/status.d.ts.map +1 -1
  375. package/dist/workspace/status.js +14 -2
  376. package/dist/workspace/status.schema.json +6 -0
  377. package/dist/workspace/work-cli.d.ts.map +1 -1
  378. package/dist/workspace/work-cli.js +5 -3
  379. package/dist/workspace/work-evidence-ops.d.ts +83 -0
  380. package/dist/workspace/work-evidence-ops.d.ts.map +1 -0
  381. package/dist/workspace/work-evidence-ops.js +169 -0
  382. package/dist/workspace/work-evidence.d.ts +12 -2
  383. package/dist/workspace/work-evidence.d.ts.map +1 -1
  384. package/dist/workspace/work-evidence.js +21 -3
  385. package/dist/workspace/work-evidence.schema.json +49 -2
  386. package/dist/workspace/workspace-gates.d.ts +110 -0
  387. package/dist/workspace/workspace-gates.d.ts.map +1 -0
  388. package/dist/workspace/workspace-gates.js +140 -0
  389. package/dist/workspace/workspace-kinds.schema.json +58 -3
  390. package/dist/workspace/workspace-ledger.d.ts +76 -0
  391. package/dist/workspace/workspace-ledger.d.ts.map +1 -0
  392. package/dist/workspace/workspace-ledger.js +91 -0
  393. package/dist/workspace/workspace-run.d.ts +112 -0
  394. package/dist/workspace/workspace-run.d.ts.map +1 -0
  395. package/dist/workspace/workspace-run.js +432 -0
  396. package/package.json +1 -1
  397. package/src/__golden__/wave-loop/op-by-apply-after-fails.json +10 -0
  398. package/src/__golden__/wave-loop/op-by-apply-hold.json +10 -0
  399. package/src/__golden__/wave-loop/op-by-apply-ignored.json +10 -0
  400. package/src/__golden__/wave-loop/op-by-apply-pass.json +10 -0
  401. package/src/__golden__/wave-loop/op-by-apply-skip.json +10 -0
  402. package/src/__golden__/wave-loop/op-cost-member-clash.json +6 -0
  403. package/src/__golden__/wave-loop/op-cost-member.json +13 -0
  404. package/src/__golden__/wave-loop/op-denied-source.json +16 -0
  405. package/src/__golden__/wave-loop/op-denied.json +16 -0
  406. package/src/__golden__/wave-loop/op-discover-rest-short.json +6 -0
  407. package/src/__golden__/wave-loop/op-discover-rest-stops.json +13 -0
  408. package/src/__golden__/wave-loop/op-discover-rest.json +26 -0
  409. package/src/__golden__/wave-loop/op-discover-wide.json +26 -0
  410. package/src/__golden__/wave-loop/op-held-on-again.json +16 -0
  411. package/src/__golden__/wave-loop/op-held-on-before-apply.json +16 -0
  412. package/src/__golden__/wave-loop/op-held-on.json +16 -0
  413. package/src/__golden__/wave-loop/op-hooks-apply-fails.json +13 -0
  414. package/src/__golden__/wave-loop/op-hooks-fail-after-apply.json +13 -0
  415. package/src/__golden__/wave-loop/op-hooks-fail-before-apply.json +13 -0
  416. package/src/__golden__/wave-loop/op-hooks-fail-before-plan.json +13 -0
  417. package/src/__golden__/wave-loop/op-hooks-held.json +13 -0
  418. package/src/__golden__/wave-loop/op-hooks-hold-before-apply.json +13 -0
  419. package/src/__golden__/wave-loop/op-hooks-pass.json +13 -0
  420. package/src/__golden__/wave-loop/op-hooks-refused-at-base.json +13 -0
  421. package/src/__golden__/wave-loop/op-hooks-share.json +33 -0
  422. package/src/__golden__/wave-loop/op-outcome-held.json +10 -0
  423. package/src/__golden__/wave-loop/op-outcome-replan-approved.json +10 -0
  424. package/src/__golden__/wave-loop/op-outcome-replan-forever.json +10 -0
  425. package/src/__golden__/wave-loop/op-outcome-replan.json +10 -0
  426. package/src/__golden__/wave-loop/op-outcome-stood-down.json +10 -0
  427. package/src/__golden__/wave-loop/op-outcome-unreadable.json +10 -0
  428. package/src/__golden__/wave-loop/op-overridden.json +16 -0
  429. package/src/__golden__/wave-loop/op-per-run.json +20 -0
  430. package/src/__golden__/wave-loop/op-point.json +13 -0
  431. package/src/__golden__/wave-loop/op-pr-review.json +10 -0
  432. package/src/__golden__/wave-loop/op-sealed.json +10 -0
  433. package/src/__golden__/wave-loop/op-spend-change-only.json +10 -0
  434. package/src/__golden__/wave-loop/op-spend.json +10 -0
  435. package/src/__golden__/wave-loop/op-wave-commands.json +55 -0
  436. package/src/__golden__/wave-loop/op-wide-refused.json +26 -0
  437. package/src/__golden__/wave-loop/op-wide-waiting.json +29 -0
  438. package/src/__golden__/wave-loop/op-wide.json +39 -0
  439. package/src/action-pins.test.ts +78 -0
  440. package/src/action-pins.ts +92 -0
  441. package/src/cli/commands/carve-score-once.test.ts +99 -0
  442. package/src/cli/commands/carve.ts +37 -6
  443. package/src/cli/commands/check-lexicon.test.ts +2 -2
  444. package/src/cli/commands/import-report.ts +71 -0
  445. package/src/cli/commands/import.ts +33 -3
  446. package/src/cli/commands/migrate-dialect.test.ts +145 -0
  447. package/src/cli/commands/migrate.ts +132 -11
  448. package/src/cli/handlers/__snapshots__/build-member.test.ts.snap +4 -4
  449. package/src/cli/handlers/approve.ts +23 -2
  450. package/src/cli/handlers/build.ts +7 -1
  451. package/src/cli/handlers/ledger.ts +54 -0
  452. package/src/cli/handlers/migrate.ts +21 -12
  453. package/src/cli/handlers/misc.ts +10 -1
  454. package/src/cli/handlers/pr-env.ts +2 -2
  455. package/src/cli/handlers/pr.ts +138 -36
  456. package/src/cli/handlers/query.ts +16 -4
  457. package/src/cli/handlers/release.ts +6 -3
  458. package/src/cli/handlers/search.ts +13 -34
  459. package/src/cli/main.test.ts +15 -0
  460. package/src/cli/main.ts +65 -14
  461. package/src/cli/parse-args.ts +27 -2
  462. package/src/cli/registry.ts +14 -4
  463. package/src/components/builders.ts +8 -0
  464. package/src/components/capability-plugin-loader.ts +13 -1
  465. package/src/components/capability-plugin.ts +16 -0
  466. package/src/components/pr-hooks.test.ts +73 -5
  467. package/src/components/pr-hooks.ts +95 -5
  468. package/src/components/pr-pipeline.ts +64 -16
  469. package/src/components/pr-policy.test.ts +64 -1
  470. package/src/components/pr-policy.ts +53 -0
  471. package/src/components/registry.test.ts +1 -1
  472. package/src/components/starter-plugin.ts +6 -0
  473. package/src/components/verbs/index.ts +2 -0
  474. package/src/components/verbs/publish-artifact.test.ts +60 -0
  475. package/src/components/verbs/publish-artifact.ts +122 -0
  476. package/src/components/verbs/publish-image.test.ts +101 -0
  477. package/src/components/verbs/publish-image.ts +265 -0
  478. package/src/config-environments.ts +28 -1
  479. package/src/config.ts +8 -1
  480. package/src/gate-decision-bundle.test.ts +1 -0
  481. package/src/gate-decision-identity.test.ts +9 -0
  482. package/src/gate-decision.ts +26 -3
  483. package/src/graph-ir.ts +27 -0
  484. package/src/graph-walk.test.ts +44 -0
  485. package/src/graph-walk.ts +102 -0
  486. package/src/import/parser.ts +20 -0
  487. package/src/lexicon.ts +74 -6
  488. package/src/lifecycle/gate-ledger.ts +8 -0
  489. package/src/lifecycle/git.ts +2 -2
  490. package/src/lifecycle/ledger-read.test.ts +107 -0
  491. package/src/lifecycle/ledger-read.ts +195 -0
  492. package/src/lifecycle/release-workstation.ts +24 -0
  493. package/src/lifecycle/run-ledger.ts +5 -2
  494. package/src/lint/cross-root.test.ts +122 -0
  495. package/src/lint/discover.ts +3 -1
  496. package/src/lint/post-synth.ts +212 -2
  497. package/src/lint/rules/op/index.ts +2 -0
  498. package/src/lint/rules/op/ops019-workspace-op-target.ts +89 -0
  499. package/src/op/activities/activity-contracts.ts +5 -2
  500. package/src/op/activities/index.ts +2 -2
  501. package/src/op/activities/review-agent.test.ts +221 -4
  502. package/src/op/activities/review-agent.ts +265 -22
  503. package/src/op/activities/work-evidence.ts +9 -1
  504. package/src/op/builders.ts +9 -5
  505. package/src/op/composites/apply-op.test.ts +108 -0
  506. package/src/op/composites/apply-op.ts +37 -2
  507. package/src/op/gate-approval.test.ts +6 -1
  508. package/src/op/gate-approval.ts +12 -6
  509. package/src/op/gate-quorum-at-base.test.ts +211 -0
  510. package/src/op/gate-quorum.ts +156 -8
  511. package/src/op/gate-review.ts +14 -0
  512. package/src/op/gate.ts +67 -8
  513. package/src/op/index.ts +3 -2
  514. package/src/op/local-executor.ts +69 -9
  515. package/src/op/op-waves-depends-on.test.ts +81 -0
  516. package/src/op/op-waves-hook-targets.test.ts +4 -2
  517. package/src/op/op-waves-quorum-product.test.ts +158 -0
  518. package/src/op/op-waves-run.ts +288 -14
  519. package/src/op/op-waves.ts +143 -14
  520. package/src/op/runtime.ts +8 -0
  521. package/src/op/schedule-overdue.ts +33 -0
  522. package/src/op/stage-hooks.ts +13 -5
  523. package/src/op/workspace-run-env.ts +29 -0
  524. package/src/pr-forge-bundle.test.ts +2 -2
  525. package/src/pr-forge.test.ts +47 -0
  526. package/src/pr-forge.ts +68 -0
  527. package/src/pr-post-open.e2e.test.ts +36 -0
  528. package/src/pr-post.test.ts +102 -3
  529. package/src/pr-post.ts +89 -1
  530. package/src/read-filter.test.ts +54 -0
  531. package/src/read-filter.ts +193 -0
  532. package/src/run-event.ts +1 -1
  533. package/src/stage-hooks-bundle.test.ts +1 -0
  534. package/src/terraform/carve.ts +52 -15
  535. package/src/terraform/cuts.test.ts +173 -0
  536. package/src/terraform/cuts.ts +188 -0
  537. package/src/terraform/folds.ts +44 -0
  538. package/src/terraform/graph.ts +9 -0
  539. package/src/terraform/radius.test.ts +90 -0
  540. package/src/terraform/reach.test.ts +146 -0
  541. package/src/terraform/reach.ts +247 -0
  542. package/src/terraform/score.ts +27 -29
  543. package/src/terraform/state.ts +3 -1
  544. package/src/terraform/types.ts +8 -0
  545. package/src/wave-blocked.ts +28 -3
  546. package/src/wave-loop-golden.test.ts +4 -3
  547. package/src/workspace/behold-kinds.test.ts +1 -1
  548. package/src/workspace/check-contract.test.ts +7 -6
  549. package/src/workspace/check.schema.json +11 -2
  550. package/src/workspace/checks/ci-branch.test.ts +128 -0
  551. package/src/workspace/checks/ci-branch.ts +141 -0
  552. package/src/workspace/checks/links.ts +12 -1
  553. package/src/workspace/checks/org.ts +169 -0
  554. package/src/workspace/checks.ts +64 -7
  555. package/src/workspace/ci-branches.ts +31 -0
  556. package/src/workspace/ci-green-branches.test.ts +153 -0
  557. package/src/workspace/ci-green-cli.ts +7 -7
  558. package/src/workspace/ci-green-workflow.test.ts +4 -3
  559. package/src/workspace/ci-green-workflow.ts +13 -9
  560. package/src/workspace/ci-green.ts +49 -12
  561. package/src/workspace/ci-run.test.ts +72 -1
  562. package/src/workspace/ci-run.ts +68 -8
  563. package/src/workspace/ci-serve.test.ts +95 -0
  564. package/src/workspace/ci-serve.ts +106 -65
  565. package/src/workspace/component-environments.test.ts +25 -2
  566. package/src/workspace/compose-graph.ts +9 -2
  567. package/src/workspace/composites.schema.json +22 -0
  568. package/src/workspace/composites.test.ts +39 -8
  569. package/src/workspace/composites.ts +6 -2
  570. package/src/workspace/conformance/__writer_fixture__/work/work.schema.json +105 -1
  571. package/src/workspace/cross-root-lint.test.ts +90 -0
  572. package/src/workspace/cross-root-lint.ts +126 -0
  573. package/src/workspace/dbt-kind.test.ts +3 -2
  574. package/src/workspace/dbt-models-reader.ts +362 -0
  575. package/src/workspace/dbt-models.test.ts +248 -0
  576. package/src/workspace/dbt-project-common.ts +113 -0
  577. package/src/workspace/dbt-project-reader.ts +29 -112
  578. package/src/workspace/decision-rank.ts +40 -0
  579. package/src/workspace/declaration.schema.json +133 -24
  580. package/src/workspace/declaration.ts +84 -15
  581. package/src/workspace/environments.ts +40 -4
  582. package/src/workspace/flyway-dir.ts +113 -0
  583. package/src/workspace/flyway-layout.ts +337 -0
  584. package/src/workspace/graph-cli.ts +25 -10
  585. package/src/workspace/graph-contract.test.ts +4 -2
  586. package/src/workspace/graph.schema.json +34 -0
  587. package/src/workspace/identity.test.ts +14 -2
  588. package/src/workspace/intent.ts +6 -14
  589. package/src/workspace/kind-link-readers.ts +68 -3
  590. package/src/workspace/kinds.test.ts +12 -7
  591. package/src/workspace/kinds.ts +132 -7
  592. package/src/workspace/ledger.schema.json +158 -0
  593. package/src/workspace/lexicon-outputs.ts +132 -13
  594. package/src/workspace/lineage-check.ts +30 -7
  595. package/src/workspace/links.ts +24 -1
  596. package/src/workspace/ls-contract.test.ts +1 -1
  597. package/src/workspace/ls.schema.json +7 -0
  598. package/src/workspace/ls.ts +10 -1
  599. package/src/workspace/member-commands.ts +60 -2
  600. package/src/workspace/member-file-schema.test.ts +124 -0
  601. package/src/workspace/migrate-links.test.ts +99 -0
  602. package/src/workspace/migrate-links.ts +108 -0
  603. package/src/workspace/migration-dirs.test.ts +272 -0
  604. package/src/workspace/migration-dirs.ts +267 -0
  605. package/src/workspace/org-scope.test.ts +238 -0
  606. package/src/workspace/reason-codes.test.ts +6 -1
  607. package/src/workspace/reason-codes.ts +11 -0
  608. package/src/workspace/records-cli.ts +58 -5
  609. package/src/workspace/records.schema.json +5 -0
  610. package/src/workspace/release-candidates.ts +7 -2
  611. package/src/workspace/release-train.ts +157 -0
  612. package/src/workspace/release-workflow.ts +4 -3
  613. package/src/workspace/repo-ref.ts +113 -0
  614. package/src/workspace/run-event.schema.json +2 -2
  615. package/src/workspace/runs-cli.ts +48 -4
  616. package/src/workspace/runs.schema.json +7 -0
  617. package/src/workspace/status-contract.test.ts +46 -1
  618. package/src/workspace/status-decisions.test.ts +100 -0
  619. package/src/workspace/status-decisions.ts +151 -0
  620. package/src/workspace/status-gates.ts +9 -0
  621. package/src/workspace/status.schema.json +6 -0
  622. package/src/workspace/status.ts +17 -3
  623. package/src/workspace/where-filters.test.ts +39 -0
  624. package/src/workspace/work-cli.ts +5 -3
  625. package/src/workspace/work-evidence-ops.test.ts +176 -0
  626. package/src/workspace/work-evidence-ops.ts +189 -0
  627. package/src/workspace/work-evidence.schema.json +49 -2
  628. package/src/workspace/work-evidence.ts +22 -3
  629. package/src/workspace/workspace-gates.ts +200 -0
  630. package/src/workspace/workspace-kinds.schema.json +58 -3
  631. package/src/workspace/workspace-ledger.ts +130 -0
  632. package/src/workspace/workspace-run.test.ts +322 -0
  633. package/src/workspace/workspace-run.ts +461 -0
@@ -0,0 +1,124 @@
1
+ /**
2
+ * A member kind names a JSON Schema its member's file must pass (#3672): the
3
+ * kinds file's memberFile block, the schema read as data from the package,
4
+ * and WSP012 from `chant workspace check`.
5
+ */
6
+ import { execFileSync } from "node:child_process";
7
+ import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
8
+ import { tmpdir } from "node:os";
9
+ import { dirname, join } from "node:path";
10
+ import { afterAll, describe, expect, test } from "vitest";
11
+ import { runDeclarationChecks } from "./checks";
12
+ import { checkMemberFile, parseKindData, readPackageKinds } from "./kinds";
13
+
14
+ const scratch: string[] = [];
15
+ afterAll(() => {
16
+ for (const d of scratch) rmSync(d, { recursive: true, force: true });
17
+ });
18
+ function repo(files: Record<string, string>): string {
19
+ const root = realpathSync(mkdtempSync(join(tmpdir(), "chant-3672-")));
20
+ scratch.push(root);
21
+ execFileSync("git", ["init", "-q"], { cwd: root });
22
+ for (const [path, text] of Object.entries(files)) {
23
+ mkdirSync(dirname(join(root, path)), { recursive: true });
24
+ writeFileSync(join(root, path), text);
25
+ }
26
+ return root;
27
+ }
28
+
29
+ const controlSchema = {
30
+ $schema: "https://json-schema.org/draft/2020-12/schema",
31
+ type: "object",
32
+ required: ["estates"],
33
+ properties: { estates: { type: "array", items: { type: "string" } } },
34
+ additionalProperties: false,
35
+ };
36
+
37
+ const controlKind = (memberFile: unknown = { path: "terragucci.yml", schema: "schemas/control.schema.json" }) => ({
38
+ name: "control",
39
+ description: "a terragucci control repo, with a terragucci.yml",
40
+ precedence: 300,
41
+ probe: { anyFile: ["terragucci.yml"] },
42
+ memberFile,
43
+ });
44
+
45
+ /** A kinds-only package at kinds/control, and a workspace that pins it. */
46
+ function workspace(files: Record<string, string>, opts: { schema?: string | null; kind?: unknown } = {}): string {
47
+ const schemaText = opts.schema === undefined ? JSON.stringify(controlSchema) : opts.schema;
48
+ return repo({
49
+ "chant.workspace.json": JSON.stringify({ name: "acme", schema: 1, pins: [{ path: "kinds/control" }], members: [{ name: "ctl", dir: "ctl", kind: "control" }] }),
50
+ "kinds/control/package.json": JSON.stringify({ name: "control-kinds", exports: { "./workspace-kinds": "./workspace-kinds.json" } }),
51
+ "kinds/control/workspace-kinds.json": JSON.stringify({ schema: 1, kinds: [opts.kind ?? controlKind()] }),
52
+ ...(schemaText === null ? {} : { "kinds/control/schemas/control.schema.json": schemaText }),
53
+ ...files,
54
+ });
55
+ }
56
+
57
+ const wsp012 = async (root: string) =>
58
+ (await runDeclarationChecks(root)).diagnostics.filter((d) => d.ruleId === "WSP012" || d.ruleId === "WSP002").map((d) => `${d.ruleId}: ${d.message}`);
59
+
60
+ describe("the kinds file's memberFile block", () => {
61
+ test("is kind data: a path in the member and a .json schema in the package", () => {
62
+ const ok = parseKindData(JSON.stringify({ schema: 1, kinds: [controlKind()] }), "p");
63
+ expect(ok.problems).toEqual([]);
64
+ expect(ok.kinds[0].memberFile).toEqual({ path: "terragucci.yml", schema: "schemas/control.schema.json" });
65
+ for (const bad of [{ path: "../x.yml", schema: "s.json" }, { path: "x.yml", schema: "s.yaml" }, { path: "x.yml", schema: "../s.json" }, { path: "x.yml" }]) {
66
+ expect(parseKindData(JSON.stringify({ schema: 1, kinds: [controlKind(bad)] }), "p").problems).not.toEqual([]);
67
+ }
68
+ });
69
+
70
+ test("reading the package compiles the schema as data, and reports one that is missing or not a schema", () => {
71
+ const good = workspace({});
72
+ const read = readPackageKinds(join(good, "kinds/control"));
73
+ expect(read.problems).toEqual([]);
74
+ expect(typeof read.kinds[0].memberFile?.validate).toBe("function");
75
+ expect(readPackageKinds(join(workspace({}, { schema: null }), "kinds/control")).problems[0]).toMatch(/does not exist/);
76
+ expect(readPackageKinds(join(workspace({}, { schema: "{ nope" }), "kinds/control")).problems[0]).toMatch(/is not JSON/);
77
+ expect(readPackageKinds(join(workspace({}, { schema: JSON.stringify({ type: 12 }) }), "kinds/control")).problems[0]).toMatch(/does not compile/);
78
+ });
79
+ });
80
+
81
+ describe("checkMemberFile", () => {
82
+ const mf = () => readPackageKinds(join(workspace({}), "kinds/control")).kinds[0].memberFile!;
83
+ test("parses YAML by extension and JSON otherwise, and names each failing place", () => {
84
+ expect(checkMemberFile(mf(), "terragucci.yml", "estates: [a, b]\n")).toEqual([]);
85
+ expect(checkMemberFile(mf(), "t.json", '{"estates":["a"]}')).toEqual([]);
86
+ expect(checkMemberFile(mf(), "terragucci.yml", "estates: [1]\nextra: true\n").map((p) => p.message).sort()).toEqual(["/estates/0 must be string", "the file must NOT have additional properties (extra)"]);
87
+ expect(checkMemberFile(mf(), "t.json", "estates: []")[0].message).toMatch(/^is not JSON/);
88
+ });
89
+ });
90
+
91
+ describe("WSP012: a member's file passes its kind's schema", () => {
92
+ test("a member whose file passes has no finding", async () => {
93
+ expect(await wsp012(workspace({ "ctl/terragucci.yml": "estates: [prod]\n" }))).toEqual([]);
94
+ });
95
+
96
+ test("a file that fails the schema fails the check, once per reason", async () => {
97
+ const report = await runDeclarationChecks(workspace({ "ctl/terragucci.yml": "estates: prod\n" }));
98
+ expect(report.ok).toBe(false);
99
+ const found = report.diagnostics.filter((d) => d.ruleId === "WSP012");
100
+ expect(found.map((d) => d.message)).toEqual(["member ctl's terragucci.yml fails kind control's schema schemas/control.schema.json: /estates must be array"]);
101
+ expect(found[0].file).toMatch(/ctl\/terragucci\.yml$/);
102
+ });
103
+
104
+ test("a member without the file fails it", async () => {
105
+ // The probe also wants the file; the declared kind still names the schema, so WSP012 says what is missing.
106
+ expect(await wsp012(workspace({ "ctl/README.md": "" }))).toEqual(["WSP012: member ctl (ctl) has no terragucci.yml, which kind control requires"]);
107
+ });
108
+
109
+ test("a schema that doesn't load is WSP002, and its members can't pass WSP012", async () => {
110
+ const found = await wsp012(workspace({ "ctl/terragucci.yml": "estates: []\n" }, { schema: null }));
111
+ expect(found).toHaveLength(2);
112
+ expect(found[0]).toMatch(/^WSP002: .*kind control names member-file schema schemas\/control.schema.json, which does not exist/);
113
+ expect(found[1]).toMatch(/^WSP012: .*did not load/);
114
+ });
115
+
116
+ test("the declaration can turn it down", async () => {
117
+ const root = workspace({ "ctl/terragucci.yml": "nope: 1\n" });
118
+ const decl = JSON.parse(execFileSync("cat", [join(root, "chant.workspace.json")], { encoding: "utf-8" }));
119
+ writeFileSync(join(root, "chant.workspace.json"), JSON.stringify({ ...decl, checks: { WSP012: "warning" } }));
120
+ const report = await runDeclarationChecks(root);
121
+ expect(report.ok).toBe(true);
122
+ expect(report.diagnostics.filter((d) => d.ruleId === "WSP012").every((d) => d.severity === "warning")).toBe(true);
123
+ });
124
+ });
@@ -0,0 +1,99 @@
1
+ /**
2
+ * The links `chant migrate` writes into a workspace declaration (#4399):
3
+ * which members are the producer and the consumer, that the edit touches only
4
+ * the consumer's `links`, that a second run adds nothing, and that every case
5
+ * with nothing to link says why.
6
+ */
7
+ import { afterAll, describe, expect, test } from "vitest";
8
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
9
+ import { tmpdir } from "node:os";
10
+ import { dirname, join } from "node:path";
11
+ import { parseDeclaration } from "./declaration";
12
+ import { planMigrateLinks } from "./migrate-links";
13
+
14
+ const scratch: string[] = [];
15
+ afterAll(() => {
16
+ for (const d of scratch) rmSync(d, { recursive: true, force: true });
17
+ });
18
+
19
+ function repo(declaration: string, file = "chant.workspace.json"): string {
20
+ const root = mkdtempSync(join(tmpdir(), "chant-migrate-links-"));
21
+ scratch.push(root);
22
+ for (const [path, text] of Object.entries({ [file]: declaration, "db/schema.sql": "", "mirror/mirrors.ts": "", "other/x.ts": "" })) {
23
+ mkdirSync(dirname(join(root, path)), { recursive: true });
24
+ writeFileSync(join(root, path), text);
25
+ }
26
+ return root;
27
+ }
28
+
29
+ const decl = (members: unknown[]) => `${JSON.stringify({ name: "shop", schema: 1, members }, null, 2)}\n`;
30
+ const MEMBERS = [
31
+ { name: "db", dir: "db", kind: "chant" },
32
+ { name: "mirror", dir: "mirror", kind: "chant" },
33
+ ];
34
+ const outputs = ["public.users.id", "public.users.email"];
35
+
36
+ describe("planMigrateLinks", () => {
37
+ test("adds one link per output to the member holding the output file, and changes nothing else", () => {
38
+ const root = repo(decl(MEMBERS));
39
+ const r = planMigrateLinks({ sourcePath: join(root, "db/schema.sql"), outputPath: join(root, "mirror/mirrors.ts"), outputs });
40
+ if (!r || !("plan" in r)) throw new Error(JSON.stringify(r));
41
+ expect(r.plan).toMatchObject({ producer: "db", consumer: "mirror", added: outputs });
42
+ const members = parseDeclaration(r.plan.text, "chant.workspace.json").members;
43
+ expect(members.find((m) => m.name === "mirror")!.links.map((l) => `${l.member} ${l.output}`)).toEqual(outputs.map((o) => `db ${o}`));
44
+ expect(members.find((m) => m.name === "db")!.links).toEqual([]);
45
+ expect(r.plan.text.startsWith('{\n "name": "shop"')).toBe(true);
46
+ });
47
+
48
+ test("keeps the links already there and adds only the new ones; a second plan adds nothing", () => {
49
+ const root = repo(decl([MEMBERS[0], { ...MEMBERS[1], links: [{ member: "db", output: "public.users.id" }, { member: "other", output: "x" }] }, { name: "other", dir: "other", kind: "chant" }]));
50
+ const r = planMigrateLinks({ sourcePath: join(root, "db"), outputPath: join(root, "mirror/mirrors.ts"), outputs });
51
+ if (!r || !("plan" in r)) throw new Error(JSON.stringify(r));
52
+ expect(r.plan.added).toEqual(["public.users.email"]);
53
+ const links = parseDeclaration(r.plan.text, "x.json").members.find((m) => m.name === "mirror")!.links;
54
+ expect(links.map((l) => `${l.member} ${l.output}`)).toEqual(["db public.users.id", "other x", "db public.users.email"]);
55
+ writeFileSync(join(root, "chant.workspace.json"), r.plan.text);
56
+ const again = planMigrateLinks({ sourcePath: join(root, "db"), outputPath: join(root, "mirror/mirrors.ts"), outputs });
57
+ expect(again).toMatchObject({ plan: { added: [] } });
58
+ });
59
+
60
+ test("reads a jsonc declaration and keeps its comments", () => {
61
+ const text = `{\n // the shop\n "name": "shop", "schema": 1,\n "members": [\n { "name": "db", "dir": "db", "kind": "chant" },\n { "name": "mirror", "dir": "mirror", "kind": "chant" } // the mirror\n ]\n}\n`;
62
+ const root = repo(text, "chant.workspace.jsonc");
63
+ const r = planMigrateLinks({ sourcePath: join(root, "db/schema.sql"), outputPath: join(root, "mirror/mirrors.ts"), outputs });
64
+ if (!r || !("plan" in r)) throw new Error(JSON.stringify(r));
65
+ expect(r.plan.text).toContain("// the shop");
66
+ expect(r.plan.text).toContain("// the mirror");
67
+ expect(r.plan.added).toEqual(outputs);
68
+ });
69
+
70
+ test("the deepest member holds a path: a nested member is not its parent's", () => {
71
+ const root = repo(decl([{ name: "all", dir: ".", kind: "chant" }, { name: "db", dir: "db", kind: "chant" }, { name: "mirror", dir: "mirror", kind: "chant" }]));
72
+ const r = planMigrateLinks({ sourcePath: join(root, "db/schema.sql"), outputPath: join(root, "mirror/mirrors.ts"), outputs });
73
+ expect(r).toMatchObject({ plan: { producer: "db", consumer: "mirror" } });
74
+ });
75
+
76
+ test("no workspace above the source is no outcome at all", () => {
77
+ const root = mkdtempSync(join(tmpdir(), "chant-no-workspace-"));
78
+ scratch.push(root);
79
+ writeFileSync(join(root, "schema.sql"), "");
80
+ expect(planMigrateLinks({ sourcePath: join(root, "schema.sql"), outputPath: join(root, "out.ts"), outputs })).toBeUndefined();
81
+ });
82
+
83
+ test.each([
84
+ ["output to stdout", (root: string) => ({ sourcePath: join(root, "db/schema.sql"), outputPath: undefined }), "pass --output inside the ClickHouse member"],
85
+ ["a source in no member", (root: string) => ({ sourcePath: join(root, "elsewhere/schema.sql"), outputPath: join(root, "mirror/mirrors.ts") }), "is in no member"],
86
+ ["an output in no member", (root: string) => ({ sourcePath: join(root, "db/schema.sql"), outputPath: join(root, "elsewhere/out.ts") }), "is in no member"],
87
+ ["a source and output in one member", (root: string) => ({ sourcePath: join(root, "db/schema.sql"), outputPath: join(root, "db/out.ts") }), "does not link to itself"],
88
+ ])("%s is skipped with a reason", (_name, paths, reason) => {
89
+ const root = repo(decl(MEMBERS));
90
+ const r = planMigrateLinks({ ...paths(root), outputs });
91
+ expect(r && "skipped" in r ? r.skipped : r).toContain(reason);
92
+ });
93
+
94
+ test("a declaration that does not read is skipped with its error, never rewritten", () => {
95
+ const root = repo("{ not json");
96
+ const r = planMigrateLinks({ sourcePath: join(root, "db/schema.sql"), outputPath: join(root, "mirror/mirrors.ts"), outputs });
97
+ expect(r && "skipped" in r ? r.skipped : r).toContain("does not read, so no link was written");
98
+ });
99
+ });
@@ -0,0 +1,108 @@
1
+ /**
2
+ * The workspace links `chant migrate` writes (#4399).
3
+ *
4
+ * A migration reads one member (the Postgres tables) and writes into another
5
+ * (the ClickHouse mirrors). When both sit in a workspace, the migrated
6
+ * output reads the source member's outputs, and the declaration should say
7
+ * so: `chant workspace check` then fails when the source drops or renames an
8
+ * output until the mirror follows (WSP093, `./checks/links.ts`).
9
+ *
10
+ * {@link planMigrateLinks} finds the declaration above the source, the member
11
+ * that holds the source and the member that holds the output file, and adds
12
+ * a `links` entry on the second for each output the migration names. It
13
+ * changes only the text of that member's `links`, as the other declaration
14
+ * write commands do (`./json-edit.ts`). It returns a plan; the caller writes
15
+ * it. A migration with no workspace around it, or whose source and output
16
+ * are one member, has nothing to link and returns a reason when that is
17
+ * worth telling.
18
+ */
19
+
20
+ import { existsSync, readFileSync } from "node:fs";
21
+ import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
22
+ import { DECLARATION_FILES, parseDeclaration, type Declaration, type Member } from "./declaration";
23
+ import { appendElement, setProperty } from "./json-edit";
24
+
25
+ export interface MigrateLinkPlan {
26
+ /** The declaration file, absolute. */
27
+ file: string;
28
+ /** The producer: the member that holds the source. */
29
+ producer: string;
30
+ /** The consumer: the member that holds the output file. */
31
+ consumer: string;
32
+ /** Outputs the consumer did not link to before. */
33
+ added: string[];
34
+ /** The declaration's new text; equal to the old when nothing was added. */
35
+ text: string;
36
+ }
37
+
38
+ export type MigrateLinkOutcome = { plan: MigrateLinkPlan } | { skipped: string } | undefined;
39
+
40
+ /** The workspace declaration at or above `start`. */
41
+ function findDeclaration(start: string): string | undefined {
42
+ let dir = resolve(start);
43
+ for (;;) {
44
+ for (const name of DECLARATION_FILES) {
45
+ const candidate = join(dir, name);
46
+ if (existsSync(candidate)) return candidate;
47
+ }
48
+ const parent = dirname(dir);
49
+ if (parent === dir) return undefined;
50
+ dir = parent;
51
+ }
52
+ }
53
+
54
+ /** The deepest member whose directory holds `path`. */
55
+ function memberHolding(declaration: Declaration, root: string, path: string): Member | undefined {
56
+ let best: { member: Member; depth: number } | undefined;
57
+ for (const m of declaration.members) {
58
+ const memberRoot = resolve(root, m.dir);
59
+ const rel = relative(memberRoot, path);
60
+ if (rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel)) continue;
61
+ // The longer root is the deeper member.
62
+ if (!best || memberRoot.length > best.depth) best = { member: m, depth: memberRoot.length };
63
+ }
64
+ return best?.member;
65
+ }
66
+
67
+ /**
68
+ * Plan the links from the member holding `sourcePath` to the outputs a
69
+ * migration read, on the member holding `outputPath`.
70
+ */
71
+ export function planMigrateLinks(opts: { sourcePath: string; outputPath: string | undefined; outputs: readonly string[] }): MigrateLinkOutcome {
72
+ const source = resolve(opts.sourcePath);
73
+ const file = findDeclaration(source);
74
+ if (!file) return undefined;
75
+ if (opts.outputPath === undefined) return { skipped: "pass --output inside the ClickHouse member to link its columns to the Postgres member" };
76
+ const root = dirname(file);
77
+ const text = readFileSync(file, "utf8");
78
+ let declaration: Declaration;
79
+ try {
80
+ declaration = parseDeclaration(text, file);
81
+ } catch (err) {
82
+ return { skipped: `the workspace declaration ${file} does not read, so no link was written: ${err instanceof Error ? err.message : String(err)}` };
83
+ }
84
+ const producer = memberHolding(declaration, root, source);
85
+ const consumer = memberHolding(declaration, root, resolve(opts.outputPath));
86
+ if (!producer) return { skipped: `${opts.sourcePath} is in no member of ${file}, so no link was written` };
87
+ if (!consumer) return { skipped: `${opts.outputPath} is in no member of ${file}, so no link was written` };
88
+ if (producer.name === consumer.name) return { skipped: `the source and the output are both in member ${producer.name}; a member does not link to itself` };
89
+
90
+ const have = new Set(consumer.links.filter((l) => l.member === producer.name && l.kind === null).map((l) => l.output));
91
+ const added = [...new Set(opts.outputs)].filter((o) => !have.has(o));
92
+ if (added.length === 0) return { plan: { file, producer: producer.name, consumer: consumer.name, added, text } };
93
+
94
+ const jsonc = file.endsWith(".jsonc");
95
+ const links = added.map((output) => ({ member: producer.name, output }));
96
+ let next = text;
97
+ if (consumer.links.length === 0) {
98
+ next = setProperty(next, jsonc, consumer.pointer, "links", links);
99
+ } else {
100
+ for (const l of links) next = appendElement(next, jsonc, `${consumer.pointer}/links`, l);
101
+ }
102
+ // The edit is never written unless it reads back with the links it meant to add.
103
+ const back = parseDeclaration(next, file).members.find((m) => m.name === consumer.name);
104
+ if (!back || back.links.length !== consumer.links.length + added.length) {
105
+ return { skipped: `the edit to ${file} did not read back as the links it meant to add, so nothing was written` };
106
+ }
107
+ return { plan: { file, producer: producer.name, consumer: consumer.name, added, text: next } };
108
+ }
@@ -0,0 +1,272 @@
1
+ /**
2
+ * The migration-directory readers (#4400): goose, golang-migrate,
3
+ * clickhouse-migrations and Flyway. Each lists a fixture directory's
4
+ * migrations in the order the tool applies them. The trees are in memory:
5
+ * a reader parses files and never runs the tool.
6
+ */
7
+
8
+ import { describe, expect, test } from "vitest";
9
+ import type { KindReaderInput } from "./kind-link-readers";
10
+ import { LINK_READERS } from "./kind-link-readers";
11
+ import { migrationDigest, REPLAY_FILE, replayFiles } from "./migration-dirs";
12
+ import { withReaderOutputs } from "./links";
13
+ import { classifyFlywayFile, compareVersions } from "./flyway-dir";
14
+ import { parseToml, readFlywayConfig, substitute, FlywayPlaceholderError } from "./flyway-layout";
15
+ import { builtinKindRegistry } from "./kinds";
16
+ import type { WorkspaceTree } from "./tree";
17
+
18
+ function memoryTree(files: Record<string, string>): WorkspaceTree {
19
+ const dirs = new Set([""]);
20
+ for (const f of Object.keys(files)) {
21
+ const parts = f.split("/");
22
+ for (let i = 1; i < parts.length; i++) dirs.add(parts.slice(0, i).join("/"));
23
+ }
24
+ return {
25
+ label: "",
26
+ stat: (p) => (p in files ? "file" : dirs.has(p) ? "dir" : undefined),
27
+ list(p) {
28
+ if (!dirs.has(p)) return undefined;
29
+ const prefix = p === "" ? "" : `${p}/`;
30
+ const seen = new Map<string, "file" | "dir">();
31
+ for (const f of Object.keys(files)) {
32
+ if (!f.startsWith(prefix)) continue;
33
+ const rest = f.slice(prefix.length);
34
+ const [head] = rest.split("/");
35
+ seen.set(head!, rest.includes("/") ? "dir" : "file");
36
+ }
37
+ return [...seen].map(([name, type]) => ({ name, type }));
38
+ },
39
+ read(p) {
40
+ if (!(p in files)) throw new Error(`no such file ${p}`);
41
+ return files[p]!;
42
+ },
43
+ };
44
+ }
45
+
46
+ const run = (reader: keyof typeof LINK_READERS, files: Record<string, string>, fields: Record<string, string> = {}, dir = "") =>
47
+ LINK_READERS[reader]({ member: { name: "m" } as never, dir, fields: fields as KindReaderInput["fields"], tree: memoryTree(files) });
48
+ /** The gaps about the files, without the one that asks for a first replay. */
49
+ const gapsOf = (r: ReturnType<typeof run>) => r.gaps.filter((g) => !g.message.startsWith(`no ${REPLAY_FILE}`));
50
+ const summary = (r: ReturnType<typeof run>) => (r.migrations ?? []).map((m) => `${String(m.position)} ${m.version} ${m.name} ${m.type}`);
51
+
52
+ describe("the readers are registered", () => {
53
+ test("each kind of the sql lexicon names a reader chant has", () => {
54
+ const names = Object.keys(LINK_READERS);
55
+ expect(names).toEqual(expect.arrayContaining(["goose-dir", "golang-migrate-dir", "clickhouse-migrations-dir", "flyway-dir"]));
56
+ expect(builtinKindRegistry().get("goose")).toBeUndefined();
57
+ });
58
+ });
59
+
60
+ describe("goose", () => {
61
+ const up = (extra = "") => `-- +goose Up\n${extra}CREATE TABLE t (id int);\n`;
62
+ test("lists .sql files by version number, sequential and timestamp alike, with their dialect", () => {
63
+ const r = run("goose-dir", {
64
+ "00010_ten.sql": up(),
65
+ "00002_two.sql": `${up()}-- +goose Down\nDROP TABLE t;\n`,
66
+ "20240101120000_stamp.sql": up(),
67
+ "README.md": "x",
68
+ }, { dialect: "clickhouse" });
69
+ expect(summary(r)).toEqual(["1 2 two versioned", "2 10 ten versioned", "3 20240101120000 stamp versioned"]);
70
+ expect(r.migrations!.map((m) => m.down)).toEqual([true, false, false]);
71
+ expect(r.migrations!.every((m) => m.dialect === "clickhouse")).toBe(true);
72
+ expect(r.migrations![0]).toMatchObject({ file: "00002_two.sql", line: 1 });
73
+ expect(gapsOf(r)).toEqual([]);
74
+ });
75
+
76
+ test("annotations are case-insensitive and NO TRANSACTION is read", () => {
77
+ const r = run("goose-dir", { "1_a.sql": "-- +goose NO TRANSACTION\n-- +goose up\nselect 1;\n" });
78
+ expect(r.migrations![0]).toMatchObject({ transaction: false, line: 2 });
79
+ });
80
+
81
+ test("a file without Up, a Go migration, a bad name and a duplicate version are gaps", () => {
82
+ const r = run("goose-dir", { "1_a.sql": "select 1;\n", "2_b.go": "package m\n", "x_c.sql": up(), "3_d.sql": up(), "03_e.sql": up() });
83
+ expect(gapsOf(r).map((g) => `${g.file}: ${g.message.split(",")[0]}`)).toEqual([
84
+ "1_a.sql: 1_a.sql has no -- +goose Up annotation",
85
+ "2_b.go: 2_b.go is a Go migration; its code is not SQL",
86
+ "x_c.sql: x_c.sql has no version goose can read: the name starts with a positive integer",
87
+ "3_d.sql: version 3 is also 03_e.sql; goose refuses a duplicate version",
88
+ ]);
89
+ expect(summary(r)).toEqual(["1 3 e versioned", "2 3 d versioned"]);
90
+ });
91
+
92
+ test("migrationsDir names the directory under the member's", () => {
93
+ const r = run("goose-dir", { "db/migrations/1_a.sql": up() }, { migrationsDir: "migrations" }, "db");
94
+ expect(r.migrations![0]!.file).toBe("db/migrations/1_a.sql");
95
+ });
96
+ });
97
+
98
+ describe("golang-migrate", () => {
99
+ test("lists the up files by numeric version, and pairs each with its down file", () => {
100
+ const r = run("golang-migrate-dir", {
101
+ "10_ten.up.sql": "select 1;\n",
102
+ "2_two.up.sql": "select 1;\n",
103
+ "2_two.down.sql": "select 1;\n",
104
+ "1700000000_stamp.up.sql": "select 1;\n",
105
+ "notes.txt": "",
106
+ });
107
+ expect(summary(r)).toEqual(["1 2 two versioned", "2 10 ten versioned", "3 1700000000 stamp versioned"]);
108
+ expect(r.migrations!.map((m) => m.down)).toEqual(["2_two.down.sql", false, false]);
109
+ expect(gapsOf(r)).toEqual([]);
110
+ });
111
+
112
+ test("a down file with no up file, and a duplicate version, are gaps", () => {
113
+ const r = run("golang-migrate-dir", { "1_a.up.sql": "", "01_b.up.sql": "", "5_c.down.sql": "" });
114
+ expect(gapsOf(r).map((g) => g.message)).toEqual([
115
+ "1_a.up.sql: another up file already has version 1; golang-migrate refuses a duplicate",
116
+ "version 5 has a down file and no up file",
117
+ ]);
118
+ });
119
+ });
120
+
121
+ describe("clickhouse-migrations", () => {
122
+ test("lists <version>_<name>.sql in numeric order, with the Python tool's .down.sql rollbacks", () => {
123
+ const r = run("clickhouse-migrations-dir", { "10_b.sql": "", "002_a.sql": "", "002_a.down.sql": "", "README.md": "" });
124
+ expect(summary(r)).toEqual(["1 002 a versioned", "2 10 b versioned"]);
125
+ expect(r.migrations!.map((m) => m.down)).toEqual(["002_a.down.sql", false]);
126
+ expect(r.migrations!.every((m) => m.dialect === "clickhouse")).toBe(true);
127
+ });
128
+
129
+ test("a name with no version and a version written twice are gaps", () => {
130
+ const r = run("clickhouse-migrations-dir", { "init.sql": "", "1_a.sql": "", "001_b.sql": "" });
131
+ expect(gapsOf(r).map((g) => g.message.split(",")[0])).toEqual([
132
+ "init.sql has no version before its first _",
133
+ "version 1 is also 001_b.sql; leading zeros are not significant",
134
+ ]);
135
+ });
136
+ });
137
+
138
+ describe("flyway", () => {
139
+ test("orders versions as numbers, then repeatable migrations by description, and skips callbacks", () => {
140
+ const r = run("flyway-dir", {
141
+ "V10__ten.sql": "",
142
+ "V2__two.sql": "",
143
+ "V1_1__one_one.sql": "",
144
+ "R__views.sql": "",
145
+ "R__a_first.sql": "",
146
+ "beforeMigrate.sql": "",
147
+ "U2__undo_two.sql": "",
148
+ "schema.sql": "",
149
+ });
150
+ expect(summary(r)).toEqual(["1 1.1 one one versioned", "2 2 two versioned", "3 10 ten versioned", "4 a first repeatable", "5 views repeatable"]);
151
+ expect(r.migrations![1]!.down).toBe("U2__undo_two.sql");
152
+ expect(gapsOf(r)).toEqual([]);
153
+ });
154
+
155
+ test("locations come from flyway.toml, read recursively", () => {
156
+ const r = run("flyway-dir", {
157
+ "flyway.toml": '[flyway]\nlocations = ["filesystem:sql", "classpath:db/migration"]\n\n[environments.default]\nurl = "jdbc:clickhouse://localhost:8123/shop"\n',
158
+ "sql/V1__a.sql": "",
159
+ "sql/more/V2__b.sql": "",
160
+ "src/main/resources/db/migration/V3__c.sql": "",
161
+ "elsewhere/V9__x.sql": "",
162
+ });
163
+ expect(summary(r)).toEqual(["1 1 a versioned", "2 2 b versioned", "3 3 c versioned"]);
164
+ expect(r.migrations![0]!.dialect).toBe("clickhouse");
165
+ });
166
+
167
+ test("flyway.conf locations, a configuration above the member, and the dialect field", () => {
168
+ const r = run("flyway-dir", { "flyway.conf": "flyway.locations=filesystem:db/sql\nflyway.url=jdbc:postgresql://h/d\n", "db/sql/V1__a.sql": "" }, { dialect: "clickhouse" }, "db");
169
+ // The configuration sits above the member, so db/sql is relative to the root.
170
+ expect(r.migrations!.map((m) => `${m.file} ${m.dialect}`)).toEqual(["db/sql/V1__a.sql clickhouse"]);
171
+ });
172
+
173
+ test("without a configuration the member's directory is the location", () => {
174
+ expect(summary(run("flyway-dir", { "db/V1__a.sql": "" }, {}, "db"))).toEqual(["1 1 a versioned"]);
175
+ });
176
+
177
+ test("a code migration, a duplicate version and an outside location are gaps", () => {
178
+ const r = run("flyway-dir", {
179
+ "flyway.toml": '[flyway]\nlocations = ["filesystem:sql", "filesystem:../shared"]\n',
180
+ "sql/V1__a.sql": "",
181
+ "sql/V1_0__b.sql": "",
182
+ "sql/V2__Code.java": "",
183
+ });
184
+ expect(gapsOf(r).map((g) => g.message.split(",")[0])).toEqual([
185
+ "the location filesystem:../shared lies outside the workspace",
186
+ "V2__Code.java is a code migration; its code is not SQL",
187
+ "version 1 is also sql/V1_0__b.sql; Flyway refuses two migrations with one version",
188
+ ]);
189
+ });
190
+ });
191
+
192
+ describe("the shared Flyway layout", () => {
193
+ test("version comparison is numeric, part by part", () => {
194
+ expect(["10", "2", "1.10", "1.2", "1"].sort(compareVersions)).toEqual(["1", "1.2", "1.10", "2", "10"]);
195
+ });
196
+
197
+ test("file names follow the configured prefixes, separator and suffixes", () => {
198
+ const config = readFlywayConfig(memoryTree({ "flyway.conf": "flyway.sqlMigrationPrefix=M\nflyway.sqlMigrationSeparator=--\nflyway.sqlMigrationSuffixes=.ddl\n" }), "flyway.conf", "");
199
+ expect(classifyFlywayFile("M3--add.ddl", config)).toMatchObject({ type: "versioned", version: "3", description: "add" });
200
+ expect(classifyFlywayFile("V3__add.sql", config)).toBeUndefined();
201
+ });
202
+
203
+ test("toml tables, arrays and comments parse", () => {
204
+ expect(parseToml('# c\n[flyway]\nlocations = [\n "a", # x\n "b"\n]\nvalidateOnMigrate = true\n').flyway).toEqual({ locations: ["a", "b"], validateOnMigrate: true });
205
+ });
206
+
207
+ test("placeholders substitute, and a missing one names its file and line", () => {
208
+ const config = readFlywayConfig(memoryTree({ "flyway.conf": "flyway.placeholders.env=prod\nflyway.table=hist\n" }), "flyway.conf", "");
209
+ expect(substitute("a ${env}\nb ${flyway:table}", "V1__a.sql", config)).toBe("a prod\nb hist");
210
+ expect(() => substitute("ok\n${nope}", "V1__a.sql", config)).toThrow(FlywayPlaceholderError);
211
+ expect(() => substitute("ok\n${nope}", "V1__a.sql", config)).toThrow(/V1__a\.sql:2: no value for the placeholder \$\{nope\}/);
212
+ });
213
+ });
214
+
215
+ describe("the recorded replay", () => {
216
+ const goose = { "1_a.sql": "-- +goose Up\nCREATE TABLE a (id int);\n", "2_b.sql": "-- +goose Up\nALTER TABLE a ADD c int;\n" };
217
+ const snapshot = (files: Record<string, string>, outputs: string[]) => {
218
+ const tree = memoryTree(files);
219
+ const r = run("goose-dir", files);
220
+ const digest = migrationDigest((f) => tree.read(f), replayFiles(tree, r.migrations!));
221
+ return JSON.stringify({ schema: 1, dialect: "postgres", digest, outputs }, null, 2);
222
+ };
223
+
224
+ test("without a snapshot the outputs are a gap that says to replay", () => {
225
+ const r = run("goose-dir", goose);
226
+ expect(r.outputs).toBeUndefined();
227
+ expect(r.gaps).toEqual([expect.objectContaining({ outputs: true, message: expect.stringContaining("chant sql replay") })]);
228
+ });
229
+
230
+ test("a snapshot whose digest matches gives its names as outputs, with the line that states each", () => {
231
+ const r = run("goose-dir", { ...goose, [REPLAY_FILE]: snapshot(goose, ["public.a", "public.a.id", "public.a.c"]) });
232
+ expect(r.gaps).toEqual([]);
233
+ expect(r.outputs).toEqual([
234
+ { name: "public.a", file: REPLAY_FILE, line: 6 },
235
+ { name: "public.a.id", file: REPLAY_FILE, line: 7 },
236
+ { name: "public.a.c", file: REPLAY_FILE, line: 8 },
237
+ ]);
238
+ });
239
+
240
+ test("an edited migration makes the snapshot stale: no outputs and a gap", () => {
241
+ const stale = { ...goose, "2_b.sql": "-- +goose Up\nALTER TABLE a DROP COLUMN id;\n" };
242
+ const r = run("goose-dir", { ...stale, [REPLAY_FILE]: snapshot(goose, ["public.a"]) });
243
+ expect(r.outputs).toBeUndefined();
244
+ expect(r.gaps[0]).toMatchObject({ file: REPLAY_FILE, outputs: true, message: expect.stringContaining("changed since") });
245
+ });
246
+
247
+ test("a new migration makes it stale too, and a snapshot that is not one is a gap", () => {
248
+ expect(run("goose-dir", { ...goose, "3_c.sql": "-- +goose Up\nselect 1;\n", [REPLAY_FILE]: snapshot(goose, []) }).gaps[0]!.message).toContain("changed since");
249
+ expect(run("goose-dir", { ...goose, [REPLAY_FILE]: "{}" }).gaps[0]!.message).toContain("not a replay snapshot");
250
+ expect(run("goose-dir", { ...goose, [REPLAY_FILE]: "{" }).gaps[0]!.message).toContain("could not be read");
251
+ });
252
+
253
+ test("a Flyway snapshot also depends on the configuration, which holds the placeholders", () => {
254
+ const files = { "flyway.conf": "flyway.placeholders.t=a\n", "V1__a.sql": "create table ${t} (id int);\n" };
255
+ const tree = memoryTree(files);
256
+ const r = run("flyway-dir", files);
257
+ const digest = migrationDigest((f) => tree.read(f), replayFiles(tree, r.migrations!, ["flyway.conf"]));
258
+ const snap = JSON.stringify({ schema: 1, dialect: "postgres", digest, outputs: ["public.a"] });
259
+ expect(run("flyway-dir", { ...files, [REPLAY_FILE]: snap }).outputs!.map((o) => o.name)).toEqual(["public.a"]);
260
+ expect(run("flyway-dir", { ...files, "flyway.conf": "flyway.placeholders.t=b\n", [REPLAY_FILE]: snap }).outputs).toBeUndefined();
261
+ });
262
+
263
+ test("the outputs reach a consumer's link through withReaderOutputs: a column the migrations drop is missing", () => {
264
+ const base = new Map([["db", { outputs: [], complete: true, why: null, exposesNone: false }]]) as never;
265
+ const read = run("goose-dir", { ...goose, [REPLAY_FILE]: snapshot(goose, ["public.a", "public.a.id"]) });
266
+ const handles = withReaderOutputs(base, new Map([["db", read]]));
267
+ expect(handles.get("db")!.outputs.map((o) => o.name)).toEqual(["public.a", "public.a.id"]);
268
+ expect(handles.get("db")!.complete).toBe(true);
269
+ const unreplayed = withReaderOutputs(base, new Map([["db", run("goose-dir", goose)]]));
270
+ expect(unreplayed.get("db")!.complete).toBe(false);
271
+ });
272
+ });