@intentius/chant-lexicon-terraform 0.124.54 → 0.124.56

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 (351) hide show
  1. package/README.md +7 -4
  2. package/dist/ci-setup.d.ts +11 -0
  3. package/dist/ci-setup.d.ts.map +1 -0
  4. package/dist/ci-setup.js +41 -0
  5. package/dist/components/capability-plugin.d.ts +40 -6
  6. package/dist/components/capability-plugin.d.ts.map +1 -1
  7. package/dist/components/capability-plugin.js +174 -30
  8. package/dist/components/compose-group.d.ts +1 -1
  9. package/dist/components/compose-group.d.ts.map +1 -1
  10. package/dist/components/compose-group.js +1 -1
  11. package/dist/components/release-inputs.d.ts +29 -0
  12. package/dist/components/release-inputs.d.ts.map +1 -0
  13. package/dist/components/release-inputs.js +57 -0
  14. package/dist/components/trace-facts.d.ts +16 -0
  15. package/dist/components/trace-facts.d.ts.map +1 -0
  16. package/dist/components/trace-facts.js +19 -0
  17. package/dist/cone.d.ts +53 -0
  18. package/dist/cone.d.ts.map +1 -0
  19. package/dist/cone.js +119 -0
  20. package/dist/cost-hold.d.ts +27 -0
  21. package/dist/cost-hold.d.ts.map +1 -0
  22. package/dist/cost-hold.js +21 -0
  23. package/dist/drift-rows.d.ts +61 -0
  24. package/dist/drift-rows.d.ts.map +1 -0
  25. package/dist/drift-rows.js +108 -0
  26. package/dist/exit-codes.d.ts +55 -0
  27. package/dist/exit-codes.d.ts.map +1 -0
  28. package/dist/exit-codes.js +48 -0
  29. package/dist/index.d.ts +2 -0
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +3 -0
  32. package/dist/integrity.json +7 -4
  33. package/dist/lint/audit-catalog.d.ts.map +1 -1
  34. package/dist/lint/audit-catalog.js +27 -3
  35. package/dist/lint/audit-lineage.d.ts +14 -2
  36. package/dist/lint/audit-lineage.d.ts.map +1 -1
  37. package/dist/lint/post-synth/index.d.ts.map +1 -1
  38. package/dist/lint/post-synth/index.js +4 -0
  39. package/dist/lint/post-synth/record-store.d.ts +54 -0
  40. package/dist/lint/post-synth/record-store.d.ts.map +1 -0
  41. package/dist/lint/post-synth/record-store.js +104 -0
  42. package/dist/lint/post-synth/tf001.d.ts +2 -2
  43. package/dist/lint/post-synth/tf001.js +2 -2
  44. package/dist/lint/post-synth/tf024.d.ts +35 -11
  45. package/dist/lint/post-synth/tf024.d.ts.map +1 -1
  46. package/dist/lint/post-synth/tf024.js +92 -20
  47. package/dist/lint/post-synth/tf046.d.ts +64 -0
  48. package/dist/lint/post-synth/tf046.d.ts.map +1 -0
  49. package/dist/lint/post-synth/tf046.js +175 -0
  50. package/dist/lint/post-synth/tf047.d.ts +36 -0
  51. package/dist/lint/post-synth/tf047.d.ts.map +1 -0
  52. package/dist/lint/post-synth/tf047.js +94 -0
  53. package/dist/lock-rows.d.ts +36 -0
  54. package/dist/lock-rows.d.ts.map +1 -0
  55. package/dist/lock-rows.js +60 -0
  56. package/dist/lock-timeout.d.ts +22 -0
  57. package/dist/lock-timeout.d.ts.map +1 -0
  58. package/dist/lock-timeout.js +32 -0
  59. package/dist/manifest.json +1 -1
  60. package/dist/module-catalog/index.d.ts +69 -0
  61. package/dist/module-catalog/index.d.ts.map +1 -0
  62. package/dist/module-catalog/index.js +110 -0
  63. package/dist/module-catalog/refresh-cli.d.ts +4 -0
  64. package/dist/module-catalog/refresh-cli.d.ts.map +1 -0
  65. package/dist/module-catalog/refresh-cli.js +64 -0
  66. package/dist/module-catalog/static-tier.d.ts +6 -0
  67. package/dist/module-catalog/static-tier.d.ts.map +1 -0
  68. package/dist/module-catalog/static-tier.js +1650 -0
  69. package/dist/module-catalog/types.d.ts +65 -0
  70. package/dist/module-catalog/types.d.ts.map +1 -0
  71. package/dist/module-catalog/types.js +33 -0
  72. package/dist/okf/index.md +3 -1
  73. package/dist/okf/rules/TF024.md +2 -2
  74. package/dist/okf/rules/TF046.md +11 -0
  75. package/dist/okf/rules/TF047.md +11 -0
  76. package/dist/op/activities/terraform.d.ts +29 -0
  77. package/dist/op/activities/terraform.d.ts.map +1 -1
  78. package/dist/op/activities/terraform.js +19 -4
  79. package/dist/pin/consumers.d.ts +35 -0
  80. package/dist/pin/consumers.d.ts.map +1 -0
  81. package/dist/pin/consumers.js +70 -0
  82. package/dist/plugin.d.ts.map +1 -1
  83. package/dist/plugin.js +5 -0
  84. package/dist/registry/address.d.ts +63 -0
  85. package/dist/registry/address.d.ts.map +1 -0
  86. package/dist/registry/address.js +65 -0
  87. package/dist/registry/catalog-read.d.ts +14 -0
  88. package/dist/registry/catalog-read.d.ts.map +1 -0
  89. package/dist/registry/catalog-read.js +58 -0
  90. package/dist/registry/deprecated.d.ts +37 -0
  91. package/dist/registry/deprecated.d.ts.map +1 -0
  92. package/dist/registry/deprecated.js +43 -0
  93. package/dist/registry/docs.d.ts +96 -0
  94. package/dist/registry/docs.d.ts.map +1 -0
  95. package/dist/registry/docs.js +210 -0
  96. package/dist/registry/edge/edge.d.ts +46 -0
  97. package/dist/registry/edge/edge.d.ts.map +1 -0
  98. package/dist/registry/edge/edge.js +194 -0
  99. package/dist/registry/edge/index.d.ts +11 -0
  100. package/dist/registry/edge/index.d.ts.map +1 -0
  101. package/dist/registry/edge/index.js +10 -0
  102. package/dist/registry/edge/lambda-edge.d.ts +76 -0
  103. package/dist/registry/edge/lambda-edge.d.ts.map +1 -0
  104. package/dist/registry/edge/lambda-edge.js +95 -0
  105. package/dist/registry/edge/sigv4-web.d.ts +33 -0
  106. package/dist/registry/edge/sigv4-web.d.ts.map +1 -0
  107. package/dist/registry/edge/sigv4-web.js +56 -0
  108. package/dist/registry/edge/token-source.d.ts +73 -0
  109. package/dist/registry/edge/token-source.d.ts.map +1 -0
  110. package/dist/registry/edge/token-source.js +115 -0
  111. package/dist/registry/edge/worker.d.ts +45 -0
  112. package/dist/registry/edge/worker.d.ts.map +1 -0
  113. package/dist/registry/edge/worker.js +26 -0
  114. package/dist/registry/handler.d.ts +25 -0
  115. package/dist/registry/handler.d.ts.map +1 -0
  116. package/dist/registry/handler.js +75 -0
  117. package/dist/registry/headers-file.d.ts +21 -0
  118. package/dist/registry/headers-file.d.ts.map +1 -0
  119. package/dist/registry/headers-file.js +57 -0
  120. package/dist/registry/index.d.ts +31 -0
  121. package/dist/registry/index.d.ts.map +1 -0
  122. package/dist/registry/index.js +30 -0
  123. package/dist/registry/mirror-paths.d.ts +9 -0
  124. package/dist/registry/mirror-paths.d.ts.map +1 -0
  125. package/dist/registry/mirror-paths.js +8 -0
  126. package/dist/registry/mirror.d.ts +130 -0
  127. package/dist/registry/mirror.d.ts.map +1 -0
  128. package/dist/registry/mirror.js +319 -0
  129. package/dist/registry/proxies.d.ts +73 -0
  130. package/dist/registry/proxies.d.ts.map +1 -0
  131. package/dist/registry/proxies.js +111 -0
  132. package/dist/registry/reader.d.ts +57 -0
  133. package/dist/registry/reader.d.ts.map +1 -0
  134. package/dist/registry/reader.js +198 -0
  135. package/dist/registry/semver.d.ts +16 -0
  136. package/dist/registry/semver.d.ts.map +1 -0
  137. package/dist/registry/semver.js +24 -0
  138. package/dist/registry/serve.d.ts +24 -0
  139. package/dist/registry/serve.d.ts.map +1 -0
  140. package/dist/registry/serve.js +64 -0
  141. package/dist/registry/source-forms.d.ts +79 -0
  142. package/dist/registry/source-forms.d.ts.map +1 -0
  143. package/dist/registry/source-forms.js +202 -0
  144. package/dist/registry/targets/artifactory.d.ts +28 -0
  145. package/dist/registry/targets/artifactory.d.ts.map +1 -0
  146. package/dist/registry/targets/artifactory.js +206 -0
  147. package/dist/registry/targets/fake-artifactory.d.ts +30 -0
  148. package/dist/registry/targets/fake-artifactory.d.ts.map +1 -0
  149. package/dist/registry/targets/fake-artifactory.js +142 -0
  150. package/dist/registry/targets/fake-gitlab.d.ts +39 -0
  151. package/dist/registry/targets/fake-gitlab.d.ts.map +1 -0
  152. package/dist/registry/targets/fake-gitlab.js +136 -0
  153. package/dist/registry/targets/fake-hcp.d.ts +50 -0
  154. package/dist/registry/targets/fake-hcp.d.ts.map +1 -0
  155. package/dist/registry/targets/fake-hcp.js +202 -0
  156. package/dist/registry/targets/fake-http.d.ts +31 -0
  157. package/dist/registry/targets/fake-http.d.ts.map +1 -0
  158. package/dist/registry/targets/fake-http.js +87 -0
  159. package/dist/registry/targets/fake-nexus.d.ts +27 -0
  160. package/dist/registry/targets/fake-nexus.d.ts.map +1 -0
  161. package/dist/registry/targets/fake-nexus.js +137 -0
  162. package/dist/registry/targets/gitlab.d.ts +53 -0
  163. package/dist/registry/targets/gitlab.d.ts.map +1 -0
  164. package/dist/registry/targets/gitlab.js +187 -0
  165. package/dist/registry/targets/hcp.d.ts +29 -0
  166. package/dist/registry/targets/hcp.d.ts.map +1 -0
  167. package/dist/registry/targets/hcp.js +310 -0
  168. package/dist/registry/targets/hosted.d.ts +62 -0
  169. package/dist/registry/targets/hosted.d.ts.map +1 -0
  170. package/dist/registry/targets/hosted.js +74 -0
  171. package/dist/registry/targets/index.d.ts +18 -0
  172. package/dist/registry/targets/index.d.ts.map +1 -0
  173. package/dist/registry/targets/index.js +17 -0
  174. package/dist/registry/targets/module-target.d.ts +68 -0
  175. package/dist/registry/targets/module-target.d.ts.map +1 -0
  176. package/dist/registry/targets/module-target.js +63 -0
  177. package/dist/registry/targets/nexus.d.ts +43 -0
  178. package/dist/registry/targets/nexus.d.ts.map +1 -0
  179. package/dist/registry/targets/nexus.js +250 -0
  180. package/dist/registry/targets/zip.d.ts +55 -0
  181. package/dist/registry/targets/zip.d.ts.map +1 -0
  182. package/dist/registry/targets/zip.js +278 -0
  183. package/dist/registry/writer.d.ts +101 -0
  184. package/dist/registry/writer.d.ts.map +1 -0
  185. package/dist/registry/writer.js +109 -0
  186. package/dist/rules/record-store.ts +122 -0
  187. package/dist/rules/tf001.ts +2 -2
  188. package/dist/rules/tf024.ts +105 -22
  189. package/dist/rules/tf046.ts +204 -0
  190. package/dist/rules/tf047.ts +114 -0
  191. package/dist/state/azure-blob.d.ts +8 -67
  192. package/dist/state/azure-blob.d.ts.map +1 -1
  193. package/dist/state/azure-blob.js +6 -199
  194. package/dist/state/gcs.d.ts +7 -61
  195. package/dist/state/gcs.d.ts.map +1 -1
  196. package/dist/state/gcs.js +5 -222
  197. package/dist/state/object-store.d.ts +4 -33
  198. package/dist/state/object-store.d.ts.map +1 -1
  199. package/dist/state/object-store.js +4 -17
  200. package/dist/state/s3.d.ts +11 -58
  201. package/dist/state/s3.d.ts.map +1 -1
  202. package/dist/state/s3.js +10 -161
  203. package/dist/state/store.js +2 -2
  204. package/dist/state-ops.d.ts +255 -0
  205. package/dist/state-ops.d.ts.map +1 -0
  206. package/dist/state-ops.js +305 -0
  207. package/dist/state-reads.d.ts +57 -0
  208. package/dist/state-reads.d.ts.map +1 -0
  209. package/dist/state-reads.js +104 -0
  210. package/dist/used-sources.d.ts +54 -0
  211. package/dist/used-sources.d.ts.map +1 -0
  212. package/dist/used-sources.js +157 -0
  213. package/package.json +19 -3
  214. package/src/__fixtures__/drift-record/README.md +5 -0
  215. package/src/__fixtures__/drift-rows/live-root-plan.json +70 -0
  216. package/src/__fixtures__/drift-rows/refresh-plan.json +61 -0
  217. package/src/__fixtures__/used-sources/root/extra.tofu +1 -0
  218. package/src/__fixtures__/used-sources/root/main.tf +31 -0
  219. package/src/__fixtures__/used-sources/root/modules/net/main.tf +14 -0
  220. package/src/__fixtures__/used-sources/root/modules/net/versions.tf.json +7 -0
  221. package/src/__fixtures__/used-sources/unit/terragrunt.hcl +3 -0
  222. package/src/ci-setup.ts +42 -0
  223. package/src/components/capability-plugin.test.ts +26 -14
  224. package/src/components/capability-plugin.ts +185 -31
  225. package/src/components/compose-group.ts +1 -1
  226. package/src/components/pr-loop-compose.tofu.e2e.test.ts +1 -1
  227. package/src/components/pr-loop.tofu.test.ts +1 -1
  228. package/src/components/release-inputs.ts +69 -0
  229. package/src/components/trace-facts.test.ts +21 -0
  230. package/src/components/trace-facts.ts +27 -0
  231. package/src/cone.ts +138 -0
  232. package/src/cost-hold.ts +29 -0
  233. package/src/describe-resources.engine-facts.test.ts +2 -3
  234. package/src/drift-rows.test.ts +135 -0
  235. package/src/drift-rows.ts +137 -0
  236. package/src/exit-codes.test.ts +23 -0
  237. package/src/exit-codes.ts +59 -0
  238. package/src/index.ts +3 -0
  239. package/src/lint/audit-catalog.ts +29 -3
  240. package/src/lint/audit-lineage.ts +14 -2
  241. package/src/lint/post-synth/fixtures/TF024/live-with-record-store/main.tf +18 -0
  242. package/src/lint/post-synth/fixtures/TF024/sidecar-with-backend/estate.chdf.hcl +1 -0
  243. package/src/lint/post-synth/fixtures/TF024/sidecar-with-backend/main.tf +10 -0
  244. package/src/lint/post-synth/fixtures/TF046/azurerm-no-resource-group/main.tf +15 -0
  245. package/src/lint/post-synth/fixtures/TF046/backend-config-only/main.tf +14 -0
  246. package/src/lint/post-synth/fixtures/TF046/derived-s3/backend.tf +7 -0
  247. package/src/lint/post-synth/fixtures/TF046/derived-s3/main.tf +9 -0
  248. package/src/lint/post-synth/fixtures/TF046/explicit-local/main.tf +16 -0
  249. package/src/lint/post-synth/fixtures/TF046/explicit-s3-beside-local-backend/main.tf +15 -0
  250. package/src/lint/post-synth/fixtures/TF046/implied-local/main.tf +9 -0
  251. package/src/lint/post-synth/fixtures/TF046/sidecar-implied/estate.chdf.hcl +1 -0
  252. package/src/lint/post-synth/fixtures/TF046/sidecar-implied/main.tf +5 -0
  253. package/src/lint/post-synth/fixtures/TF047/clear/main.tf +12 -0
  254. package/src/lint/post-synth/fixtures/TF047/refused/main.tf +12 -0
  255. package/src/lint/post-synth/fixtures/TF047/unknown-module/main.tf +12 -0
  256. package/src/lint/post-synth/fixtures/TF047/unknown-version/main.tf +12 -0
  257. package/src/lint/post-synth/index.ts +4 -0
  258. package/src/lint/post-synth/record-store.ts +122 -0
  259. package/src/lint/post-synth/tf001.ts +2 -2
  260. package/src/lint/post-synth/tf024.test.ts +26 -5
  261. package/src/lint/post-synth/tf024.ts +105 -22
  262. package/src/lint/post-synth/tf046.test.ts +137 -0
  263. package/src/lint/post-synth/tf046.ts +204 -0
  264. package/src/lint/post-synth/tf047.test.ts +56 -0
  265. package/src/lint/post-synth/tf047.ts +114 -0
  266. package/src/lock-rows.test.ts +70 -0
  267. package/src/lock-rows.ts +82 -0
  268. package/src/lock-timeout.ts +34 -0
  269. package/src/module-catalog/index.test.ts +93 -0
  270. package/src/module-catalog/index.ts +159 -0
  271. package/src/module-catalog/refresh-cli.ts +68 -0
  272. package/src/module-catalog/static-tier.ts +1659 -0
  273. package/src/module-catalog/types.ts +88 -0
  274. package/src/op/activities/terraform.ts +46 -5
  275. package/src/op-wave-step.test.ts +109 -0
  276. package/src/pin/__fixtures__/consumers/live/dev/main.tf +4 -0
  277. package/src/pin/__fixtures__/consumers/live/prod/main.tf +21 -0
  278. package/src/pin/__fixtures__/consumers/units/vpc/terragrunt.hcl +7 -0
  279. package/src/pin/consumers.test.ts +57 -0
  280. package/src/pin/consumers.ts +95 -0
  281. package/src/plugin.ts +6 -0
  282. package/src/registry/__fixtures__/docs/documented/.terraform-docs.yml +3 -0
  283. package/src/registry/__fixtures__/docs/documented/README.md +1 -0
  284. package/src/registry/__fixtures__/docs/documented/main.tf +8 -0
  285. package/src/registry/__fixtures__/docs/json/main.tf.json +14 -0
  286. package/src/registry/__fixtures__/docs/vpc/README.md +3 -0
  287. package/src/registry/__fixtures__/docs/vpc/examples/complete/main.tf +5 -0
  288. package/src/registry/__fixtures__/docs/vpc/examples/simple/README.md +1 -0
  289. package/src/registry/__fixtures__/docs/vpc/examples/simple/main.tf +4 -0
  290. package/src/registry/__fixtures__/docs/vpc/main.tf +14 -0
  291. package/src/registry/__fixtures__/docs/vpc/outputs.tf +9 -0
  292. package/src/registry/__fixtures__/docs/vpc/variables.tf +16 -0
  293. package/src/registry/__fixtures__/proxied-root/main.tf +12 -0
  294. package/src/registry/address.ts +100 -0
  295. package/src/registry/catalog-read.ts +56 -0
  296. package/src/registry/deprecated.test.ts +139 -0
  297. package/src/registry/deprecated.ts +76 -0
  298. package/src/registry/docs.test.ts +140 -0
  299. package/src/registry/docs.ts +299 -0
  300. package/src/registry/edge/bundle.test.ts +50 -0
  301. package/src/registry/edge/edge.test.ts +176 -0
  302. package/src/registry/edge/edge.ts +211 -0
  303. package/src/registry/edge/index.ts +10 -0
  304. package/src/registry/edge/lambda-edge.ts +127 -0
  305. package/src/registry/edge/sigv4-web.ts +79 -0
  306. package/src/registry/edge/token-source.test.ts +258 -0
  307. package/src/registry/edge/token-source.ts +169 -0
  308. package/src/registry/edge/worker.ts +61 -0
  309. package/src/registry/handler.ts +89 -0
  310. package/src/registry/headers-file.ts +61 -0
  311. package/src/registry/index.ts +31 -0
  312. package/src/registry/mirror-paths.ts +10 -0
  313. package/src/registry/mirror.test.ts +198 -0
  314. package/src/registry/mirror.ts +395 -0
  315. package/src/registry/proxies.test.ts +153 -0
  316. package/src/registry/proxies.ts +132 -0
  317. package/src/registry/reader.ts +225 -0
  318. package/src/registry/registry.test.ts +262 -0
  319. package/src/registry/semver.ts +34 -0
  320. package/src/registry/serve.ts +82 -0
  321. package/src/registry/source-forms.test.ts +87 -0
  322. package/src/registry/source-forms.ts +216 -0
  323. package/src/registry/targets/artifactory.test.ts +248 -0
  324. package/src/registry/targets/artifactory.ts +257 -0
  325. package/src/registry/targets/fake-artifactory.ts +158 -0
  326. package/src/registry/targets/fake-gitlab.ts +161 -0
  327. package/src/registry/targets/fake-hcp.ts +233 -0
  328. package/src/registry/targets/fake-http.ts +97 -0
  329. package/src/registry/targets/fake-nexus.ts +152 -0
  330. package/src/registry/targets/gitlab.test.ts +208 -0
  331. package/src/registry/targets/gitlab.ts +223 -0
  332. package/src/registry/targets/hcp.test.ts +230 -0
  333. package/src/registry/targets/hcp.ts +360 -0
  334. package/src/registry/targets/hosted.ts +117 -0
  335. package/src/registry/targets/index.ts +17 -0
  336. package/src/registry/targets/module-target.ts +131 -0
  337. package/src/registry/targets/nexus.test.ts +220 -0
  338. package/src/registry/targets/nexus.ts +314 -0
  339. package/src/registry/targets/zip.test.ts +65 -0
  340. package/src/registry/targets/zip.ts +287 -0
  341. package/src/registry/writer.ts +155 -0
  342. package/src/state/azure-blob.ts +6 -221
  343. package/src/state/gcs.ts +5 -252
  344. package/src/state/object-store.ts +4 -32
  345. package/src/state/s3.ts +10 -182
  346. package/src/state/store.ts +2 -2
  347. package/src/state-ops.test.ts +182 -0
  348. package/src/state-ops.ts +462 -0
  349. package/src/state-reads.ts +116 -0
  350. package/src/used-sources.test.ts +75 -0
  351. package/src/used-sources.ts +204 -0
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Every module source and provider address a root uses (chant#3924), read
3
+ * from its files before `init` downloads anything, so a product can check
4
+ * them against an allow list (terragucci's `sources:`) or a proxy map
5
+ * (chant#4606) in a check job.
6
+ *
7
+ * For a root and, with `followLocal`, each local module it calls (`./` and
8
+ * `../`, followed down to the modules they call), it reads each `module`
9
+ * block's `source`, each `required_providers` entry, and the providers a
10
+ * module uses with no entry: a `provider` block, or a resource, data or
11
+ * ephemeral block's `provider` argument or type prefix, which `init` installs
12
+ * as `hashicorp/<name>`. In the root itself it also reads a Terragrunt unit's
13
+ * `terraform { source }`. Files are `.tf`, `.tofu` and their `.json` forms;
14
+ * `binary` narrows them to the set that binary reads, with OpenTofu's rule
15
+ * that `x.tofu` hides `x.tf`.
16
+ *
17
+ * Each source carries its file and its block path from the root
18
+ * (`module.a > module.b > required_providers.x`) and its normal form
19
+ * (./registry/source-forms.ts). A local call that cannot be followed
20
+ * (outside the project, or missing) is named in `unread`, since what it
21
+ * uses cannot be read.
22
+ */
23
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
24
+ import { join, posix, relative, sep } from "node:path";
25
+ import { loadHcl2json } from "@intentius/chant/terraform/hcl2json";
26
+ import { isJsonConfigFile, jsonConfigTree, selectConfigFiles } from "./hcl/files.js";
27
+ import { impliedProvider, normalizeModuleSource, providerAddress, withoutQuery } from "./registry/source-forms.js";
28
+ /** A block list as hcl2json writes it (an array) or a JSON file may (one object). */
29
+ const bodies = (v) => (Array.isArray(v) ? v : v && typeof v === "object" ? [v] : []);
30
+ const ALL_SUFFIXES = [".tf", ".tofu", ".tf.json", ".tofu.json"];
31
+ /** The configuration files of one module directory, plus terragrunt.hcl when `terragrunt`. */
32
+ function moduleFiles(dir, terragrunt, binary) {
33
+ if (!existsSync(dir) || !statSync(dir).isDirectory())
34
+ return [];
35
+ const names = readdirSync(dir).filter((n) => {
36
+ try {
37
+ return statSync(join(dir, n)).isFile();
38
+ }
39
+ catch {
40
+ return false;
41
+ }
42
+ });
43
+ const config = binary ? selectConfigFiles(names, binary) : names.filter((n) => ALL_SUFFIXES.some((s) => n.endsWith(s))).sort();
44
+ return terragrunt && names.includes("terragrunt.hcl") ? [...config, "terragrunt.hcl"] : config;
45
+ }
46
+ /** The local name of the provider a resource, data or ephemeral block uses: its `provider` argument's, else its type's prefix. */
47
+ function providerOf(type, body) {
48
+ const ref = typeof body.provider === "string" ? /^(?:\$\{)?([A-Za-z][A-Za-z0-9_-]*)/.exec(body.provider) : null;
49
+ return ref ? ref[1] : type.split("_")[0];
50
+ }
51
+ const isLocal = (source) => /^\.\.?\//.test(source);
52
+ async function readDir(abs, shown, prefix, top, o) {
53
+ const providers = [];
54
+ const calls = [];
55
+ const declared = new Set();
56
+ const implied = new Map();
57
+ const provider = (file, block, source, what) => ({
58
+ kind: "provider",
59
+ file,
60
+ block,
61
+ source,
62
+ normal: providerAddress(source, { binary: o.binary, proxies: o.proxies }) ?? source.trim().toLowerCase(),
63
+ ...(what ? { implied: what } : {}),
64
+ });
65
+ for (const name of moduleFiles(abs, top, o.binary)) {
66
+ const file = shown === "." ? name : posix.join(shown, name);
67
+ const text = readFileSync(join(abs, name), "utf-8");
68
+ let tree;
69
+ try {
70
+ tree = (isJsonConfigFile(name) ? jsonConfigTree(text) : await o.parser.parse(file, text));
71
+ }
72
+ catch (e) {
73
+ throw new Error(`${file} could not be read (${e.message.split("\n")[0]}), so its module and provider sources are unknown`);
74
+ }
75
+ if (name === "terragrunt.hcl") {
76
+ for (const b of bodies(tree.terraform))
77
+ if (typeof b.source === "string")
78
+ calls.push({ file, block: `${prefix}terraform`, source: b.source });
79
+ continue;
80
+ }
81
+ for (const [call, list] of Object.entries(tree.module ?? {})) {
82
+ for (const b of bodies(list))
83
+ if (typeof b.source === "string")
84
+ calls.push({ file, block: `${prefix}module.${call}`, source: b.source });
85
+ }
86
+ for (const t of bodies(tree.terraform)) {
87
+ for (const req of bodies(t.required_providers)) {
88
+ for (const [local, v] of Object.entries(req)) {
89
+ declared.add(local);
90
+ const source = v && typeof v === "object" && typeof v.source === "string" ? v.source : `hashicorp/${local}`;
91
+ providers.push(provider(file, `${prefix}required_providers.${local}`, source));
92
+ }
93
+ }
94
+ }
95
+ for (const local of Object.keys(tree.provider ?? {}))
96
+ if (!implied.has(local))
97
+ implied.set(local, { file, what: `provider "${local}"` });
98
+ for (const kind of ["resource", "data", "ephemeral"]) {
99
+ for (const [type, named] of Object.entries(tree[kind] ?? {})) {
100
+ for (const [name, list] of Object.entries(named ?? {})) {
101
+ for (const b of bodies(list)) {
102
+ const local = providerOf(type, b);
103
+ if (!implied.has(local))
104
+ implied.set(local, { file, what: `${kind} "${type}" "${name}"` });
105
+ }
106
+ }
107
+ }
108
+ }
109
+ }
110
+ for (const [local, at] of implied) {
111
+ const source = declared.has(local) ? undefined : impliedProvider(local);
112
+ if (source)
113
+ providers.push(provider(at.file, `${prefix}provider.${local}`, source, at.what));
114
+ }
115
+ return { providers, calls };
116
+ }
117
+ /**
118
+ * Every module source and provider address the root at `dir` uses, and each
119
+ * local call that could not be followed. Throws, naming the file, when a
120
+ * file cannot be parsed, so a check fails loudly instead of checking less.
121
+ */
122
+ export async function usedSources(dir, options = {}) {
123
+ const project = options.project ?? dir;
124
+ const o = { ...options, parser: options.parser ?? (await loadHcl2json()) };
125
+ const follow = options.followLocal !== false;
126
+ const used = [];
127
+ const unread = [];
128
+ const shownOf = (abs) => relative(project, abs).split(sep).join("/") || ".";
129
+ const walk = async (rel, prefix, chain, top) => {
130
+ const { providers, calls } = await readDir(join(project, rel), rel, prefix, top, o);
131
+ used.push(...providers);
132
+ for (const c of calls) {
133
+ used.push({ kind: "module", file: c.file, block: c.block, source: c.source, normal: isLocal(c.source) ? withoutQuery(c.source) : normalizeModuleSource(withoutQuery(c.source), { binary: o.binary, proxies: o.proxies }) });
134
+ if (!follow || !isLocal(c.source))
135
+ continue;
136
+ // A Terragrunt source's `//` marks the directory it copies; the module is the path with it read as one `/`.
137
+ const path = withoutQuery(c.source).replace(/\/\/+/g, "/");
138
+ const next = posix.normalize(posix.join(rel, path)).replace(/\/+$/, "") || ".";
139
+ if (next === ".." || next.startsWith("../")) {
140
+ unread.push({ file: c.file, block: c.block, source: c.source, why: "it is outside the project, so the sources it uses cannot be read" });
141
+ continue;
142
+ }
143
+ if (!existsSync(join(project, next))) {
144
+ unread.push({ file: c.file, block: c.block, source: c.source, why: `${next} does not exist, so the sources it uses cannot be read` });
145
+ continue;
146
+ }
147
+ if (chain.includes(next) || moduleFiles(join(project, next), false, o.binary).length === 0)
148
+ continue;
149
+ await walk(next, `${c.block} > `, [...chain, next], false);
150
+ }
151
+ };
152
+ const start = shownOf(dir);
153
+ if (start === ".." || start.startsWith("../"))
154
+ throw new Error(`${dir} is outside the project ${project}`);
155
+ await walk(start, "", [start], true);
156
+ return { used, unread };
157
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/chant-lexicon-terraform",
3
- "version": "0.124.54",
3
+ "version": "0.124.56",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "src/",
@@ -34,6 +34,11 @@
34
34
  "types": "./dist/pin/index.d.ts",
35
35
  "default": "./dist/pin/index.js"
36
36
  },
37
+ "./module-catalog": {
38
+ "development": "./src/module-catalog/index.ts",
39
+ "types": "./dist/module-catalog/index.d.ts",
40
+ "default": "./dist/module-catalog/index.js"
41
+ },
37
42
  "./terragrunt": {
38
43
  "development": "./src/terragrunt/index.ts",
39
44
  "types": "./dist/terragrunt/index.d.ts",
@@ -44,6 +49,16 @@
44
49
  "types": "./dist/state/index.d.ts",
45
50
  "default": "./dist/state/index.js"
46
51
  },
52
+ "./registry": {
53
+ "development": "./src/registry/index.ts",
54
+ "types": "./dist/registry/index.d.ts",
55
+ "default": "./dist/registry/index.js"
56
+ },
57
+ "./registry/edge": {
58
+ "development": "./src/registry/edge/index.ts",
59
+ "types": "./dist/registry/edge/index.d.ts",
60
+ "default": "./dist/registry/edge/index.js"
61
+ },
47
62
  "./lint/post-synth": {
48
63
  "development": "./src/lint/post-synth/index.ts",
49
64
  "types": "./dist/lint/post-synth/index.d.ts",
@@ -59,7 +74,8 @@
59
74
  "docs": "npx tsx src/codegen/docs-cli.ts",
60
75
  "build": "../../scripts/tsc7.sh -p tsconfig.build.json --sourceMap false && tsc-alias -p tsconfig.build.json",
61
76
  "prepack": "npm run generate && npm run bundle && npm run validate && npm run build",
62
- "bundle": "tsx src/package-cli.ts"
77
+ "bundle": "tsx src/package-cli.ts",
78
+ "module-catalog:refresh": "npx tsx src/module-catalog/refresh-cli.ts"
63
79
  },
64
80
  "devDependencies": {
65
81
  "@cdktn/hcl2json": "^0.24.0",
@@ -72,7 +88,7 @@
72
88
  "access": "public"
73
89
  },
74
90
  "peerDependencies": {
75
- "@intentius/chant": "^0.124.54",
91
+ "@intentius/chant": "^0.124.56",
76
92
  "zod": "^4.3.6"
77
93
  },
78
94
  "description": "terraform lexicon for chant, existing Terraform root modules as declarable entities",
@@ -9,3 +9,8 @@ It was not recorded from a run.
9
9
 
10
10
  `drift-record.test.ts` also reads the recorded fixtures one directory up: `live-plan.json`,
11
11
  `live-ls.json`, `show-state.json`, `show-plan.json` and `live-plan-depth/`.
12
+
13
+ `../drift-rows/` holds two hand-written plans for `drift-rows.test.ts` (#4580), also not recorded
14
+ from a run: `refresh-plan.json`, a stock root's refresh-only plan whose `resource_drift` has an update,
15
+ a delete, a create and a data row, and `live-root-plan.json`, a full plan of a root under live resource
16
+ markers with a create, a delete, an update, both replacement orders, a no-op and two data rows.
@@ -0,0 +1,70 @@
1
+ {
2
+ "format_version": "1.2",
3
+ "terraform_version": "1.9.8",
4
+ "resource_drift": [
5
+ {
6
+ "address": "aws_s3_bucket.ignored",
7
+ "mode": "managed",
8
+ "type": "aws_s3_bucket",
9
+ "change": { "actions": ["update"], "before": { "bucket": "ignored" }, "after": { "bucket": "ignored-too" } }
10
+ }
11
+ ],
12
+ "resource_changes": [
13
+ {
14
+ "address": "aws_s3_bucket.assets",
15
+ "mode": "managed",
16
+ "type": "aws_s3_bucket",
17
+ "name": "assets",
18
+ "change": { "actions": ["create"], "before": null, "after": { "bucket": "acme-assets" }, "after_unknown": { "arn": true, "id": true } }
19
+ },
20
+ {
21
+ "address": "aws_sqs_queue.stray",
22
+ "mode": "managed",
23
+ "type": "aws_sqs_queue",
24
+ "name": "stray",
25
+ "change": { "actions": ["delete"], "before": { "name": "acme-stray", "id": "https://sqs.local/000000000000/acme-stray" }, "after": null }
26
+ },
27
+ {
28
+ "address": "aws_lambda_function.api",
29
+ "mode": "managed",
30
+ "type": "aws_lambda_function",
31
+ "name": "api",
32
+ "change": { "actions": ["update"], "before": { "function_name": "acme-api", "memory_size": 256, "version": "7" }, "after": { "function_name": "acme-api", "memory_size": 512 }, "after_unknown": { "version": true } }
33
+ },
34
+ {
35
+ "address": "aws_iam_role.deployer",
36
+ "mode": "managed",
37
+ "type": "aws_iam_role",
38
+ "name": "deployer",
39
+ "change": { "actions": ["delete", "create"], "before": { "name": "acme-deployer-old", "arn": "arn:aws:iam::000000000000:role/acme-deployer-old" }, "after": { "name": "acme-deployer" }, "after_unknown": { "arn": true } }
40
+ },
41
+ {
42
+ "address": "aws_security_group.edge",
43
+ "mode": "managed",
44
+ "type": "aws_security_group",
45
+ "name": "edge",
46
+ "change": { "actions": ["create", "delete"], "before": { "name": "", "id": "sg-0001" }, "after": { "name": "" }, "after_unknown": { "id": true } }
47
+ },
48
+ {
49
+ "address": "aws_s3_bucket.steady",
50
+ "mode": "managed",
51
+ "type": "aws_s3_bucket",
52
+ "name": "steady",
53
+ "change": { "actions": ["no-op"], "before": { "bucket": "acme-steady" }, "after": { "bucket": "acme-steady" } }
54
+ },
55
+ {
56
+ "address": "data.aws_region.here",
57
+ "mode": "data",
58
+ "type": "aws_region",
59
+ "name": "here",
60
+ "change": { "actions": ["read"], "before": null, "after": { "name": "us-east-1" } }
61
+ },
62
+ {
63
+ "address": "data.aws_caller_identity.me",
64
+ "mode": "data",
65
+ "type": "aws_caller_identity",
66
+ "name": "me",
67
+ "change": { "actions": ["update"], "before": { "account_id": "000000000000" }, "after": { "account_id": "000000000001" } }
68
+ }
69
+ ]
70
+ }
@@ -0,0 +1,61 @@
1
+ {
2
+ "format_version": "1.2",
3
+ "terraform_version": "1.9.8",
4
+ "prior_state": {
5
+ "values": {
6
+ "root_module": {
7
+ "resources": [
8
+ { "address": "aws_s3_bucket.logs", "mode": "managed", "type": "aws_s3_bucket", "values": { "bucket": "acme-logs", "tags": { "team": "data" } } },
9
+ { "address": "aws_lambda_function.worker", "mode": "managed", "type": "aws_lambda_function", "values": { "function_name": "acme-worker", "memory_size": 128 } },
10
+ { "address": "aws_iam_role.runner", "mode": "managed", "type": "aws_iam_role", "values": { "name": "acme-runner", "arn": "arn:aws:iam::000000000000:role/acme-runner" } }
11
+ ]
12
+ }
13
+ }
14
+ },
15
+ "resource_drift": [
16
+ {
17
+ "address": "aws_s3_bucket.logs",
18
+ "mode": "managed",
19
+ "type": "aws_s3_bucket",
20
+ "name": "logs",
21
+ "change": { "actions": ["update"], "before": { "bucket": "acme-logs", "tags": { "team": "data" } }, "after": { "bucket": "acme-logs", "tags": { "team": "ops" } } }
22
+ },
23
+ {
24
+ "address": "aws_lambda_function.worker",
25
+ "mode": "managed",
26
+ "type": "aws_lambda_function",
27
+ "name": "worker",
28
+ "change": { "actions": ["delete"], "before": { "function_name": "acme-worker", "memory_size": 128 }, "after": null }
29
+ },
30
+ {
31
+ "address": "aws_sqs_queue.jobs",
32
+ "mode": "managed",
33
+ "type": "aws_sqs_queue",
34
+ "name": "jobs",
35
+ "change": { "actions": ["create"], "before": null, "after": { "name": "acme-jobs", "id": "https://sqs.local/000000000000/acme-jobs" } }
36
+ },
37
+ {
38
+ "address": "aws_iam_role.runner",
39
+ "mode": "managed",
40
+ "type": "aws_iam_role",
41
+ "name": "runner",
42
+ "change": { "actions": ["update"], "before": { "name": "", "arn": "arn:aws:iam::000000000000:role/acme-runner" }, "after": { "name": "", "arn": "arn:aws:iam::000000000000:role/acme-runner", "max_session_duration": 7200 } }
43
+ },
44
+ {
45
+ "address": "data.aws_caller_identity.me",
46
+ "mode": "data",
47
+ "type": "aws_caller_identity",
48
+ "name": "me",
49
+ "change": { "actions": ["update"], "before": { "account_id": "000000000000" }, "after": { "account_id": "000000000001" } }
50
+ }
51
+ ],
52
+ "resource_changes": [
53
+ {
54
+ "address": "aws_s3_bucket.logs",
55
+ "mode": "managed",
56
+ "type": "aws_s3_bucket",
57
+ "name": "logs",
58
+ "change": { "actions": ["no-op"], "before": { "bucket": "acme-logs", "tags": { "team": "ops" } }, "after": { "bucket": "acme-logs", "tags": { "team": "ops" } } }
59
+ }
60
+ ]
61
+ }
@@ -0,0 +1 @@
1
+ provider "google" {}
@@ -0,0 +1,31 @@
1
+ terraform {
2
+ required_providers {
3
+ aws = {
4
+ source = "hashicorp/aws"
5
+ version = "~> 5.0"
6
+ }
7
+ }
8
+ }
9
+
10
+ module "vpc" {
11
+ source = "terraform-aws-modules/vpc/aws"
12
+ version = "5.1.0"
13
+ }
14
+
15
+ module "net" {
16
+ source = "./modules/net"
17
+ }
18
+
19
+ module "gone" {
20
+ source = "./modules/missing"
21
+ }
22
+
23
+ module "outside" {
24
+ source = "../../outside"
25
+ }
26
+
27
+ resource "random_id" "x" {
28
+ byte_length = 4
29
+ }
30
+
31
+ resource "terraform_data" "t" {}
@@ -0,0 +1,14 @@
1
+ module "sg" {
2
+ source = "git::https://git.acme.dev/sg.git?ref=v1.0.0"
3
+ }
4
+
5
+ module "back" {
6
+ source = "../.."
7
+ }
8
+
9
+ resource "aws_vpc" "v" {
10
+ provider = aws.west
11
+ cidr_block = "10.0.0.0/16"
12
+ }
13
+
14
+ resource "thing_widget" "w" {}
@@ -0,0 +1,7 @@
1
+ {
2
+ "terraform": {
3
+ "required_providers": {
4
+ "thing": { "source": "Registry.Acme.dev/acme/thing" }
5
+ }
6
+ }
7
+ }
@@ -0,0 +1,3 @@
1
+ terraform {
2
+ source = "tfr:///acme/vpc/aws?version=1.0.0"
3
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * The engine install lines a Terraform project's pull request pipeline runs
3
+ * in chant's default image (#4550, `LexiconPlugin.ciSetup`): OpenTofu, and
4
+ * choudoufu when a root runs it. Each line skips the install when the binary
5
+ * is already on PATH.
6
+ *
7
+ * Whether a root runs choudoufu is read from the text of the project's
8
+ * `chant.config.ts` (`binary: "choudoufu"`), never by running it, as lexicon
9
+ * detection reads source.
10
+ */
11
+ import { existsSync, readFileSync } from "node:fs";
12
+ import { join } from "node:path";
13
+ import { PR_LOOP_SETUP } from "@intentius/chant/components/pr-pipeline";
14
+
15
+ /** The choudoufu release the default lines install. A product's images pin their own. */
16
+ export const CHOUDOUFU_CI_VERSION = "0.31.0";
17
+
18
+ /** choudoufu into /usr/local/bin from its release archive. */
19
+ export function choudoufuInstallLine(version = CHOUDOUFU_CI_VERSION): string {
20
+ const arch = `$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')`;
21
+ const url = `https://github.com/INTENTIUS/choudoufu/releases/download/v${version}/choudoufu_v${version}_linux_${arch}.tar.gz`;
22
+ return `command -v choudoufu >/dev/null 2>&1 || (mkdir -p /tmp/choudoufu && curl -fsSL "${url}" | tar -xz -C /tmp/choudoufu && install "$(find /tmp/choudoufu -type f -name choudoufu | head -n 1)" /usr/local/bin/choudoufu)`;
23
+ }
24
+
25
+ /** Whether the project's config names choudoufu as a root's binary. */
26
+ export function usesChoudoufu(dir: string): boolean {
27
+ for (const name of ["chant.config.ts", "chant.config.mts", "chant.config.js"]) {
28
+ const file = join(dir, name);
29
+ if (!existsSync(file)) continue;
30
+ try {
31
+ if (/binary\s*:\s*["']choudoufu["']/.test(readFileSync(file, "utf-8"))) return true;
32
+ } catch {
33
+ // unreadable: no choudoufu line
34
+ }
35
+ }
36
+ return false;
37
+ }
38
+
39
+ /** The lines for the project at `dir`. */
40
+ export function terraformCiSetup(ctx: { dir: string }): string[] {
41
+ return [...PR_LOOP_SETUP, ...(usesChoudoufu(ctx.dir) ? [choudoufuInstallLine()] : [])];
42
+ }
@@ -24,6 +24,9 @@ const planTexts: Array<boolean | undefined> = [];
24
24
  let planError: Error | undefined;
25
25
 
26
26
  vi.mock("../op/activities/terraform", () => ({
27
+ // The exit constants exit-codes.ts maps choudoufu's statuses with (#4553), as the module has them.
28
+ CHOUDOUFU_APPROVAL_EXIT_CODE: 3,
29
+ CHOUDOUFU_WAVE_EXIT: { landed: 0, error: 1, setMoved: 3, rootFailed: 4 },
27
30
  choudoufuWaveRoot: vi.fn(async () => waveRoot),
28
31
  choudoufuPlanSet: vi.fn(async (args: { root: string }) => {
29
32
  calls.push(`plan-set ${args.root}`);
@@ -52,7 +55,8 @@ vi.mock("../op/activities/terraform", () => ({
52
55
  planTargets.push(args.targets);
53
56
  planTexts.push(args.text);
54
57
  if (planError) throw planError;
55
- return { planFile: "chant.tfplan", planDigest: `jcs1-sha256:${args.root}`, json: planJson, ...(changeSet ? { changeSet } : {}) };
58
+ // `dir` as the real result has it (#4545 writes the plan document beside the plan file there).
59
+ return { planFile: "chant.tfplan", dir: `/x/${args.root}`, planDigest: `jcs1-sha256:${args.root}`, json: planJson, ...(changeSet ? { changeSet } : {}) };
56
60
  }),
57
61
  terraformApply: vi.fn(async (args: { root: string; planFile: string }) => {
58
62
  calls.push(`apply ${args.root} ${args.planFile}`);
@@ -133,7 +137,8 @@ describe("terraform-apply on a choudoufu root in a gated wave (#3459)", () => {
133
137
  expect(calls).toEqual(["plan-set e01"]);
134
138
  expect(planned.member).toBe("e01");
135
139
  expect(planned.planDigest).toBe("jcs1-sha256:e01");
136
- expect(planned.artifact).toEqual(wavePlan);
140
+ // #4552: the set path keeps the root's lock rows beside the plan (none here: the fake set's plan has no changes).
141
+ expect(planned.artifact).toEqual({ ...wavePlan, lockRows: [] });
137
142
  expect(planned.changeSet).toEqual({ member: { member: "e01", nativeDigest: "sha256:root-e01" }, entries: [] });
138
143
  });
139
144
 
@@ -203,7 +208,7 @@ describe("terraform-apply on a choudoufu root in a gated wave (#3459)", () => {
203
208
 
204
209
  describe("terraform-apply", () => {
205
210
  test("the plugin contributes it", () => {
206
- expect(terraformCapabilityPlugin.capabilities().map((c) => c.kind)).toEqual(["terraform-apply"]);
211
+ expect(terraformCapabilityPlugin.capabilities().map((c) => c.kind)).toEqual(["terraform-apply", "terraform-state-unlock", "terraform-state-push", "terraform-copy"]);
207
212
  });
208
213
 
209
214
  test("plan inits and plans the root, with its vars, and names the root as the member", async () => {
@@ -341,17 +346,24 @@ describe("terraform-apply, continued", () => {
341
346
  expect(calls).not.toContain("output app");
342
347
  });
343
348
 
344
- test.each(["approval-mismatch", "wrong-estate"])(
345
- "choudoufu's %s refusal of the wave's plan throws core's plan-moved mark, so the command exits 3 (#3997)",
346
- async (refused) => {
347
- applyResult = { applied: false, refused, refusal: "refused" };
348
- const err = await terraformApplyCapability
349
- .run({ env: "test", component: "app", plans: { app: { planFile: "chant.tfplan", planDigest: "d" } } }, { root: "app" })
350
- .catch((e: unknown) => e);
351
- expect(err).toBeInstanceOf(TerraformPlanMovedError);
352
- expect(isPlanMovedError(err)).toBe(true);
353
- },
354
- );
349
+ test("choudoufu's approval-mismatch refusal of the wave's plan throws core's plan-moved mark, so the command exits moved (#3997, #4553)", async () => {
350
+ applyResult = { applied: false, refused: "approval-mismatch", refusal: "refused" };
351
+ const err = await terraformApplyCapability
352
+ .run({ env: "test", component: "app", plans: { app: { planFile: "chant.tfplan", planDigest: "d" } } }, { root: "app" })
353
+ .catch((e: unknown) => e);
354
+ expect(err).toBeInstanceOf(TerraformPlanMovedError);
355
+ expect(isPlanMovedError(err)).toBe(true);
356
+ });
357
+
358
+ test("choudoufu's wrong-estate refusal is an ordinary failure: the pipeline handed the wrong file, and no approval fixes that (#4553)", async () => {
359
+ applyResult = { applied: false, refused: "wrong-estate", refusal: "refused" };
360
+ const err = await terraformApplyCapability
361
+ .run({ env: "test", component: "app", plans: { app: { planFile: "chant.tfplan", planDigest: "d" } } }, { root: "app" })
362
+ .catch((e: unknown) => e);
363
+ expect(err).toBeInstanceOf(Error);
364
+ expect(err).not.toBeInstanceOf(TerraformPlanMovedError);
365
+ expect(isPlanMovedError(err)).toBe(false);
366
+ });
355
367
 
356
368
  test("a refusal of a plan this run made itself is an ordinary failure: the next run plans afresh", async () => {
357
369
  applyResult = { applied: false, refused: "approval-mismatch", refusal: "refused" };