@intentius/chant-lexicon-terraform 0.124.52 → 0.124.55

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 (641) hide show
  1. package/README.md +14 -10
  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/ci-setup.d.ts +11 -0
  27. package/dist/ci-setup.d.ts.map +1 -0
  28. package/dist/ci-setup.js +41 -0
  29. package/dist/commands.d.ts.map +1 -1
  30. package/dist/commands.js +4 -3
  31. package/dist/components/capability-plugin.d.ts +105 -9
  32. package/dist/components/capability-plugin.d.ts.map +1 -1
  33. package/dist/components/capability-plugin.js +357 -36
  34. package/dist/components/compose-group.d.ts +145 -0
  35. package/dist/components/compose-group.d.ts.map +1 -0
  36. package/dist/components/compose-group.js +407 -0
  37. package/dist/components/placeholder-preview.d.ts +78 -0
  38. package/dist/components/placeholder-preview.d.ts.map +1 -0
  39. package/dist/components/placeholder-preview.js +254 -0
  40. package/dist/components/release-inputs.d.ts +29 -0
  41. package/dist/components/release-inputs.d.ts.map +1 -0
  42. package/dist/components/release-inputs.js +57 -0
  43. package/dist/components/trace-facts.d.ts +16 -0
  44. package/dist/components/trace-facts.d.ts.map +1 -0
  45. package/dist/components/trace-facts.js +19 -0
  46. package/dist/compose/apply-events.d.ts +46 -0
  47. package/dist/compose/apply-events.d.ts.map +1 -0
  48. package/dist/compose/apply-events.js +77 -0
  49. package/dist/compose/estate.d.ts +194 -0
  50. package/dist/compose/estate.d.ts.map +1 -0
  51. package/dist/compose/estate.js +363 -0
  52. package/dist/compose/hcl.d.ts +99 -0
  53. package/dist/compose/hcl.d.ts.map +1 -0
  54. package/dist/compose/hcl.js +302 -0
  55. package/dist/compose/module-address.d.ts +24 -0
  56. package/dist/compose/module-address.d.ts.map +1 -0
  57. package/dist/compose/module-address.js +43 -0
  58. package/dist/compose/root.d.ts +127 -0
  59. package/dist/compose/root.d.ts.map +1 -0
  60. package/dist/compose/root.js +586 -0
  61. package/dist/compose/run.d.ts +183 -0
  62. package/dist/compose/run.d.ts.map +1 -0
  63. package/dist/compose/run.js +447 -0
  64. package/dist/compose/slice.d.ts +102 -0
  65. package/dist/compose/slice.d.ts.map +1 -0
  66. package/dist/compose/slice.js +249 -0
  67. package/dist/compose/state-address.d.ts +28 -0
  68. package/dist/compose/state-address.d.ts.map +1 -0
  69. package/dist/compose/state-address.js +154 -0
  70. package/dist/compose/state-merge.d.ts +52 -0
  71. package/dist/compose/state-merge.d.ts.map +1 -0
  72. package/dist/compose/state-merge.js +135 -0
  73. package/dist/compose.d.ts +179 -0
  74. package/dist/compose.d.ts.map +1 -0
  75. package/dist/compose.js +482 -0
  76. package/dist/composites/terraform-apply-op.d.ts.map +1 -1
  77. package/dist/composites/terraform-apply-op.js +7 -3
  78. package/dist/cone.d.ts +53 -0
  79. package/dist/cone.d.ts.map +1 -0
  80. package/dist/cone.js +119 -0
  81. package/dist/config.d.ts +59 -13
  82. package/dist/config.d.ts.map +1 -1
  83. package/dist/config.js +43 -9
  84. package/dist/cost-hold.d.ts +27 -0
  85. package/dist/cost-hold.d.ts.map +1 -0
  86. package/dist/cost-hold.js +21 -0
  87. package/dist/describe-resources.d.ts +74 -12
  88. package/dist/describe-resources.d.ts.map +1 -1
  89. package/dist/describe-resources.js +374 -66
  90. package/dist/drift-read.d.ts +33 -0
  91. package/dist/drift-read.d.ts.map +1 -0
  92. package/dist/drift-read.js +243 -0
  93. package/dist/drift-record.d.ts +147 -0
  94. package/dist/drift-record.d.ts.map +1 -0
  95. package/dist/drift-record.js +383 -0
  96. package/dist/drift-rows.d.ts +61 -0
  97. package/dist/drift-rows.d.ts.map +1 -0
  98. package/dist/drift-rows.js +108 -0
  99. package/dist/exit-codes.d.ts +55 -0
  100. package/dist/exit-codes.d.ts.map +1 -0
  101. package/dist/exit-codes.js +48 -0
  102. package/dist/hcl/edges.d.ts.map +1 -1
  103. package/dist/hcl/edges.js +24 -1
  104. package/dist/hcl/files.d.ts +61 -0
  105. package/dist/hcl/files.d.ts.map +1 -0
  106. package/dist/hcl/files.js +221 -0
  107. package/dist/hcl/instances.d.ts +82 -0
  108. package/dist/hcl/instances.d.ts.map +1 -0
  109. package/dist/hcl/instances.js +390 -0
  110. package/dist/hcl/parse.d.ts +81 -9
  111. package/dist/hcl/parse.d.ts.map +1 -1
  112. package/dist/hcl/parse.js +114 -16
  113. package/dist/hcl/roots.d.ts.map +1 -1
  114. package/dist/hcl/roots.js +5 -0
  115. package/dist/hcl/suppressions.d.ts.map +1 -1
  116. package/dist/hcl/suppressions.js +8 -2
  117. package/dist/index.d.ts +13 -2
  118. package/dist/index.d.ts.map +1 -1
  119. package/dist/index.js +24 -2
  120. package/dist/integrity.json +8 -5
  121. package/dist/lint/audit-catalog.d.ts.map +1 -1
  122. package/dist/lint/audit-catalog.js +27 -3
  123. package/dist/lint/audit-lineage.d.ts +14 -2
  124. package/dist/lint/audit-lineage.d.ts.map +1 -1
  125. package/dist/lint/post-synth/index.d.ts.map +1 -1
  126. package/dist/lint/post-synth/index.js +4 -0
  127. package/dist/lint/post-synth/record-store.d.ts +54 -0
  128. package/dist/lint/post-synth/record-store.d.ts.map +1 -0
  129. package/dist/lint/post-synth/record-store.js +104 -0
  130. package/dist/lint/post-synth/tf001.d.ts +2 -2
  131. package/dist/lint/post-synth/tf001.js +2 -2
  132. package/dist/lint/post-synth/tf024.d.ts +35 -11
  133. package/dist/lint/post-synth/tf024.d.ts.map +1 -1
  134. package/dist/lint/post-synth/tf024.js +92 -20
  135. package/dist/lint/post-synth/tf046.d.ts +64 -0
  136. package/dist/lint/post-synth/tf046.d.ts.map +1 -0
  137. package/dist/lint/post-synth/tf046.js +175 -0
  138. package/dist/lint/post-synth/tf047.d.ts +36 -0
  139. package/dist/lint/post-synth/tf047.d.ts.map +1 -0
  140. package/dist/lint/post-synth/tf047.js +94 -0
  141. package/dist/live-plan-reasons.d.ts +21 -0
  142. package/dist/live-plan-reasons.d.ts.map +1 -0
  143. package/dist/live-plan-reasons.js +47 -0
  144. package/dist/lock-rows.d.ts +36 -0
  145. package/dist/lock-rows.d.ts.map +1 -0
  146. package/dist/lock-rows.js +60 -0
  147. package/dist/lock-timeout.d.ts +22 -0
  148. package/dist/lock-timeout.d.ts.map +1 -0
  149. package/dist/lock-timeout.js +32 -0
  150. package/dist/lsp/option-keys.d.ts.map +1 -1
  151. package/dist/lsp/option-keys.js +2 -0
  152. package/dist/manifest.json +1 -1
  153. package/dist/module-catalog/index.d.ts +69 -0
  154. package/dist/module-catalog/index.d.ts.map +1 -0
  155. package/dist/module-catalog/index.js +110 -0
  156. package/dist/module-catalog/refresh-cli.d.ts +4 -0
  157. package/dist/module-catalog/refresh-cli.d.ts.map +1 -0
  158. package/dist/module-catalog/refresh-cli.js +64 -0
  159. package/dist/module-catalog/static-tier.d.ts +6 -0
  160. package/dist/module-catalog/static-tier.d.ts.map +1 -0
  161. package/dist/module-catalog/static-tier.js +1650 -0
  162. package/dist/module-catalog/types.d.ts +65 -0
  163. package/dist/module-catalog/types.d.ts.map +1 -0
  164. package/dist/module-catalog/types.js +33 -0
  165. package/dist/okf/index.md +3 -1
  166. package/dist/okf/rules/TF024.md +2 -2
  167. package/dist/okf/rules/TF046.md +11 -0
  168. package/dist/okf/rules/TF047.md +11 -0
  169. package/dist/op/activities/index.d.ts +6 -2
  170. package/dist/op/activities/index.d.ts.map +1 -1
  171. package/dist/op/activities/index.js +5 -1
  172. package/dist/op/activities/live-detect.d.ts +4 -3
  173. package/dist/op/activities/live-detect.d.ts.map +1 -1
  174. package/dist/op/activities/live-detect.js +57 -32
  175. package/dist/op/activities/pin-rollout.d.ts +2 -0
  176. package/dist/op/activities/pin-rollout.d.ts.map +1 -1
  177. package/dist/op/activities/pin-rollout.js +1 -0
  178. package/dist/op/activities/terraform.d.ts +182 -5
  179. package/dist/op/activities/terraform.d.ts.map +1 -1
  180. package/dist/op/activities/terraform.js +224 -24
  181. package/dist/op/activity-contracts.d.ts +27 -0
  182. package/dist/op/activity-contracts.d.ts.map +1 -1
  183. package/dist/op/activity-contracts.js +19 -1
  184. package/dist/op/builders.d.ts +7 -1
  185. package/dist/op/builders.d.ts.map +1 -1
  186. package/dist/op/builders.js +9 -0
  187. package/dist/pin/consumers.d.ts +35 -0
  188. package/dist/pin/consumers.d.ts.map +1 -0
  189. package/dist/pin/consumers.js +70 -0
  190. package/dist/pin/edit.d.ts.map +1 -1
  191. package/dist/pin/edit.js +35 -0
  192. package/dist/pin/rollout.d.ts +2 -0
  193. package/dist/pin/rollout.d.ts.map +1 -1
  194. package/dist/pin/rollout.js +14 -11
  195. package/dist/plan-shape.d.ts +30 -0
  196. package/dist/plan-shape.d.ts.map +1 -0
  197. package/dist/plan-shape.js +125 -0
  198. package/dist/plugin.d.ts.map +1 -1
  199. package/dist/plugin.js +37 -18
  200. package/dist/registry/address.d.ts +63 -0
  201. package/dist/registry/address.d.ts.map +1 -0
  202. package/dist/registry/address.js +65 -0
  203. package/dist/registry/catalog-read.d.ts +14 -0
  204. package/dist/registry/catalog-read.d.ts.map +1 -0
  205. package/dist/registry/catalog-read.js +58 -0
  206. package/dist/registry/deprecated.d.ts +37 -0
  207. package/dist/registry/deprecated.d.ts.map +1 -0
  208. package/dist/registry/deprecated.js +43 -0
  209. package/dist/registry/docs.d.ts +96 -0
  210. package/dist/registry/docs.d.ts.map +1 -0
  211. package/dist/registry/docs.js +210 -0
  212. package/dist/registry/edge/edge.d.ts +46 -0
  213. package/dist/registry/edge/edge.d.ts.map +1 -0
  214. package/dist/registry/edge/edge.js +194 -0
  215. package/dist/registry/edge/index.d.ts +11 -0
  216. package/dist/registry/edge/index.d.ts.map +1 -0
  217. package/dist/registry/edge/index.js +10 -0
  218. package/dist/registry/edge/lambda-edge.d.ts +76 -0
  219. package/dist/registry/edge/lambda-edge.d.ts.map +1 -0
  220. package/dist/registry/edge/lambda-edge.js +95 -0
  221. package/dist/registry/edge/sigv4-web.d.ts +33 -0
  222. package/dist/registry/edge/sigv4-web.d.ts.map +1 -0
  223. package/dist/registry/edge/sigv4-web.js +56 -0
  224. package/dist/registry/edge/token-source.d.ts +73 -0
  225. package/dist/registry/edge/token-source.d.ts.map +1 -0
  226. package/dist/registry/edge/token-source.js +115 -0
  227. package/dist/registry/edge/worker.d.ts +45 -0
  228. package/dist/registry/edge/worker.d.ts.map +1 -0
  229. package/dist/registry/edge/worker.js +26 -0
  230. package/dist/registry/handler.d.ts +25 -0
  231. package/dist/registry/handler.d.ts.map +1 -0
  232. package/dist/registry/handler.js +75 -0
  233. package/dist/registry/headers-file.d.ts +21 -0
  234. package/dist/registry/headers-file.d.ts.map +1 -0
  235. package/dist/registry/headers-file.js +57 -0
  236. package/dist/registry/index.d.ts +31 -0
  237. package/dist/registry/index.d.ts.map +1 -0
  238. package/dist/registry/index.js +30 -0
  239. package/dist/registry/mirror-paths.d.ts +9 -0
  240. package/dist/registry/mirror-paths.d.ts.map +1 -0
  241. package/dist/registry/mirror-paths.js +8 -0
  242. package/dist/registry/mirror.d.ts +130 -0
  243. package/dist/registry/mirror.d.ts.map +1 -0
  244. package/dist/registry/mirror.js +319 -0
  245. package/dist/registry/proxies.d.ts +73 -0
  246. package/dist/registry/proxies.d.ts.map +1 -0
  247. package/dist/registry/proxies.js +111 -0
  248. package/dist/registry/reader.d.ts +57 -0
  249. package/dist/registry/reader.d.ts.map +1 -0
  250. package/dist/registry/reader.js +198 -0
  251. package/dist/registry/semver.d.ts +16 -0
  252. package/dist/registry/semver.d.ts.map +1 -0
  253. package/dist/registry/semver.js +24 -0
  254. package/dist/registry/serve.d.ts +24 -0
  255. package/dist/registry/serve.d.ts.map +1 -0
  256. package/dist/registry/serve.js +64 -0
  257. package/dist/registry/source-forms.d.ts +79 -0
  258. package/dist/registry/source-forms.d.ts.map +1 -0
  259. package/dist/registry/source-forms.js +202 -0
  260. package/dist/registry/targets/artifactory.d.ts +28 -0
  261. package/dist/registry/targets/artifactory.d.ts.map +1 -0
  262. package/dist/registry/targets/artifactory.js +206 -0
  263. package/dist/registry/targets/fake-artifactory.d.ts +30 -0
  264. package/dist/registry/targets/fake-artifactory.d.ts.map +1 -0
  265. package/dist/registry/targets/fake-artifactory.js +142 -0
  266. package/dist/registry/targets/fake-gitlab.d.ts +39 -0
  267. package/dist/registry/targets/fake-gitlab.d.ts.map +1 -0
  268. package/dist/registry/targets/fake-gitlab.js +136 -0
  269. package/dist/registry/targets/fake-hcp.d.ts +50 -0
  270. package/dist/registry/targets/fake-hcp.d.ts.map +1 -0
  271. package/dist/registry/targets/fake-hcp.js +202 -0
  272. package/dist/registry/targets/fake-http.d.ts +31 -0
  273. package/dist/registry/targets/fake-http.d.ts.map +1 -0
  274. package/dist/registry/targets/fake-http.js +87 -0
  275. package/dist/registry/targets/fake-nexus.d.ts +27 -0
  276. package/dist/registry/targets/fake-nexus.d.ts.map +1 -0
  277. package/dist/registry/targets/fake-nexus.js +137 -0
  278. package/dist/registry/targets/gitlab.d.ts +53 -0
  279. package/dist/registry/targets/gitlab.d.ts.map +1 -0
  280. package/dist/registry/targets/gitlab.js +187 -0
  281. package/dist/registry/targets/hcp.d.ts +29 -0
  282. package/dist/registry/targets/hcp.d.ts.map +1 -0
  283. package/dist/registry/targets/hcp.js +310 -0
  284. package/dist/registry/targets/hosted.d.ts +62 -0
  285. package/dist/registry/targets/hosted.d.ts.map +1 -0
  286. package/dist/registry/targets/hosted.js +74 -0
  287. package/dist/registry/targets/index.d.ts +18 -0
  288. package/dist/registry/targets/index.d.ts.map +1 -0
  289. package/dist/registry/targets/index.js +17 -0
  290. package/dist/registry/targets/module-target.d.ts +68 -0
  291. package/dist/registry/targets/module-target.d.ts.map +1 -0
  292. package/dist/registry/targets/module-target.js +63 -0
  293. package/dist/registry/targets/nexus.d.ts +43 -0
  294. package/dist/registry/targets/nexus.d.ts.map +1 -0
  295. package/dist/registry/targets/nexus.js +250 -0
  296. package/dist/registry/targets/zip.d.ts +55 -0
  297. package/dist/registry/targets/zip.d.ts.map +1 -0
  298. package/dist/registry/targets/zip.js +278 -0
  299. package/dist/registry/writer.d.ts +101 -0
  300. package/dist/registry/writer.d.ts.map +1 -0
  301. package/dist/registry/writer.js +109 -0
  302. package/dist/report.d.ts +15 -6
  303. package/dist/report.d.ts.map +1 -1
  304. package/dist/report.js +28 -55
  305. package/dist/rules/record-store.ts +122 -0
  306. package/dist/rules/tf001.ts +2 -2
  307. package/dist/rules/tf024.ts +105 -22
  308. package/dist/rules/tf046.ts +204 -0
  309. package/dist/rules/tf047.ts +114 -0
  310. package/dist/skills/chant-terraform.md +1 -1
  311. package/dist/state/azure-blob.d.ts +9 -0
  312. package/dist/state/azure-blob.d.ts.map +1 -0
  313. package/dist/state/azure-blob.js +8 -0
  314. package/dist/state/backend.d.ts +158 -0
  315. package/dist/state/backend.d.ts.map +1 -0
  316. package/dist/state/backend.js +406 -0
  317. package/dist/state/exec.d.ts +39 -0
  318. package/dist/state/exec.d.ts.map +1 -0
  319. package/dist/state/exec.js +80 -0
  320. package/dist/state/gcs.d.ts +8 -0
  321. package/dist/state/gcs.d.ts.map +1 -0
  322. package/dist/state/gcs.js +7 -0
  323. package/dist/state/index.d.ts +15 -0
  324. package/dist/state/index.d.ts.map +1 -0
  325. package/dist/state/index.js +14 -0
  326. package/dist/state/locks.d.ts +105 -0
  327. package/dist/state/locks.d.ts.map +1 -0
  328. package/dist/state/locks.js +130 -0
  329. package/dist/state/object-store.d.ts +8 -0
  330. package/dist/state/object-store.d.ts.map +1 -0
  331. package/dist/state/object-store.js +7 -0
  332. package/dist/state/root-states.d.ts +131 -0
  333. package/dist/state/root-states.d.ts.map +1 -0
  334. package/dist/state/root-states.js +241 -0
  335. package/dist/state/s3.d.ts +21 -0
  336. package/dist/state/s3.d.ts.map +1 -0
  337. package/dist/state/s3.js +19 -0
  338. package/dist/state/store.d.ts +51 -0
  339. package/dist/state/store.d.ts.map +1 -0
  340. package/dist/state/store.js +185 -0
  341. package/dist/state-ops.d.ts +255 -0
  342. package/dist/state-ops.d.ts.map +1 -0
  343. package/dist/state-ops.js +305 -0
  344. package/dist/state-reads.d.ts +57 -0
  345. package/dist/state-reads.d.ts.map +1 -0
  346. package/dist/state-reads.js +104 -0
  347. package/dist/terragrunt/affected.d.ts.map +1 -1
  348. package/dist/terragrunt/affected.js +2 -1
  349. package/dist/used-sources.d.ts +54 -0
  350. package/dist/used-sources.d.ts.map +1 -0
  351. package/dist/used-sources.js +157 -0
  352. package/package.json +24 -4
  353. package/src/__fixtures__/ACCEPTANCE.md +69 -7
  354. package/src/__fixtures__/compose/apply-failed-in-app.jsonl +12 -0
  355. package/src/__fixtures__/compose/state-app.json +66 -0
  356. package/src/__fixtures__/compose/state-edge.json +40 -0
  357. package/src/__fixtures__/compose/state-net.json +58 -0
  358. package/src/__fixtures__/compose/three-app.plan.json +184 -0
  359. package/src/__fixtures__/compose/three-composed.plan.json +509 -0
  360. package/src/__fixtures__/compose/three-edge.plan.json +144 -0
  361. package/src/__fixtures__/compose/three-net.plan.json +174 -0
  362. package/src/__fixtures__/compose/unknown-composed.plan.json +228 -0
  363. package/src/__fixtures__/compose/working-after-apply.json +145 -0
  364. package/src/__fixtures__/config-blocks/app/main.tf +61 -0
  365. package/src/__fixtures__/config-blocks/app/modules/network/main.tf +4 -0
  366. package/src/__fixtures__/config-blocks/app-json/main.tf.json +39 -0
  367. package/src/__fixtures__/config-files/shadow/main.tf +3 -0
  368. package/src/__fixtures__/config-files/shadow/main.tofu +3 -0
  369. package/src/__fixtures__/config-files/shadow/other.tf +3 -0
  370. package/src/__fixtures__/config-files/tf/main.tf +11 -0
  371. package/src/__fixtures__/config-files/tf-json/main.tf.json +18 -0
  372. package/src/__fixtures__/config-files/tofu/main.tofu +11 -0
  373. package/src/__fixtures__/config-files/tofu-json/main.tofu.json +18 -0
  374. package/src/__fixtures__/config-files/tofu-only/main.tofu +3 -0
  375. package/src/__fixtures__/drift-record/README.md +16 -0
  376. package/src/__fixtures__/drift-record/live-plan-sections.json +27 -0
  377. package/src/__fixtures__/drift-rows/live-root-plan.json +70 -0
  378. package/src/__fixtures__/drift-rows/refresh-plan.json +61 -0
  379. package/src/__fixtures__/instance-forms/app/main.tf +110 -0
  380. package/src/__fixtures__/instance-forms/app/modules/svc/main.tf +3 -0
  381. package/src/__fixtures__/instance-vars/app/main.tf +52 -0
  382. package/src/__fixtures__/instance-vars/app/modules/app/main.tf +12 -0
  383. package/src/__fixtures__/instance-vars/app/modules/edge/main.tf +3 -0
  384. package/src/__fixtures__/instance-vars/app/terraform.tfvars +1 -0
  385. package/src/__fixtures__/live-module-keyed/live-graph.json +259 -0
  386. package/src/__fixtures__/live-module-keyed/live-plan.json +87 -0
  387. package/src/__fixtures__/live-module-keyed/root/estate.chdf.hcl +1 -0
  388. package/src/__fixtures__/live-module-keyed/root/main.tf +21 -0
  389. package/src/__fixtures__/live-module-keyed/root/modules/app/main.tf +17 -0
  390. package/src/__fixtures__/live-module-keyed/root/modules/edge/main.tf +7 -0
  391. package/src/__fixtures__/live-module-keyed/root/versions.tf +30 -0
  392. package/src/__fixtures__/live-module-keyed/show-state.json +146 -0
  393. package/src/__fixtures__/pr-loop/README.md +50 -0
  394. package/src/__fixtures__/pr-loop/chant.config.json +13 -0
  395. package/src/__fixtures__/pr-loop/estate.component.ts +10 -0
  396. package/src/__fixtures__/pr-loop/roots/a/main.tf +11 -0
  397. package/src/__fixtures__/pr-loop/roots/app/main.tf +19 -0
  398. package/src/__fixtures__/pr-loop/roots/b/main.tf +7 -0
  399. package/src/__fixtures__/pr-loop/roots/dns/main.tf +3 -0
  400. package/src/__fixtures__/pr-loop/roots/net/main.tf +7 -0
  401. package/src/__fixtures__/report/applied.show.json +51 -0
  402. package/src/__fixtures__/report/generate.ts +63 -0
  403. package/src/__fixtures__/report/plan.show.json +200 -0
  404. package/src/__fixtures__/report/tf-apply/change-set.json +170 -0
  405. package/src/__fixtures__/report/tf-apply/report.json +338 -0
  406. package/src/__fixtures__/report/tf-plan/change-set.json +170 -0
  407. package/src/__fixtures__/report/tf-plan/report.json +320 -0
  408. package/src/__fixtures__/three-cloud-azure.graph.json +290 -0
  409. package/src/__fixtures__/used-sources/root/extra.tofu +1 -0
  410. package/src/__fixtures__/used-sources/root/main.tf +31 -0
  411. package/src/__fixtures__/used-sources/root/modules/net/main.tf +14 -0
  412. package/src/__fixtures__/used-sources/root/modules/net/versions.tf.json +7 -0
  413. package/src/__fixtures__/used-sources/unit/terragrunt.hcl +3 -0
  414. package/src/applied-state.ts +52 -0
  415. package/src/backend-config.test.ts +105 -0
  416. package/src/backend-config.ts +133 -0
  417. package/src/behaviour/kinds.ts +4 -0
  418. package/src/behaviour/predict.ts +5 -2
  419. package/src/behaviour/reference-catalog.plugin.test.ts +65 -0
  420. package/src/behaviour/reference-catalog.ts +16 -5
  421. package/src/behaviour/request.ts +3 -2
  422. package/src/change-set-instances.test.ts +98 -0
  423. package/src/changed-roots.ts +14 -16
  424. package/src/ci-setup.ts +42 -0
  425. package/src/commands.ts +4 -3
  426. package/src/components/capability-plugin.test.ts +198 -19
  427. package/src/components/capability-plugin.ts +383 -35
  428. package/src/components/change-set-graph.e2e.test.ts +11 -5
  429. package/src/components/compose-group.test.ts +486 -0
  430. package/src/components/compose-group.ts +489 -0
  431. package/src/components/demo-composed-roots.e2e.test.ts +440 -0
  432. package/src/components/member-outputs.test.ts +76 -0
  433. package/src/components/placeholder-preview.test.ts +147 -0
  434. package/src/components/placeholder-preview.ts +257 -0
  435. package/src/components/pr-loop-compose.tofu.e2e.test.ts +363 -0
  436. package/src/components/pr-loop.tofu.test.ts +113 -57
  437. package/src/components/release-inputs.ts +69 -0
  438. package/src/components/trace-facts.test.ts +21 -0
  439. package/src/components/trace-facts.ts +27 -0
  440. package/src/compose/apply-events.test.ts +62 -0
  441. package/src/compose/apply-events.ts +100 -0
  442. package/src/compose/estate.choudoufu.e2e.test.ts +112 -0
  443. package/src/compose/estate.test.ts +421 -0
  444. package/src/compose/estate.ts +443 -0
  445. package/src/compose/hcl.ts +311 -0
  446. package/src/compose/module-address.ts +49 -0
  447. package/src/compose/root.test.ts +416 -0
  448. package/src/compose/root.ts +592 -0
  449. package/src/compose/run.test.ts +440 -0
  450. package/src/compose/run.tofu.test.ts +176 -0
  451. package/src/compose/run.ts +527 -0
  452. package/src/compose/slice.test.ts +184 -0
  453. package/src/compose/slice.ts +290 -0
  454. package/src/compose/state-address.ts +157 -0
  455. package/src/compose/state-merge.test.ts +131 -0
  456. package/src/compose/state-merge.ts +148 -0
  457. package/src/compose.test.ts +248 -0
  458. package/src/compose.ts +627 -0
  459. package/src/composites/terraform-apply-op.test.ts +9 -0
  460. package/src/composites/terraform-apply-op.ts +7 -3
  461. package/src/cone.ts +138 -0
  462. package/src/config-file-listers.test.ts +265 -0
  463. package/src/config.ts +44 -9
  464. package/src/cost-hold.ts +29 -0
  465. package/src/describe-resources.engine-facts.test.ts +411 -0
  466. package/src/describe-resources.live-module-keyed.test.ts +290 -0
  467. package/src/describe-resources.ts +416 -70
  468. package/src/drift-read.ts +284 -0
  469. package/src/drift-record.test.ts +223 -0
  470. package/src/drift-record.ts +455 -0
  471. package/src/drift-rows.test.ts +135 -0
  472. package/src/drift-rows.ts +137 -0
  473. package/src/exit-codes.test.ts +23 -0
  474. package/src/exit-codes.ts +59 -0
  475. package/src/hcl/config-blocks.test.ts +174 -0
  476. package/src/hcl/edges.ts +21 -1
  477. package/src/hcl/files.ts +234 -0
  478. package/src/hcl/instance-forms.test.ts +94 -0
  479. package/src/hcl/instances.test.ts +137 -0
  480. package/src/hcl/instances.ts +422 -0
  481. package/src/hcl/parse.test.ts +139 -1
  482. package/src/hcl/parse.ts +149 -16
  483. package/src/hcl/roots.ts +6 -0
  484. package/src/hcl/suppressions.ts +8 -2
  485. package/src/index.ts +79 -0
  486. package/src/lint/audit-catalog.ts +29 -3
  487. package/src/lint/audit-lineage.ts +14 -2
  488. package/src/lint/post-synth/fixtures/TF024/live-with-record-store/main.tf +18 -0
  489. package/src/lint/post-synth/fixtures/TF024/sidecar-with-backend/estate.chdf.hcl +1 -0
  490. package/src/lint/post-synth/fixtures/TF024/sidecar-with-backend/main.tf +10 -0
  491. package/src/lint/post-synth/fixtures/TF046/azurerm-no-resource-group/main.tf +15 -0
  492. package/src/lint/post-synth/fixtures/TF046/backend-config-only/main.tf +14 -0
  493. package/src/lint/post-synth/fixtures/TF046/derived-s3/backend.tf +7 -0
  494. package/src/lint/post-synth/fixtures/TF046/derived-s3/main.tf +9 -0
  495. package/src/lint/post-synth/fixtures/TF046/explicit-local/main.tf +16 -0
  496. package/src/lint/post-synth/fixtures/TF046/explicit-s3-beside-local-backend/main.tf +15 -0
  497. package/src/lint/post-synth/fixtures/TF046/implied-local/main.tf +9 -0
  498. package/src/lint/post-synth/fixtures/TF046/sidecar-implied/estate.chdf.hcl +1 -0
  499. package/src/lint/post-synth/fixtures/TF046/sidecar-implied/main.tf +5 -0
  500. package/src/lint/post-synth/fixtures/TF047/clear/main.tf +12 -0
  501. package/src/lint/post-synth/fixtures/TF047/refused/main.tf +12 -0
  502. package/src/lint/post-synth/fixtures/TF047/unknown-module/main.tf +12 -0
  503. package/src/lint/post-synth/fixtures/TF047/unknown-version/main.tf +12 -0
  504. package/src/lint/post-synth/index.ts +4 -0
  505. package/src/lint/post-synth/record-store.ts +122 -0
  506. package/src/lint/post-synth/tf001.ts +2 -2
  507. package/src/lint/post-synth/tf024.test.ts +26 -5
  508. package/src/lint/post-synth/tf024.ts +105 -22
  509. package/src/lint/post-synth/tf046.test.ts +137 -0
  510. package/src/lint/post-synth/tf046.ts +204 -0
  511. package/src/lint/post-synth/tf047.test.ts +56 -0
  512. package/src/lint/post-synth/tf047.ts +114 -0
  513. package/src/live-plan-reasons.ts +54 -0
  514. package/src/lock-rows.test.ts +70 -0
  515. package/src/lock-rows.ts +82 -0
  516. package/src/lock-timeout.ts +34 -0
  517. package/src/lsp/completions.test.ts +1 -1
  518. package/src/lsp/option-keys.ts +2 -0
  519. package/src/module-catalog/index.test.ts +93 -0
  520. package/src/module-catalog/index.ts +159 -0
  521. package/src/module-catalog/refresh-cli.ts +68 -0
  522. package/src/module-catalog/static-tier.ts +1659 -0
  523. package/src/module-catalog/types.ts +88 -0
  524. package/src/op/activities/choudoufu-plan-set-op.test.ts +111 -0
  525. package/src/op/activities/index.ts +9 -0
  526. package/src/op/activities/live-detect.ts +55 -26
  527. package/src/op/activities/pin-rollout.ts +3 -0
  528. package/src/op/activities/registry.test.ts +2 -1
  529. package/src/op/activities/terraform.test.ts +55 -0
  530. package/src/op/activities/terraform.ts +312 -27
  531. package/src/op/activity-contracts.test.ts +1 -0
  532. package/src/op/activity-contracts.ts +24 -1
  533. package/src/op/builders.ts +14 -0
  534. package/src/op-wave-step.test.ts +109 -0
  535. package/src/pin/__fixtures__/consumers/live/dev/main.tf +4 -0
  536. package/src/pin/__fixtures__/consumers/live/prod/main.tf +21 -0
  537. package/src/pin/__fixtures__/consumers/units/vpc/terragrunt.hcl +7 -0
  538. package/src/pin/bundle.test.ts +2 -0
  539. package/src/pin/consumers.test.ts +57 -0
  540. package/src/pin/consumers.ts +95 -0
  541. package/src/pin/edit.ts +30 -0
  542. package/src/pin/rollout.ts +16 -11
  543. package/src/plan-shape.test.ts +148 -0
  544. package/src/plan-shape.ts +128 -0
  545. package/src/plugin.ts +39 -19
  546. package/src/registry/__fixtures__/docs/documented/.terraform-docs.yml +3 -0
  547. package/src/registry/__fixtures__/docs/documented/README.md +1 -0
  548. package/src/registry/__fixtures__/docs/documented/main.tf +8 -0
  549. package/src/registry/__fixtures__/docs/json/main.tf.json +14 -0
  550. package/src/registry/__fixtures__/docs/vpc/README.md +3 -0
  551. package/src/registry/__fixtures__/docs/vpc/examples/complete/main.tf +5 -0
  552. package/src/registry/__fixtures__/docs/vpc/examples/simple/README.md +1 -0
  553. package/src/registry/__fixtures__/docs/vpc/examples/simple/main.tf +4 -0
  554. package/src/registry/__fixtures__/docs/vpc/main.tf +14 -0
  555. package/src/registry/__fixtures__/docs/vpc/outputs.tf +9 -0
  556. package/src/registry/__fixtures__/docs/vpc/variables.tf +16 -0
  557. package/src/registry/__fixtures__/proxied-root/main.tf +12 -0
  558. package/src/registry/address.ts +100 -0
  559. package/src/registry/catalog-read.ts +56 -0
  560. package/src/registry/deprecated.test.ts +139 -0
  561. package/src/registry/deprecated.ts +76 -0
  562. package/src/registry/docs.test.ts +140 -0
  563. package/src/registry/docs.ts +299 -0
  564. package/src/registry/edge/bundle.test.ts +50 -0
  565. package/src/registry/edge/edge.test.ts +176 -0
  566. package/src/registry/edge/edge.ts +211 -0
  567. package/src/registry/edge/index.ts +10 -0
  568. package/src/registry/edge/lambda-edge.ts +127 -0
  569. package/src/registry/edge/sigv4-web.ts +79 -0
  570. package/src/registry/edge/token-source.test.ts +258 -0
  571. package/src/registry/edge/token-source.ts +169 -0
  572. package/src/registry/edge/worker.ts +61 -0
  573. package/src/registry/handler.ts +89 -0
  574. package/src/registry/headers-file.ts +61 -0
  575. package/src/registry/index.ts +31 -0
  576. package/src/registry/mirror-paths.ts +10 -0
  577. package/src/registry/mirror.test.ts +198 -0
  578. package/src/registry/mirror.ts +395 -0
  579. package/src/registry/proxies.test.ts +153 -0
  580. package/src/registry/proxies.ts +132 -0
  581. package/src/registry/reader.ts +225 -0
  582. package/src/registry/registry.test.ts +262 -0
  583. package/src/registry/semver.ts +34 -0
  584. package/src/registry/serve.ts +82 -0
  585. package/src/registry/source-forms.test.ts +87 -0
  586. package/src/registry/source-forms.ts +216 -0
  587. package/src/registry/targets/artifactory.test.ts +248 -0
  588. package/src/registry/targets/artifactory.ts +257 -0
  589. package/src/registry/targets/fake-artifactory.ts +158 -0
  590. package/src/registry/targets/fake-gitlab.ts +161 -0
  591. package/src/registry/targets/fake-hcp.ts +233 -0
  592. package/src/registry/targets/fake-http.ts +97 -0
  593. package/src/registry/targets/fake-nexus.ts +152 -0
  594. package/src/registry/targets/gitlab.test.ts +208 -0
  595. package/src/registry/targets/gitlab.ts +223 -0
  596. package/src/registry/targets/hcp.test.ts +230 -0
  597. package/src/registry/targets/hcp.ts +360 -0
  598. package/src/registry/targets/hosted.ts +117 -0
  599. package/src/registry/targets/index.ts +17 -0
  600. package/src/registry/targets/module-target.ts +131 -0
  601. package/src/registry/targets/nexus.test.ts +220 -0
  602. package/src/registry/targets/nexus.ts +314 -0
  603. package/src/registry/targets/zip.test.ts +65 -0
  604. package/src/registry/targets/zip.ts +287 -0
  605. package/src/registry/writer.ts +155 -0
  606. package/src/report.test.ts +82 -1
  607. package/src/report.ts +31 -54
  608. package/src/signal-only-root.test.ts +72 -0
  609. package/src/skills/chant-terraform.md +1 -1
  610. package/src/state/azure-blob.ts +8 -0
  611. package/src/state/backend-verdict.test.ts +187 -0
  612. package/src/state/backend.test.ts +127 -0
  613. package/src/state/backend.ts +488 -0
  614. package/src/state/exec.ts +111 -0
  615. package/src/state/gcs.ts +7 -0
  616. package/src/state/index.ts +15 -0
  617. package/src/state/locks.test.ts +84 -0
  618. package/src/state/locks.ts +201 -0
  619. package/src/state/object-store.ts +7 -0
  620. package/src/state/root-states-floci.e2e.test.ts +66 -0
  621. package/src/state/root-states.test.ts +462 -0
  622. package/src/state/root-states.ts +309 -0
  623. package/src/state/s3.ts +21 -0
  624. package/src/state/store.ts +225 -0
  625. package/src/state-ops.test.ts +182 -0
  626. package/src/state-ops.ts +462 -0
  627. package/src/state-reads.ts +116 -0
  628. package/src/terragrunt/affected.ts +2 -1
  629. package/src/used-sources.test.ts +75 -0
  630. package/src/used-sources.ts +204 -0
  631. package/src/workspace-kinds.test.ts +4 -17
  632. package/workspace-kinds.json +1 -24
  633. package/dist/workspace-member.d.ts +0 -30
  634. package/dist/workspace-member.d.ts.map +0 -1
  635. package/dist/workspace-member.js +0 -91
  636. package/schemas/pulumi.schema.json +0 -34
  637. package/src/testdata/pulumi-member/Pulumi.yaml +0 -2
  638. package/src/testdata/pulumi-member/stack-export.json +0 -53
  639. package/src/testdata/pulumi-unexported/Pulumi.yaml +0 -2
  640. package/src/workspace-member.test.ts +0 -89
  641. package/src/workspace-member.ts +0 -126
@@ -0,0 +1,94 @@
1
+ /**
2
+ * The expansion forms #4489 counts, and the rollup reason on every block the
3
+ * declared graph still leaves as one node. The fixture is
4
+ * `src/__fixtures__/instance-forms/app/`, built as `chant graph --format ir`
5
+ * builds a terraform root's graph.
6
+ */
7
+
8
+ import { dirname, join } from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+ import { beforeAll, describe, expect, it } from "vitest";
11
+ import { buildGraphIr, withInstanceNodes, type GraphIR, type IRNode } from "@intentius/chant/graph-ir";
12
+ import { INSTANCE_ROLLUP_CODES } from "@intentius/chant/instance-nodes";
13
+ import type { Declarable } from "@intentius/chant/declarable";
14
+ import { renderTerraformRoots } from "./roots";
15
+
16
+ const TREE = join(dirname(fileURLToPath(import.meta.url)), "..", "__fixtures__", "instance-forms");
17
+
18
+ let entities: Map<string, Declarable>;
19
+ let ir: GraphIR;
20
+
21
+ beforeAll(async () => {
22
+ const rendered = await renderTerraformRoots({ projectRoot: TREE, roots: { estate: { dir: "./app" } } });
23
+ entities = rendered.entities;
24
+ ir = withInstanceNodes(buildGraphIr(entities), entities);
25
+ });
26
+
27
+ function instancesOf(block: string): string[] {
28
+ return ir.nodes.filter((n) => n.instanceOf === block).map((n) => n.id).sort();
29
+ }
30
+
31
+ function node(id: string): IRNode {
32
+ const n = ir.nodes.find((x) => x.id === id);
33
+ if (!n) throw new Error(`no node ${id}`);
34
+ return n;
35
+ }
36
+
37
+ describe("forms counted since #4489", () => {
38
+ it("counts `count = local.x` over a literal local", () => {
39
+ expect(instancesOf("estate/aws_eip.local_count")).toEqual(["estate/aws_eip.local_count[0]", "estate/aws_eip.local_count[1]"]);
40
+ expect(node("estate/aws_eip.local_count").instanceRollup).toBeUndefined();
41
+ });
42
+
43
+ it("counts `count = length(local.x)`", () => {
44
+ expect(instancesOf("estate/aws_sqs_queue.local_length")).toHaveLength(3);
45
+ });
46
+
47
+ it("keys `for_each = toset(local.l)` and `for_each = local.m`", () => {
48
+ expect(instancesOf("estate/aws_s3_bucket.local_set")).toEqual(['estate/aws_s3_bucket.local_set["p"]', 'estate/aws_s3_bucket.local_set["q"]']);
49
+ expect(instancesOf("estate/aws_lb_listener.local_map")).toEqual(['estate/aws_lb_listener.local_map["http"]', 'estate/aws_lb_listener.local_map["https"]']);
50
+ });
51
+
52
+ it("counts `var.on ? 1 : 0` over a known bool, and a local that reads one", () => {
53
+ expect(instancesOf("estate/aws_sns_topic.enabled")).toEqual(["estate/aws_sns_topic.enabled[0]"]);
54
+ expect(instancesOf("estate/aws_sns_topic.disabled")).toEqual([]);
55
+ expect(node("estate/aws_sns_topic.disabled").instanceRollup).toBeUndefined();
56
+ expect(instancesOf("estate/aws_sns_topic.local_flag")).toHaveLength(2);
57
+ });
58
+ });
59
+
60
+ describe("forms still rolled up, with the node's reason", () => {
61
+ it.each([
62
+ ["estate/aws_sns_topic.flag_unknown", "instances-value-unknown", "count", "var.flag ? 1 : 0"],
63
+ ["estate/aws_sns_topic.local_unknown", "instances-value-unknown", "count", "local.computed"],
64
+ ["estate/aws_iam_group.each", "instances-each-value", "for_each", "each.value"],
65
+ ["estate/aws_iam_policy.arith", "instances-form-unread", "count", "local.replicas + 1"],
66
+ ["estate/aws_eip.too_many", "instances-over-limit", "count", "300"],
67
+ ["estate/module.svc", "instances-value-unknown", "count", "var.unknown"],
68
+ ])("%s is one node with %s", (id, code, argument, expression) => {
69
+ expect(instancesOf(id)).toEqual([]);
70
+ expect(node(id).instanceRollup).toEqual({ code, argument, expression });
71
+ });
72
+
73
+ it("names merge() and a for expression", () => {
74
+ expect(node("estate/aws_iam_user.merged").instanceRollup).toMatchObject({ code: "instances-merge", argument: "for_each" });
75
+ expect(node("estate/aws_iam_user.merged").instanceRollup?.expression).toMatch(/^merge\(/);
76
+ expect(node("estate/aws_iam_role.for_expr").instanceRollup).toMatchObject({ code: "instances-form-unread", argument: "for_each" });
77
+ });
78
+
79
+ it("names the uncounted module call on a block under it", () => {
80
+ expect(instancesOf("estate/module.svc/aws_iam_role.runner")).toEqual([]);
81
+ expect(node("estate/module.svc/aws_iam_role.runner").instanceRollup).toEqual({
82
+ code: "instances-module-uncounted",
83
+ argument: "count",
84
+ expression: "var.unknown",
85
+ call: "module.svc",
86
+ });
87
+ });
88
+
89
+ it("uses only the listed codes", () => {
90
+ for (const n of ir.nodes) {
91
+ if (n.instanceRollup) expect(INSTANCE_ROLLUP_CODES).toContain(n.instanceRollup.code);
92
+ }
93
+ });
94
+ });
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Instance nodes counted from variables, and under expanded module calls
3
+ * (chant #4486).
4
+ *
5
+ * The fixture is `src/__fixtures__/instance-vars/app/`, rendered through
6
+ * `renderTerraformRoots`, then core's `buildGraphIr` and `withInstanceNodes`,
7
+ * which is how `chant graph --format ir` builds a terraform root's graph.
8
+ */
9
+
10
+ import { dirname, join } from "node:path";
11
+ import { fileURLToPath } from "node:url";
12
+ import { beforeAll, describe, expect, it } from "vitest";
13
+ import { buildGraphIr, withInstanceNodes, type GraphIR } from "@intentius/chant/graph-ir";
14
+ import { graphNodeFor } from "@intentius/chant/change-set-graph";
15
+ import { terraformInstancePath } from "@intentius/chant/instance-nodes";
16
+ import type { Declarable } from "@intentius/chant/declarable";
17
+ import { renderTerraformRoots } from "./roots";
18
+
19
+ const TREE = join(dirname(fileURLToPath(import.meta.url)), "..", "__fixtures__", "instance-vars");
20
+
21
+ let entities: Map<string, Declarable>;
22
+ let ir: GraphIR;
23
+
24
+ beforeAll(async () => {
25
+ const rendered = await renderTerraformRoots({ projectRoot: TREE, roots: { estate: { dir: "./app" } } });
26
+ expect(rendered.warnings).toEqual([]);
27
+ entities = rendered.entities;
28
+ ir = withInstanceNodes(buildGraphIr(entities), entities);
29
+ });
30
+
31
+ /** The instance nodes under one block node, as `[id, instanceKey, instanceScope]`. */
32
+ function instancesOf(block: string): Array<[string, string | null, string | null]> {
33
+ return ir.nodes
34
+ .filter((n) => n.instanceOf === block)
35
+ .map((n) => [n.id, n.instanceKey ?? null, n.instanceScope ?? null] as [string, string | null, string | null])
36
+ .sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0));
37
+ }
38
+
39
+ describe("count and for_each resolved from variables", () => {
40
+ it("counts `count = var.n` from terraform.tfvars over the default", () => {
41
+ expect(instancesOf("estate/aws_eip.pool")).toEqual([
42
+ ["estate/aws_eip.pool[0]", "[0]", null],
43
+ ["estate/aws_eip.pool[1]", "[1]", null],
44
+ ["estate/aws_eip.pool[2]", "[2]", null],
45
+ ]);
46
+ });
47
+
48
+ it("keys `for_each = var.names` over a set variable's default", () => {
49
+ expect(instancesOf("estate/aws_s3_bucket.named").map((i) => i[0])).toEqual([
50
+ 'estate/aws_s3_bucket.named["a"]',
51
+ 'estate/aws_s3_bucket.named["b"]',
52
+ ]);
53
+ });
54
+
55
+ it("counts `count = length(var.zones)`", () => {
56
+ expect(instancesOf("estate/aws_sqs_queue.zoned").map((i) => i[0])).toEqual([
57
+ "estate/aws_sqs_queue.zoned[0]",
58
+ "estate/aws_sqs_queue.zoned[1]",
59
+ ]);
60
+ });
61
+
62
+ it("leaves a block whose variable has no known value as one node", () => {
63
+ expect(instancesOf("estate/aws_sns_topic.uncounted")).toEqual([]);
64
+ expect(ir.nodes.some((n) => n.id === "estate/aws_sns_topic.uncounted")).toBe(true);
65
+ });
66
+ });
67
+
68
+ describe("instances under an expanded module call", () => {
69
+ it("gives the module call its own instance nodes", () => {
70
+ expect(instancesOf("estate/module.app")).toEqual([
71
+ ["estate/module.app[0]", "[0]", null],
72
+ ["estate/module.app[1]", "[1]", null],
73
+ ]);
74
+ });
75
+
76
+ it("puts the module key on the call segment and the block key at the end", () => {
77
+ // `size = var.n` passes the root's 3 into the child's `count = var.size`.
78
+ expect(instancesOf("estate/module.app/aws_instance.web")).toEqual([
79
+ ["estate/module.app[0]/aws_instance.web[0]", "[0]", "module.app[0]"],
80
+ ["estate/module.app[0]/aws_instance.web[1]", "[1]", "module.app[0]"],
81
+ ["estate/module.app[0]/aws_instance.web[2]", "[2]", "module.app[0]"],
82
+ ["estate/module.app[1]/aws_instance.web[0]", "[0]", "module.app[1]"],
83
+ ["estate/module.app[1]/aws_instance.web[1]", "[1]", "module.app[1]"],
84
+ ["estate/module.app[1]/aws_instance.web[2]", "[2]", "module.app[1]"],
85
+ ]);
86
+ });
87
+
88
+ it("gives a block that is not expanded itself one instance per module instance, with no instanceKey", () => {
89
+ expect(instancesOf("estate/module.app/aws_iam_role.runner")).toEqual([
90
+ ["estate/module.app[0]/aws_iam_role.runner", null, "module.app[0]"],
91
+ ["estate/module.app[1]/aws_iam_role.runner", null, "module.app[1]"],
92
+ ]);
93
+ expect(instancesOf("estate/module.edge/aws_route53_record.front")).toEqual([
94
+ ['estate/module.edge["east"]/aws_route53_record.front', null, 'module.edge["east"]'],
95
+ ['estate/module.edge["west"]/aws_route53_record.front', null, 'module.edge["west"]'],
96
+ ]);
97
+ });
98
+
99
+ it("keeps the block node as the rollup, in the same groups", () => {
100
+ expect(ir.nodes.some((n) => n.id === "estate/module.app/aws_instance.web" && !n.instanceOf)).toBe(true);
101
+ expect(ir.groups.byStack?.estate).toEqual(
102
+ expect.arrayContaining(["estate/module.app/aws_instance.web", "estate/module.app[1]/aws_instance.web[2]"]),
103
+ );
104
+ });
105
+
106
+ it("places a plan address on the same id the graph lists", () => {
107
+ const graph = { member: null, at: null, ids: ir.nodes.map((n) => n.id) };
108
+ expect(graphNodeFor({ member: "estate", planner: "terraform", address: "module.app[1].aws_instance.web[2]" }, graph)).toEqual({
109
+ node: "estate/module.app[1]/aws_instance.web[2]",
110
+ instanceOf: "estate/module.app/aws_instance.web",
111
+ });
112
+ expect(graphNodeFor({ member: "estate", planner: "tofu", address: 'module.edge["east"].aws_route53_record.front' }, graph)).toEqual({
113
+ node: 'estate/module.edge["east"]/aws_route53_record.front',
114
+ instanceOf: "estate/module.edge/aws_route53_record.front",
115
+ });
116
+ for (const n of ir.nodes.filter((x) => x.instanceOf)) expect(graph.ids).toContain(n.id);
117
+ });
118
+ });
119
+
120
+ describe("terraformInstancePath", () => {
121
+ it("maps an address to its block key and instance key", () => {
122
+ expect(terraformInstancePath("aws_eip.pool[1]")).toEqual({ block: "aws_eip.pool", instance: "aws_eip.pool[1]", key: "[1]" });
123
+ expect(terraformInstancePath("aws_eip.pool")).toBeUndefined();
124
+ expect(terraformInstancePath("module.app[0].aws_eip.pool[1]")).toEqual({
125
+ block: "module.app/aws_eip.pool",
126
+ instance: "module.app[0]/aws_eip.pool[1]",
127
+ key: "[1]",
128
+ scope: "module.app[0]",
129
+ });
130
+ expect(terraformInstancePath('module.a["x.y"].module.b.data.http.site')).toEqual({
131
+ block: "module.a/module.b/data.http.site",
132
+ instance: 'module.a["x.y"]/module.b/data.http.site',
133
+ scope: 'module.a["x.y"].module.b',
134
+ });
135
+ expect(terraformInstancePath("module.app[0]")).toEqual({ block: "module.app", instance: "module.app[0]", key: "[0]" });
136
+ });
137
+ });
@@ -0,0 +1,422 @@
1
+ /**
2
+ * Instance nodes the declared graph can count from variables, and instance
3
+ * nodes under expanded module calls (chant #4486).
4
+ *
5
+ * `./parse.ts` gives a block its `instanceKeys` when its `count` or
6
+ * `for_each` is a literal (#4454). This pass runs once a root and its child
7
+ * modules are parsed and adds two cases that need the whole root:
8
+ *
9
+ * - An expansion that reads a variable, `count = var.n`, gets its keys when
10
+ * the variable's value is known from the files a plan would read: the
11
+ * `variable` block's `default`, then `terraform.tfvars`,
12
+ * `terraform.tfvars.json`, `*.auto.tfvars(.json)` in filename order, then
13
+ * `terraform.roots.<name>.varFiles`, later over earlier. In a child module
14
+ * the value is the call's argument, when it is a literal or a `var.x`
15
+ * known in the caller, else the child variable's default when the call
16
+ * passes nothing. `TF_CLI_ARGS` is not read, so the declared graph does not
17
+ * change with the environment the build runs in.
18
+ * - A block under a module call that is itself expanded gets one instance
19
+ * per module instance (times its own instances), with the module key on
20
+ * the call's segment: `estate/module.app[0]/aws_eip.pool[1]`. The entity
21
+ * lists those as `instances` (full ids), which core's `withInstanceNodes`
22
+ * reads in place of `instanceKeys` (`@intentius/chant/instance-nodes`
23
+ * holds the id rule). A module call with a countable expansion gets
24
+ * instance nodes of its own, `estate/module.app[0]`.
25
+ *
26
+ * The forms read are `var.n`, `length(var.x)`, `var.m` (a map, or a set
27
+ * variable) for `for_each`, and `toset(var.l)`; since #4489 the same with
28
+ * `local.<x>` in place of `var.<x>`, where the local's value is a literal or
29
+ * one of the `var` forms in its scope, and `var.on ? 1 : 0` (any two whole
30
+ * numbers, and `local.on` too) over a known bool. Anything else, or any
31
+ * value that is unknown, leaves the block as one node, which is what it
32
+ * was, and the entity gets an `instanceRollup` saying why (#4489, the codes
33
+ * in `@intentius/chant/instance-nodes`). `each.value` and `merge()` stay
34
+ * rolled up by ruling. A block under an expanded call whose own expansion
35
+ * or whose call's expansion cannot be counted gets no instances at all,
36
+ * since a partial list would name ids the plan never prints.
37
+ */
38
+
39
+ import { readdirSync, readFileSync, statSync } from "node:fs";
40
+ import { join, resolve } from "node:path";
41
+ import { isResourceDeclarable, type Declarable } from "@intentius/chant/declarable";
42
+ import type { InstanceRollup, InstanceRollupCode } from "@intentius/chant/instance-nodes";
43
+ import { loadHcl2json, type Hcl2Json } from "@intentius/chant/terraform/hcl2json";
44
+ import {
45
+ DATA_TYPE,
46
+ LOCALS_TYPE,
47
+ MODULE_TYPE,
48
+ RESOURCE_TYPE,
49
+ VARIABLE_TYPE,
50
+ callersOfKey,
51
+ literalInstanceKeys,
52
+ scopeOfKey,
53
+ type BlockBody,
54
+ type TerraformEntity,
55
+ } from "./parse";
56
+
57
+ /** The most instance nodes one block gets; past it the block draws alone. */
58
+ const MAX_INSTANCES = 256;
59
+
60
+ const NAME = "[A-Za-z_][A-Za-z0-9_-]*";
61
+ const INTERPOLATION_RE = /^\$\{\s*([\s\S]*?)\s*\}$/;
62
+ /** `var.<x>` or `local.<x>` (#4489): the kind and the name. */
63
+ const REF = `(var|local)\\.(${NAME})`;
64
+ const REF_RE = new RegExp(`^${REF}$`);
65
+ const LENGTH_RE = new RegExp(`^length\\(\\s*${REF}\\s*\\)$`);
66
+ const TOSET_RE = new RegExp(`^toset\\(\\s*${REF}\\s*\\)$`);
67
+ /** `var.on ? 1 : 0` (#4489): a bool reference choosing between two whole numbers. */
68
+ const COND_RE = new RegExp(`^${REF}\\s*\\?\\s*(\\d+)\\s*:\\s*(\\d+)$`);
69
+ const EACH_RE = /\beach\.(?:value|key)\b/;
70
+ const MERGE_RE = /\bmerge\s*\(/;
71
+
72
+ /** A variable's known value, and whether it is declared as a set. */
73
+ interface VarValue {
74
+ value: unknown;
75
+ set: boolean;
76
+ }
77
+
78
+ /** Variable name to known value, for one module scope. A name that is absent is unknown. */
79
+ type ScopeVars = Map<string, VarValue>;
80
+
81
+ /** A scope's known `var` and `local` values (#4489). */
82
+ interface Scope {
83
+ vars: ScopeVars;
84
+ locals: ScopeVars;
85
+ }
86
+
87
+ const NO_LOCALS: ScopeVars = new Map();
88
+
89
+ /** An instance node an entity lists: full id, its own key and its scope, as core's `entityInstances` reads them. */
90
+ interface ListedInstance {
91
+ id: string;
92
+ key?: string;
93
+ scope?: string;
94
+ }
95
+
96
+ export interface ResolveInstancesOptions {
97
+ /** The root's directory, where its tfvars files sit. */
98
+ dir: string;
99
+ /** `terraform.roots.<name>.varFiles`, relative to `dir`. */
100
+ varFiles?: readonly string[];
101
+ hcl2json?: Hcl2Json;
102
+ }
103
+
104
+ /** Whether a value still holds an expression a read of the files cannot evaluate. */
105
+ function holdsExpression(value: unknown): boolean {
106
+ if (typeof value === "string") return value.includes("${");
107
+ if (Array.isArray(value)) return value.some(holdsExpression);
108
+ if (typeof value === "object" && value !== null) {
109
+ return Object.entries(value).some(([k, v]) => k.includes("${") || holdsExpression(v));
110
+ }
111
+ return false;
112
+ }
113
+
114
+ /** A literal value as it stands, or undefined when it holds an expression. */
115
+ function literal(value: unknown): unknown {
116
+ return value === undefined || holdsExpression(value) ? undefined : value;
117
+ }
118
+
119
+ /**
120
+ * Evaluate an argument in a scope: a literal, a bare `var.x` or `local.x`,
121
+ * `length(...)` or `toset(...)` of one, or `var.on ? 1 : 0`. Undefined when
122
+ * unknown.
123
+ */
124
+ function evaluate(value: unknown, vars: ScopeVars, locals: ScopeVars = NO_LOCALS): VarValue | undefined {
125
+ if (typeof value !== "string" || !value.includes("${")) {
126
+ const v = literal(value);
127
+ return v === undefined ? undefined : { value: v, set: false };
128
+ }
129
+ const m = INTERPOLATION_RE.exec(value.trim());
130
+ if (!m) return undefined;
131
+ const inner = m[1]!;
132
+ if (/^\d+$/.test(inner)) return { value: Number(inner), set: false };
133
+ const ref = (kind: string, name: string): VarValue | undefined => (kind === "var" ? vars : locals).get(name);
134
+ let r: RegExpExecArray | null;
135
+ if ((r = REF_RE.exec(inner))) return ref(r[1]!, r[2]!);
136
+ if ((r = COND_RE.exec(inner))) {
137
+ const on = ref(r[1]!, r[2]!)?.value;
138
+ return typeof on === "boolean" ? { value: Number(on ? r[3] : r[4]), set: false } : undefined;
139
+ }
140
+ if ((r = LENGTH_RE.exec(inner))) {
141
+ const v = ref(r[1]!, r[2]!)?.value;
142
+ if (Array.isArray(v)) return { value: v.length, set: false };
143
+ if (typeof v === "object" && v !== null) return { value: Object.keys(v).length, set: false };
144
+ if (typeof v === "string") return { value: [...v].length, set: false };
145
+ return undefined;
146
+ }
147
+ if ((r = TOSET_RE.exec(inner))) {
148
+ const v = ref(r[1]!, r[2]!)?.value;
149
+ return Array.isArray(v) ? { value: v, set: true } : undefined;
150
+ }
151
+ return undefined;
152
+ }
153
+
154
+ /** The expression a `count` or `for_each` holds, as written, without the `${ }` around it. */
155
+ function expressionText(value: unknown): string {
156
+ if (typeof value === "string") return (INTERPOLATION_RE.exec(value.trim())?.[1] ?? value).trim();
157
+ return JSON.stringify(value) ?? String(value);
158
+ }
159
+
160
+ /** Why an expansion the graph could not count stays rolled up (#4489). */
161
+ function rollupCodeOf(value: unknown): InstanceRollupCode {
162
+ if (typeof value !== "string" || !value.includes("${")) return "instances-value-unknown";
163
+ const text = expressionText(value);
164
+ if (EACH_RE.test(text)) return "instances-each-value";
165
+ if (MERGE_RE.test(text)) return "instances-merge";
166
+ const m = INTERPOLATION_RE.exec(value.trim());
167
+ if (m && [/^\d+$/, REF_RE, LENGTH_RE, TOSET_RE, COND_RE].some((re) => re.test(m[1]!))) return "instances-value-unknown";
168
+ return "instances-form-unread";
169
+ }
170
+
171
+ /**
172
+ * A block's expansion in a scope: its instance keys as Terraform prints them,
173
+ * `null` when the block is not expanded, or why the keys are not known.
174
+ */
175
+ export function expansionIn(body: BlockBody, scope: Scope): { keys: string[] } | { rollup: InstanceRollup } | null {
176
+ if (!("count" in body) && !("for_each" in body)) return null;
177
+ const argument = "count" in body ? "count" : "for_each";
178
+ const expr = body[argument];
179
+ const rolled = (code: InstanceRollupCode): { rollup: InstanceRollup } => ({ rollup: { code, argument, expression: expressionText(expr) } });
180
+ const lit = literalInstanceKeys(body);
181
+ if (lit) return { keys: lit };
182
+ let keys: string[] | undefined;
183
+ if (argument === "count") {
184
+ const v = evaluate(expr, scope.vars, scope.locals)?.value;
185
+ if (typeof v === "number" && Number.isInteger(v) && v >= 0) keys = Array.from({ length: v }, (_, i) => `[${i}]`);
186
+ } else {
187
+ const v = evaluate(expr, scope.vars, scope.locals);
188
+ if (v && Array.isArray(v.value)) {
189
+ if (v.set && v.value.every((s) => typeof s === "string")) {
190
+ keys = [...new Set(v.value as string[])].sort().map((k) => `[${JSON.stringify(k)}]`);
191
+ }
192
+ } else if (v && typeof v.value === "object" && v.value !== null) {
193
+ keys = Object.keys(v.value).sort().map((k) => `[${JSON.stringify(k)}]`);
194
+ }
195
+ }
196
+ if (keys === undefined) return rolled(rollupCodeOf(expr));
197
+ if (keys.length > MAX_INSTANCES) return rolled("instances-over-limit");
198
+ return { keys };
199
+ }
200
+
201
+ /**
202
+ * The instance keys a block's `count` or `for_each` gives in `vars`, as
203
+ * Terraform prints them. `null` when the block is not expanded, undefined
204
+ * when it is and the keys are not known.
205
+ */
206
+ export function instanceKeysIn(body: BlockBody, vars: ScopeVars, locals: ScopeVars = NO_LOCALS): string[] | null | undefined {
207
+ const e = expansionIn(body, { vars, locals });
208
+ return e === null ? null : "keys" in e ? e.keys : undefined;
209
+ }
210
+
211
+ /** A root's tfvars files, in the order a plan reads them, later over earlier. */
212
+ function tfvarsFiles(dir: string, varFiles: readonly string[]): string[] {
213
+ let names: string[] = [];
214
+ try {
215
+ names = readdirSync(dir).filter((n) => statSync(join(dir, n)).isFile());
216
+ } catch {
217
+ return [...varFiles];
218
+ }
219
+ return [
220
+ ...["terraform.tfvars", "terraform.tfvars.json"].filter((n) => names.includes(n)),
221
+ ...names.filter((n) => n.endsWith(".auto.tfvars") || n.endsWith(".auto.tfvars.json")).sort(),
222
+ ...varFiles,
223
+ ];
224
+ }
225
+
226
+ /** The values a root's tfvars files set, by variable name. A file that cannot be read or parsed sets nothing. */
227
+ async function rootTfvars(opts: ResolveInstancesOptions): Promise<Map<string, unknown>> {
228
+ const out = new Map<string, unknown>();
229
+ let parser: Hcl2Json | undefined = opts.hcl2json;
230
+ for (const file of tfvarsFiles(opts.dir, opts.varFiles ?? [])) {
231
+ let text: string;
232
+ try {
233
+ text = readFileSync(resolve(opts.dir, file), "utf-8");
234
+ } catch {
235
+ continue;
236
+ }
237
+ let tree: Record<string, unknown>;
238
+ try {
239
+ if (file.endsWith(".json")) tree = JSON.parse(text) as Record<string, unknown>;
240
+ else {
241
+ parser ??= await loadHcl2json();
242
+ tree = (await parser.parse(file, text)) as Record<string, unknown>;
243
+ }
244
+ } catch {
245
+ continue;
246
+ }
247
+ for (const [k, v] of Object.entries(tree)) out.set(k, v);
248
+ }
249
+ return out;
250
+ }
251
+
252
+ function bodyOf(entity: TerraformEntity): BlockBody {
253
+ const body = (entity.props as Partial<TerraformEntity["props"]>).body;
254
+ return (typeof body === "object" && body !== null ? body : {}) as BlockBody;
255
+ }
256
+
257
+ function isSetType(body: BlockBody): boolean {
258
+ return typeof body.type === "string" && /^\$\{\s*set\(/.test(body.type);
259
+ }
260
+
261
+ /**
262
+ * Add instance nodes the declared graph can count to one root's entities, in
263
+ * place (#4486). Entities of other roots in the map are left alone.
264
+ */
265
+ export async function resolveInstanceNodes(
266
+ entities: Map<string, Declarable>,
267
+ root: string,
268
+ opts: ResolveInstancesOptions,
269
+ ): Promise<void> {
270
+ const own = [...entities].filter(
271
+ ([key, e]) => (key === root || key.startsWith(`${root}/`)) && isResourceDeclarable(e),
272
+ ) as Array<[string, TerraformEntity]>;
273
+ if (own.length === 0) return;
274
+
275
+ // Variable declarations per scope.
276
+ const declared = new Map<string, Map<string, TerraformEntity>>();
277
+ for (const [key, e] of own) {
278
+ if (e.entityType !== VARIABLE_TYPE) continue;
279
+ const address = e.props.address;
280
+ if (!address.startsWith("var.")) continue;
281
+ const scope = scopeOfKey(key);
282
+ (declared.get(scope) ?? declared.set(scope, new Map()).get(scope)!).set(address.slice(4), e);
283
+ }
284
+
285
+ // Local values per scope (#4489): every `locals` block's names.
286
+ const localBodies = new Map<string, Map<string, unknown>>();
287
+ for (const [key, e] of own) {
288
+ if (e.entityType !== LOCALS_TYPE) continue;
289
+ const scope = scopeOfKey(key);
290
+ const into = localBodies.get(scope) ?? localBodies.set(scope, new Map()).get(scope)!;
291
+ for (const [name, value] of Object.entries(bodyOf(e))) into.set(name, value);
292
+ }
293
+
294
+ const tfvars = await rootTfvars(opts);
295
+ const scopes = new Map<string, ScopeVars>();
296
+ const localScopes = new Map<string, ScopeVars>();
297
+
298
+ /**
299
+ * A scope's known local values: a literal, or a `var` form over the
300
+ * scope's variables. A local that reads another local is unknown.
301
+ */
302
+ const localsOf = (scope: string): ScopeVars => {
303
+ const cached = localScopes.get(scope);
304
+ if (cached) return cached;
305
+ const out: ScopeVars = new Map();
306
+ localScopes.set(scope, out);
307
+ for (const [name, value] of localBodies.get(scope) ?? []) {
308
+ const v = evaluate(value, varsOf(scope));
309
+ if (v !== undefined) out.set(name, v);
310
+ }
311
+ return out;
312
+ };
313
+ const scopeOf = (scope: string): Scope => ({ vars: varsOf(scope), locals: localsOf(scope) });
314
+
315
+ /** A scope's known variable values, computed once, outermost first. */
316
+ const varsOf = (scope: string): ScopeVars => {
317
+ const cached = scopes.get(scope);
318
+ if (cached) return cached;
319
+ const vars: ScopeVars = new Map();
320
+ scopes.set(scope, vars);
321
+ const decls = declared.get(scope) ?? new Map<string, TerraformEntity>();
322
+ if (scope === root) {
323
+ for (const [name, decl] of decls) {
324
+ const body = bodyOf(decl);
325
+ const set = isSetType(body);
326
+ const v = tfvars.has(name) ? literal(tfvars.get(name)) : literal(body.default);
327
+ if (v !== undefined) vars.set(name, { value: v, set });
328
+ }
329
+ return vars;
330
+ }
331
+ const call = entities.get(scope) as TerraformEntity | undefined;
332
+ const parent = scopeOfKey(scope);
333
+ const callBody = call ? bodyOf(call) : {};
334
+ const parentVars = varsOf(parent);
335
+ for (const [name, decl] of decls) {
336
+ const body = bodyOf(decl);
337
+ const set = isSetType(body);
338
+ if (name in callBody) {
339
+ const v = evaluate(callBody[name], parentVars, localsOf(parent));
340
+ if (v !== undefined) vars.set(name, { value: v.value, set: set || v.set });
341
+ } else {
342
+ const v = literal(body.default);
343
+ if (v !== undefined) vars.set(name, { value: v, set });
344
+ }
345
+ }
346
+ return vars;
347
+ };
348
+
349
+ /** A module call's expansion, by the call entity's key; a call that is not a module call is not known. */
350
+ const callExpansion = (callKey: string): ReturnType<typeof expansionIn> | undefined => {
351
+ const call = entities.get(callKey) as TerraformEntity | undefined;
352
+ if (!call || call.entityType !== MODULE_TYPE) return undefined;
353
+ return expansionIn(bodyOf(call), scopeOf(scopeOfKey(callKey)));
354
+ };
355
+ const keysOf = (e: ReturnType<typeof expansionIn> | undefined): string[] | null | undefined =>
356
+ e === undefined ? undefined : e === null ? null : "keys" in e ? e.keys : undefined;
357
+
358
+ for (const [key, entity] of own) {
359
+ if (entity.entityType !== RESOURCE_TYPE && entity.entityType !== DATA_TYPE && entity.entityType !== MODULE_TYPE) continue;
360
+ const callers = callersOfKey(key);
361
+ const calls = callers.map((_, i) => callExpansion(`${root}/${callers.slice(0, i + 1).join("/")}`));
362
+ const chain: Array<string[] | null | undefined> = calls.map(keysOf);
363
+ const own = expansionIn(bodyOf(entity), scopeOf(scopeOfKey(key)));
364
+ const ownKeys = keysOf(own);
365
+ const ownRollup = own !== null && "rollup" in own ? own.rollup : undefined;
366
+ const expandedChain = chain.some((k) => k !== null);
367
+
368
+ if (!expandedChain) {
369
+ if (ownKeys && !entity.instanceKeys) entities.set(key, { ...entity, instanceKeys: ownKeys } as TerraformEntity);
370
+ else if (ownRollup) entities.set(key, { ...entity, instanceRollup: ownRollup } as TerraformEntity);
371
+ continue;
372
+ }
373
+
374
+ // Under an expanded module call the literal `instanceKeys` would name ids
375
+ // under the unexpanded call, which no plan prints: the listed instances
376
+ // replace them, or nothing does.
377
+ const { instanceKeys: _dropped, ...rest } = entity;
378
+ if (chain.some((k) => k === undefined) || ownKeys === undefined) {
379
+ // The block's own reason first, else the first call on the way that could not be counted.
380
+ const at = chain.findIndex((k) => k === undefined);
381
+ const callRollup = at === -1 ? undefined : calls[at];
382
+ const rollup: InstanceRollup | undefined = ownRollup
383
+ ?? (callRollup && "rollup" in callRollup
384
+ ? { ...callRollup.rollup, code: "instances-module-uncounted", call: callers.slice(0, at + 1).join(".") }
385
+ : undefined);
386
+ entities.set(key, (rollup ? { ...rest, instanceRollup: rollup } : rest) as TerraformEntity);
387
+ continue;
388
+ }
389
+ let combos: string[][] = [[]];
390
+ for (const keys of chain) combos = combos.flatMap((c) => (keys ?? [""]).map((k) => [...c, k]));
391
+ const tails = ownKeys ?? [""];
392
+ if (combos.length * tails.length > MAX_INSTANCES) {
393
+ // Named on the block's own expansion, or on the first expanded call when the block has none.
394
+ const body = bodyOf(entity);
395
+ const at = chain.findIndex((k) => k !== null);
396
+ const on = ownKeys !== null || at === -1 ? body : bodyOf(entities.get(`${root}/${callers.slice(0, at + 1).join("/")}`) as TerraformEntity);
397
+ const argument = "count" in on ? "count" : "for_each";
398
+ const rollup: InstanceRollup = {
399
+ code: "instances-over-limit",
400
+ argument,
401
+ expression: expressionText(on[argument]),
402
+ ...(on === body ? {} : { call: callers.slice(0, at + 1).join(".") }),
403
+ };
404
+ entities.set(key, { ...rest, instanceRollup: rollup } as TerraformEntity);
405
+ continue;
406
+ }
407
+ const address = entity.props.address;
408
+ const instances: ListedInstance[] = [];
409
+ for (const combo of combos) {
410
+ const segments = callers.map((c, i) => `${c}${combo[i]}`);
411
+ const scope = segments.join(".");
412
+ for (const tail of tails) {
413
+ instances.push({
414
+ id: `${root}/${segments.join("/")}/${address}${tail}`,
415
+ ...(tail ? { key: tail } : {}),
416
+ scope,
417
+ });
418
+ }
419
+ }
420
+ entities.set(key, { ...rest, instances } as TerraformEntity);
421
+ }
422
+ }