@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,88 @@
1
+ /**
2
+ * The shape of choudoufu's `live/module-compat.json` (choudoufu#2292,
3
+ * `tools/modcompat/modcompat.go`), as far as the known-module catalog reads
4
+ * it. Only the static tier is typed here; see `./refresh-cli.ts`.
5
+ */
6
+
7
+ /** One refusal on a static row: a choudoufu refusal ID and where it is documented. */
8
+ export interface StaticRefusal {
9
+ id: string;
10
+ layer: string;
11
+ sites: number;
12
+ docs_ref: string;
13
+ docs_url: string;
14
+ }
15
+
16
+ /** One terraform-aws-modules example measured offline in the onboarded form. */
17
+ export interface StaticRow {
18
+ tier: "static";
19
+ /** Registry address, `terraform-aws-modules/vpc/aws`. */
20
+ module: string;
21
+ /** The module repository's tag, `v6.6.1`. */
22
+ version: string;
23
+ /** The example directory inside the module repository. */
24
+ example: string;
25
+ date: string;
26
+ commit: string;
27
+ aws_provider: string;
28
+ verdict: "clear" | "refused" | "unmeasured";
29
+ /** Eligible data-source reads. These never refuse and are not refusals. */
30
+ reads: number;
31
+ refusals: StaticRefusal[];
32
+ reason?: string;
33
+ partial_schemas?: string[];
34
+ }
35
+
36
+ export interface StaticSource {
37
+ module: string;
38
+ repo: string;
39
+ version: string;
40
+ commit: string;
41
+ glob: string;
42
+ examples: number;
43
+ }
44
+
45
+ /** How the static tier was measured. */
46
+ export interface StaticMeasurement {
47
+ date: string;
48
+ commit: string;
49
+ instrument: string;
50
+ form: string;
51
+ schemas: boolean;
52
+ second_pass: boolean;
53
+ aws_provider: string;
54
+ sources: StaticSource[];
55
+ }
56
+
57
+ /** The parts of the file the catalog reads. */
58
+ export interface ModuleCompatFile {
59
+ schema: number;
60
+ static_measurement: StaticMeasurement;
61
+ static: StaticRow[];
62
+ }
63
+
64
+ const VERDICTS = new Set(["clear", "refused", "unmeasured"]);
65
+
66
+ /**
67
+ * Check a parsed `live/module-compat.json` against the shape above and return
68
+ * it. Throws on a schema other than 1, a row outside the static tier, or a
69
+ * row whose verdict disagrees with its refusals, so a refresh never vendors a
70
+ * table the catalog would misread.
71
+ */
72
+ export function parseModuleCompat(raw: unknown): ModuleCompatFile {
73
+ const file = raw as Partial<ModuleCompatFile>;
74
+ if (file?.schema !== 1) throw new Error(`module-compat.json: schema ${String(file?.schema)}, expected 1`);
75
+ if (!file.static_measurement || !Array.isArray(file.static)) {
76
+ throw new Error("module-compat.json: missing static_measurement or static");
77
+ }
78
+ for (const row of file.static) {
79
+ const where = `${row.module} ${row.version} ${row.example}`;
80
+ if (row.tier !== "static") throw new Error(`module-compat.json: ${where} is in the static array with tier ${row.tier}`);
81
+ if (!VERDICTS.has(row.verdict)) throw new Error(`module-compat.json: ${where} has verdict ${row.verdict}`);
82
+ if (!Array.isArray(row.refusals)) throw new Error(`module-compat.json: ${where} has no refusals list`);
83
+ if ((row.verdict === "refused") !== (row.refusals.length > 0)) {
84
+ throw new Error(`module-compat.json: ${where} is ${row.verdict} with ${row.refusals.length} refusals`);
85
+ }
86
+ }
87
+ return { schema: file.schema, static_measurement: file.static_measurement, static: file.static };
88
+ }
@@ -53,6 +53,8 @@ import type { ChangeSetPart } from "@intentius/chant/change-set";
53
53
  import type { TerraformConfig, TerraformRootConfig } from "../../config";
54
54
  import { detectLiveEstate } from "./live-detect";
55
55
  import { backendConfigValues } from "../../backend-config";
56
+ import { lockTimeoutArgs } from "../../lock-timeout";
57
+ import { coneArgs, readPlanCone, type PlanCone } from "../../cone";
56
58
  import {
57
59
  parseAdoptionCommands,
58
60
  readAdoptionLedger,
@@ -195,6 +197,14 @@ export interface TerraformRootArgs {
195
197
  * `process.cwd()`, which is the project root under `chant run`.
196
198
  */
197
199
  cwd?: string;
200
+ /**
201
+ * The directory the root's files are read from in place of the entry's
202
+ * `dir`, relative to the project root (chant#4542). A promote or rollback
203
+ * under a release sets it to the release's recorded copy of the root; the
204
+ * entry's workspace, var files, backend config and estate still apply, and
205
+ * its var files and backend-config files resolve against this directory.
206
+ */
207
+ dir?: string;
198
208
  }
199
209
 
200
210
  export interface TerraformInitArgs extends TerraformRootArgs {
@@ -231,6 +241,19 @@ export interface TerraformPlanArgs extends TerraformRootArgs {
231
241
  * the text, such as the `terraform-apply` capability. Default: `true`.
232
242
  */
233
243
  text?: boolean;
244
+ /**
245
+ * How long the plan waits for another run's state lock (#4545), passed as
246
+ * `-lock-timeout` unless `TF_CLI_ARGS` or `TF_CLI_ARGS_plan` names one.
247
+ * Default: no flag, terraform's own (no wait).
248
+ */
249
+ lockTimeout?: string;
250
+ /**
251
+ * choudoufu only (#4545): plan the cone of the change since this revision
252
+ * (`-cone=<base>`) on a root under a `live` block that is not an estate
253
+ * file. Any other root, binary, or a plan with `targets` or `destroy`,
254
+ * plans in full. {@link TerraformPlanResult.cone} says which it was.
255
+ */
256
+ cone?: string;
234
257
  }
235
258
 
236
259
  export interface TerraformApplyArgs extends TerraformRootArgs {
@@ -251,6 +274,8 @@ export interface TerraformApplyArgs extends TerraformRootArgs {
251
274
  * separate to pass.
252
275
  */
253
276
  planFile?: string;
277
+ /** How long the apply waits for another run's state lock (#4545), as {@link TerraformPlanArgs.lockTimeout}. */
278
+ lockTimeout?: string;
254
279
  }
255
280
 
256
281
  export interface TerraformShowArgs extends TerraformRootArgs {
@@ -367,6 +392,8 @@ export interface TerraformPlanResult extends PlanChangeCounts {
367
392
  * `composeChangeSet` and bind the run's gate to the document's digest.
368
393
  */
369
394
  changeSet: ChangeSetPart;
395
+ /** Set when the plan was asked for a cone (#4545): the cone it planned, or why it planned the whole estate. */
396
+ cone?: PlanCone;
370
397
  }
371
398
 
372
399
  /**
@@ -617,9 +644,12 @@ export function terraformPlanCommand(opts: {
617
644
  vars?: Record<string, unknown>;
618
645
  destroy?: boolean;
619
646
  targets?: string[];
647
+ /** Extra flags after the fixed ones (#4545): `-lock-timeout`, `-cone`. */
648
+ extra?: readonly string[];
620
649
  }): string {
621
650
  const parts = [opts.binary, "plan", "-input=false", "-detailed-exitcode"];
622
651
  if (opts.destroy) parts.push("-destroy");
652
+ for (const flag of opts.extra ?? []) parts.push(quoteArg(flag));
623
653
  for (const target of [...new Set(opts.targets ?? [])].sort()) parts.push(`-target=${quoteArg(target)}`);
624
654
  for (const varFile of opts.varFiles ?? []) parts.push(`-var-file=${quoteArg(varFile)}`);
625
655
  for (const [name, value] of Object.entries(opts.vars ?? {}).sort(([a], [b]) => a.localeCompare(b))) {
@@ -636,8 +666,9 @@ export function terraformPlanCommand(opts: {
636
666
  * block, and a matched one applies without re-prompting, exactly as stock's
637
667
  * own `apply <planfile>` does, so no `-auto-approve` is needed or passed.
638
668
  */
639
- export function terraformApplyCommand(opts: { binary: string; planFile: string }): string {
640
- return `${opts.binary} apply -input=false ${quoteArg(opts.planFile)}`;
669
+ export function terraformApplyCommand(opts: { binary: string; planFile: string; extra?: readonly string[] }): string {
670
+ const extra = (opts.extra ?? []).map((f) => ` ${quoteArg(f)}`).join("");
671
+ return `${opts.binary} apply -input=false${extra} ${quoteArg(opts.planFile)}`;
641
672
  }
642
673
 
643
674
  /**
@@ -937,7 +968,7 @@ async function resolveRoot(args: TerraformRootArgs, signal?: AbortSignal): Promi
937
968
  );
938
969
  }
939
970
  const binary = terraformBinary(namespace);
940
- const dir = resolve(projectRoot, root.dir);
971
+ const dir = resolve(projectRoot, args.dir ?? root.dir);
941
972
  await ensureChoudoufuVersion(binary, signal);
942
973
  const estate = binary === "choudoufu" ? detectLiveEstate(dir) : undefined;
943
974
  // `estate` stays what it has always been: the estate the root's own HCL
@@ -1075,9 +1106,13 @@ export async function terraformPlan(
1075
1106
  args: TerraformPlanArgs,
1076
1107
  signal?: AbortSignal,
1077
1108
  ): Promise<TerraformPlanResult> {
1078
- const { binary, root, dir } = await resolveRoot(args, signal);
1109
+ const { binary, root, dir, live } = await resolveRoot(args, signal);
1079
1110
  const planFile = args.planFile ?? DEFAULT_PLAN_FILE;
1080
1111
  const env = terraformEnvironment(root);
1112
+ const lock = args.lockTimeout !== undefined ? lockTimeoutArgs("plan", args.lockTimeout) : [];
1113
+ // #4545: a cone only on a live root that is not an estate file; -target and -destroy plan in full.
1114
+ const fixed = [...(args.destroy ? ["-destroy"] : []), ...((args.targets ?? []).length > 0 ? ["-target"] : [])];
1115
+ const cone = args.cone !== undefined ? coneArgs(binary, args.cone, fixed, live && !existsSync(join(dir, "estate.chdf.hcl"))) : [];
1081
1116
  const cmd = terraformPlanCommand({
1082
1117
  binary,
1083
1118
  planFile,
@@ -1085,12 +1120,15 @@ export async function terraformPlan(
1085
1120
  ...(args.vars ? { vars: args.vars } : {}),
1086
1121
  ...(args.destroy ? { destroy: true } : {}),
1087
1122
  ...(args.targets && args.targets.length > 0 ? { targets: args.targets } : {}),
1123
+ ...(cone.length + lock.length > 0 ? { extra: [...cone, ...lock] } : {}),
1088
1124
  });
1089
1125
 
1090
1126
  let changed: boolean;
1127
+ let output = "";
1091
1128
  try {
1092
1129
  const { stdout, stderr } = await run(cmd, dir, env, signal);
1093
1130
  report(stdout, stderr);
1131
+ output = `${stdout}\n${stderr}`;
1094
1132
  changed = false;
1095
1133
  } catch (err) {
1096
1134
  // An abort or a spawn failure carries no numeric exit code. That is not
@@ -1104,6 +1142,7 @@ export async function terraformPlan(
1104
1142
  );
1105
1143
  }
1106
1144
  report(failure.stdout ?? "", failure.stderr ?? "");
1145
+ output = `${failure.stdout ?? ""}\n${failure.stderr ?? ""}`;
1107
1146
  changed = true;
1108
1147
  }
1109
1148
 
@@ -1125,6 +1164,7 @@ export async function terraformPlan(
1125
1164
  ...((root.estate ?? root.workspace) ? { scope: root.estate ?? root.workspace } : {}),
1126
1165
  }),
1127
1166
  ...countPlanChanges(json),
1167
+ ...(cone.length > 0 ? { cone: readPlanCone(output, join(dir, planFile), args.cone!) } : {}),
1128
1168
  };
1129
1169
  }
1130
1170
 
@@ -1176,7 +1216,8 @@ export async function terraformApply(
1176
1216
  }
1177
1217
 
1178
1218
  const planFile = args.planFile!;
1179
- const cmd = terraformApplyCommand({ binary, planFile });
1219
+ const lock = args.lockTimeout !== undefined ? lockTimeoutArgs("apply", args.lockTimeout) : [];
1220
+ const cmd = terraformApplyCommand({ binary, planFile, ...(lock.length > 0 ? { extra: lock } : {}) });
1180
1221
 
1181
1222
  try {
1182
1223
  const { stdout, stderr } = await run(cmd, dir, terraformEnvironment(root), signal);
@@ -0,0 +1,109 @@
1
+ /**
2
+ * The terraform side of op waves through capabilities (#4545): lock
3
+ * timeouts, choudoufu cone plans, reads from another root's state and the
4
+ * cost hold preset, moved here from terragucci's wave callbacks.
5
+ */
6
+ import { describe, expect, test } from "vitest";
7
+ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
8
+ import { join } from "node:path";
9
+ import { tmpdir } from "node:os";
10
+ import { DEFAULT_LOCK_TIMEOUT, isLockTimeout, lockTimeoutArgs } from "./lock-timeout";
11
+ import { coneArgs, conePlanDigest, coneRecordPath, coneText, readPlanCone } from "./cone";
12
+ import { atmosStateReads, fillStateReads, readOutputValue, stateReadsProblems, waitingReadsReason } from "./state-reads";
13
+ import { INFRACOST_ARGV, terraformCostHold } from "./cost-hold";
14
+ import { terraformApplyCommand, terraformPlanCommand } from "./op/activities/terraform";
15
+
16
+ describe("lock timeout (#4545)", () => {
17
+ test("adds -lock-timeout unless the job sets one", () => {
18
+ expect(lockTimeoutArgs("plan", DEFAULT_LOCK_TIMEOUT, {})).toEqual(["-lock-timeout=5m"]);
19
+ expect(lockTimeoutArgs("apply", "30s", { TF_CLI_ARGS: "-no-color" })).toEqual(["-lock-timeout=30s"]);
20
+ expect(lockTimeoutArgs("plan", "5m", { TF_CLI_ARGS_plan: "-lock-timeout=1m" })).toEqual([]);
21
+ expect(lockTimeoutArgs("apply", "5m", { TF_CLI_ARGS: "--lock-timeout 2m" })).toEqual([]);
22
+ expect(lockTimeoutArgs("plan", false, {})).toEqual([]);
23
+ expect(() => lockTimeoutArgs("plan", "five", {})).toThrow(/not a duration/);
24
+ expect(isLockTimeout("1h30m")).toBe(true);
25
+ });
26
+
27
+ test("the plan and apply commands carry the extra flags", () => {
28
+ expect(terraformPlanCommand({ binary: "tofu", planFile: "chant.tfplan", extra: ["-cone=origin/main", "-lock-timeout=5m"] })).toBe(
29
+ "tofu plan -input=false -detailed-exitcode -cone=origin/main -lock-timeout=5m -out=chant.tfplan",
30
+ );
31
+ expect(terraformApplyCommand({ binary: "tofu", planFile: "chant.tfplan", extra: ["-lock-timeout=5m"] })).toBe("tofu apply -input=false -lock-timeout=5m chant.tfplan");
32
+ expect(terraformApplyCommand({ binary: "tofu", planFile: "chant.tfplan" })).toBe("tofu apply -input=false chant.tfplan");
33
+ });
34
+ });
35
+
36
+ describe("cone plans (#4545)", () => {
37
+ test("only choudoufu on a live root, and never beside -target or -destroy", () => {
38
+ expect(coneArgs("choudoufu", "origin/main", [], true)).toEqual(["-cone=origin/main"]);
39
+ expect(coneArgs("/usr/local/bin/choudoufu", "origin/main", [], true)).toEqual(["-cone=origin/main"]);
40
+ expect(coneArgs("tofu", "origin/main", [], true)).toEqual([]);
41
+ expect(coneArgs("choudoufu", "origin/main", [], false)).toEqual([]);
42
+ expect(coneArgs("choudoufu", "origin/main", ["-target"], true)).toEqual([]);
43
+ expect(coneArgs("choudoufu", undefined, [], true)).toEqual([]);
44
+ });
45
+
46
+ test("reads the record beside the plan, or the guard's fallback", () => {
47
+ const dir = mkdtempSync(join(tmpdir(), "cone-"));
48
+ const plan = join(dir, "chant.tfplan");
49
+ writeFileSync(coneRecordPath(plan), JSON.stringify({ base: "origin/main", base_sha: "abc", targets: ["b.x", "a.y"] }));
50
+ const cone = readPlanCone("Warning: Cone plan\n\nThis plan covers the cone: 2 of the estate's 40 blocks\n\nPlan: 1 to add", plan, "origin/main");
51
+ expect(cone).toEqual({ base: "origin/main", blocks: 2, of: 40, record: { base: "origin/main", base_sha: "abc", targets: ["a.y", "b.x"] } });
52
+ expect(coneText(cone)).toBe("the cone of the change since origin/main, 2 of the estate's 40 blocks");
53
+ const fell = readPlanCone("Warning: Cone plan fell back to a full plan\n\nThis plan covers the whole estate (guard lockfile): the lock file changed.\n", plan, "origin/main");
54
+ expect(fell).toEqual({ base: "origin/main", fallback: { kind: "lockfile", reason: "the lock file changed" } });
55
+ rmSync(dir, { recursive: true, force: true });
56
+ });
57
+
58
+ test("the cone's targets join the digest; a full plan keeps its own", () => {
59
+ const d = `sha256:${"1".repeat(64)}`;
60
+ expect(conePlanDigest(d, undefined)).toBe(d);
61
+ expect(conePlanDigest(d, { base: "origin/main", fallback: { kind: "x", reason: "y" } })).toBe(d);
62
+ const a = conePlanDigest(d, { base: "origin/main", record: { base: "origin/main", base_sha: "", targets: ["a"] } });
63
+ const b = conePlanDigest(d, { base: "origin/main", record: { base: "origin/main", base_sha: "", targets: ["b"] } });
64
+ expect(a).not.toBe(d);
65
+ expect(a).not.toBe(b);
66
+ });
67
+ });
68
+
69
+ describe("reads from another root's state (#4545)", () => {
70
+ test("fills each read once per upstream, and waits on a missing output", async () => {
71
+ let reads = 0;
72
+ const filled = await fillStateReads(
73
+ [
74
+ { var: "vpc_id", root: "network", output: ["vpc", "id"] },
75
+ { var: "subnets", root: "network", output: ["subnets"] },
76
+ { var: "db", root: "data", output: ["endpoint"] },
77
+ ],
78
+ async (root) => {
79
+ reads++;
80
+ return root === "network" ? { vpc: { id: "vpc-1" }, subnets: ["a", "b"] } : {};
81
+ },
82
+ );
83
+ expect(reads).toBe(2);
84
+ expect(filled.vars).toEqual({ vpc_id: "vpc-1", subnets: ["a", "b"] });
85
+ expect(filled.waiting.map((w) => w.why)).toEqual(["has no state yet"]);
86
+ expect(waitingReadsReason(filled.waiting)).toBe("not planned: it reads db from data, which has no state yet; apply data first");
87
+ });
88
+
89
+ test("a state that cannot be read is an error, not a wait", async () => {
90
+ const filled = await fillStateReads([{ var: "x", root: "up", output: ["x"] }], async () => {
91
+ throw new Error("init failed");
92
+ });
93
+ expect(filled.errors).toEqual(["it reads x from up, whose state could not be read: init failed"]);
94
+ });
95
+
96
+ test("checks the reads, reads a nested output, and maps Atmos edges", () => {
97
+ expect(stateReadsProblems([{ var: "a", root: "r", output: ["o"] }, { var: "a", root: "", output: [] }])).toHaveLength(3);
98
+ expect(readOutputValue({ o: { k: 1 } }, ["o", "k"])).toEqual({ value: 1 });
99
+ expect(readOutputValue({ o: { k: 1 } }, ["o", "j"])).toBeUndefined();
100
+ expect(atmosStateReads({ reads: [{ var: "v", upstream: "dev/vpc", output: ["id"], function: "!terraform.state vpc id" }] }, (u) => u.replace("/", "-"))).toEqual([{ var: "v", root: "dev-vpc", output: ["id"] }]);
101
+ });
102
+ });
103
+
104
+ describe("cost hold preset (#4545)", () => {
105
+ test("prices each root's plan document with infracost", () => {
106
+ expect(terraformCostHold({ approveAbove: 100 })).toEqual({ command: [...INFRACOST_ARGV], approveAbove: 100, format: "infracost" });
107
+ expect(() => terraformCostHold({ approveAbove: 1, command: ["infracost"] })).toThrow(/\{planJson\}/);
108
+ });
109
+ });
@@ -0,0 +1,4 @@
1
+ module "vpc" {
2
+ source = "REGISTRY.acme.dev/acme/vpc/aws"
3
+ version = "v2.0.0"
4
+ }
@@ -0,0 +1,21 @@
1
+ module "vpc" {
2
+ source = "registry.acme.dev/acme/vpc/aws"
3
+ version = "1.0.0"
4
+ }
5
+
6
+ module "eks" {
7
+ source = "acme/eks/aws"
8
+ version = "~> 2.0"
9
+ }
10
+
11
+ module "net" {
12
+ source = "git::https://git.acme.dev/acme/net.git?ref=v1.2.0"
13
+ }
14
+
15
+ module "cache" {
16
+ source = "oci://registry.acme.dev/modules/cache?tag=3.1.0"
17
+ }
18
+
19
+ module "local" {
20
+ source = "./modules/local"
21
+ }
@@ -0,0 +1,7 @@
1
+ terraform {
2
+ source = "tfr://registry.acme.dev/acme/vpc/aws?version=1.1.0"
3
+ }
4
+
5
+ inputs = {
6
+ cidr = "10.0.0.0/16"
7
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * The consumer scan (chant#4599): registry, git ref and OCI tag pins, a
3
+ * constraint left unpinned, a Terragrunt unit's `terraform { source }`, and
4
+ * local calls left out, all read from source files.
5
+ */
6
+ import { join } from "node:path";
7
+ import { describe, expect, test } from "vitest";
8
+ import { isLocalSource, moduleConsumers, normalizeModuleSource } from "./consumers";
9
+
10
+ const REPO = join(import.meta.dirname, "__fixtures__", "consumers");
11
+
12
+ describe("moduleConsumers", () => {
13
+ test("lists every published module call in the roots, sorted", async () => {
14
+ const calls = await moduleConsumers(REPO, ["live/prod", "live/dev", "units/vpc"]);
15
+ expect(calls.map((c) => [c.root, c.address, c.module, c.pin])).toEqual([
16
+ ["live/dev", "module.vpc", "registry.acme.dev/acme/vpc/aws", "v2.0.0"],
17
+ ["live/prod", "module.cache", "oci://registry.acme.dev/modules/cache", "3.1.0"],
18
+ ["live/prod", "module.eks", "registry.terraform.io/acme/eks/aws", null],
19
+ ["live/prod", "module.net", "git::https://git.acme.dev/acme/net.git", "v1.2.0"],
20
+ ["live/prod", "module.vpc", "registry.acme.dev/acme/vpc/aws", "1.0.0"],
21
+ ["units/vpc", "terragrunt.terraform", "registry.acme.dev/acme/vpc/aws", "1.1.0"],
22
+ ]);
23
+ });
24
+
25
+ test("keeps where each pin sits and why a call has none", async () => {
26
+ const calls = await moduleConsumers(REPO, ["live/prod", "units/vpc"]);
27
+ const by = (a: string, root = "live/prod") => calls.find((c) => c.address === a && c.root === root)!;
28
+ expect(by("module.vpc")).toMatchObject({ kind: "terraform", file: "live/prod/main.tf", at: "version", source: "registry.acme.dev/acme/vpc/aws" });
29
+ expect(by("module.net")).toMatchObject({ at: "source", param: "ref" });
30
+ expect(by("module.cache")).toMatchObject({ at: "source", param: "tag" });
31
+ expect(by("module.eks").unpinned).toContain("constraint");
32
+ expect(by("terragrunt.terraform", "units/vpc")).toMatchObject({ kind: "terragrunt", file: "units/vpc/terragrunt.hcl", at: "source", param: "version" });
33
+ });
34
+
35
+ test("leaves local calls out", async () => {
36
+ const calls = await moduleConsumers(REPO, ["live/prod"]);
37
+ expect(calls.find((c) => c.address === "module.local")).toBeUndefined();
38
+ expect(isLocalSource("./modules/local")).toBe(true);
39
+ expect(isLocalSource("../shared")).toBe(true);
40
+ expect(isLocalSource("acme/vpc/aws")).toBe(false);
41
+ });
42
+ });
43
+
44
+ describe("normalizeModuleSource", () => {
45
+ test.each([
46
+ ["acme/vpc/aws", "registry.terraform.io/acme/vpc/aws"],
47
+ ["acme/vpc/aws//modules/x", "registry.terraform.io/acme/vpc/aws//modules/x"],
48
+ ["Registry.Acme.dev/acme/vpc/aws", "registry.acme.dev/acme/vpc/aws"],
49
+ ["tfr:///terraform-aws-modules/vpc/aws", "registry.terraform.io/terraform-aws-modules/vpc/aws"],
50
+ ["tfr://registry.acme.dev/acme/vpc/aws", "registry.acme.dev/acme/vpc/aws"],
51
+ ["github.com/acme/vpc", "github.com/acme/vpc"],
52
+ ["git::https://git.acme.dev/acme/net.git", "git::https://git.acme.dev/acme/net.git"],
53
+ ["oci://registry.acme.dev/modules/cache", "oci://registry.acme.dev/modules/cache"],
54
+ ])("%s is %s", (from, to) => {
55
+ expect(normalizeModuleSource(from)).toBe(to);
56
+ });
57
+ });
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Where a repo calls its modules (chant#4599, terragucci#1289 bundle B): one
3
+ * entry per module call in the roots given, Terragrunt units included, read
4
+ * from the source files with chant's HCL parse and never from a plan, so the
5
+ * scan runs in a check job before anything is planned.
6
+ *
7
+ * Each call carries its root, file and block path, the source as written,
8
+ * the module it names with the pin taken out and normalised
9
+ * ({@link normalizeModuleSource}), and the pin `readModulePin` (./source.ts)
10
+ * reads. A local call (`./`, `../`) names no published module and is left
11
+ * out. The release catalog lists these as a version's consumers, and
12
+ * `deprecatedPins` (../registry/deprecated.ts) checks them against it.
13
+ *
14
+ * The `terragucci_module_pin` metric keeps reading the plan; this is the
15
+ * source-side view of the same calls. The normal form is chant#3924's
16
+ * (../registry/source-forms.ts), shared with `usedSources` (../sources.ts).
17
+ */
18
+ import { join, relative } from "node:path";
19
+ import type { Hcl2Json } from "@intentius/chant/terraform/hcl2json";
20
+ import { MODULE_TYPE, TERRAGRUNT_SOURCE_TYPE, parseTerraformRootDir, type TerraformEntity } from "../hcl/parse";
21
+ import { normalizeModuleSource, PUBLIC_REGISTRY_HOST } from "../registry/source-forms";
22
+ import { readModulePin, type ModuleCallPin } from "./source";
23
+
24
+ export { normalizeModuleSource, PUBLIC_REGISTRY_HOST };
25
+
26
+ /** One module call in a consuming repo. */
27
+ export interface ModuleConsumer {
28
+ /** The root (or Terragrunt unit) directory, from the repo's top, `/`-separated. */
29
+ root: string;
30
+ /** The file holding the call, from the repo's top. */
31
+ file: string;
32
+ /** The block: `module.<name>`, or `terragrunt.terraform` for a unit's source. */
33
+ address: string;
34
+ /** `terraform` for a module block, `terragrunt` for a unit's `terraform { source }`. */
35
+ kind: "terraform" | "terragrunt";
36
+ /** The `source` as written. */
37
+ source: string;
38
+ /** The module the call names: the source with the pin taken out, normalised. */
39
+ module: string;
40
+ /** The pinned version, tag, digest or ref, or null when the call pins none. */
41
+ pin: string | null;
42
+ /** Where the pin is, as `readModulePin` reads it. */
43
+ at: ModuleCallPin["at"];
44
+ param?: ModuleCallPin["param"];
45
+ /** Why there is no pin, when `pin` is null. */
46
+ unpinned?: string;
47
+ }
48
+
49
+ /** A source names a local directory, not a published module. */
50
+ export function isLocalSource(source: string): boolean {
51
+ return source.startsWith("./") || source.startsWith("../");
52
+ }
53
+
54
+ function str(v: unknown): string | undefined {
55
+ return typeof v === "string" ? v : undefined;
56
+ }
57
+
58
+ /**
59
+ * Every module call in `roots` (directories from `repo`), sorted by root,
60
+ * file and address. A root whose files cannot be parsed throws, naming the
61
+ * root, so a check job fails loudly instead of listing fewer consumers.
62
+ */
63
+ export async function moduleConsumers(repo: string, roots: readonly string[], parser?: Hcl2Json): Promise<ModuleConsumer[]> {
64
+ const out: ModuleConsumer[] = [];
65
+ for (const root of [...new Set(roots)].sort()) {
66
+ const dir = join(repo, root);
67
+ const rel = relative(repo, dir).split("\\").join("/") || ".";
68
+ let entities: TerraformEntity[];
69
+ try {
70
+ entities = [...(await parseTerraformRootDir(dir, "consumers", parser)).values()] as TerraformEntity[];
71
+ } catch (e) {
72
+ throw new Error(`${rel}: ${(e as Error).message}`);
73
+ }
74
+ for (const e of entities) {
75
+ if (e.entityType !== MODULE_TYPE && e.entityType !== TERRAGRUNT_SOURCE_TYPE) continue;
76
+ const source = str(e.props.body.source);
77
+ if (source === undefined || isLocalSource(source)) continue;
78
+ const version = e.entityType === MODULE_TYPE ? str(e.props.body.version) : undefined;
79
+ const pin = readModulePin(source, version);
80
+ out.push({
81
+ root: rel,
82
+ file: rel === "." ? e.props.file : `${rel}/${e.props.file}`,
83
+ address: e.props.address,
84
+ kind: e.entityType === MODULE_TYPE ? "terraform" : "terragrunt",
85
+ source,
86
+ module: normalizeModuleSource(pin.module),
87
+ pin: pin.pin,
88
+ at: pin.at,
89
+ ...(pin.param ? { param: pin.param } : {}),
90
+ ...(pin.unpinned ? { unpinned: pin.unpinned } : {}),
91
+ });
92
+ }
93
+ }
94
+ return out.sort((a, b) => a.root.localeCompare(b.root) || a.file.localeCompare(b.file) || a.address.localeCompare(b.address));
95
+ }
package/src/plugin.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { AuditEntitiesInput, LexiconPlugin } from "@intentius/chant/lexicon";
2
+ import { terraformCiSetup } from "./ci-setup";
2
3
  import { terraformBehaviourKinds } from "./behaviour/kinds";
3
4
  import type { CompletionContext, HoverContext } from "@intentius/chant/lsp/types";
4
5
  import type { Declarable } from "@intentius/chant/declarable";
@@ -84,6 +85,11 @@ export const terraformPlugin: LexiconPlugin = {
84
85
  serializer: terraformSerializer,
85
86
  configSchema: terraformConfigSchema,
86
87
 
88
+ // #4550: the engine install lines for the pull request pipeline's default image.
89
+ ciSetup(ctx: { dir: string }): string[] {
90
+ return terraformCiSetup(ctx);
91
+ },
92
+
87
93
  // ── Required lifecycle methods ────────────────────────────────
88
94
 
89
95
  async generate(options?: { verbose?: boolean }): Promise<void> {
@@ -0,0 +1,3 @@
1
+ formatter: markdown table
2
+ output:
3
+ mode: inject
@@ -0,0 +1 @@
1
+ # documented
@@ -0,0 +1,8 @@
1
+ variable "name" {
2
+ type = string
3
+ description = "The bucket's name."
4
+ }
5
+
6
+ output "bucket" {
7
+ value = var.name
8
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "terraform": {
3
+ "required_providers": {
4
+ "google": { "source": "hashicorp/google", "version": "~> 6.0" }
5
+ }
6
+ },
7
+ "variable": {
8
+ "project": { "type": "string", "description": "The project id." },
9
+ "region": { "type": "string", "default": "us-central1" }
10
+ },
11
+ "output": {
12
+ "name": { "value": "${var.project}", "description": "The project id, echoed." }
13
+ }
14
+ }
@@ -0,0 +1,3 @@
1
+ # vpc
2
+
3
+ A VPC with flow logs.
@@ -0,0 +1,5 @@
1
+ module "vpc" {
2
+ source = "../.."
3
+ cidr = "10.1.0.0/16"
4
+ tags = { team = "net" }
5
+ }
@@ -0,0 +1,4 @@
1
+ module "vpc" {
2
+ source = "../.."
3
+ cidr = "10.0.0.0/16"
4
+ }
@@ -0,0 +1,14 @@
1
+ terraform {
2
+ required_version = ">= 1.5"
3
+ required_providers {
4
+ aws = {
5
+ source = "hashicorp/aws"
6
+ version = ">= 5.0"
7
+ }
8
+ }
9
+ }
10
+
11
+ resource "aws_vpc" "this" {
12
+ cidr_block = var.cidr
13
+ tags = var.tags
14
+ }
@@ -0,0 +1,9 @@
1
+ output "vpc_id" {
2
+ value = aws_vpc.this.id
3
+ description = "The VPC's id."
4
+ }
5
+
6
+ output "arn" {
7
+ value = aws_vpc.this.arn
8
+ sensitive = true
9
+ }
@@ -0,0 +1,16 @@
1
+ variable "cidr" {
2
+ type = string
3
+ description = "The VPC's IPv4 CIDR block."
4
+ }
5
+
6
+ variable "tags" {
7
+ type = map(string)
8
+ default = {}
9
+ description = "Tags for every resource."
10
+ }
11
+
12
+ variable "flow_log_key" {
13
+ type = string
14
+ default = "not-a-real-key"
15
+ sensitive = true
16
+ }