@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
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * A fast, regex-based read of whether a root module directory declares a
3
3
  * live estate: an `estate.chdf.hcl` sidecar, or a `live { ... }` block nested
4
- * inside a `terraform { ... }` block of some `.tf` file (#2103).
4
+ * inside a `terraform { ... }` block of some configuration file (#2103), the
5
+ * `terraform` object of a `.tf.json` file included (chant#4504).
5
6
  *
6
7
  * Deliberately separate from `../../hcl/parse.ts`'s AST-accurate detection
7
8
  * (`blocksToEntities`'s `liveBlocksIn`), which the build and lint paths use.
@@ -17,8 +18,38 @@
17
18
  * reason.
18
19
  */
19
20
 
20
- import { existsSync, readFileSync, readdirSync } from "node:fs";
21
+ import { existsSync, readFileSync } from "node:fs";
21
22
  import { join } from "node:path";
23
+ import { isJsonConfigFile, jsonLiveBlocks, listConfigFiles } from "../../hcl/files";
24
+
25
+ /**
26
+ * The configuration files of `dir` a live check reads, in filename order.
27
+ * A `live` block takes effect only under choudoufu, so the set is the one
28
+ * choudoufu reads (`.tf`, `.tf.json`, `.tofu` and `.tofu.json`, `x.tofu`
29
+ * over `x.tf`) from the lexicon's one lister (chant#4457), which reads the
30
+ * directory and nothing else.
31
+ */
32
+ function liveFiles(dir: string, binary: string): string[] {
33
+ return listConfigFiles(dir, binary);
34
+ }
35
+
36
+ /**
37
+ * The `live` block bodies of one file: a JSON-syntax file's from its
38
+ * `terraform` blocks (chant#4504), an HCL file's first one by brace counting.
39
+ * HCL bodies come back as text and JSON ones as objects.
40
+ */
41
+ function liveBlocksOf(name: string, source: string): Array<string | Record<string, unknown>> {
42
+ if (isJsonConfigFile(name)) return jsonLiveBlocks(source);
43
+ const terraformBlock = extractBalancedBlock(source, "terraform");
44
+ if (terraformBlock === undefined) return [];
45
+ const liveBlock = extractBalancedBlock(terraformBlock, "live");
46
+ return liveBlock === undefined ? [] : [liveBlock];
47
+ }
48
+
49
+ /** A non-empty string, or undefined. */
50
+ function nonEmpty(v: unknown): string | undefined {
51
+ return typeof v === "string" && v !== "" ? v : undefined;
52
+ }
22
53
 
23
54
  /** Kept in sync with `LIVE_SIDECAR_FILENAME` in `../../hcl/parse.ts`. */
24
55
  const LIVE_SIDECAR_FILENAME = "estate.chdf.hcl";
@@ -69,7 +100,7 @@ function extractQuotedAttr(body: string, key: string): string | undefined {
69
100
  * no sidecar (or one this function cannot read) yields `undefined` rather
70
101
  * than throwing, since this is a best-effort check, not the authoritative parse.
71
102
  */
72
- export function detectLiveEstate(dir: string): string | undefined {
103
+ export function detectLiveEstate(dir: string, binary = "choudoufu"): string | undefined {
73
104
  const sidecarPath = join(dir, LIVE_SIDECAR_FILENAME);
74
105
  if (existsSync(sidecarPath)) {
75
106
  try {
@@ -82,9 +113,7 @@ export function detectLiveEstate(dir: string): string | undefined {
82
113
 
83
114
  let names: string[];
84
115
  try {
85
- names = readdirSync(dir)
86
- .filter((f) => f.endsWith(".tf"))
87
- .sort();
116
+ names = liveFiles(dir, binary);
88
117
  } catch {
89
118
  return undefined;
90
119
  }
@@ -96,12 +125,10 @@ export function detectLiveEstate(dir: string): string | undefined {
96
125
  } catch {
97
126
  continue;
98
127
  }
99
- const terraformBlock = extractBalancedBlock(source, "terraform");
100
- if (terraformBlock === undefined) continue;
101
- const liveBlock = extractBalancedBlock(terraformBlock, "live");
102
- if (liveBlock === undefined) continue;
103
- const estate = extractEstateAttr(liveBlock);
104
- if (estate) return estate;
128
+ for (const liveBlock of liveBlocksOf(name, source)) {
129
+ const estate = typeof liveBlock === "string" ? extractEstateAttr(liveBlock) : nonEmpty(liveBlock.estate);
130
+ if (estate) return estate;
131
+ }
105
132
  }
106
133
 
107
134
  return undefined;
@@ -129,12 +156,10 @@ export interface LivePolicyVerbs {
129
156
  undeclaredUntagged?: string;
130
157
  }
131
158
 
132
- export function detectLivePolicyVerbs(dir: string): LivePolicyVerbs | undefined {
159
+ export function detectLivePolicyVerbs(dir: string, binary = "choudoufu"): LivePolicyVerbs | undefined {
133
160
  let names: string[];
134
161
  try {
135
- names = readdirSync(dir)
136
- .filter((f) => f.endsWith(".tf"))
137
- .sort();
162
+ names = liveFiles(dir, binary);
138
163
  } catch {
139
164
  return undefined;
140
165
  }
@@ -146,16 +171,20 @@ export function detectLivePolicyVerbs(dir: string): LivePolicyVerbs | undefined
146
171
  } catch {
147
172
  continue;
148
173
  }
149
- const terraformBlock = extractBalancedBlock(source, "terraform");
150
- if (terraformBlock === undefined) continue;
151
- const liveBlock = extractBalancedBlock(terraformBlock, "live");
152
- if (liveBlock === undefined) continue;
153
- const policyBlock = extractBalancedBlock(liveBlock, "policy");
154
- if (policyBlock === undefined) continue;
155
- return {
156
- undeclaredTagged: extractQuotedAttr(policyBlock, "undeclared_tagged"),
157
- undeclaredUntagged: extractQuotedAttr(policyBlock, "undeclared_untagged"),
158
- };
174
+ for (const liveBlock of liveBlocksOf(name, source)) {
175
+ if (typeof liveBlock === "string") {
176
+ const policyBlock = extractBalancedBlock(liveBlock, "policy");
177
+ if (policyBlock === undefined) continue;
178
+ return {
179
+ undeclaredTagged: extractQuotedAttr(policyBlock, "undeclared_tagged"),
180
+ undeclaredUntagged: extractQuotedAttr(policyBlock, "undeclared_untagged"),
181
+ };
182
+ }
183
+ // JSON syntax writes the nested `policy` block as one object or an array of them.
184
+ const policy = [liveBlock.policy].flat().find((p): p is Record<string, unknown> => typeof p === "object" && p !== null && !Array.isArray(p));
185
+ if (!policy) continue;
186
+ return { undeclaredTagged: nonEmpty(policy.undeclared_tagged), undeclaredUntagged: nonEmpty(policy.undeclared_untagged) };
187
+ }
159
188
  }
160
189
 
161
190
  return undefined;
@@ -41,6 +41,8 @@ export interface TerraformPinRolloutArgs {
41
41
  appliedCheck?: string;
42
42
  /** A directory in the repository. Default: the working directory. */
43
43
  cwd?: string;
44
+ /** The binary the roots run, which says whether their `.tofu` files are read (chant#4457). Default `terraform`. */
45
+ binary?: string;
44
46
  }
45
47
 
46
48
  export type TerraformPinRolloutResult = PinRolloutResult;
@@ -73,5 +75,6 @@ export async function readPinRollout(args: TerraformPinRolloutArgs): Promise<Ter
73
75
  ...(args.base ? { base: args.base } : {}),
74
76
  ...(args.remote ? { remote: args.remote } : {}),
75
77
  ...(args.appliedCheck ? { appliedCheck: args.appliedCheck } : {}),
78
+ ...(args.binary ? { binary: args.binary } : {}),
76
79
  });
77
80
  }
@@ -15,7 +15,7 @@ import { describe, test, expect } from "vitest";
15
15
  import { loadActivities, resolveActivity } from "@intentius/chant/op";
16
16
 
17
17
  describe("loadActivities([\"terraform\"]) (#2086, #2103)", () => {
18
- test("resolves all seven activity names", async () => {
18
+ test("resolves its activity names", async () => {
19
19
  const activities = await loadActivities(["terraform"]);
20
20
  for (const name of [
21
21
  "terraformInit",
@@ -25,6 +25,7 @@ describe("loadActivities([\"terraform\"]) (#2086, #2103)", () => {
25
25
  "choudoufuLivePlan",
26
26
  "choudoufuLiveLs",
27
27
  "choudoufuLiveCheck",
28
+ "choudoufuPlanSet",
28
29
  ]) {
29
30
  expect(typeof resolveActivity(activities, name)).toBe("function");
30
31
  }
@@ -183,6 +183,22 @@ describe("terraformInitCommand (#2086)", () => {
183
183
  ).toBe("tofu init -input=false -backend-config=bucket=tf-state -backend-config=key=app/terraform.tfstate");
184
184
  });
185
185
 
186
+ test("backendConfigFiles become -backend-config=<path> flags, in order and before the pairs (chant#4459)", () => {
187
+ expect(
188
+ terraformInitCommand({
189
+ binary: "tofu",
190
+ backendConfig: { key: "app/terraform.tfstate" },
191
+ backendConfigFiles: ["backend.hcl", "/etc/chant/shared.hcl"],
192
+ }),
193
+ ).toBe("tofu init -input=false -backend-config=backend.hcl -backend-config=/etc/chant/shared.hcl -backend-config=key=app/terraform.tfstate");
194
+ });
195
+
196
+ test("the init line a refusal prints carries the files too, relative to the root it -chdirs into (chant#4459)", () => {
197
+ expect(terraformInitLine({ binary: "terraform", dir: "/repo/roots/app", backendConfigFiles: ["backend.hcl"], backendConfig: { key: "k" } })).toBe(
198
+ "terraform -chdir=/repo/roots/app init -input=false -backend-config=backend.hcl -backend-config=key=k",
199
+ );
200
+ });
201
+
186
202
  test("-upgrade and -reconfigure are opt-in", () => {
187
203
  expect(terraformInitCommand({ binary: "terraform", upgrade: true, reconfigure: true })).toBe(
188
204
  "terraform init -input=false -upgrade -reconfigure",
@@ -408,6 +424,32 @@ describe("terraformInit (#2086)", () => {
408
424
  expect(execCalls[0].opts.env?.TF_WORKSPACE).toBe("prod");
409
425
  expect(result.workspace).toBe("prod");
410
426
  });
427
+
428
+ test("a failed init carries the root it resolved as known outputs (chant#4512)", async () => {
429
+ const dir = project({ roots: { app: { dir: "./infra", workspace: "prod" } } });
430
+ // A workspace root, so the root has a workspace-relative path to publish.
431
+ // The walk up to it stops at the git root, so the project is one.
432
+ mkdirSync(join(dir, ".git"));
433
+ writeFileSync(join(dir, "chant.workspace.json"), JSON.stringify({ name: "w", schema: 1, members: [{ name: "app", dir: ".", kind: "chant" }] }));
434
+ replies.push({ match: "init", reply: execError(1, "Error: Failed to get existing workspaces: backend unreachable") });
435
+ const failure = await terraformInit({ root: "app", cwd: dir }).then(
436
+ () => undefined,
437
+ (err: unknown) => err,
438
+ );
439
+ expect(failure).toBeInstanceOf(Error);
440
+ expect((failure as { knownOutputs?: unknown }).knownOutputs).toEqual({ dir: resolve(dir, "infra"), workspace: "prod", rootPath: "infra" });
441
+ });
442
+
443
+ test("a root that does not resolve fails with no known outputs (chant#4512)", async () => {
444
+ const dir = project({ roots: { app: { dir: "./infra" } } });
445
+ const failure = await terraformInit({ root: "nope", cwd: dir }).then(
446
+ () => undefined,
447
+ (err: unknown) => err,
448
+ );
449
+ expect(failure).toBeInstanceOf(Error);
450
+ expect(failure).not.toHaveProperty("knownOutputs");
451
+ expect(execCalls).toHaveLength(0);
452
+ });
411
453
  });
412
454
 
413
455
  // ── terraformPlan ───────────────────────────────────────────────────────────
@@ -429,6 +471,19 @@ describe("terraformPlan — the -detailed-exitcode mapping (#2086)", () => {
429
471
  ]);
430
472
  });
431
473
 
474
+ test("text: false runs show -json alone, and the result's text is empty (#4479)", async () => {
475
+ const dir = project({ roots: { app: { dir: "./infra" } } });
476
+ showReplies();
477
+ const result = await terraformPlan({ root: "app", cwd: dir, text: false });
478
+ expect(result.text).toBe("");
479
+ expect(result.json).toEqual(JSON.parse(PLAN_JSON));
480
+ expect(result.planDigest).toBe(terraformPlanDigest(JSON.parse(PLAN_JSON)));
481
+ expect(execCalls.map((c) => c.cmd)).toEqual([
482
+ "terraform plan -input=false -detailed-exitcode -out=chant.tfplan",
483
+ "terraform show -json chant.tfplan",
484
+ ]);
485
+ });
486
+
432
487
  // #2300 — the Plan phase is where the plan gets its identity, so the
433
488
  // activity's own result carries it and the Gate step references it from
434
489
  // there rather than re-reading the file.