@intentius/chant-lexicon-terraform 0.124.52 → 0.124.54

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 (336) hide show
  1. package/README.md +7 -6
  2. package/dist/__fixtures__/pr-loop/estate.component.d.ts +7 -0
  3. package/dist/__fixtures__/pr-loop/estate.component.d.ts.map +1 -0
  4. package/dist/__fixtures__/pr-loop/estate.component.js +7 -0
  5. package/dist/__fixtures__/report/generate.d.ts +26 -0
  6. package/dist/__fixtures__/report/generate.d.ts.map +1 -0
  7. package/dist/__fixtures__/report/generate.js +58 -0
  8. package/dist/applied-state.d.ts +27 -0
  9. package/dist/applied-state.d.ts.map +1 -0
  10. package/dist/applied-state.js +44 -0
  11. package/dist/backend-config.d.ts +53 -0
  12. package/dist/backend-config.d.ts.map +1 -0
  13. package/dist/backend-config.js +136 -0
  14. package/dist/behaviour/kinds.d.ts.map +1 -1
  15. package/dist/behaviour/kinds.js +4 -0
  16. package/dist/behaviour/predict.d.ts.map +1 -1
  17. package/dist/behaviour/predict.js +6 -2
  18. package/dist/behaviour/reference-catalog.d.ts +16 -5
  19. package/dist/behaviour/reference-catalog.d.ts.map +1 -1
  20. package/dist/behaviour/reference-catalog.js +16 -5
  21. package/dist/behaviour/request.d.ts.map +1 -1
  22. package/dist/behaviour/request.js +3 -2
  23. package/dist/changed-roots.d.ts +6 -4
  24. package/dist/changed-roots.d.ts.map +1 -1
  25. package/dist/changed-roots.js +14 -18
  26. package/dist/commands.d.ts.map +1 -1
  27. package/dist/commands.js +4 -3
  28. package/dist/components/capability-plugin.d.ts +65 -3
  29. package/dist/components/capability-plugin.d.ts.map +1 -1
  30. package/dist/components/capability-plugin.js +197 -13
  31. package/dist/components/compose-group.d.ts +145 -0
  32. package/dist/components/compose-group.d.ts.map +1 -0
  33. package/dist/components/compose-group.js +407 -0
  34. package/dist/components/placeholder-preview.d.ts +78 -0
  35. package/dist/components/placeholder-preview.d.ts.map +1 -0
  36. package/dist/components/placeholder-preview.js +254 -0
  37. package/dist/compose/apply-events.d.ts +46 -0
  38. package/dist/compose/apply-events.d.ts.map +1 -0
  39. package/dist/compose/apply-events.js +77 -0
  40. package/dist/compose/estate.d.ts +194 -0
  41. package/dist/compose/estate.d.ts.map +1 -0
  42. package/dist/compose/estate.js +363 -0
  43. package/dist/compose/hcl.d.ts +99 -0
  44. package/dist/compose/hcl.d.ts.map +1 -0
  45. package/dist/compose/hcl.js +302 -0
  46. package/dist/compose/module-address.d.ts +24 -0
  47. package/dist/compose/module-address.d.ts.map +1 -0
  48. package/dist/compose/module-address.js +43 -0
  49. package/dist/compose/root.d.ts +127 -0
  50. package/dist/compose/root.d.ts.map +1 -0
  51. package/dist/compose/root.js +586 -0
  52. package/dist/compose/run.d.ts +183 -0
  53. package/dist/compose/run.d.ts.map +1 -0
  54. package/dist/compose/run.js +447 -0
  55. package/dist/compose/slice.d.ts +102 -0
  56. package/dist/compose/slice.d.ts.map +1 -0
  57. package/dist/compose/slice.js +249 -0
  58. package/dist/compose/state-address.d.ts +28 -0
  59. package/dist/compose/state-address.d.ts.map +1 -0
  60. package/dist/compose/state-address.js +154 -0
  61. package/dist/compose/state-merge.d.ts +52 -0
  62. package/dist/compose/state-merge.d.ts.map +1 -0
  63. package/dist/compose/state-merge.js +135 -0
  64. package/dist/compose.d.ts +179 -0
  65. package/dist/compose.d.ts.map +1 -0
  66. package/dist/compose.js +482 -0
  67. package/dist/composites/terraform-apply-op.d.ts.map +1 -1
  68. package/dist/composites/terraform-apply-op.js +7 -3
  69. package/dist/config.d.ts +59 -13
  70. package/dist/config.d.ts.map +1 -1
  71. package/dist/config.js +43 -9
  72. package/dist/describe-resources.d.ts +74 -12
  73. package/dist/describe-resources.d.ts.map +1 -1
  74. package/dist/describe-resources.js +374 -66
  75. package/dist/drift-read.d.ts +33 -0
  76. package/dist/drift-read.d.ts.map +1 -0
  77. package/dist/drift-read.js +243 -0
  78. package/dist/drift-record.d.ts +147 -0
  79. package/dist/drift-record.d.ts.map +1 -0
  80. package/dist/drift-record.js +383 -0
  81. package/dist/hcl/edges.d.ts.map +1 -1
  82. package/dist/hcl/edges.js +24 -1
  83. package/dist/hcl/files.d.ts +61 -0
  84. package/dist/hcl/files.d.ts.map +1 -0
  85. package/dist/hcl/files.js +221 -0
  86. package/dist/hcl/instances.d.ts +82 -0
  87. package/dist/hcl/instances.d.ts.map +1 -0
  88. package/dist/hcl/instances.js +390 -0
  89. package/dist/hcl/parse.d.ts +81 -9
  90. package/dist/hcl/parse.d.ts.map +1 -1
  91. package/dist/hcl/parse.js +114 -16
  92. package/dist/hcl/roots.d.ts.map +1 -1
  93. package/dist/hcl/roots.js +5 -0
  94. package/dist/hcl/suppressions.d.ts.map +1 -1
  95. package/dist/hcl/suppressions.js +8 -2
  96. package/dist/index.d.ts +11 -2
  97. package/dist/index.d.ts.map +1 -1
  98. package/dist/index.js +21 -2
  99. package/dist/integrity.json +3 -3
  100. package/dist/live-plan-reasons.d.ts +21 -0
  101. package/dist/live-plan-reasons.d.ts.map +1 -0
  102. package/dist/live-plan-reasons.js +47 -0
  103. package/dist/lsp/option-keys.d.ts.map +1 -1
  104. package/dist/lsp/option-keys.js +2 -0
  105. package/dist/manifest.json +1 -1
  106. package/dist/op/activities/index.d.ts +6 -2
  107. package/dist/op/activities/index.d.ts.map +1 -1
  108. package/dist/op/activities/index.js +5 -1
  109. package/dist/op/activities/live-detect.d.ts +4 -3
  110. package/dist/op/activities/live-detect.d.ts.map +1 -1
  111. package/dist/op/activities/live-detect.js +57 -32
  112. package/dist/op/activities/pin-rollout.d.ts +2 -0
  113. package/dist/op/activities/pin-rollout.d.ts.map +1 -1
  114. package/dist/op/activities/pin-rollout.js +1 -0
  115. package/dist/op/activities/terraform.d.ts +153 -5
  116. package/dist/op/activities/terraform.d.ts.map +1 -1
  117. package/dist/op/activities/terraform.js +205 -20
  118. package/dist/op/activity-contracts.d.ts +27 -0
  119. package/dist/op/activity-contracts.d.ts.map +1 -1
  120. package/dist/op/activity-contracts.js +19 -1
  121. package/dist/op/builders.d.ts +7 -1
  122. package/dist/op/builders.d.ts.map +1 -1
  123. package/dist/op/builders.js +9 -0
  124. package/dist/pin/edit.d.ts.map +1 -1
  125. package/dist/pin/edit.js +35 -0
  126. package/dist/pin/rollout.d.ts +2 -0
  127. package/dist/pin/rollout.d.ts.map +1 -1
  128. package/dist/pin/rollout.js +14 -11
  129. package/dist/plan-shape.d.ts +30 -0
  130. package/dist/plan-shape.d.ts.map +1 -0
  131. package/dist/plan-shape.js +125 -0
  132. package/dist/plugin.d.ts.map +1 -1
  133. package/dist/plugin.js +32 -18
  134. package/dist/report.d.ts +15 -6
  135. package/dist/report.d.ts.map +1 -1
  136. package/dist/report.js +28 -55
  137. package/dist/skills/chant-terraform.md +1 -1
  138. package/dist/state/azure-blob.d.ts +68 -0
  139. package/dist/state/azure-blob.d.ts.map +1 -0
  140. package/dist/state/azure-blob.js +201 -0
  141. package/dist/state/backend.d.ts +158 -0
  142. package/dist/state/backend.d.ts.map +1 -0
  143. package/dist/state/backend.js +406 -0
  144. package/dist/state/exec.d.ts +39 -0
  145. package/dist/state/exec.d.ts.map +1 -0
  146. package/dist/state/exec.js +80 -0
  147. package/dist/state/gcs.d.ts +62 -0
  148. package/dist/state/gcs.d.ts.map +1 -0
  149. package/dist/state/gcs.js +224 -0
  150. package/dist/state/index.d.ts +15 -0
  151. package/dist/state/index.d.ts.map +1 -0
  152. package/dist/state/index.js +14 -0
  153. package/dist/state/locks.d.ts +105 -0
  154. package/dist/state/locks.d.ts.map +1 -0
  155. package/dist/state/locks.js +130 -0
  156. package/dist/state/object-store.d.ts +37 -0
  157. package/dist/state/object-store.d.ts.map +1 -0
  158. package/dist/state/object-store.js +20 -0
  159. package/dist/state/root-states.d.ts +131 -0
  160. package/dist/state/root-states.d.ts.map +1 -0
  161. package/dist/state/root-states.js +241 -0
  162. package/dist/state/s3.d.ts +68 -0
  163. package/dist/state/s3.d.ts.map +1 -0
  164. package/dist/state/s3.js +170 -0
  165. package/dist/state/store.d.ts +51 -0
  166. package/dist/state/store.d.ts.map +1 -0
  167. package/dist/state/store.js +185 -0
  168. package/dist/terragrunt/affected.d.ts.map +1 -1
  169. package/dist/terragrunt/affected.js +2 -1
  170. package/package.json +7 -3
  171. package/src/__fixtures__/ACCEPTANCE.md +69 -7
  172. package/src/__fixtures__/compose/apply-failed-in-app.jsonl +12 -0
  173. package/src/__fixtures__/compose/state-app.json +66 -0
  174. package/src/__fixtures__/compose/state-edge.json +40 -0
  175. package/src/__fixtures__/compose/state-net.json +58 -0
  176. package/src/__fixtures__/compose/three-app.plan.json +184 -0
  177. package/src/__fixtures__/compose/three-composed.plan.json +509 -0
  178. package/src/__fixtures__/compose/three-edge.plan.json +144 -0
  179. package/src/__fixtures__/compose/three-net.plan.json +174 -0
  180. package/src/__fixtures__/compose/unknown-composed.plan.json +228 -0
  181. package/src/__fixtures__/compose/working-after-apply.json +145 -0
  182. package/src/__fixtures__/config-blocks/app/main.tf +61 -0
  183. package/src/__fixtures__/config-blocks/app/modules/network/main.tf +4 -0
  184. package/src/__fixtures__/config-blocks/app-json/main.tf.json +39 -0
  185. package/src/__fixtures__/config-files/shadow/main.tf +3 -0
  186. package/src/__fixtures__/config-files/shadow/main.tofu +3 -0
  187. package/src/__fixtures__/config-files/shadow/other.tf +3 -0
  188. package/src/__fixtures__/config-files/tf/main.tf +11 -0
  189. package/src/__fixtures__/config-files/tf-json/main.tf.json +18 -0
  190. package/src/__fixtures__/config-files/tofu/main.tofu +11 -0
  191. package/src/__fixtures__/config-files/tofu-json/main.tofu.json +18 -0
  192. package/src/__fixtures__/config-files/tofu-only/main.tofu +3 -0
  193. package/src/__fixtures__/drift-record/README.md +11 -0
  194. package/src/__fixtures__/drift-record/live-plan-sections.json +27 -0
  195. package/src/__fixtures__/instance-forms/app/main.tf +110 -0
  196. package/src/__fixtures__/instance-forms/app/modules/svc/main.tf +3 -0
  197. package/src/__fixtures__/instance-vars/app/main.tf +52 -0
  198. package/src/__fixtures__/instance-vars/app/modules/app/main.tf +12 -0
  199. package/src/__fixtures__/instance-vars/app/modules/edge/main.tf +3 -0
  200. package/src/__fixtures__/instance-vars/app/terraform.tfvars +1 -0
  201. package/src/__fixtures__/live-module-keyed/live-graph.json +259 -0
  202. package/src/__fixtures__/live-module-keyed/live-plan.json +87 -0
  203. package/src/__fixtures__/live-module-keyed/root/estate.chdf.hcl +1 -0
  204. package/src/__fixtures__/live-module-keyed/root/main.tf +21 -0
  205. package/src/__fixtures__/live-module-keyed/root/modules/app/main.tf +17 -0
  206. package/src/__fixtures__/live-module-keyed/root/modules/edge/main.tf +7 -0
  207. package/src/__fixtures__/live-module-keyed/root/versions.tf +30 -0
  208. package/src/__fixtures__/live-module-keyed/show-state.json +146 -0
  209. package/src/__fixtures__/pr-loop/README.md +50 -0
  210. package/src/__fixtures__/pr-loop/chant.config.json +13 -0
  211. package/src/__fixtures__/pr-loop/estate.component.ts +10 -0
  212. package/src/__fixtures__/pr-loop/roots/a/main.tf +11 -0
  213. package/src/__fixtures__/pr-loop/roots/app/main.tf +19 -0
  214. package/src/__fixtures__/pr-loop/roots/b/main.tf +7 -0
  215. package/src/__fixtures__/pr-loop/roots/dns/main.tf +3 -0
  216. package/src/__fixtures__/pr-loop/roots/net/main.tf +7 -0
  217. package/src/__fixtures__/report/applied.show.json +51 -0
  218. package/src/__fixtures__/report/generate.ts +63 -0
  219. package/src/__fixtures__/report/plan.show.json +200 -0
  220. package/src/__fixtures__/report/tf-apply/change-set.json +170 -0
  221. package/src/__fixtures__/report/tf-apply/report.json +338 -0
  222. package/src/__fixtures__/report/tf-plan/change-set.json +170 -0
  223. package/src/__fixtures__/report/tf-plan/report.json +320 -0
  224. package/src/__fixtures__/three-cloud-azure.graph.json +290 -0
  225. package/src/applied-state.ts +52 -0
  226. package/src/backend-config.test.ts +105 -0
  227. package/src/backend-config.ts +133 -0
  228. package/src/behaviour/kinds.ts +4 -0
  229. package/src/behaviour/predict.ts +5 -2
  230. package/src/behaviour/reference-catalog.plugin.test.ts +65 -0
  231. package/src/behaviour/reference-catalog.ts +16 -5
  232. package/src/behaviour/request.ts +3 -2
  233. package/src/change-set-instances.test.ts +98 -0
  234. package/src/changed-roots.ts +14 -16
  235. package/src/commands.ts +4 -3
  236. package/src/components/capability-plugin.test.ts +172 -5
  237. package/src/components/capability-plugin.ts +212 -11
  238. package/src/components/change-set-graph.e2e.test.ts +11 -5
  239. package/src/components/compose-group.test.ts +486 -0
  240. package/src/components/compose-group.ts +489 -0
  241. package/src/components/demo-composed-roots.e2e.test.ts +440 -0
  242. package/src/components/member-outputs.test.ts +76 -0
  243. package/src/components/placeholder-preview.test.ts +147 -0
  244. package/src/components/placeholder-preview.ts +257 -0
  245. package/src/components/pr-loop-compose.tofu.e2e.test.ts +363 -0
  246. package/src/components/pr-loop.tofu.test.ts +112 -56
  247. package/src/compose/apply-events.test.ts +62 -0
  248. package/src/compose/apply-events.ts +100 -0
  249. package/src/compose/estate.choudoufu.e2e.test.ts +112 -0
  250. package/src/compose/estate.test.ts +421 -0
  251. package/src/compose/estate.ts +443 -0
  252. package/src/compose/hcl.ts +311 -0
  253. package/src/compose/module-address.ts +49 -0
  254. package/src/compose/root.test.ts +416 -0
  255. package/src/compose/root.ts +592 -0
  256. package/src/compose/run.test.ts +440 -0
  257. package/src/compose/run.tofu.test.ts +176 -0
  258. package/src/compose/run.ts +527 -0
  259. package/src/compose/slice.test.ts +184 -0
  260. package/src/compose/slice.ts +290 -0
  261. package/src/compose/state-address.ts +157 -0
  262. package/src/compose/state-merge.test.ts +131 -0
  263. package/src/compose/state-merge.ts +148 -0
  264. package/src/compose.test.ts +248 -0
  265. package/src/compose.ts +627 -0
  266. package/src/composites/terraform-apply-op.test.ts +9 -0
  267. package/src/composites/terraform-apply-op.ts +7 -3
  268. package/src/config-file-listers.test.ts +265 -0
  269. package/src/config.ts +44 -9
  270. package/src/describe-resources.engine-facts.test.ts +412 -0
  271. package/src/describe-resources.live-module-keyed.test.ts +290 -0
  272. package/src/describe-resources.ts +416 -70
  273. package/src/drift-read.ts +284 -0
  274. package/src/drift-record.test.ts +223 -0
  275. package/src/drift-record.ts +455 -0
  276. package/src/hcl/config-blocks.test.ts +174 -0
  277. package/src/hcl/edges.ts +21 -1
  278. package/src/hcl/files.ts +234 -0
  279. package/src/hcl/instance-forms.test.ts +94 -0
  280. package/src/hcl/instances.test.ts +137 -0
  281. package/src/hcl/instances.ts +422 -0
  282. package/src/hcl/parse.test.ts +139 -1
  283. package/src/hcl/parse.ts +149 -16
  284. package/src/hcl/roots.ts +6 -0
  285. package/src/hcl/suppressions.ts +8 -2
  286. package/src/index.ts +76 -0
  287. package/src/live-plan-reasons.ts +54 -0
  288. package/src/lsp/completions.test.ts +1 -1
  289. package/src/lsp/option-keys.ts +2 -0
  290. package/src/op/activities/choudoufu-plan-set-op.test.ts +111 -0
  291. package/src/op/activities/index.ts +9 -0
  292. package/src/op/activities/live-detect.ts +55 -26
  293. package/src/op/activities/pin-rollout.ts +3 -0
  294. package/src/op/activities/registry.test.ts +2 -1
  295. package/src/op/activities/terraform.test.ts +55 -0
  296. package/src/op/activities/terraform.ts +266 -22
  297. package/src/op/activity-contracts.test.ts +1 -0
  298. package/src/op/activity-contracts.ts +24 -1
  299. package/src/op/builders.ts +14 -0
  300. package/src/pin/bundle.test.ts +2 -0
  301. package/src/pin/edit.ts +30 -0
  302. package/src/pin/rollout.ts +16 -11
  303. package/src/plan-shape.test.ts +148 -0
  304. package/src/plan-shape.ts +128 -0
  305. package/src/plugin.ts +33 -19
  306. package/src/report.test.ts +82 -1
  307. package/src/report.ts +31 -54
  308. package/src/signal-only-root.test.ts +72 -0
  309. package/src/skills/chant-terraform.md +1 -1
  310. package/src/state/azure-blob.ts +223 -0
  311. package/src/state/backend-verdict.test.ts +187 -0
  312. package/src/state/backend.test.ts +127 -0
  313. package/src/state/backend.ts +488 -0
  314. package/src/state/exec.ts +111 -0
  315. package/src/state/gcs.ts +254 -0
  316. package/src/state/index.ts +15 -0
  317. package/src/state/locks.test.ts +84 -0
  318. package/src/state/locks.ts +201 -0
  319. package/src/state/object-store.ts +35 -0
  320. package/src/state/root-states-floci.e2e.test.ts +66 -0
  321. package/src/state/root-states.test.ts +462 -0
  322. package/src/state/root-states.ts +309 -0
  323. package/src/state/s3.ts +193 -0
  324. package/src/state/store.ts +225 -0
  325. package/src/terragrunt/affected.ts +2 -1
  326. package/src/workspace-kinds.test.ts +4 -17
  327. package/workspace-kinds.json +1 -24
  328. package/dist/workspace-member.d.ts +0 -30
  329. package/dist/workspace-member.d.ts.map +0 -1
  330. package/dist/workspace-member.js +0 -91
  331. package/schemas/pulumi.schema.json +0 -34
  332. package/src/testdata/pulumi-member/Pulumi.yaml +0 -2
  333. package/src/testdata/pulumi-member/stack-export.json +0 -53
  334. package/src/testdata/pulumi-unexported/Pulumi.yaml +0 -2
  335. package/src/workspace-member.test.ts +0 -89
  336. package/src/workspace-member.ts +0 -126
@@ -0,0 +1,482 @@
1
+ /**
2
+ * Which roots a change composes, in what order, and why the others fall back
3
+ * (chant#4438, epic #4419). Pure functions over a project's config, its
4
+ * components and its roots' files: nothing here runs a binary.
5
+ *
6
+ * A composed run plans the `terraform-apply` components a change reaches,
7
+ * and the ones they read, as one plan of a composed root of `module` calls,
8
+ * and applies it as one apply, so a value one root creates reaches the roots
9
+ * that read it inside that apply. Writing the composed root is #4441;
10
+ * splitting its state is #4442; running it is #4444.
11
+ *
12
+ * Lifted from terragucci's `packages/terragucci/src/compose.ts` at c493e0e
13
+ * (`Composition`, `composeSet`, `readOrder`, `moduleNames`, `compositionFor`,
14
+ * `fallbackOf`, `fallbackLine`), with chant's inputs in place of terragucci's:
15
+ *
16
+ * - The roots are `terraform.roots`, each deployed by a component whose step
17
+ * is `{ kind: "terraform-apply", root }`. A member is a component.
18
+ * - A read between members is a `stackOutput()` in the step's `vars`, a
19
+ * `terraform_remote_state` block whose address is another member's
20
+ * backend, or a `terraform_estate_outputs` block naming another member's
21
+ * estate.
22
+ * - `waves.after` is the component's `dependsOn`; `oidc.roles` is the apply
23
+ * identity the caller resolves per member (`ci.identity.cloud`, #4296);
24
+ * `TF_CLI_ARGS*` is read from the environment as before.
25
+ *
26
+ * Unlike terragucci, part of a change can compose (#4419 ruling 1): each
27
+ * member that cannot is left out with its own reason, and the rest compose
28
+ * when two or more remain. A component of another kind, or a root left out,
29
+ * on a dependency path between two members splits them (ruling 2), and only
30
+ * a component whose deploy is that one step joins (ruling 3).
31
+ *
32
+ * Off unless `terraform.compose.enabled` is set: {@link compositionIfEnabled}
33
+ * is the entry point, and calls nothing when it is not.
34
+ */
35
+ import { isAbsolute, resolve } from "node:path";
36
+ import { wiringReads } from "@intentius/chant/components/driver";
37
+ import { lockRefusal, readBackend } from "./state/backend.js";
38
+ import { estateOutputReads, importLiteral, jsonImportRefused, liveEstate, localModules, lockedProviders, relPath, rootState, rootTexts, sameState, blockBody, } from "./compose/hcl.js";
39
+ export { blankComments, lockedProviders, blockBody, hclAttr, rootState, liveEstate, estateOutputReads } from "./compose/hcl.js";
40
+ /** `terraform.compose.max` when unset. */
41
+ export const DEFAULT_COMPOSE_MAX = 10;
42
+ /** The step kind a member's deploy is. */
43
+ export const COMPOSE_STEP_KIND = "terraform-apply";
44
+ /** A member's identity as {@link CompositionInput.identity} compares it: its apply trade, or `""` for none. */
45
+ export function identityKey(identity) {
46
+ return identity ? JSON.stringify(identity.apply) : "";
47
+ }
48
+ const plural = (n, one, many = `${one}s`) => `${n} ${n === 1 ? one : many}`;
49
+ const list = (xs) => xs.map((x) => `\`${x}\``).join(", ");
50
+ /**
51
+ * The module name of each root: its name with every character a name cannot
52
+ * hold as `_` (`envs/prod-app` is `envs_prod_app`), `root` for an empty one,
53
+ * `r_` before a leading digit, and `_2`, `_3` on a clash, given in name order.
54
+ */
55
+ export function moduleNames(roots) {
56
+ const out = new Map();
57
+ const taken = new Set();
58
+ for (const r of [...new Set(roots)].sort()) {
59
+ let base = r === "." || r === "" ? "root" : r.replace(/[^A-Za-z0-9_]/g, "_").replace(/^_+|_+$/g, "") || "root";
60
+ if (/^[0-9]/.test(base))
61
+ base = `r_${base}`;
62
+ let name = base;
63
+ for (let i = 2; taken.has(name); i++)
64
+ name = `${base}_${i}`;
65
+ taken.add(name);
66
+ out.set(r, name);
67
+ }
68
+ return out;
69
+ }
70
+ /** The members of `set`, each after every member of the set it reads; members that read none of each other in name order. */
71
+ export function readOrder(set, deps) {
72
+ const left = new Set(set);
73
+ const out = [];
74
+ while (left.size > 0) {
75
+ const ready = [...left].filter((r) => [...(deps.get(r) ?? [])].every((u) => !left.has(u))).sort();
76
+ // A cycle the waves would refuse: take the rest in name order rather than stop.
77
+ const take = ready.length > 0 ? ready : [...left].sort();
78
+ for (const r of take) {
79
+ out.push(r);
80
+ left.delete(r);
81
+ }
82
+ }
83
+ return out;
84
+ }
85
+ /** The members a composed run covers: those of `all` the change reaches and every one of `all` they read, followed upstream. */
86
+ export function composeSet(all, selected, deps) {
87
+ const set = new Set(selected ? [...selected].filter((r) => all.includes(r)) : all);
88
+ const queue = [...set];
89
+ while (queue.length > 0) {
90
+ const r = queue.pop();
91
+ for (const u of deps.get(r) ?? []) {
92
+ if (!set.has(u) && all.includes(u)) {
93
+ set.add(u);
94
+ queue.push(u);
95
+ }
96
+ }
97
+ }
98
+ return [...set].sort();
99
+ }
100
+ // ── components ────────────────────────────────────────────────────────────
101
+ /** Every leaf step of a deploy, gates included, in order. */
102
+ function leafSteps(phases) {
103
+ const out = [];
104
+ const walk = (steps) => {
105
+ for (const s of steps) {
106
+ if (typeof s.phase === "string" && Array.isArray(s.steps))
107
+ walk(s.steps);
108
+ else
109
+ out.push(s);
110
+ }
111
+ };
112
+ for (const p of phases)
113
+ walk(p.steps);
114
+ return out;
115
+ }
116
+ /** A component's kind, for a reason: its first step's kind. */
117
+ const kindOf = (c) => leafSteps(c.deploy)[0]?.kind ?? "empty";
118
+ /** The top-level vars of a step and the members' outputs each reads through `stackOutput()` or `@<component>.publish.*`. */
119
+ function varsReads(step) {
120
+ const vars = step.vars && typeof step.vars === "object" && !Array.isArray(step.vars) ? step.vars : {};
121
+ const out = [];
122
+ for (const [variable, value] of Object.entries(vars)) {
123
+ for (const r of wiringReads({ [variable]: value }, {}))
124
+ out.push({ variable, upstream: r.source, output: r.ref.slice(r.source.length + 1) });
125
+ }
126
+ return out;
127
+ }
128
+ /** Every component another reads or depends on, from any step's wiring and its `dependsOn`, plus the HCL reads between roots. */
129
+ function dependencyGraph(components, hcl) {
130
+ const names = new Set(components.map((c) => c.name));
131
+ const out = new Map();
132
+ for (const c of components) {
133
+ const deps = new Set((c.dependsOn ?? []).filter((d) => names.has(d)));
134
+ for (const step of leafSteps(c.deploy)) {
135
+ for (const r of wiringReads(step, {}))
136
+ if (names.has(r.source) && r.source !== c.name)
137
+ deps.add(r.source);
138
+ }
139
+ for (const r of hcl.get(c.name) ?? [])
140
+ deps.add(r.upstream);
141
+ out.set(c.name, deps);
142
+ }
143
+ return out;
144
+ }
145
+ /** Each root component's HCL reads of another root component. */
146
+ function hclReads(rcs) {
147
+ const out = new Map();
148
+ for (const r of rcs) {
149
+ const reads = [];
150
+ if (r.state && r.dir) {
151
+ for (const read of r.state.reads) {
152
+ for (const o of rcs)
153
+ if (o.name !== r.name && o.state?.own && sameState(o.state.own, read))
154
+ reads.push({ via: "remote-state", upstream: o.name, name: read.name });
155
+ }
156
+ for (const read of estateOutputReads(r.dir, r.binary)) {
157
+ for (const o of rcs)
158
+ if (o.name !== r.name && o.estate === read.estate)
159
+ reads.push({ via: "estate-outputs", upstream: o.name, name: read.name });
160
+ }
161
+ }
162
+ out.set(r.name, reads);
163
+ }
164
+ return out;
165
+ }
166
+ /** Every component of `input` that deploys a root, its files read once. */
167
+ function rootComponents(input) {
168
+ const tf = input.terraform;
169
+ const projectRoot = resolve(input.projectRoot);
170
+ const rcs = [];
171
+ for (const c of input.components) {
172
+ const steps = leafSteps(c.deploy);
173
+ const step = steps.find((s) => s.kind === COMPOSE_STEP_KIND);
174
+ if (!step)
175
+ continue;
176
+ const root = typeof step.root === "string" ? step.root : undefined;
177
+ const config = root !== undefined ? tf.roots[root] : undefined;
178
+ const rc = { name: c.name, step, steps: steps.length, live: false, ...(root !== undefined ? { root } : {}) };
179
+ if (config && step.cwd === undefined) {
180
+ const dir = isAbsolute(config.dir) ? config.dir : resolve(projectRoot, config.dir);
181
+ const rel = relPath(projectRoot, dir);
182
+ const binary = tf.binary;
183
+ const live = liveEstate(dir, binary ?? "terraform");
184
+ const estate = config.estate ?? live.estate;
185
+ // The state address from the one backend verdict (chant#4478): the code, TF_CLI_ARGS*, the backend-config files and pairs, and what init recorded.
186
+ const state = rootState(dir, rel, {
187
+ ...(input.env ? { env: input.env } : {}),
188
+ ...(binary ? { binary } : {}),
189
+ ...(config.backendConfig ? { backendConfig: config.backendConfig } : {}),
190
+ ...(config.backendConfigFiles ? { backendConfigFiles: config.backendConfigFiles } : {}),
191
+ });
192
+ Object.assign(rc, { config, dir, rel, state, live: live.live, ...(estate ? { estate } : {}), ...(binary ? { binary } : {}) });
193
+ }
194
+ rcs.push(rc);
195
+ }
196
+ return rcs;
197
+ }
198
+ /**
199
+ * Each component's `terraform_remote_state` and `terraform_estate_outputs`
200
+ * reads of another component of `input` that deploys a root, as the
201
+ * composition finds them (#4447): what `terraform-apply`'s `groupReads`
202
+ * reports to core. Components with no such read are left out.
203
+ */
204
+ export function rootHclReads(input) {
205
+ return new Map([...hclReads(rootComponents(input))].filter(([, reads]) => reads.length > 0));
206
+ }
207
+ const readKey = (r) => `${r.upstream}\0${r.via}\0${r.via === "vars" ? `${r.variable}\0${r.output}` : r.name}`;
208
+ // ── the composition ───────────────────────────────────────────────────────
209
+ /** `TF_CLI_ARGS*` flags a composed run cannot take: their addresses name a root's resources without the module in front, and `-var` names no root. */
210
+ const CLI_REFUSED = /(^|\s)-{1,2}(target|exclude|replace|var)(=|\s|$)/;
211
+ /**
212
+ * The `off` fallback when `terraform.compose` is not enabled, else undefined:
213
+ * what `terraform-apply`'s `groupFallback` answers first (ws-107).
214
+ */
215
+ export function composeOff(terraform) {
216
+ return terraform.compose?.enabled === true ? undefined : { kind: "off", reason: "`terraform.compose` is not enabled" };
217
+ }
218
+ /**
219
+ * The composition when `terraform.compose.enabled` is set, else undefined
220
+ * without reading anything. The only caller of {@link compositionFor} a run
221
+ * should use.
222
+ */
223
+ export function compositionIfEnabled(input) {
224
+ if (input.terraform.compose?.enabled !== true)
225
+ return undefined;
226
+ return compositionFor(input);
227
+ }
228
+ /**
229
+ * What `terraform.compose` comes to for a change: the members, their order,
230
+ * modules and reads, each candidate left out with its reason, or why nothing
231
+ * composes. Per-member checks run first (root, steps, targets, binary,
232
+ * backend, identity, provider lock, reads, imports and paths, order, split);
233
+ * a member left out gets the first that applies to it. `cli-args`,
234
+ * `too-many-roots` and `one-root` then judge what is left as a whole.
235
+ */
236
+ export function compositionFor(input) {
237
+ const tf = input.terraform;
238
+ const max = tf.compose?.max ?? DEFAULT_COMPOSE_MAX;
239
+ const projectRoot = resolve(input.projectRoot);
240
+ const byName = new Map(input.components.map((c) => [c.name, c]));
241
+ const rcs = rootComponents(input);
242
+ const rcOf = new Map(rcs.map((r) => [r.name, r]));
243
+ const hcl = hclReads(rcs);
244
+ const graph = dependencyGraph(input.components, hcl);
245
+ const candidates = composeSet(rcs.map((r) => r.name), input.selected, graph);
246
+ const roots = new Map(candidates.flatMap((m) => (rcOf.get(m).root !== undefined ? [[m, rcOf.get(m).root]] : [])));
247
+ const dirs = new Map(candidates.flatMap((m) => (rcOf.get(m).dir ? [[m, rcOf.get(m).dir]] : [])));
248
+ // Each member's reads of every other candidate; narrowed to members at the end.
249
+ const allReads = new Map();
250
+ for (const m of candidates) {
251
+ const rc = rcOf.get(m);
252
+ const reads = [...varsReads(rc.step).map((r) => ({ via: "vars", ...r })), ...(hcl.get(m) ?? [])];
253
+ allReads.set(m, reads.filter((r) => r.upstream !== m && rcOf.has(r.upstream)));
254
+ }
255
+ const left = new Map();
256
+ let members = [...candidates];
257
+ const drop = (m, kind, reason) => {
258
+ left.set(m, { kind, member: m, reason });
259
+ members = members.filter((x) => x !== m);
260
+ };
261
+ const each = (check) => {
262
+ for (const m of [...members]) {
263
+ const hit = check(m, rcOf.get(m));
264
+ if (hit)
265
+ drop(m, hit[0], hit[1]);
266
+ }
267
+ };
268
+ // Where the root is, and that its deploy is that one step.
269
+ each((m, rc) => {
270
+ if (rc.root === undefined)
271
+ return ["path", `\`${m}\`'s terraform-apply step names no \`root\``];
272
+ if (rc.step.cwd !== undefined)
273
+ return ["path", `\`${m}\`'s terraform-apply step sets \`cwd\`, so its root is read from another chant.config`];
274
+ if (!rc.config)
275
+ return ["path", `\`${m}\` deploys root \`${rc.root}\`, which \`terraform.roots\` does not list`];
276
+ return undefined;
277
+ });
278
+ const rootTaken = new Map();
279
+ each((m, rc) => {
280
+ const had = rootTaken.get(rc.root);
281
+ if (had)
282
+ return ["same-root", `\`${m}\` deploys root \`${rc.root}\`, which \`${had}\` deploys too, and a composed run calls each root once`];
283
+ rootTaken.set(rc.root, m);
284
+ return undefined;
285
+ });
286
+ each((m, rc) => rc.steps !== 1 ? ["steps", `\`${m}\`'s deploy has ${plural(rc.steps, "step")}, and only a component whose deploy is one terraform-apply step composes`] : undefined);
287
+ each((m) => {
288
+ const t = input.targets?.[m] ?? [];
289
+ return t.length > 0 ? ["targets", `\`${m}\` is given ${plural(t.length, "target")} (\`--target\`), and a composed run plans each member whole`] : undefined;
290
+ });
291
+ // One binary process: one binary at one version.
292
+ const binaryOf = (m) => input.binary?.(m) ?? { name: tf.binary ?? "terraform" };
293
+ const binName = (b) => `${b.name}${b.version ? ` ${b.version}` : ""}`;
294
+ const binRef = majority(members, (m) => binName(binaryOf(m)));
295
+ each((m) => {
296
+ if (!binRef || binName(binaryOf(m)) === binRef.key)
297
+ return undefined;
298
+ return ["binary", `\`${m}\` runs ${binName(binaryOf(m))} and \`${binRef.first}\` ${binRef.key}, and one run has one binary`];
299
+ });
300
+ // Every member runs choudoufu and one has a `live` block: the members compose as estates (choudoufu#2059).
301
+ const estate = members.length > 1 && members.every((m) => binaryOf(m).name === "choudoufu") && members.some((m) => rcOf.get(m).live);
302
+ each((m, rc) => {
303
+ if (estate)
304
+ return rc.live ? undefined : ["backend", `\`${m}\` has no \`live\` block, and a composed run of estates calls only directories that carry one`];
305
+ const ws = rc.config.workspace;
306
+ if (ws && ws !== "default")
307
+ return ["workspace", `\`${m}\` plans in workspace ${ws}, and a composed run plans in default`];
308
+ // The one backend verdict (chant#4478): the code and flags before init, compared with what init recorded after.
309
+ const refusal = lockRefusal(readBackend(rc.dir, {
310
+ env: input.env,
311
+ ...(rc.binary ? { binary: rc.binary } : {}),
312
+ ...(rc.config.backendConfig ? { backendConfig: rc.config.backendConfig } : {}),
313
+ ...(rc.config.backendConfigFiles ? { backendConfigFiles: rc.config.backendConfigFiles } : {}),
314
+ }));
315
+ if (refusal)
316
+ return ["backend", `\`${m}\`: ${refusal.reason}`];
317
+ return undefined;
318
+ });
319
+ // One binary process has one set of credentials. Estates take each one's role from its own provider blocks.
320
+ if (!estate && input.identity) {
321
+ const idOf = (m) => input.identity(m) ?? "";
322
+ const idRef = majority(members, idOf);
323
+ each((m) => {
324
+ if (!idRef || idOf(m) === idRef.key)
325
+ return undefined;
326
+ const same = members.filter((x) => idOf(x) === idRef.key);
327
+ return ["identity", `\`${m}\` applies under another cloud identity (\`ci.identity.cloud\`) than ${list(same)}, and one run has one set of credentials`];
328
+ });
329
+ }
330
+ // One run installs one version of each provider: the first member in name order to lock it sets it.
331
+ const locked = new Map();
332
+ each((m, rc) => {
333
+ const mine = lockedProviders(rc.dir);
334
+ for (const [p, v] of mine) {
335
+ const had = locked.get(p);
336
+ if (had && had.version !== v)
337
+ return ["provider-lock", `\`${m}\` locks ${p} at ${v} and \`${had.member}\` at ${had.version}, and one run installs one version`];
338
+ }
339
+ for (const [p, v] of mine)
340
+ if (!locked.has(p))
341
+ locked.set(p, { version: v, member: m });
342
+ return undefined;
343
+ });
344
+ // Reads a composed root cannot rewrite.
345
+ each((m, rc) => {
346
+ for (const r of allReads.get(m) ?? []) {
347
+ if (r.via !== "remote-state" || !members.includes(r.upstream))
348
+ continue;
349
+ if (rc.state.reads.find((x) => x.name === r.name)?.repeated) {
350
+ return ["read", `\`${m}\` reads \`${r.upstream}\` through a block with count or for_each (\`data.terraform_remote_state.${r.name}\`)`];
351
+ }
352
+ }
353
+ for (const mod of localModules(rc.dir, rc.binary)) {
354
+ const modRel = relPath(projectRoot, mod);
355
+ for (const read of rootState(mod, modRel, { recorded: null, env: {}, ...(rc.binary ? { binary: rc.binary } : {}) }).reads) {
356
+ const up = members.find((o) => o !== m && rcOf.get(o).state?.own && sameState(rcOf.get(o).state.own, read));
357
+ if (up)
358
+ return ["read", `\`${m}\` reads \`${up}\` from its module \`${modRel}\`, and a composed run rewrites only a root's own reads`];
359
+ }
360
+ }
361
+ return undefined;
362
+ });
363
+ // What a module call cannot carry.
364
+ each((m, rc) => {
365
+ for (const t of rootTexts(rc.dir, rc.binary)) {
366
+ if (t.json) {
367
+ if (jsonImportRefused(t.text))
368
+ return ["import", `an \`import\` block of \`${m}\` takes no literal string \`id\`, so it cannot be lifted into the composed root`];
369
+ continue;
370
+ }
371
+ for (const im of t.text.matchAll(/(^|\n)\s*import\s*\{/g)) {
372
+ if (!importLiteral(blockBody(t.text, im.index + im[1].length))) {
373
+ return ["import", `an \`import\` block of \`${m}\` takes no literal string \`id\`, so it cannot be lifted into the composed root`];
374
+ }
375
+ }
376
+ if (/\bpath\.(root|cwd)\b/.test(t.text))
377
+ return ["path", `\`${m}\` uses \`path.root\` or \`path.cwd\`, which would name the composed root instead of \`${rc.rel}\``];
378
+ }
379
+ return undefined;
380
+ });
381
+ // An order with no read: a module with its own providers takes no depends_on.
382
+ each((m) => {
383
+ const reads = new Set((allReads.get(m) ?? []).map((r) => r.upstream));
384
+ const u = (byName.get(m)?.dependsOn ?? []).find((d) => members.includes(d) && !reads.has(d));
385
+ return u ? ["order-only", `\`${m}\` depends on \`${u}\` (\`dependsOn\`) with no read between them, and a module with its own providers takes no depends_on`] : undefined;
386
+ });
387
+ // A component outside the run on a path between two members splits them (#4419 ruling 2): the one after it leaves.
388
+ for (let moved = true; moved;) {
389
+ moved = false;
390
+ for (const m of [...members]) {
391
+ const hit = pathOutside(m, members, graph);
392
+ if (!hit)
393
+ continue;
394
+ const via = byName.get(hit.via);
395
+ const what = rcOf.has(hit.via) ? "which is left out of the composed run and applies between them" : `a ${via ? kindOf(via) : "other"} component that applies between them`;
396
+ drop(m, "split", `\`${m}\` depends on \`${hit.member}\` through \`${hit.via}\`, ${what}`);
397
+ moved = true;
398
+ break;
399
+ }
400
+ }
401
+ const fallbacks = candidates.flatMap((m) => (left.has(m) ? [left.get(m)] : []));
402
+ const base = { candidates, max, roots, dirs, fallbacks, ...(tf.binary ? { binary: tf.binary } : {}) };
403
+ const none = (fallback) => ({ ...base, members: [], order: [], modules: new Map(), reads: new Map(), fallback });
404
+ if (members.length >= 2) {
405
+ for (const k of ["TF_CLI_ARGS", "TF_CLI_ARGS_plan", "TF_CLI_ARGS_apply"]) {
406
+ const cli = (input.env[k] ?? "").match(CLI_REFUSED);
407
+ if (cli)
408
+ return none({ kind: "cli-args", reason: `\`${k}\` carries \`-${cli[2]}\`, which names a root's own addresses or variables, not the composed run's` });
409
+ }
410
+ }
411
+ if (members.length > max)
412
+ return none({ kind: "too-many-roots", reason: `this change composes ${plural(members.length, "root")}, more than \`terraform.compose.max\` (${max})` });
413
+ if (members.length <= 1) {
414
+ if (candidates.length === 0)
415
+ return none({ kind: "one-root", reason: "this change reaches no root" });
416
+ if (candidates.length === 1)
417
+ return none({ kind: "one-root", member: candidates[0], reason: `this change reaches one root, \`${candidates[0]}\`, which applies in its own run` });
418
+ return none({
419
+ kind: "one-root",
420
+ ...(members[0] ? { member: members[0] } : {}),
421
+ reason: members.length === 0 ? "no root of this change can compose" : `only \`${members[0]}\` of this change can compose, and one root applies in its own run`,
422
+ });
423
+ }
424
+ const inGroup = new Map(members.map((m) => [m, new Set([...(graph.get(m) ?? [])].filter((u) => members.includes(u)))]));
425
+ const order = readOrder(members, inGroup);
426
+ const names = moduleNames(members.map((m) => roots.get(m)));
427
+ const modules = new Map(members.map((m) => [m, names.get(roots.get(m))]));
428
+ const reads = new Map(members.map((m) => {
429
+ const seen = new Map();
430
+ for (const r of allReads.get(m) ?? [])
431
+ if (members.includes(r.upstream))
432
+ seen.set(readKey(r), r);
433
+ return [m, [...seen.entries()].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)).map(([, r]) => r)];
434
+ }));
435
+ return { ...base, members, order, modules, reads, ...(estate ? { estate } : {}) };
436
+ }
437
+ /** The value most members share, and the first member in name order that has it; a tie goes to the value the earliest member has. */
438
+ function majority(members, key) {
439
+ const groups = new Map();
440
+ for (const m of [...members].sort())
441
+ groups.set(key(m), [...(groups.get(key(m)) ?? []), m]);
442
+ let best;
443
+ for (const [k, ms] of groups)
444
+ if (!best || ms.length > best.n)
445
+ best = { key: k, first: ms[0], n: ms.length };
446
+ return best ? { key: best.key, first: best.first } : undefined;
447
+ }
448
+ /**
449
+ * A member that `m` depends on through a component that is not a member,
450
+ * and the first such component on the way, or undefined. Paths through
451
+ * another member stop there: that member is checked on its own.
452
+ */
453
+ function pathOutside(m, members, graph) {
454
+ const seen = new Set();
455
+ const stack = [...(graph.get(m) ?? [])].sort().map((at) => ({ at }));
456
+ while (stack.length > 0) {
457
+ const { at, via } = stack.shift();
458
+ const key = `${at}\0${via ?? ""}`;
459
+ if (seen.has(key) || at === m)
460
+ continue;
461
+ seen.add(key);
462
+ if (members.includes(at)) {
463
+ if (via)
464
+ return { member: at, via };
465
+ continue;
466
+ }
467
+ for (const u of [...(graph.get(at) ?? [])].sort())
468
+ stack.push({ at: u, via: via ?? at });
469
+ }
470
+ return undefined;
471
+ }
472
+ /** The composition with its plan failed: nothing composes, and each member plans wave by wave. */
473
+ export function withPlanFailed(c, message) {
474
+ const first = message.trim().split("\n")[0] ?? "";
475
+ return { ...c, members: [], order: [], modules: new Map(), reads: new Map(), fallback: { kind: "plan-failed", reason: `the composed plan failed${first ? `: ${first}` : ""}` } };
476
+ }
477
+ /** The one line a note, a log and the report say for a fallback: the whole change's, or one member's. */
478
+ export function fallbackLine(f) {
479
+ return f.member !== undefined && f.kind !== "one-root"
480
+ ? `**\`${f.member}\` applies in waves** (\`terraform.compose\`): ${f.reason}.`
481
+ : `**Applies in waves** (\`terraform.compose\`): ${f.reason}.`;
482
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"terraform-apply-op.d.ts","sourceRoot":"","sources":["../../src/composites/terraform-apply-op.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgFG;AAEH,OAAO,EAA6B,UAAU,EAAE,KAAK,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAW/F;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,iBAAiB,GAAG,YAAY,GAAG,QAAQ,GAAG,OAAO,CAAC;AAElE,MAAM,WAAW,sBAAsB;IACrC,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC;IACb,+GAA+G;IAC/G,IAAI,EAAE,MAAM,CAAC;IACb,0GAA0G;IAC1G,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,IAAI,CAAC,EAAE,iBAAiB,CAAC;IACzB,qEAAqE;IACrE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qGAAqG;IACrG,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2DAA2D;IAC3D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,4EAA4E;IAC5E,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;OAKG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;;;;;;;;OAWG;IACH,UAAU,CAAC,EAAE,OAAO,GAAG;QACrB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,qGAAqG;QACrG,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED,MAAM,WAAW,yBAAyB;IACxC,mEAAmE;IACnE,EAAE,EAAE,YAAY,CAAC,OAAO,UAAU,CAAC,CAAC;CACrC;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,sBAAsB,GAAG,yBAAyB,CAkH1F"}
1
+ {"version":3,"file":"terraform-apply-op.d.ts","sourceRoot":"","sources":["../../src/composites/terraform-apply-op.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgFG;AAEH,OAAO,EAA6B,UAAU,EAAE,KAAK,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAW/F;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,iBAAiB,GAAG,YAAY,GAAG,QAAQ,GAAG,OAAO,CAAC;AAElE,MAAM,WAAW,sBAAsB;IACrC,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC;IACb,+GAA+G;IAC/G,IAAI,EAAE,MAAM,CAAC;IACb,0GAA0G;IAC1G,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,IAAI,CAAC,EAAE,iBAAiB,CAAC;IACzB,qEAAqE;IACrE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qGAAqG;IACrG,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2DAA2D;IAC3D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,4EAA4E;IAC5E,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;OAKG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;;;;;;;;OAWG;IACH,UAAU,CAAC,EAAE,OAAO,GAAG;QACrB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,qGAAqG;QACrG,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED,MAAM,WAAW,yBAAyB;IACxC,mEAAmE;IACnE,EAAE,EAAE,YAAY,CAAC,OAAO,UAAU,CAAC,CAAC;CACrC;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,sBAAsB,GAAG,yBAAyB,CAsH1F"}
@@ -130,9 +130,13 @@ export function TerraformApplyOp(config) {
130
130
  "answer instead of the account's.");
131
131
  }
132
132
  }
133
- const phases = [
134
- phase("Init", [initStep(config.root, { ...where, ...(config.upgrade ? { upgrade: true } : {}) })]),
135
- ];
133
+ // The Init step names the root's directory in the workspace (chant#4482),
134
+ // so a read of the run ledger can tell which member's root this run applied
135
+ // when the Op is declared in another member: `chant ledger runs`'s
136
+ // `root_member` column comes from this outcome.
137
+ const init = initStep(config.root, { ...where, ...(config.upgrade ? { upgrade: true } : {}) });
138
+ init.outcomeAttribute = { name: "TerraformRootPath", from: "rootPath" };
139
+ const phases = [phase("Init", [init])];
136
140
  // `id` is what makes `plan.out` legal, and `plan.out.planFile` is how the
137
141
  // Apply step below names this step's saved plan. On a live root that file
138
142
  // is also the approval artifact the apply re-plans against, so the same
package/dist/config.d.ts CHANGED
@@ -65,8 +65,15 @@ export declare const terraformRootSchema: z.ZodObject<{
65
65
  workspace: z.ZodOptional<z.ZodString>;
66
66
  /** `-var-file` arguments, in order, relative to `dir`. */
67
67
  varFiles: z.ZodOptional<z.ZodArray<z.ZodString>>;
68
- /** `-backend-config` key/value pairs handed to `init`. */
68
+ /** `-backend-config` key/value pairs handed to `init`, after {@link backendConfigFiles}. */
69
69
  backendConfig: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
70
+ /**
71
+ * Backend-config files handed to `init` as `-backend-config=<path>`, in
72
+ * order and before the {@link backendConfig} pairs, relative to `dir`
73
+ * (chant#4459). The composition reads their `key = "value"` lines as it
74
+ * reads the pairs, to find a root's state before init.
75
+ */
76
+ backendConfigFiles: z.ZodOptional<z.ZodArray<z.ZodString>>;
70
77
  /**
71
78
  * The choudoufu estate this root runs against, for a live root whose HCL
72
79
  * does not declare one of its own (#2479).
@@ -118,6 +125,16 @@ export declare const terraformRootSchema: z.ZodObject<{
118
125
  plan: "plan";
119
126
  state: "state";
120
127
  }>>;
128
+ /**
129
+ * The root is declared only so a change to it brings this project into the
130
+ * change (#4484). A dns member that declares another cloud's root, so that
131
+ * an azure change reaches dns, sets it on that root. Change detection
132
+ * (`changedUnits`) still counts the root; the build and every live read
133
+ * skip it, so it is never drawn or read as this project's own: its entities
134
+ * are not built, and `describeResources`, drift and the owned-resource
135
+ * reads leave it out. An Op step that names the root still runs against it.
136
+ */
137
+ signalOnly: z.ZodOptional<z.ZodBoolean>;
121
138
  }, z.core.$strict>;
122
139
  export declare const terraformConfigSchema: z.ZodObject<{
123
140
  /**
@@ -164,6 +181,21 @@ export declare const terraformConfigSchema: z.ZodObject<{
164
181
  plan: "plan";
165
182
  state: "state";
166
183
  }>>;
184
+ /**
185
+ * Composed runs (#4419, #4438), off unless `enabled` is true. When on, the
186
+ * `terraform-apply` components a change reaches, and the ones they read,
187
+ * plan as one plan and apply as one apply of a composed root of `module`
188
+ * calls, so a value one root creates reaches its readers in that apply. A
189
+ * component that cannot compose plans wave by wave as without the setting,
190
+ * and the report says why (`compositionFor` in `compose.ts`).
191
+ *
192
+ * `max` caps how many roots compose; a change over it applies wholly in
193
+ * waves. Default 10.
194
+ */
195
+ compose: z.ZodOptional<z.ZodObject<{
196
+ enabled: z.ZodBoolean;
197
+ max: z.ZodOptional<z.ZodNumber>;
198
+ }, z.core.$strict>>;
167
199
  /** Named root modules. The name is the entity-key prefix, so keep it stable. */
168
200
  roots: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
169
201
  /** Root module directory, relative to the project root (where `chant.config.*` lives). */
@@ -172,8 +204,15 @@ export declare const terraformConfigSchema: z.ZodObject<{
172
204
  workspace: z.ZodOptional<z.ZodString>;
173
205
  /** `-var-file` arguments, in order, relative to `dir`. */
174
206
  varFiles: z.ZodOptional<z.ZodArray<z.ZodString>>;
175
- /** `-backend-config` key/value pairs handed to `init`. */
207
+ /** `-backend-config` key/value pairs handed to `init`, after {@link backendConfigFiles}. */
176
208
  backendConfig: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
209
+ /**
210
+ * Backend-config files handed to `init` as `-backend-config=<path>`, in
211
+ * order and before the {@link backendConfig} pairs, relative to `dir`
212
+ * (chant#4459). The composition reads their `key = "value"` lines as it
213
+ * reads the pairs, to find a root's state before init.
214
+ */
215
+ backendConfigFiles: z.ZodOptional<z.ZodArray<z.ZodString>>;
177
216
  /**
178
217
  * The choudoufu estate this root runs against, for a live root whose HCL
179
218
  * does not declare one of its own (#2479).
@@ -225,20 +264,27 @@ export declare const terraformConfigSchema: z.ZodObject<{
225
264
  plan: "plan";
226
265
  state: "state";
227
266
  }>>;
267
+ /**
268
+ * The root is declared only so a change to it brings this project into the
269
+ * change (#4484). A dns member that declares another cloud's root, so that
270
+ * an azure change reaches dns, sets it on that root. Change detection
271
+ * (`changedUnits`) still counts the root; the build and every live read
272
+ * skip it, so it is never drawn or read as this project's own: its entities
273
+ * are not built, and `describeResources`, drift and the owned-resource
274
+ * reads leave it out. An Op step that names the root still runs against it.
275
+ */
276
+ signalOnly: z.ZodOptional<z.ZodBoolean>;
228
277
  }, z.core.$strict>>>;
229
- /**
230
- * One pulumi workspace member read into the graph (#3179). `chant workspace
231
- * graph` writes this into the reader project it makes for a `pulumi` member;
232
- * it is not meant to be written by hand. `dir` is the member's directory,
233
- * absolute; `label` names the entities. The graph is the checked-in
234
- * `stack-export.json` there; the program is never run.
235
- */
236
- pulumiMember: z.ZodOptional<z.ZodObject<{
237
- dir: z.ZodString;
238
- label: z.ZodOptional<z.ZodString>;
239
- }, z.core.$strict>>;
240
278
  }, z.core.$strict>;
241
279
  export type TerraformRootConfig = z.infer<typeof terraformRootSchema>;
280
+ /**
281
+ * The roots a project owns: `roots` without the ones marked `signalOnly`
282
+ * (#4484), which only feed change detection. The build and the live reads
283
+ * take these; `changedUnits` takes every root.
284
+ */
285
+ export declare function ownRoots<R extends {
286
+ signalOnly?: boolean;
287
+ }>(roots: Record<string, R> | undefined): Record<string, R>;
242
288
  export type TerraformConfig = z.infer<typeof terraformConfigSchema>;
243
289
  declare module "@intentius/chant/config" {
244
290
  interface ChantConfig {
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAE3D;;;;;;;;;;GAUG;AACH,eAAO,MAAM,yBAAyB;;;;EAA2C,CAAC;AAClF,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAE5E;;;;;;;;GAQG;AACH;;;GAGG;AACH,eAAO,MAAM,2BAA2B;;;EAA4B,CAAC;AAErE,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAEhF,eAAO,MAAM,mBAAmB;IAC9B,0FAA0F;;IAE1F,4EAA4E;;IAE5E,0DAA0D;;IAE1D,0DAA0D;;IAE1D;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;;IAEH,+EAA+E;;;;;;IAE/E;;;;;;;;;;;;;;OAcG;;;;;kBAEH,CAAC;AAEH,eAAO,MAAM,qBAAqB;IAChC;;;;;;;OAOG;;;;;;IAEH;;;;;;;OAOG;;;;;;IAEH;;;;;;;;;;;OAWG;;IAEH,6FAA6F;;;;;IAE7F,gFAAgF;;QAxFhF,0FAA0F;;QAE1F,4EAA4E;;QAE5E,0DAA0D;;QAE1D,0DAA0D;;QAE1D;;;;;;;;;;;;;;;;;;;;;;;;WAwBG;;QAEH,+EAA+E;;;;;;QAE/E;;;;;;;;;;;;;;WAcG;;;;;;IAwCH;;;;;;OAMG;;;;;kBAEH,CAAC;AAEH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AACtE,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,OAAO,QAAQ,yBAAyB,CAAC;IACvC,UAAU,WAAW;QACnB,SAAS,CAAC,EAAE,eAAe,CAAC;KAC7B;CACF;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,wBAAwB,GAAG,WAAW,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAE3D;;;;;;;;;;GAUG;AACH,eAAO,MAAM,yBAAyB;;;;EAA2C,CAAC;AAClF,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAE5E;;;;;;;;GAQG;AACH;;;GAGG;AACH,eAAO,MAAM,2BAA2B;;;EAA4B,CAAC;AAErE,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAEhF,eAAO,MAAM,mBAAmB;IAC9B,0FAA0F;;IAE1F,4EAA4E;;IAE5E,0DAA0D;;IAE1D,4FAA4F;;IAE5F;;;;;OAKG;;IAEH;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;;IAEH,+EAA+E;;;;;;IAE/E;;;;;;;;;;;;;;OAcG;;;;;IAEH;;;;;;;;OAQG;;kBAEH,CAAC;AAEH,eAAO,MAAM,qBAAqB;IAChC;;;;;;;OAOG;;;;;;IAEH;;;;;;;OAOG;;;;;;IAEH;;;;;;;;;;;OAWG;;IAEH,6FAA6F;;;;;IAE7F;;;;;;;;;;OAUG;;;;;IAOH,gFAAgF;;QA1HhF,0FAA0F;;QAE1F,4EAA4E;;QAE5E,0DAA0D;;QAE1D,4FAA4F;;QAE5F;;;;;WAKG;;QAEH;;;;;;;;;;;;;;;;;;;;;;;;WAwBG;;QAEH,+EAA+E;;;;;;QAE/E;;;;;;;;;;;;;;WAcG;;;;;QAEH;;;;;;;;WAQG;;;kBAyDH,CAAC;AAEH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEtE;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,CAAC,SAAS;IAAE,UAAU,CAAC,EAAE,OAAO,CAAA;CAAE,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAEpH;AACD,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,OAAO,QAAQ,yBAAyB,CAAC;IACvC,UAAU,WAAW;QACnB,SAAS,CAAC,EAAE,eAAe,CAAC;KAC7B;CACF;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,wBAAwB,GAAG,WAAW,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC,CAAC"}