@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,360 @@
1
+ /**
2
+ * HCP Terraform's private module registry as a module target (chant#4604),
3
+ * on the API flow terragucci#1292's ruling settled for every tier, with no
4
+ * VCS connection:
5
+ *
6
+ * 1. GET /api/v2/organizations/:org/registry-modules/private/:org/:name/:provider
7
+ * and, only when that answers 404,
8
+ * POST /api/v2/organizations/:org/registry-modules
9
+ * {type: registry-modules, attributes: {name, provider, registry-name: private}}
10
+ * 2. POST /api/v2/organizations/:org/registry-modules/private/:org/:name/:provider/versions
11
+ * {type: registry-module-versions, attributes: {version, commit-sha}}
12
+ * whose 201 carries `links.upload`
13
+ * 3. PUT <links.upload>, Content-Type: application/octet-stream, the gzip tarball
14
+ *
15
+ * (developer.hashicorp.com/terraform/cloud-docs/api-docs/private-registry/modules).
16
+ * The archive is the one the bucket registry serves and the attestation signs
17
+ * (terragucci#1290 ruling B), and `commit-sha` carries the revision.
18
+ *
19
+ * `stage` makes the three calls. A module already there is not created
20
+ * again, and a version already there is judged by its content (`held`): the
21
+ * same content comes back `present` with nothing sent, other content is
22
+ * refused. HCP lists a version as soon as it is created, so there is nothing
23
+ * to hide before a reveal; `reveal` reads the module until the version's
24
+ * status is `ok`, through the contract's `sleep` and `now`, and a version
25
+ * still parsing when the wait runs out is reported in a line, without failing.
26
+ *
27
+ * The token is `TFE_TOKEN` (or the declaration's `tokenEnv`), else the one
28
+ * Terraform sends the host: `TF_TOKEN_<host>` or a credentials block
29
+ * (core's terraform/credentials). It goes in `Authorization: Bearer` to the
30
+ * API host only. The upload link is a signed archivist URL, so the token is
31
+ * not sent there, as go-tfe's upload sends none.
32
+ *
33
+ * Every call is retried on a 429 after `Retry-After`, else with a doubling
34
+ * backoff, up to {@link HCP_RATE_RETRIES} times; HCP's API allows 30 requests
35
+ * a second per user. A 404 on a create names the two causes the docs give:
36
+ * a token whose team lacks the Manage modules permission, or an organization
37
+ * without the private registry entitlement.
38
+ *
39
+ * Deprecation: the HCP docs read for this target (the private registry
40
+ * modules API and the publish page) give no call to deprecate a version, so
41
+ * HCP shows none and the bucket catalog carries it. Left for the maintainer
42
+ * run, as terragucci#1292 left them open: which token types work (team,
43
+ * user, organization), moving a module between API and VCS publishing, any
44
+ * archive size ceiling, and whether the upload link wants the token.
45
+ */
46
+ import { PublishTargetError, type PublishContextBase, type PublishItem, type PublishedTarget, type StagedTarget } from "@intentius/chant/lifecycle/release-publish";
47
+ import { terraformCredential, tokenVariable } from "@intentius/chant/terraform/credentials";
48
+ import { createHash } from "node:crypto";
49
+ import { RegistryError, fetchTarball, registryLocation, type RegistryReadOptions } from "../reader";
50
+ import { compareSemver, parseSemver } from "../semver";
51
+ import {
52
+ answerError,
53
+ checkModuleVersion,
54
+ contentDigest,
55
+ defaultPublishFetch,
56
+ versionHeldError,
57
+ type HeldVersion,
58
+ type ModuleAddress,
59
+ type ModulePublishTarget,
60
+ type ModuleTargetDeps,
61
+ type ModuleVersionContext,
62
+ } from "./module-target";
63
+
64
+ /** How an HCP Terraform (or Terraform Enterprise) module target is declared. */
65
+ export interface HcpModuleTargetOptions {
66
+ /** The organization, which is also the namespace private module sources name. */
67
+ organization: string;
68
+ /** The host. Default app.terraform.io; a Terraform Enterprise host otherwise. */
69
+ host?: string;
70
+ /** The variable the token is read from first, in place of `TFE_TOKEN`. */
71
+ tokenEnv?: string;
72
+ /** How long a reveal waits for the version's status to be `ok`. Default {@link HCP_PARSE_WAIT_MS}. */
73
+ waitMs?: number;
74
+ /** How long a reveal waits between reads. Default {@link HCP_POLL_MS}. */
75
+ pollMs?: number;
76
+ }
77
+
78
+ export const HCP_DEFAULT_HOST = "app.terraform.io";
79
+ /** How many times a call answered 429 is sent again. */
80
+ export const HCP_RATE_RETRIES = 5;
81
+ /** The first backoff after a 429 with no `Retry-After`, doubled on each retry. */
82
+ export const HCP_BACKOFF_MS = 1000;
83
+ export const HCP_PARSE_WAIT_MS = 120_000;
84
+ export const HCP_POLL_MS = 2000;
85
+
86
+ const JSON_API = "application/vnd.api+json";
87
+
88
+ /** The variables a target names as its credentials: the token variable, and the host's `TF_TOKEN_` form. */
89
+ export function hcpTokenEnv(o: Pick<HcpModuleTargetOptions, "tokenEnv" | "host">): string[] {
90
+ const host = o.host ?? HCP_DEFAULT_HOST;
91
+ return [o.tokenEnv ?? "TFE_TOKEN", tokenVariable(host)].filter((v): v is string => !!v);
92
+ }
93
+
94
+ /** The text of a 404 from a create: the two causes HCP's docs give. */
95
+ export const hcpNotFoundText = (org: string, host: string): string =>
96
+ `${host} answered 404 for organization ${org}: either the token's team lacks the Manage modules permission, or the organization has no private module registry entitlement`;
97
+
98
+ /** How long to wait before the next try after a 429: `Retry-After` in seconds or as a date, else the backoff. */
99
+ export function retryDelay(retryAfter: string | null, attempt: number, now: number): number {
100
+ if (retryAfter) {
101
+ const secs = Number(retryAfter.trim());
102
+ if (Number.isFinite(secs) && secs >= 0) return secs * 1000;
103
+ const at = Date.parse(retryAfter);
104
+ if (Number.isFinite(at)) return Math.max(0, at - now);
105
+ }
106
+ return HCP_BACKOFF_MS * 2 ** attempt;
107
+ }
108
+
109
+ interface Answer {
110
+ status: number;
111
+ json: unknown;
112
+ text: string;
113
+ }
114
+
115
+ interface ModuleDoc {
116
+ versions: { version: string; status: string }[];
117
+ }
118
+
119
+ export function hcpModuleTarget(options: HcpModuleTargetOptions, deps: ModuleTargetDeps = {}): ModulePublishTarget<"hcp"> {
120
+ const fetch = deps.fetch ?? defaultPublishFetch;
121
+ const sleep = deps.sleep ?? ((ms: number) => new Promise<void>((r) => setTimeout(r, ms)));
122
+ const now = deps.now ?? Date.now;
123
+ const host = (options.host ?? HCP_DEFAULT_HOST).toLowerCase();
124
+ const org = options.organization;
125
+ const api = `https://${host}/api/v2`;
126
+ const tokenEnv = hcpTokenEnv(options);
127
+ const first = options.tokenEnv ?? "TFE_TOKEN";
128
+ const key = `hcp:${createHash("sha256")
129
+ .update(JSON.stringify({ organization: org, host, tokenEnv: options.tokenEnv ?? null }))
130
+ .digest("hex")
131
+ .slice(0, 16)}`;
132
+
133
+ const addressOf = (a: ModuleAddress): ModuleAddress => ({ ...a, namespace: org });
134
+ const sourceOf = (a: ModuleAddress) => `${host}/${org}/${a.name}/${a.system}`;
135
+ const moduleUrl = (a: ModuleAddress) => `${api}/organizations/${encodeURIComponent(org)}/registry-modules/private/${encodeURIComponent(org)}/${encodeURIComponent(a.name)}/${encodeURIComponent(a.system)}`;
136
+
137
+ const token = (ctx: Pick<PublishContextBase, "env" | "redactor">): { value: string; from: string } | null => {
138
+ const direct = ctx.env[first];
139
+ const found = direct ? { value: direct, from: first } : (() => {
140
+ const c = terraformCredential(host, { env: ctx.env });
141
+ return c ? { value: c.token, from: c.from } : null;
142
+ })();
143
+ if (found) ctx.redactor.add(found.value);
144
+ return found;
145
+ };
146
+ const needToken = (ctx: Pick<PublishContextBase, "env" | "redactor">) => {
147
+ const t = token(ctx);
148
+ if (!t) {
149
+ throw new PublishTargetError(
150
+ "publish-credentials-missing",
151
+ `no token for ${host}: set ${tokenEnv.join(" or ")}, or a credentials "${host}" block`,
152
+ "Use a team token whose team has the Manage modules permission.",
153
+ );
154
+ }
155
+ return t;
156
+ };
157
+
158
+ /** One call, sent again on a 429 up to the limit. `auth` false sends no token (the upload link). */
159
+ const call = async (ctx: Pick<PublishContextBase, "env" | "redactor" | "log">, method: string, url: string, body?: Uint8Array | string, auth = true): Promise<Answer> => {
160
+ const headers: Record<string, string> = {};
161
+ if (auth) headers.authorization = `Bearer ${needToken(ctx).value}`;
162
+ if (typeof body === "string") headers["content-type"] = JSON_API;
163
+ else if (body) headers["content-type"] = "application/octet-stream";
164
+ if (auth) headers.accept = JSON_API;
165
+ for (let attempt = 0; ; attempt++) {
166
+ let res: Awaited<ReturnType<typeof fetch>>;
167
+ try {
168
+ res = await fetch(url, { method, headers, ...(body !== undefined ? { body } : {}) });
169
+ } catch (e) {
170
+ throw answerError(ctx, "publish-upload-failed", `${method} ${url.split("?")[0]}: ${(e as Error).message}`);
171
+ }
172
+ const text = await res.text().catch(() => "");
173
+ if (res.status === 429) {
174
+ if (attempt >= HCP_RATE_RETRIES) {
175
+ throw answerError(ctx, "publish-rate-limited", `${host} answered 429 to ${method} ${url.split("?")[0]} ${attempt + 1} times`, "HCP allows 30 requests a second per user; run the publish again later, and it sends only what is missing.");
176
+ }
177
+ const ms = retryDelay(res.headers.get("retry-after"), attempt, now());
178
+ ctx.log(`${host} answered 429; trying again in ${Math.ceil(ms / 1000)}s`);
179
+ await sleep(ms);
180
+ continue;
181
+ }
182
+ let json: unknown = null;
183
+ try {
184
+ json = text ? JSON.parse(text) : null;
185
+ } catch {
186
+ json = null;
187
+ }
188
+ return { status: res.status, json, text };
189
+ }
190
+ };
191
+
192
+ const refusedToken = (ctx: Pick<PublishContextBase, "env" | "redactor">, status: number, what: string): PublishTargetError => {
193
+ const t = token(ctx);
194
+ return answerError(ctx, "publish-credentials-refused", `${host} answered ${status} to ${what}: it refused the token from ${t?.from ?? first}`, "Use a team token whose team has the Manage modules permission.");
195
+ };
196
+ const failed = (ctx: Pick<PublishContextBase, "redactor">, a: Answer, what: string): PublishTargetError =>
197
+ answerError(ctx, "publish-upload-failed", `${host} answered ${a.status} to ${what}: ${a.text.slice(0, 300)}`);
198
+
199
+ /** The module's versions and their statuses, or undefined when HCP answers 404. */
200
+ const readModule = async (ctx: Pick<PublishContextBase, "env" | "redactor" | "log">, a: ModuleAddress): Promise<ModuleDoc | undefined> => {
201
+ const res = await call(ctx, "GET", moduleUrl(a));
202
+ if (res.status === 404) return undefined;
203
+ if (res.status === 401 || res.status === 403) throw refusedToken(ctx, res.status, `reading ${sourceOf(a)}`);
204
+ if (res.status !== 200) throw failed(ctx, res, `reading ${sourceOf(a)}`);
205
+ const attrs = (res.json as { data?: { attributes?: Record<string, unknown> } } | null)?.data?.attributes ?? {};
206
+ const raw = Array.isArray(attrs["version-statuses"]) ? (attrs["version-statuses"] as { version?: unknown; status?: unknown }[]) : [];
207
+ return { versions: raw.filter((v) => typeof v.version === "string").map((v) => ({ version: v.version as string, status: typeof v.status === "string" ? v.status : "" })) };
208
+ };
209
+
210
+ const versions = async (address: ModuleAddress, ctx: Pick<PublishContextBase, "env" | "redactor">): Promise<string[]> => {
211
+ const doc = await readModule({ ...ctx, log: () => {} }, addressOf(address));
212
+ const vs = (doc?.versions ?? []).map((v) => v.version);
213
+ return vs.sort((x, y) => {
214
+ const a = parseSemver(x);
215
+ const b = parseSemver(y);
216
+ return a && b ? compareSemver(a, b) : x.localeCompare(y);
217
+ });
218
+ };
219
+
220
+ /** The archive of an `ok` version, read the way Terraform reads it: discovery, download, the tarball. */
221
+ const download = async (ctx: Pick<PublishContextBase, "env" | "redactor">, a: ModuleAddress, version: string): Promise<string> => {
222
+ const t = needToken(ctx);
223
+ const o: RegistryReadOptions = { token: t.value, ...(deps.registryFetch ? { fetch: deps.registryFetch } : {}) };
224
+ try {
225
+ const location = await registryLocation(sourceOf(a), version, o);
226
+ // The location is another host (archivist); the API token is not sent there.
227
+ const bytes = await fetchTarball(location, { credentials: { env: ctx.env }, ...(deps.registryFetch ? { fetch: deps.registryFetch } : {}) });
228
+ return contentDigest(bytes);
229
+ } catch (e) {
230
+ if (e instanceof RegistryError) throw answerError(ctx, "publish-read-failed", e.message.replace(/(https?:\/\/[^\s?]+)\?[^\s)"']*/g, "$1?[query]"));
231
+ throw e;
232
+ }
233
+ };
234
+
235
+ const heldIn = async (ctx: Pick<PublishContextBase, "env" | "redactor">, a: ModuleAddress, version: string, doc: ModuleDoc | undefined): Promise<HeldVersion | undefined> => {
236
+ const v = doc?.versions.find((x) => x.version === version);
237
+ if (!v) return undefined;
238
+ if (v.status !== "ok") return { content: "", status: v.status || "unknown" };
239
+ return { content: await download(ctx, a, version) };
240
+ };
241
+ const held = async (address: ModuleAddress, version: string, ctx: Pick<PublishContextBase, "env" | "redactor">): Promise<HeldVersion | undefined> => {
242
+ const a = addressOf(address);
243
+ return heldIn(ctx, a, version, await readModule({ ...ctx, log: () => {} }, a));
244
+ };
245
+
246
+ const item = (ctx: ModuleVersionContext, present?: boolean): PublishItem => {
247
+ const a = addressOf(ctx.address);
248
+ return { name: `${org}/${a.name}/${a.system} ${ctx.version}`, destination: sourceOf(a), digest: ctx.content, ...(present ? { present: true } : {}) };
249
+ };
250
+
251
+ return {
252
+ kind: "hcp",
253
+ name: `HCP Terraform registry (${org})`,
254
+ destination: `${host}/${org}`,
255
+ credentialEnv: tokenEnv,
256
+ key,
257
+ versions,
258
+ held,
259
+
260
+ async plan(ctx) {
261
+ checkModuleVersion(ctx);
262
+ needToken(ctx);
263
+ return [item(ctx)];
264
+ },
265
+
266
+ async stage(ctx): Promise<StagedTarget> {
267
+ checkModuleVersion(ctx);
268
+ needToken(ctx);
269
+ const a = addressOf(ctx.address);
270
+ const source = sourceOf(a);
271
+ const staged = (present: boolean): StagedTarget => ({ target: "hcp", destination: source, items: [item(ctx, present)], state: { organization: org, host, version: ctx.version } });
272
+
273
+ // 1. The module, created only when absent.
274
+ let doc = await readModule(ctx, a);
275
+ if (!doc) {
276
+ const res = await call(ctx, "POST", `${api}/organizations/${encodeURIComponent(org)}/registry-modules`, JSON.stringify({ data: { type: "registry-modules", attributes: { name: a.name, provider: a.system, "registry-name": "private" } } }));
277
+ if (res.status === 404) throw answerError(ctx, "publish-not-found", hcpNotFoundText(org, host), "Give the token's team the Manage modules permission, and check the organization's plan includes the private registry.");
278
+ if (res.status === 401 || res.status === 403) throw refusedToken(ctx, res.status, `creating ${source}`);
279
+ if (res.status === 422) {
280
+ // Another run created it between the read and the create.
281
+ doc = await readModule(ctx, a);
282
+ if (!doc) throw failed(ctx, res, `creating ${source}`);
283
+ } else if (res.status !== 201) throw failed(ctx, res, `creating ${source}`);
284
+ else {
285
+ ctx.log(`created ${source} in the private registry`);
286
+ doc = { versions: [] };
287
+ }
288
+ }
289
+
290
+ // A version already there: judged by its content, never sent again.
291
+ const judge = (h: HeldVersion): StagedTarget => {
292
+ if (h.status) {
293
+ throw answerError(
294
+ ctx,
295
+ "publish-version-unfinished",
296
+ `${source} ${ctx.version} is on ${host} with status ${h.status}, so its content can't be read`,
297
+ h.status === "pending" ? "An earlier run created the version and its upload did not finish; delete the version in HCP Terraform's registry and publish again." : "Look at the version in HCP Terraform's registry; delete it and publish again.",
298
+ );
299
+ }
300
+ if (h.content !== ctx.content) throw versionHeldError(source, ctx, h);
301
+ ctx.log(`${source} ${ctx.version} is already on ${host} with this content; nothing sent`);
302
+ return staged(true);
303
+ };
304
+ const before = await heldIn(ctx, a, ctx.version, doc);
305
+ if (before) return judge(before);
306
+
307
+ // 2. The version, carrying the revision.
308
+ const created = await call(
309
+ ctx,
310
+ "POST",
311
+ `${moduleUrl(a)}/versions`,
312
+ JSON.stringify({ data: { type: "registry-module-versions", attributes: { version: ctx.version, "commit-sha": ctx.revision || null } } }),
313
+ );
314
+ if (created.status === 404) throw answerError(ctx, "publish-not-found", hcpNotFoundText(org, host), "Give the token's team the Manage modules permission, and check the organization's plan includes the private registry.");
315
+ if (created.status === 401 || created.status === 403) throw refusedToken(ctx, created.status, `creating ${source} ${ctx.version}`);
316
+ if (created.status === 422) {
317
+ const again = await heldIn(ctx, a, ctx.version, await readModule(ctx, a));
318
+ if (again) return judge(again);
319
+ throw failed(ctx, created, `creating ${source} ${ctx.version}`);
320
+ }
321
+ if (created.status !== 201) throw failed(ctx, created, `creating ${source} ${ctx.version}`);
322
+ const upload = (created.json as { data?: { links?: { upload?: unknown } } } | null)?.data?.links?.upload;
323
+ if (typeof upload !== "string" || !upload) throw answerError(ctx, "publish-upload-failed", `${host} created ${source} ${ctx.version} and gave no upload link`);
324
+
325
+ // 3. The archive, to the signed upload link, with no token.
326
+ const put = await call(ctx, "PUT", upload, ctx.archive, false);
327
+ if (put.status < 200 || put.status >= 300) {
328
+ throw answerError(
329
+ ctx,
330
+ "publish-upload-failed",
331
+ `the upload of ${source} ${ctx.version} answered ${put.status}: ${put.text.slice(0, 300)}`,
332
+ "The version exists with status pending; delete it in HCP Terraform's registry and publish again.",
333
+ );
334
+ }
335
+ ctx.log(`uploaded ${source} ${ctx.version}`);
336
+ return staged(false);
337
+ },
338
+
339
+ async reveal(ctx, staged): Promise<PublishedTarget> {
340
+ const a = addressOf(ctx.address);
341
+ const wait = options.waitMs ?? HCP_PARSE_WAIT_MS;
342
+ const poll = options.pollMs ?? HCP_POLL_MS;
343
+ const start = now();
344
+ for (;;) {
345
+ const doc = await readModule(ctx, a);
346
+ const status = doc?.versions.find((v) => v.version === ctx.version)?.status;
347
+ if (status === "ok") return { target: staged.target, destination: staged.destination, items: staged.items };
348
+ // The docs name pending and ok; a status that says it failed stops the wait, any other keeps it.
349
+ if (status && /error|fail/i.test(status)) {
350
+ throw answerError(ctx, "publish-reveal-failed", `${staged.destination} ${ctx.version} has status ${status} on ${host}, so HCP did not take the archive`, "Look at the version in HCP Terraform's registry; delete it and publish again.");
351
+ }
352
+ if (now() - start >= wait) {
353
+ ctx.log(`${staged.destination} ${ctx.version} is still ${status ?? "not listed"} on ${host} after ${Math.round(wait / 1000)}s; HCP lists it once it has parsed the archive`);
354
+ return { target: staged.target, destination: staged.destination, items: staged.items };
355
+ }
356
+ await sleep(poll);
357
+ }
358
+ },
359
+ };
360
+ }
@@ -0,0 +1,117 @@
1
+ /**
2
+ * What the Artifactory and Nexus module targets share (chant#4605): basic
3
+ * `user:token` write credentials (terragucci#1299 ruling B), a binary GET of
4
+ * an uploaded archive, and the wait for a version to be listed after its
5
+ * upload, since both hosts index an upload after the PUT answers.
6
+ */
7
+ import { readCredential, type Credential } from "@intentius/chant/lifecycle/publish-credentials";
8
+ import { PublishTargetError, type PublishContextBase } from "@intentius/chant/lifecycle/release-publish";
9
+ import type { RegistryFetch } from "../reader";
10
+ import { answerError, contentDigest } from "./module-target";
11
+ import { heldContentOf } from "./zip";
12
+
13
+ /** Where a target reads its basic credentials. */
14
+ export interface BasicCredentialEnv {
15
+ /** The variable the token (or password) is read from. */
16
+ tokenEnv: string;
17
+ /** The variable the user name is read from, when `user` is not given. */
18
+ userEnv: string;
19
+ /** The user name itself, in place of `userEnv`. */
20
+ user?: string;
21
+ }
22
+
23
+ /** A user and token, and the `Authorization` header they make. The header and the token are masked. */
24
+ export interface BasicCredential {
25
+ user: string;
26
+ token: Credential;
27
+ /** `Basic <base64 of user:token>`. */
28
+ header: string;
29
+ }
30
+
31
+ /** The basic credential the environment holds, added to the redactor, or null when either half is missing. */
32
+ export function readBasicCredential(ctx: Pick<PublishContextBase, "env" | "redactor">, names: BasicCredentialEnv): BasicCredential | null {
33
+ const token = readCredential(ctx.env, [names.tokenEnv], ctx.redactor);
34
+ const user = names.user ?? ctx.env[names.userEnv];
35
+ if (!token || !user) return null;
36
+ const encoded = Buffer.from(`${user}:${token.value}`, "utf-8").toString("base64");
37
+ ctx.redactor.add(encoded);
38
+ return { user, token, header: `Basic ${encoded}` };
39
+ }
40
+
41
+ /** The basic credential, or the refusal naming both variables. */
42
+ export function needBasicCredential(ctx: Pick<PublishContextBase, "env" | "redactor">, names: BasicCredentialEnv, host: string, fix: string): BasicCredential {
43
+ const c = readBasicCredential(ctx, names);
44
+ if (c) return c;
45
+ const missing = [...(ctx.env[names.tokenEnv] ? [] : [names.tokenEnv]), ...(names.user || ctx.env[names.userEnv] ? [] : [names.userEnv])];
46
+ throw answerError(ctx, "publish-credentials-missing", `no ${host} credentials: set ${missing.join(" and ")}`, fix);
47
+ }
48
+
49
+ /** The global fetch, in the reader's shape. */
50
+ export const defaultRegistryFetch: RegistryFetch = (url, init) => fetch(url, init);
51
+
52
+ /** A GET of bytes, with its status. A network failure is a `publish-read-failed`. */
53
+ export async function getBytes(
54
+ ctx: Pick<PublishContextBase, "redactor">,
55
+ fetchFn: RegistryFetch,
56
+ url: string,
57
+ authorization?: string,
58
+ ): Promise<{ status: number; bytes: Uint8Array }> {
59
+ let res: Response;
60
+ try {
61
+ res = await fetchFn(url, { method: "GET", redirect: "follow", ...(authorization ? { headers: { Authorization: authorization } } : {}) });
62
+ } catch (e) {
63
+ throw answerError(ctx, "publish-read-failed", `GET ${url}: ${(e as Error).message}`);
64
+ }
65
+ return { status: res.status, bytes: new Uint8Array(await res.arrayBuffer()) };
66
+ }
67
+
68
+ /** How long a reveal waits for the host to list an uploaded version, and how often it asks. */
69
+ export interface ListWait {
70
+ waitMs: number;
71
+ pollMs: number;
72
+ sleep: (ms: number) => Promise<void>;
73
+ now: () => number;
74
+ }
75
+
76
+ export const HOSTED_LIST_WAIT_MS = 30_000;
77
+ export const HOSTED_POLL_MS = 2_000;
78
+
79
+ /** Reads `list` until it holds `version` or the wait is over; true when it was listed. */
80
+ export async function waitListed(list: () => Promise<string[]>, version: string, wait: ListWait): Promise<boolean> {
81
+ const start = wait.now();
82
+ for (;;) {
83
+ if ((await list()).includes(version)) return true;
84
+ if (wait.now() - start >= wait.waitMs) return false;
85
+ await wait.sleep(wait.pollMs);
86
+ }
87
+ }
88
+
89
+ /** The refusal for a 401 or 403 on an upload, naming the variables, never their values. */
90
+ export function writeRefused(ctx: Pick<PublishContextBase, "redactor">, host: string, status: number, what: string, credential: BasicCredential, names: BasicCredentialEnv, fix: string): PublishTargetError {
91
+ const userFrom = names.user ? "the declaration's user" : names.userEnv;
92
+ return answerError(ctx, "publish-credentials-refused", `${host} answered ${status} to the upload of ${what}: it refused the user in ${userFrom} with the token in ${credential.token.from}`, fix);
93
+ }
94
+
95
+ /**
96
+ * The content of the archive a download location holds, as the CLIs would
97
+ * fetch it: the registry's credential goes along only to the registry's own
98
+ * host and only when the location has no query (a signed URL carries its own
99
+ * grant). Refuses a location that answers other than 2xx.
100
+ */
101
+ export async function locationContent(
102
+ ctx: Pick<PublishContextBase, "redactor">,
103
+ fetchFn: RegistryFetch,
104
+ location: string,
105
+ registryHost: string,
106
+ authorization: string | undefined,
107
+ ): Promise<{ content: string; revision?: string }> {
108
+ const u = new URL(location);
109
+ const auth = !u.search && u.host.toLowerCase() === registryHost.toLowerCase() ? authorization : undefined;
110
+ const { status, bytes } = await getBytes(ctx, fetchFn, location, auth);
111
+ if (status < 200 || status > 299) throw answerError(ctx, "publish-read-failed", `${location.split("?")[0]} answered ${status} to the download a consumer would make`);
112
+ return heldContentOf(bytes, contentDigest);
113
+ }
114
+
115
+ /** The refusal for a download that holds other content than the version staged. */
116
+ export const revealMismatch = (ctx: Pick<PublishContextBase, "redactor">, where: string, version: string, got: string, want: string): PublishTargetError =>
117
+ answerError(ctx, "publish-reveal-mismatch", `${where} ${version} downloads as ${got}, and this publish staged ${want}`, "Check which archive the repository holds for the version; a published version never changes.");
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Module targets: publishing a module version to a hosted registry on chant
3
+ * core's publish-target contract (chant#4602): GitLab (chant#4603), HCP
4
+ * Terraform or Terraform Enterprise (chant#4604), and Artifactory and Nexus
5
+ * (chant#4605), which take the zip of the attested archive (./zip). The
6
+ * fakes the tests drive them with are at `registry/targets/fake-<host>`,
7
+ * with `fake-http` holding a module tarball builder, outside this index, so
8
+ * a product's tests can reach them without shipping them in its own code
9
+ * paths.
10
+ */
11
+ export * from "./module-target";
12
+ export * from "./gitlab";
13
+ export * from "./hcp";
14
+ export * from "./zip";
15
+ export * from "./hosted";
16
+ export * from "./artifactory";
17
+ export * from "./nexus";
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Publishing a module version to a hosted module registry (chant#4603,
3
+ * terragucci#1290 bundle C). A module target is chant core's publish-target
4
+ * contract (`PublishTarget` in core's lifecycle/release-publish.ts, chant#4047)
5
+ * over a module-version context in place of a cut release (chant#4602): the
6
+ * same `plan`, `stage` and `reveal`, the same `PublishTargetError`, the
7
+ * injected `fetch`, `sleep` and `now`, and core's credential reading and
8
+ * masking. Two reads sit beside the contract: `versions`, the versions the
9
+ * host lists, and `held`, the content a version the host holds carries, so a
10
+ * product's publish can tell a version that is already out from one holding
11
+ * other content.
12
+ *
13
+ * Every target uploads the archive the bucket registry serves and the
14
+ * attestation signs, unchanged (terragucci#1290 ruling B), so one digest
15
+ * holds across all of them. A host that renders a README shows the module's
16
+ * own.
17
+ *
18
+ * Products (terragucci's `modules.publish`) make the targets and hold the
19
+ * words; the protocol and the host calls live here.
20
+ */
21
+ import { gunzipSync } from "node:zlib";
22
+ import { createHash } from "node:crypto";
23
+ import { PublishTargetError, type PublishContextBase, type PublishDeps, type PublishFetch, type PublishTarget } from "@intentius/chant/lifecycle/release-publish";
24
+ import { REGISTRY_NAME, REGISTRY_SYSTEM } from "../address";
25
+ import { parseSemver } from "../semver";
26
+ import type { RegistryFetch } from "../reader";
27
+
28
+ /** A module in a hosted registry, without the host: `<namespace>/<name>/<system>`. */
29
+ export interface ModuleAddress {
30
+ namespace: string;
31
+ name: string;
32
+ system: string;
33
+ }
34
+
35
+ /** What a module target is given to plan and to publish: one version of one module. */
36
+ export interface ModuleVersionContext extends PublishContextBase {
37
+ /** The module. A host that fixes the namespace itself (GitLab's top-level group, HCP's organization) uses its own. */
38
+ address: ModuleAddress;
39
+ /** The version, semver without a leading `v`. */
40
+ version: string;
41
+ /** The gzip tarball the bucket registry serves and the attestation signs, with the module at its root. */
42
+ archive: Uint8Array;
43
+ /** The commit the version was built from. */
44
+ revision: string;
45
+ /** The content digest, `sha256:<hex>` of the uncompressed archive, as the bucket registry's release.json keeps it. */
46
+ content: string;
47
+ }
48
+
49
+ /** The content a version a host holds carries. */
50
+ export interface HeldVersion {
51
+ /** `sha256:<hex>` of its uncompressed archive. */
52
+ content: string;
53
+ /** The revision the host keeps for it, when it keeps one. */
54
+ revision?: string;
55
+ /**
56
+ * The host's status for a version it has not finished taking in, such as
57
+ * HCP's `pending` before its archive is parsed (chant#4604). Absent for a
58
+ * version consumers can install; `content` is empty while it is set.
59
+ */
60
+ status?: string;
61
+ }
62
+
63
+ /** The reads a module target answers beside the contract. They read, and change nothing. */
64
+ export interface ModuleTargetReads {
65
+ /** Every version the host lists for the module, oldest first; none when it has no such module. */
66
+ versions(address: ModuleAddress, ctx: Pick<PublishContextBase, "env" | "redactor">): Promise<string[]>;
67
+ /** The content of a version the host holds, or undefined when it holds no such version. */
68
+ held(address: ModuleAddress, version: string, ctx: Pick<PublishContextBase, "env" | "redactor">): Promise<HeldVersion | undefined>;
69
+ }
70
+
71
+ /** A target that publishes a module version: the publish-target contract over {@link ModuleVersionContext}, plus its reads. */
72
+ export type ModulePublishTarget<K extends string = string> = PublishTarget<ModuleVersionContext, K> & ModuleTargetReads;
73
+
74
+ /** What a module target's factory is given: core's publish deps, plus the fetch the registry reader uses. */
75
+ export interface ModuleTargetDeps extends PublishDeps {
76
+ /**
77
+ * The fetch the registry-protocol reads go through. Default: the global
78
+ * `fetch`. A test passes its fake for both this and `fetch`.
79
+ */
80
+ registryFetch?: RegistryFetch;
81
+ }
82
+
83
+ /** `sha256:<hex>` of the bytes. */
84
+ export const contentDigest = (bytes: Uint8Array): string => `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
85
+
86
+ /** The content digest of a gzip archive: the digest of its uncompressed bytes. */
87
+ export function archiveContent(archive: Uint8Array): string {
88
+ try {
89
+ return contentDigest(gunzipSync(archive));
90
+ } catch {
91
+ throw new PublishTargetError("publish-archive-invalid", "the module archive is not a gzip tarball", "Publish the .tar.gz the bucket registry serves.");
92
+ }
93
+ }
94
+
95
+ /**
96
+ * The checks every module target's `plan` makes before it reads its host: the
97
+ * address and version are ones a registry can carry, and the archive holds
98
+ * the content the context names.
99
+ */
100
+ export function checkModuleVersion(ctx: ModuleVersionContext): void {
101
+ const { namespace, name, system } = ctx.address;
102
+ if (!REGISTRY_NAME.test(namespace) || !REGISTRY_NAME.test(name)) {
103
+ throw new PublishTargetError("publish-config-invalid", `${namespace}/${name}: a registry namespace and module name are letters, digits, - and _`);
104
+ }
105
+ if (!REGISTRY_SYSTEM.test(system)) throw new PublishTargetError("publish-config-invalid", `${system}: a registry module's system is lower-case letters and digits`);
106
+ if (!parseSemver(ctx.version)) throw new PublishTargetError("publish-config-invalid", `${ctx.version} is not a semver version`);
107
+ const got = archiveContent(ctx.archive);
108
+ if (got !== ctx.content) {
109
+ throw new PublishTargetError(
110
+ "publish-artifact-digest-mismatch",
111
+ `the archive for ${namespace}/${name}/${system} ${ctx.version} holds ${got}, and the release names ${ctx.content}`,
112
+ "Publish the archive the release was built with.",
113
+ );
114
+ }
115
+ }
116
+
117
+ /** The refusal for a version a host holds with other content. */
118
+ export function versionHeldError(where: string, ctx: ModuleVersionContext, held: HeldVersion): PublishTargetError {
119
+ return new PublishTargetError(
120
+ "publish-version-held",
121
+ `${where} already holds ${ctx.version} with content ${held.content}, and this publish has ${ctx.content}`,
122
+ "A published version never changes: raise the version, or check why the module's content moved.",
123
+ );
124
+ }
125
+
126
+ /** The error message for a host's answer, with every credential masked. */
127
+ export const answerError = (ctx: Pick<PublishContextBase, "redactor">, code: string, message: string, fix?: string): PublishTargetError =>
128
+ new PublishTargetError(code, ctx.redactor.redact(message), fix === undefined ? undefined : ctx.redactor.redact(fix));
129
+
130
+ /** The global fetch, in the contract's shape. */
131
+ export const defaultPublishFetch: PublishFetch = (url, init) => fetch(url, init as RequestInit);