@quadnix/octo 0.0.30 → 0.2.0

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 (383) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +117 -8
  3. package/dist/app.type.d.ts +277 -19
  4. package/dist/app.type.js +80 -9
  5. package/dist/app.type.js.map +1 -1
  6. package/dist/decorators/action.decorator.d.ts +10 -6
  7. package/dist/decorators/action.decorator.js +19 -6
  8. package/dist/decorators/action.decorator.js.map +1 -1
  9. package/dist/decorators/anchor.decorator.d.ts +12 -5
  10. package/dist/decorators/anchor.decorator.js +12 -5
  11. package/dist/decorators/anchor.decorator.js.map +1 -1
  12. package/dist/decorators/enable-hook.decorator.d.ts +1 -1
  13. package/dist/decorators/enable-hook.decorator.js +1 -1
  14. package/dist/decorators/factory.decorator.d.ts +18 -34
  15. package/dist/decorators/factory.decorator.js +18 -34
  16. package/dist/decorators/factory.decorator.js.map +1 -1
  17. package/dist/decorators/model.decorator.d.ts +4 -6
  18. package/dist/decorators/model.decorator.js +2 -7
  19. package/dist/decorators/model.decorator.js.map +1 -1
  20. package/dist/decorators/module.decorator.d.ts +16 -10
  21. package/dist/decorators/module.decorator.js +16 -10
  22. package/dist/decorators/module.decorator.js.map +1 -1
  23. package/dist/decorators/on-event.decorator.d.ts +7 -2
  24. package/dist/decorators/on-event.decorator.js +5 -0
  25. package/dist/decorators/on-event.decorator.js.map +1 -1
  26. package/dist/decorators/resource.decorator.d.ts +50 -1
  27. package/dist/decorators/resource.decorator.js +38 -1
  28. package/dist/decorators/resource.decorator.js.map +1 -1
  29. package/dist/decorators/validate.decorator.d.ts +15 -1
  30. package/dist/decorators/validate.decorator.js +15 -1
  31. package/dist/decorators/validate.decorator.js.map +1 -1
  32. package/dist/errors/container.error.d.ts +18 -0
  33. package/dist/errors/container.error.js +18 -0
  34. package/dist/errors/container.error.js.map +1 -1
  35. package/dist/errors/index.d.ts +1 -0
  36. package/dist/errors/index.js +1 -0
  37. package/dist/errors/index.js.map +1 -1
  38. package/dist/errors/module.error.d.ts +6 -0
  39. package/dist/errors/module.error.js +6 -0
  40. package/dist/errors/module.error.js.map +1 -1
  41. package/dist/errors/node.error.d.ts +56 -0
  42. package/dist/errors/node.error.js +56 -0
  43. package/dist/errors/node.error.js.map +1 -1
  44. package/dist/errors/schema.error.d.ts +14 -1
  45. package/dist/errors/schema.error.js +11 -1
  46. package/dist/errors/schema.error.js.map +1 -1
  47. package/dist/errors/terraform.error.d.ts +20 -0
  48. package/dist/errors/terraform.error.js +24 -0
  49. package/dist/errors/terraform.error.js.map +1 -0
  50. package/dist/errors/transaction.error.d.ts +93 -2
  51. package/dist/errors/transaction.error.js +83 -0
  52. package/dist/errors/transaction.error.js.map +1 -1
  53. package/dist/events/action.event.d.ts +8 -4
  54. package/dist/events/action.event.js +2 -4
  55. package/dist/events/action.event.js.map +1 -1
  56. package/dist/events/command.event.d.ts +43 -0
  57. package/dist/events/command.event.js +33 -0
  58. package/dist/events/command.event.js.map +1 -0
  59. package/dist/events/event.model.d.ts +26 -8
  60. package/dist/events/event.model.js +15 -7
  61. package/dist/events/event.model.js.map +1 -1
  62. package/dist/events/hook/commit.hook.event.d.ts +1 -7
  63. package/dist/events/hook/commit.hook.event.js +0 -6
  64. package/dist/events/hook/commit.hook.event.js.map +1 -1
  65. package/dist/events/hook/hook.event.d.ts +1 -3
  66. package/dist/events/hook/hook.event.js +0 -2
  67. package/dist/events/hook/hook.event.js.map +1 -1
  68. package/dist/events/hook/model.hook.event.d.ts +1 -7
  69. package/dist/events/hook/model.hook.event.js +0 -6
  70. package/dist/events/hook/model.hook.event.js.map +1 -1
  71. package/dist/events/hook/resource.hook.event.d.ts +1 -7
  72. package/dist/events/hook/resource.hook.event.js +0 -6
  73. package/dist/events/hook/resource.hook.event.js.map +1 -1
  74. package/dist/events/index.d.ts +5 -0
  75. package/dist/events/index.js +5 -0
  76. package/dist/events/index.js.map +1 -1
  77. package/dist/events/mode-result.event.d.ts +57 -0
  78. package/dist/events/mode-result.event.js +24 -0
  79. package/dist/events/mode-result.event.js.map +1 -0
  80. package/dist/events/mode.event.d.ts +61 -0
  81. package/dist/events/mode.event.js +32 -0
  82. package/dist/events/mode.event.js.map +1 -0
  83. package/dist/events/module.event.d.ts +7 -5
  84. package/dist/events/module.event.js +3 -4
  85. package/dist/events/module.event.js.map +1 -1
  86. package/dist/events/registration.event.d.ts +19 -15
  87. package/dist/events/registration.event.js +18 -14
  88. package/dist/events/registration.event.js.map +1 -1
  89. package/dist/events/serialization.event.d.ts +16 -13
  90. package/dist/events/serialization.event.js +15 -12
  91. package/dist/events/serialization.event.js.map +1 -1
  92. package/dist/events/state.event.d.ts +102 -0
  93. package/dist/events/state.event.js +69 -0
  94. package/dist/events/state.event.js.map +1 -0
  95. package/dist/events/terraform.event.d.ts +78 -0
  96. package/dist/events/terraform.event.js +55 -0
  97. package/dist/events/terraform.event.js.map +1 -0
  98. package/dist/events/transaction.event.d.ts +3 -40
  99. package/dist/events/transaction.event.js +1 -29
  100. package/dist/events/transaction.event.js.map +1 -1
  101. package/dist/functions/container/container.d.ts +54 -13
  102. package/dist/functions/container/container.js +64 -19
  103. package/dist/functions/container/container.js.map +1 -1
  104. package/dist/functions/container/test-container.d.ts +43 -29
  105. package/dist/functions/container/test-container.js +71 -43
  106. package/dist/functions/container/test-container.js.map +1 -1
  107. package/dist/functions/dependency/dependency.d.ts +84 -23
  108. package/dist/functions/dependency/dependency.js +84 -14
  109. package/dist/functions/dependency/dependency.js.map +1 -1
  110. package/dist/functions/diff/aggregate-resource-actions.d.ts +19 -0
  111. package/dist/functions/diff/aggregate-resource-actions.js +41 -0
  112. package/dist/functions/diff/aggregate-resource-actions.js.map +1 -0
  113. package/dist/functions/diff/diff-metadata.d.ts +21 -3
  114. package/dist/functions/diff/diff-metadata.js +18 -0
  115. package/dist/functions/diff/diff-metadata.js.map +1 -1
  116. package/dist/functions/diff/diff.d.ts +68 -7
  117. package/dist/functions/diff/diff.js +44 -3
  118. package/dist/functions/diff/diff.js.map +1 -1
  119. package/dist/functions/hook/commit.hook.d.ts +16 -1
  120. package/dist/functions/hook/commit.hook.js +21 -6
  121. package/dist/functions/hook/commit.hook.js.map +1 -1
  122. package/dist/functions/hook/model-action.hook.d.ts +16 -1
  123. package/dist/functions/hook/model-action.hook.js +21 -6
  124. package/dist/functions/hook/model-action.hook.js.map +1 -1
  125. package/dist/functions/hook/resource-action.hook.d.ts +16 -1
  126. package/dist/functions/hook/resource-action.hook.js +21 -6
  127. package/dist/functions/hook/resource-action.hook.js.map +1 -1
  128. package/dist/functions/node/node-action.abstract.d.ts +23 -2
  129. package/dist/functions/node/node-action.abstract.js +22 -8
  130. package/dist/functions/node/node-action.abstract.js.map +1 -1
  131. package/dist/functions/node/node.abstract.d.ts +56 -5
  132. package/dist/functions/node/node.abstract.js +106 -22
  133. package/dist/functions/node/node.abstract.js.map +1 -1
  134. package/dist/functions/node/node.interface.d.ts +3 -1
  135. package/dist/functions/schema/schema.d.ts +7 -0
  136. package/dist/functions/schema/schema.js +10 -1
  137. package/dist/functions/schema/schema.js.map +1 -1
  138. package/dist/index.d.ts +12 -6
  139. package/dist/index.js +9 -4
  140. package/dist/index.js.map +1 -1
  141. package/dist/index.type.d.ts +5 -0
  142. package/dist/index.type.js +2 -0
  143. package/dist/index.type.js.map +1 -0
  144. package/dist/main.d.ts +245 -14
  145. package/dist/main.js +377 -72
  146. package/dist/main.js.map +1 -1
  147. package/dist/models/account/account.model.d.ts +43 -7
  148. package/dist/models/account/account.model.js +56 -8
  149. package/dist/models/account/account.model.js.map +1 -1
  150. package/dist/models/account/account.schema.d.ts +13 -1
  151. package/dist/models/account/account.schema.js +12 -0
  152. package/dist/models/account/account.schema.js.map +1 -1
  153. package/dist/models/app/app.model.d.ts +13 -8
  154. package/dist/models/app/app.model.js +17 -32
  155. package/dist/models/app/app.model.js.map +1 -1
  156. package/dist/models/app/app.schema.d.ts +6 -0
  157. package/dist/models/app/app.schema.js +6 -0
  158. package/dist/models/app/app.schema.js.map +1 -1
  159. package/dist/models/deployment/deployment.model.d.ts +13 -1
  160. package/dist/models/deployment/deployment.model.js +16 -4
  161. package/dist/models/deployment/deployment.model.js.map +1 -1
  162. package/dist/models/deployment/deployment.schema.d.ts +6 -0
  163. package/dist/models/deployment/deployment.schema.js +6 -0
  164. package/dist/models/deployment/deployment.schema.js.map +1 -1
  165. package/dist/models/environment/environment.model.d.ts +15 -3
  166. package/dist/models/environment/environment.model.js +22 -10
  167. package/dist/models/environment/environment.model.js.map +1 -1
  168. package/dist/models/environment/environment.schema.d.ts +2 -0
  169. package/dist/models/environment/environment.schema.js +2 -0
  170. package/dist/models/environment/environment.schema.js.map +1 -1
  171. package/dist/models/execution/execution.model.d.ts +21 -3
  172. package/dist/models/execution/execution.model.js +47 -29
  173. package/dist/models/execution/execution.model.js.map +1 -1
  174. package/dist/models/execution/execution.schema.d.ts +11 -0
  175. package/dist/models/execution/execution.schema.js +11 -0
  176. package/dist/models/execution/execution.schema.js.map +1 -1
  177. package/dist/models/filesystem/filesystem.model.d.ts +27 -1
  178. package/dist/models/filesystem/filesystem.model.js +30 -4
  179. package/dist/models/filesystem/filesystem.model.js.map +1 -1
  180. package/dist/models/filesystem/filesystem.schema.d.ts +5 -0
  181. package/dist/models/filesystem/filesystem.schema.js +5 -0
  182. package/dist/models/filesystem/filesystem.schema.js.map +1 -1
  183. package/dist/models/image/image.model.d.ts +19 -5
  184. package/dist/models/image/image.model.js +25 -11
  185. package/dist/models/image/image.model.js.map +1 -1
  186. package/dist/models/image/image.schema.d.ts +8 -0
  187. package/dist/models/image/image.schema.js +8 -0
  188. package/dist/models/image/image.schema.js.map +1 -1
  189. package/dist/models/model-action.interface.d.ts +17 -4
  190. package/dist/models/model.abstract.d.ts +96 -1
  191. package/dist/models/model.abstract.js +107 -8
  192. package/dist/models/model.abstract.js.map +1 -1
  193. package/dist/models/model.interface.d.ts +3 -0
  194. package/dist/models/pipeline/pipeline.model.d.ts +14 -2
  195. package/dist/models/pipeline/pipeline.model.js +19 -7
  196. package/dist/models/pipeline/pipeline.model.js.map +1 -1
  197. package/dist/models/pipeline/pipeline.schema.d.ts +8 -0
  198. package/dist/models/pipeline/pipeline.schema.js +8 -0
  199. package/dist/models/pipeline/pipeline.schema.js.map +1 -1
  200. package/dist/models/region/region.model.d.ts +16 -1
  201. package/dist/models/region/region.model.js +19 -4
  202. package/dist/models/region/region.model.js.map +1 -1
  203. package/dist/models/region/region.schema.d.ts +6 -0
  204. package/dist/models/region/region.schema.js +6 -0
  205. package/dist/models/region/region.schema.js.map +1 -1
  206. package/dist/models/server/server.model.d.ts +15 -3
  207. package/dist/models/server/server.model.js +18 -6
  208. package/dist/models/server/server.model.js.map +1 -1
  209. package/dist/models/server/server.schema.d.ts +3 -1
  210. package/dist/models/server/server.schema.js +3 -1
  211. package/dist/models/server/server.schema.js.map +1 -1
  212. package/dist/models/service/service.model.d.ts +37 -6
  213. package/dist/models/service/service.model.js +65 -11
  214. package/dist/models/service/service.model.js.map +1 -1
  215. package/dist/models/service/service.schema.d.ts +48 -1
  216. package/dist/models/service/service.schema.js +56 -1
  217. package/dist/models/service/service.schema.js.map +1 -1
  218. package/dist/models/subnet/subnet.model.d.ts +27 -3
  219. package/dist/models/subnet/subnet.model.js +40 -16
  220. package/dist/models/subnet/subnet.model.js.map +1 -1
  221. package/dist/models/subnet/subnet.schema.d.ts +9 -0
  222. package/dist/models/subnet/subnet.schema.js +9 -0
  223. package/dist/models/subnet/subnet.schema.js.map +1 -1
  224. package/dist/modes/apply-transaction.d.ts +23 -0
  225. package/dist/modes/apply-transaction.js +25 -0
  226. package/dist/modes/apply-transaction.js.map +1 -0
  227. package/dist/modes/commit.mode.d.ts +54 -0
  228. package/dist/modes/commit.mode.js +235 -0
  229. package/dist/modes/commit.mode.js.map +1 -0
  230. package/dist/modes/generate.mode.d.ts +30 -0
  231. package/dist/modes/generate.mode.js +50 -0
  232. package/dist/modes/generate.mode.js.map +1 -0
  233. package/dist/modes/run-action.mode.d.ts +30 -0
  234. package/dist/modes/run-action.mode.js +140 -0
  235. package/dist/modes/run-action.mode.js.map +1 -0
  236. package/dist/modes/validate.mode.d.ts +186 -0
  237. package/dist/modes/validate.mode.js +558 -0
  238. package/dist/modes/validate.mode.js.map +1 -0
  239. package/dist/modules/module.abstract.d.ts +51 -0
  240. package/dist/modules/module.abstract.js +36 -0
  241. package/dist/modules/module.abstract.js.map +1 -1
  242. package/dist/modules/module.container.d.ts +43 -1
  243. package/dist/modules/module.container.js +77 -5
  244. package/dist/modules/module.container.js.map +1 -1
  245. package/dist/modules/test-module.container.d.ts +139 -25
  246. package/dist/modules/test-module.container.js +383 -199
  247. package/dist/modules/test-module.container.js.map +1 -1
  248. package/dist/overlays/anchor.abstract.d.ts +32 -1
  249. package/dist/overlays/anchor.abstract.js +42 -11
  250. package/dist/overlays/anchor.abstract.js.map +1 -1
  251. package/dist/overlays/anchor.schema.d.ts +21 -0
  252. package/dist/overlays/anchor.schema.js +21 -0
  253. package/dist/overlays/anchor.schema.js.map +1 -1
  254. package/dist/overlays/overlay-data.repository.d.ts +18 -0
  255. package/dist/overlays/overlay-data.repository.js +18 -0
  256. package/dist/overlays/overlay-data.repository.js.map +1 -1
  257. package/dist/overlays/overlay.abstract.d.ts +56 -1
  258. package/dist/overlays/overlay.abstract.js +79 -20
  259. package/dist/overlays/overlay.abstract.js.map +1 -1
  260. package/dist/overlays/overlay.schema.d.ts +20 -0
  261. package/dist/overlays/overlay.schema.js +20 -0
  262. package/dist/overlays/overlay.schema.js.map +1 -1
  263. package/dist/resources/resource-action.interface.d.ts +29 -3
  264. package/dist/resources/resource-data.repository.d.ts +63 -1
  265. package/dist/resources/resource-data.repository.js +94 -4
  266. package/dist/resources/resource-data.repository.js.map +1 -1
  267. package/dist/resources/resource.abstract.d.ts +163 -6
  268. package/dist/resources/resource.abstract.js +223 -59
  269. package/dist/resources/resource.abstract.js.map +1 -1
  270. package/dist/resources/resource.interface.d.ts +0 -2
  271. package/dist/resources/resource.schema.d.ts +39 -1
  272. package/dist/resources/resource.schema.js +38 -0
  273. package/dist/resources/resource.schema.js.map +1 -1
  274. package/dist/resources/terraform-resource.abstract.d.ts +44 -0
  275. package/dist/resources/terraform-resource.abstract.js +45 -0
  276. package/dist/resources/terraform-resource.abstract.js.map +1 -0
  277. package/dist/services/event/event.service.d.ts +32 -5
  278. package/dist/services/event/event.service.js +58 -16
  279. package/dist/services/event/event.service.js.map +1 -1
  280. package/dist/services/input/input.service.d.ts +82 -5
  281. package/dist/services/input/input.service.js +143 -12
  282. package/dist/services/input/input.service.js.map +1 -1
  283. package/dist/services/schema-translation/schema-translation.service.d.ts +9 -0
  284. package/dist/services/schema-translation/schema-translation.service.js +9 -0
  285. package/dist/services/schema-translation/schema-translation.service.js.map +1 -1
  286. package/dist/services/serialization/model/model-serialization.service.d.ts +25 -3
  287. package/dist/services/serialization/model/model-serialization.service.js +113 -62
  288. package/dist/services/serialization/model/model-serialization.service.js.map +1 -1
  289. package/dist/services/serialization/resource/resource-serialization.service.d.ts +36 -6
  290. package/dist/services/serialization/resource/resource-serialization.service.js +77 -28
  291. package/dist/services/serialization/resource/resource-serialization.service.js.map +1 -1
  292. package/dist/services/state-management/local.state-provider.d.ts +67 -4
  293. package/dist/services/state-management/local.state-provider.js +168 -41
  294. package/dist/services/state-management/local.state-provider.js.map +1 -1
  295. package/dist/services/state-management/remote.state-provider.d.ts +216 -0
  296. package/dist/services/state-management/remote.state-provider.js +645 -0
  297. package/dist/services/state-management/remote.state-provider.js.map +1 -0
  298. package/dist/services/state-management/state-management.service.d.ts +195 -17
  299. package/dist/services/state-management/state-management.service.js +259 -10
  300. package/dist/services/state-management/state-management.service.js.map +1 -1
  301. package/dist/services/state-management/state-provider.interface.d.ts +98 -3
  302. package/dist/services/state-management/state-provider.interface.js +14 -1
  303. package/dist/services/state-management/state-provider.interface.js.map +1 -1
  304. package/dist/services/state-management/terraform-state.service.d.ts +99 -0
  305. package/dist/services/state-management/terraform-state.service.js +197 -0
  306. package/dist/services/state-management/terraform-state.service.js.map +1 -0
  307. package/dist/services/state-management/test.state-provider.d.ts +42 -4
  308. package/dist/services/state-management/test.state-provider.js +77 -25
  309. package/dist/services/state-management/test.state-provider.js.map +1 -1
  310. package/dist/services/terraform/terraform.service.d.ts +480 -0
  311. package/dist/services/terraform/terraform.service.js +1082 -0
  312. package/dist/services/terraform/terraform.service.js.map +1 -0
  313. package/dist/services/terraform/terraform.type.d.ts +387 -0
  314. package/dist/services/terraform/terraform.type.js +579 -0
  315. package/dist/services/terraform/terraform.type.js.map +1 -0
  316. package/dist/services/transaction/transaction.service.d.ts +110 -10
  317. package/dist/services/transaction/transaction.service.js +570 -251
  318. package/dist/services/transaction/transaction.service.js.map +1 -1
  319. package/dist/services/validation/validation.service.d.ts +15 -3
  320. package/dist/services/validation/validation.service.js +12 -0
  321. package/dist/services/validation/validation.service.js.map +1 -1
  322. package/dist/utilities/array/array.utility.d.ts +3 -0
  323. package/dist/utilities/array/array.utility.js +3 -0
  324. package/dist/utilities/array/array.utility.js.map +1 -1
  325. package/dist/{functions → utilities}/diff/diff.utility.d.ts +19 -3
  326. package/dist/{functions → utilities}/diff/diff.utility.js +57 -41
  327. package/dist/utilities/diff/diff.utility.js.map +1 -0
  328. package/dist/utilities/file/file.utility.d.ts +16 -2
  329. package/dist/utilities/file/file.utility.js +23 -61
  330. package/dist/utilities/file/file.utility.js.map +1 -1
  331. package/dist/utilities/hcl/hcl.utility.d.ts +128 -0
  332. package/dist/utilities/hcl/hcl.utility.js +402 -0
  333. package/dist/utilities/hcl/hcl.utility.js.map +1 -0
  334. package/dist/utilities/http/http.utility.d.ts +57 -0
  335. package/dist/utilities/http/http.utility.js +98 -0
  336. package/dist/utilities/http/http.utility.js.map +1 -0
  337. package/dist/utilities/node/node.utility.d.ts +3 -0
  338. package/dist/utilities/node/node.utility.js +3 -0
  339. package/dist/utilities/node/node.utility.js.map +1 -1
  340. package/dist/utilities/object/object.utility.d.ts +12 -0
  341. package/dist/utilities/object/object.utility.js +12 -0
  342. package/dist/utilities/object/object.utility.js.map +1 -1
  343. package/dist/utilities/project/project.utility.d.ts +28 -0
  344. package/dist/utilities/project/project.utility.js +35 -0
  345. package/dist/utilities/project/project.utility.js.map +1 -0
  346. package/dist/utilities/string/string.utility.d.ts +41 -0
  347. package/dist/utilities/string/string.utility.js +65 -0
  348. package/dist/utilities/string/string.utility.js.map +1 -0
  349. package/dist/utilities/terraform/terraform.utility.d.ts +276 -0
  350. package/dist/utilities/terraform/terraform.utility.js +623 -0
  351. package/dist/utilities/terraform/terraform.utility.js.map +1 -0
  352. package/dist/utilities/test-helpers/test-classes.d.ts +36 -30
  353. package/dist/utilities/test-helpers/test-classes.js +38 -39
  354. package/dist/utilities/test-helpers/test-classes.js.map +1 -1
  355. package/dist/utilities/test-helpers/test-models.d.ts +8 -1
  356. package/dist/utilities/test-helpers/test-models.js +37 -12
  357. package/dist/utilities/test-helpers/test-models.js.map +1 -1
  358. package/dist/utilities/test-helpers/test-modes.d.ts +315 -0
  359. package/dist/utilities/test-helpers/test-modes.js +486 -0
  360. package/dist/utilities/test-helpers/test-modes.js.map +1 -0
  361. package/dist/utilities/test-helpers/test-modules.d.ts +47 -0
  362. package/dist/utilities/test-helpers/test-modules.js +85 -0
  363. package/dist/utilities/test-helpers/test-modules.js.map +1 -0
  364. package/dist/utilities/test-helpers/test-overlays.d.ts +20 -2
  365. package/dist/utilities/test-helpers/test-overlays.js +56 -6
  366. package/dist/utilities/test-helpers/test-overlays.js.map +1 -1
  367. package/dist/utilities/test-helpers/test-resources.d.ts +23 -3
  368. package/dist/utilities/test-helpers/test-resources.js +83 -23
  369. package/dist/utilities/test-helpers/test-resources.js.map +1 -1
  370. package/dist/utilities/validation/validation.utility.d.ts +24 -0
  371. package/dist/utilities/validation/validation.utility.js +24 -0
  372. package/dist/utilities/validation/validation.utility.js.map +1 -1
  373. package/package.json +12 -6
  374. package/dist/functions/diff/diff.utility.js.map +0 -1
  375. package/dist/services/capture/capture.service.d.ts +0 -19
  376. package/dist/services/capture/capture.service.js +0 -43
  377. package/dist/services/capture/capture.service.js.map +0 -1
  378. package/dist/services/state-management/local-encryption.state-provider.d.ts +0 -20
  379. package/dist/services/state-management/local-encryption.state-provider.js +0 -90
  380. package/dist/services/state-management/local-encryption.state-provider.js.map +0 -1
  381. package/dist/utilities/cidr/cidr.utility.d.ts +0 -10
  382. package/dist/utilities/cidr/cidr.utility.js +0 -51
  383. package/dist/utilities/cidr/cidr.utility.js.map +0 -1
@@ -0,0 +1,1082 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { relative, resolve, sep } from 'node:path';
8
+ import { MatchingResource, } from '../../app.type.js';
9
+ import { Factory } from '../../decorators/factory.decorator.js';
10
+ import { getSchemaDefaults } from '../../functions/schema/schema.js';
11
+ import { StringUtility } from '../../utilities/string/string.utility.js';
12
+ import { ExpressionHclNode, HclExpression, InterpolatedStringHclNode, JsonEncodeHclNode, LiteralHclNode, MapHclNode, MapHclNodeRaw, PROJECT_ROOT_VARIABLE_NAME, RefHclNode, TerraformBlock, TerraformData, TerraformOutput, TerraformResource, TerraformVariable, TypeHclNode, toHclNode, } from './terraform.type.js';
13
+ /**
14
+ * Expands an author-provided spec object into attributes and blocks on `target`:
15
+ */
16
+ function addTerraformResourceSpec(target, spec) {
17
+ for (const [key, value] of Object.entries(spec)) {
18
+ if (value instanceof HclExpression) {
19
+ target.attribute(key, value);
20
+ }
21
+ else if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
22
+ addTerraformResourceSpec(target.block(key), value);
23
+ }
24
+ else if (Array.isArray(value) &&
25
+ value.every((v) => v !== null && typeof v === 'object' && !Array.isArray(v) && !(v instanceof HclExpression))) {
26
+ for (const item of value) {
27
+ addTerraformResourceSpec(target.block(key), item);
28
+ }
29
+ }
30
+ else {
31
+ target.attribute(key, value);
32
+ }
33
+ }
34
+ }
35
+ /**
36
+ * Full implementation of {@link OctoTerraformResourceScope}. Holds all the internal state
37
+ * that {@link TerraformService} reads during the wiring and render phases.
38
+ *
39
+ * The `outputs` map is the resource's answer sheet:
40
+ * - key is the resource's response key that other resources use for referencing.
41
+ * - name is the `${resourceId}-${key}` output name it is published under.
42
+ * - value is the terraform expression that produces the value.
43
+ */
44
+ class OctoTerraformResource {
45
+ /**
46
+ * @internal
47
+ */
48
+ constructor(resourceId, resourceContext, moduleId, destroyLosesData, explicitParentResourceIds = [], providerRef, // Fully-qualified provider reference (`aws._111111111-us-east-1`).
49
+ externalResultExpression, // External resource marker with `data.external.<name>.result` value.
50
+ responseDefaults = {}) {
51
+ this.resourceId = resourceId;
52
+ this.resourceContext = resourceContext;
53
+ this.moduleId = moduleId;
54
+ this.destroyLosesData = destroyLosesData;
55
+ this.explicitParentResourceIds = explicitParentResourceIds;
56
+ this.providerRef = providerRef;
57
+ this.externalResultExpression = externalResultExpression;
58
+ this.responseDefaults = responseDefaults;
59
+ this.outputs = new Map();
60
+ this.terraformResources = [];
61
+ // When an external script, publish the whole-result map as one output, keyed only by resource id, no output key.
62
+ if (externalResultExpression !== undefined) {
63
+ this.outputs.set(resourceId, {
64
+ name: StringUtility.sanitizeForIdentifier(resourceId),
65
+ sensitive: false,
66
+ value: new ExpressionHclNode(externalResultExpression),
67
+ });
68
+ }
69
+ }
70
+ addTerraformResource(type, name, spec = {}, options = {}) {
71
+ const chain = options.chain ?? true;
72
+ const intraResourceDependencies = chain && this.lastChainedAddress ? [this.lastChainedAddress] : [];
73
+ const terraformResource = new TerraformResource(type, StringUtility.sanitizeForIdentifier(name), intraResourceDependencies);
74
+ if (chain) {
75
+ this.lastChainedAddress = terraformResource.address;
76
+ }
77
+ if (this.providerRef && !('provider' in spec)) {
78
+ terraformResource.attribute('provider', new ExpressionHclNode(this.providerRef));
79
+ }
80
+ addTerraformResourceSpec(terraformResource, spec);
81
+ this.terraformResources.push(terraformResource);
82
+ return terraformResource;
83
+ }
84
+ /**
85
+ * @internal
86
+ */
87
+ collectRefs(into) {
88
+ this.terraformResources.forEach((r) => r.collectRefs(into));
89
+ this.outputs.forEach((o) => o.value.collectRefs(into));
90
+ }
91
+ output(outputs, options = {}) {
92
+ const sensitiveKeys = new Set(options.sensitiveKeys ?? []);
93
+ for (const [key, value] of Object.entries(outputs)) {
94
+ this.outputs.set(key, {
95
+ name: StringUtility.sanitizeForIdentifier(`${this.resourceId}-${key}`),
96
+ sensitive: sensitiveKeys.has(key),
97
+ value: toHclNode(value),
98
+ });
99
+ }
100
+ }
101
+ }
102
+ /**
103
+ * Everything that renders into one terragrunt module folder.
104
+ */
105
+ class TerraformModule {
106
+ /**
107
+ * @internal
108
+ */
109
+ constructor(moduleId) {
110
+ this.moduleId = moduleId;
111
+ this.dataSources = [];
112
+ this.hasExternalResources = false; // True when folder has external resources requiring null/external configs.
113
+ this.moves = []; // Resources taking over a state.
114
+ this.needsProjectRoot = false; // True when a resource named a local file, requiring the project root variable.
115
+ this.providerKeys = new Set(); // Specific providers this folder's resources require.
116
+ this.resources = [];
117
+ this.variables = [];
118
+ }
119
+ }
120
+ /**
121
+ * Terraform providers that take no `region` argument - the hashicorp utility providers, which take
122
+ * no provider arguments at all. Writing one onto their block fails the plan. Regional providers
123
+ * (`aws`, `google`, and the rest) are not listed: they keep the `region` they have always been
124
+ * given, and anything unlisted that does not want one can pass `setRegionAttribute: false`.
125
+ */
126
+ const REGIONLESS_PROVIDER_TYPES = new Set(['archive', 'external', 'local', 'null', 'random', 'template', 'time']);
127
+ /**
128
+ * Provider types that can be told which account they are allowed to authenticate as. Only `aws`
129
+ * spells it `allowed_account_ids`; the equivalent everywhere else is a different word for a
130
+ * different thing - google scopes to a project, azure to a subscription - so nothing else is listed.
131
+ */
132
+ const ACCOUNT_GUARDED_PROVIDER_TYPES = new Set(['aws']);
133
+ /**
134
+ * Collects what every resource emitted through {@link TerraformModuleScope}, wires the references
135
+ * between module folders, and renders the terragrunt files {@link Octo.generate} writes.
136
+ *
137
+ * @internal
138
+ */
139
+ export class TerraformService {
140
+ /**
141
+ * @internal
142
+ */
143
+ get step() {
144
+ return ' '.repeat(this.indentLength);
145
+ }
146
+ /**
147
+ * @internal
148
+ */
149
+ constructor(octoCommandPrefix = 'octo', indentLength = 2) {
150
+ this.octoCommandPrefix = octoCommandPrefix;
151
+ this.indentLength = indentLength;
152
+ this.minTerraformVersion = '1.6.0';
153
+ this.modules = new Map();
154
+ this.pendingExternalResourcesInputWiring = [];
155
+ this.providers = new Map(); // TF provider blocks.
156
+ this.requiredProviders = {}; // TF config block.
157
+ this.resourceRegistry = new Map();
158
+ this.sanitizedResourceIds = new Map(); // Sanitized id -> original id.
159
+ }
160
+ /**
161
+ * Sets the minimum terraform version and the `required_providers` sources/versions that render
162
+ * into every folder's config block (scoped to the providers each folder actually uses).
163
+ */
164
+ addTerraformConfig({ minTerraformVersion = '1.6.0', providers = {}, } = {}) {
165
+ this.minTerraformVersion = minTerraformVersion;
166
+ this.requiredProviders = { ...this.requiredProviders, ...providers };
167
+ }
168
+ /**
169
+ * Registers a Terraform provider block for the given provider type, account, and region.
170
+ *
171
+ * An `aws` provider is also given `allowed_account_ids = [accountId]`, so terraform refuses at plan
172
+ * when the credentials it resolves belong to another account. It is omitted when `accountName` is
173
+ * set (the repository has asked for the id not to appear in committed terraform), when `spec`
174
+ * already carries `allowed_account_ids`, and when `spec` sets `skip_requesting_account_id`, which
175
+ * stops the provider learning the account there is to check.
176
+ *
177
+ * @param providerType - The terraform provider type, e.g. `aws`, `google`.
178
+ * @param accountId - The account/project/subscription ID.
179
+ * @param regionId - The region ID.
180
+ * @param spec - Provider arguments beyond `alias` and `region`.
181
+ * - Primitives become attributes (`key = value`).
182
+ * - Plain objects become blocks (`key { }`).
183
+ * - Wrap with `mapAttr()` when the provider schema expects a map attribute (`key = { }`).
184
+ * @param options - optional provider configurations.
185
+ * - options.accountName - What to call the account in the provider alias, in place of its id.
186
+ * `prod` renders `aws.prod-us-east-1` where the id renders `aws._123456789012-us-east-1`. Two
187
+ * accounts may not share a name within one provider type and region, and the generated terraform
188
+ * is committed, so a name is also how a repository keeps its account number out of it. It also
189
+ * drops the `allowed_account_ids` guard above, which would put that id straight back.
190
+ * - options.setRegionAttribute - Whether to write `region = <regionId>` onto the provider block.
191
+ * Defaults to true, except for the utility providers in {@link REGIONLESS_PROVIDER_TYPES}, which
192
+ * accept no arguments at all and fail the plan outright with
193
+ * `An argument named "region" is not expected here`. Set it explicitly to override either way -
194
+ * a regional provider this list does not know about still needs `false` passed by hand.
195
+ */
196
+ addTerraformProvider(providerType, accountId, regionId, spec = {}, { accountName, setRegionAttribute } = {}) {
197
+ if (!(providerType in this.requiredProviders)) {
198
+ throw new Error(`Provider type "${providerType}" is not configured! Declare it under "terraform.requiredProviders" in the definition file, or pass it to registerTerraformConfig(), before registering providers.`);
199
+ }
200
+ // Terraform identifiers (here, the provider alias) must start with a letter or underscore.
201
+ const sanitizedAlias = StringUtility.sanitizeForIdentifier(`${accountName ?? accountId}-${regionId}`);
202
+ const alias = /^[0-9]/.test(sanitizedAlias) ? `_${sanitizedAlias}` : sanitizedAlias;
203
+ const key = this.providerKey(providerType, accountId, regionId);
204
+ if (!this.providers.has(key)) {
205
+ // A name is the caller's to pick, so two accounts can collide on one where two ids cannot.
206
+ const taken = [...this.providers.values()].find((provider) => provider.providerType === providerType && provider.alias === alias);
207
+ if (taken) {
208
+ throw new Error(`Terraform provider alias "${alias}" is already taken by account "${taken.accountId}"! Give one of them a different accountName.`);
209
+ }
210
+ const block = new TerraformBlock(`provider "${providerType}"`);
211
+ block.attribute('alias', alias);
212
+ if (setRegionAttribute ?? !REGIONLESS_PROVIDER_TYPES.has(providerType)) {
213
+ block.attribute('region', regionId);
214
+ }
215
+ // Bind the provider to the account the definition names. `accountId` is otherwise only a label
216
+ // octo builds the alias from: terraform authenticates from the ambient credential chain, so a
217
+ // profile naming another account plans green and applies into it. This is the one form of the
218
+ // check that runs on the chain terraform applies with, and it fails at plan.
219
+ if (ACCOUNT_GUARDED_PROVIDER_TYPES.has(providerType) &&
220
+ accountName === undefined &&
221
+ !('allowed_account_ids' in spec) &&
222
+ !spec['skip_requesting_account_id']) {
223
+ block.attribute('allowed_account_ids', [accountId]);
224
+ }
225
+ addTerraformResourceSpec(block, spec);
226
+ this.providers.set(key, { accountId, alias, block, providerType, regionId });
227
+ }
228
+ }
229
+ /**
230
+ * Returns the folder record of the current sweep: one entry per folder-bearing module — a
231
+ * preloaded shell the sweep left empty is an emptied folder, not folder-bearing, so it drops out
232
+ * of the record. Call after the sweep has contributed every resource (the same point
233
+ * {@link renderAllModules} is valid).
234
+ */
235
+ getFolderRecords() {
236
+ // Provider blocks are registration-time constants; a resource reference inside one is invalid.
237
+ const context = {
238
+ resolveRef: () => {
239
+ throw new Error('Resource references are not supported in provider blocks!');
240
+ },
241
+ step: this.step,
242
+ };
243
+ return [...this.modules.values()]
244
+ .filter((module) => module.resources.length > 0)
245
+ .map((module) => ({
246
+ hasExternalResources: module.hasExternalResources,
247
+ moduleId: module.moduleId,
248
+ ...this.getFolderMoves(module),
249
+ providers: [...module.providerKeys].sort().map((key) => {
250
+ const provider = this.providers.get(key);
251
+ return {
252
+ accountId: provider.accountId,
253
+ blockHcl: provider.block.render('', context),
254
+ providerType: provider.providerType,
255
+ regionId: provider.regionId,
256
+ requiredProvider: this.requiredProviders[provider.providerType],
257
+ };
258
+ }),
259
+ }));
260
+ }
261
+ /**
262
+ * The IDs of the module folders this run rendered.
263
+ */
264
+ getModuleIds() {
265
+ return [...this.modules.keys()];
266
+ }
267
+ /**
268
+ * How each octo resource maps into terraform, which validate and commit read to match a plan
269
+ * entry or an output back to the resource that produced it.
270
+ */
271
+ getOctoTerraformResourceMappings() {
272
+ return [...this.resourceRegistry.values()].map((r) => {
273
+ const isExternal = r.externalResultExpression !== undefined;
274
+ return {
275
+ destroyLosesData: r.destroyLosesData,
276
+ entireResponseOutput: isExternal ? r.outputs.get(r.resourceId).name : undefined,
277
+ moduleId: r.moduleId,
278
+ outputMappings: isExternal ? [] : [...r.outputs.entries()].map(([key, o]) => ({ key, outputName: o.name })),
279
+ resourceContext: r.resourceContext,
280
+ resourceId: r.resourceId,
281
+ terraformAddresses: r.terraformResources.map((t) => t.address),
282
+ };
283
+ });
284
+ }
285
+ /**
286
+ * Resource→resource value-reference edges, keyed by the referenced (producer) resourceId → the set
287
+ * of resourceIds that reference it. Edges follow `getRef` value references only — the same graph
288
+ * terraform cascades replacement along (force-new). `depends_on`/explicit parents are excluded:
289
+ * an ordering edge carries no value and does not force a dependent's replacement.
290
+ *
291
+ * Used by validate to attribute terraform's transitive replacement cascade: when octo replaces a
292
+ * resource, terraform recreates everything that (transitively) references it, so those changes are
293
+ * expected rather than unattributed.
294
+ */
295
+ getResourceReferrers() {
296
+ const referrers = new Map();
297
+ const addEdge = (referentId, referrerId) => {
298
+ if (referentId === referrerId) {
299
+ return;
300
+ }
301
+ if (!referrers.has(referentId)) {
302
+ referrers.set(referentId, new Set());
303
+ }
304
+ referrers.get(referentId).add(referrerId);
305
+ };
306
+ for (const resource of this.resourceRegistry.values()) {
307
+ const refs = [];
308
+ resource.collectRefs(refs);
309
+ for (const ref of refs) {
310
+ addEdge(ref.resourceId, resource.resourceId);
311
+ }
312
+ // An external resource consumes each parent's outputs as inputs — its null_resource triggers
313
+ // embed the parent's values, so a parent replace recreates it. That is a true value edge, even
314
+ // though it is recorded as an explicit parent (and its trigger refs are wired only at render
315
+ // time, so they are not yet in `collectRefs` here).
316
+ if (resource.externalResultExpression !== undefined) {
317
+ for (const parentResourceId of resource.explicitParentResourceIds) {
318
+ addEdge(parentResourceId, resource.resourceId);
319
+ }
320
+ }
321
+ }
322
+ return referrers;
323
+ }
324
+ /**
325
+ * Records that a resource takes over the terraform state of a resource octo committed, so terraform
326
+ * updates or replaces the infrastructure in place instead of creating the new resource beside the old.
327
+ * Within one folder, the folder renders a `moved` block from each of the old resource's addresses to
328
+ * the new one's. Between folders, the folder record lists the move, and `TerraformUtility`
329
+ * moves the state before terraform runs.
330
+ */
331
+ moveResource(fromModuleId, fromResourceId, toModuleId, toResourceId) {
332
+ this.module(toModuleId).moves.push({ fromModuleId, fromResourceId, toResourceId });
333
+ }
334
+ /**
335
+ * Phase 2 of rendering: prints the four files of every folder from the wiring computed in
336
+ * phase 1. Makes no decisions — when a {@link RefHclNode} renders, its answer is looked up in the
337
+ * folder's precomputed `resolvedRefs`. Cross-folder dependency cycles throw (in phase 1).
338
+ *
339
+ * A folder also keeps every provider block `previousFolders` recorded for it that none of its
340
+ * resources use any more, so terraform can still destroy what that provider created. A resource
341
+ * whose id moved to another account, for one, leaves its old address in the folder's state, bound
342
+ * to the old account's provider.
343
+ */
344
+ renderAllModules(previousFolders = []) {
345
+ const wiring = this.computeWiring();
346
+ const result = new Map();
347
+ for (const module of this.modules.values()) {
348
+ const moduleWiring = wiring.get(module.moduleId);
349
+ const context = {
350
+ resolveRef: (ref) => moduleWiring.resolvedRefs.get(`${ref.resourceId}.${ref.key}`),
351
+ step: this.step,
352
+ };
353
+ // main.tf: terraform config, provider blocks, data sources, resources.
354
+ const requiredProviders = {};
355
+ for (const key of module.providerKeys) {
356
+ const providerType = this.providers.get(key).providerType;
357
+ requiredProviders[providerType] = this.requiredProviders[providerType];
358
+ }
359
+ const retainedProviders = (previousFolders.find((folder) => folder.moduleId === module.moduleId)?.providers ?? []).filter((recorded) => ![...module.providerKeys].some((key) => {
360
+ const provider = this.providers.get(key);
361
+ return (provider.providerType === recorded.providerType &&
362
+ provider.accountId === recorded.accountId &&
363
+ provider.regionId === recorded.regionId);
364
+ }));
365
+ for (const provider of retainedProviders) {
366
+ requiredProviders[provider.providerType] ??= provider.requiredProvider;
367
+ }
368
+ if (module.hasExternalResources) {
369
+ requiredProviders['null'] = this.requiredProviders['null'] ?? { source: 'hashicorp/null' };
370
+ requiredProviders['external'] = this.requiredProviders['external'] ?? { source: 'hashicorp/external' };
371
+ }
372
+ const mainTf = [
373
+ this.renderConfigBlock(requiredProviders, context),
374
+ ...[...module.providerKeys].sort().map((key) => this.providers.get(key).block.render('', context)),
375
+ ...retainedProviders.map((provider) => provider.blockHcl),
376
+ ...module.dataSources.map((d) => d.render(context)),
377
+ ...module.resources.map((r) => r.terraformResources.map((t) => t.render(context, moduleWiring.parentDependsOn.get(r) ?? [])).join('\n\n')),
378
+ ...this.getMovedAddresses(module).map(({ from, to }) => `moved {\n${this.step}from = ${from}\n${this.step}to = ${to}\n}`),
379
+ ]
380
+ .filter(Boolean)
381
+ .join('\n\n');
382
+ // variables.tf: auto variables from cross-module refs, then user variables. An external
383
+ // producer's variable carries its entire response object (consumed as `var.<name>.<key>`), so it
384
+ // must be typed `map(string)` — terraform's external-data contract — otherwise an un-typed variable
385
+ // coerces the object to a JSON string and `var.<name>.<key>` fails at plan/apply. Per-key scalar
386
+ // variables stay un-typed.
387
+ const variablesTf = [
388
+ ...(module.needsProjectRoot
389
+ ? [`variable "${PROJECT_ROOT_VARIABLE_NAME}" {\n${this.step}type = string\n}`]
390
+ : []),
391
+ ...[...moduleWiring.autoVariables.entries()]
392
+ .sort(([a], [b]) => a.localeCompare(b))
393
+ .map(([name, v]) => v.objectKeys ? `variable "${name}" {\n${this.step}type = map(string)\n}` : `variable "${name}" {}`),
394
+ ...module.variables.map((v) => v.render(context)),
395
+ ]
396
+ .filter(Boolean)
397
+ .join('\n\n');
398
+ // outputs.tf: every resource's declared outputs.
399
+ const outputsTf = module.resources
400
+ .map((r) => [...r.outputs.values()]
401
+ .map((o) => new TerraformOutput(o.name, o.value, o.sensitive).render(context))
402
+ .join('\n\n'))
403
+ .filter(Boolean)
404
+ .join('\n\n');
405
+ const terragruntHcl = this.renderTerragrunt(moduleWiring);
406
+ result.set(module.moduleId, {
407
+ mainTf: mainTf + '\n',
408
+ outputsTf: outputsTf ? outputsTf + '\n' : '',
409
+ terragruntHcl: terragruntHcl + '\n',
410
+ variablesTf: variablesTf ? variablesTf + '\n' : '',
411
+ });
412
+ }
413
+ return result;
414
+ }
415
+ /**
416
+ * Prints the four files of an **empty** folder from a recorded folder: a `main.tf` with the
417
+ * config block rebuilt from the recorded `required_providers` entries (plus null/external when
418
+ * the folder had external resources) and the recorded provider blocks verbatim; empty
419
+ * variables/outputs; a remote_state-only `terragrunt.hcl`.
420
+ *
421
+ * Reads no module state;
422
+ * the generate mode decides which folders are emptied and which recorded folder each renders from.
423
+ */
424
+ renderEmptyModule(record) {
425
+ const context = {
426
+ resolveRef: () => {
427
+ throw new Error('Resource references are not supported in emptied folders!');
428
+ },
429
+ step: this.step,
430
+ };
431
+ const requiredProviders = {};
432
+ for (const provider of record.providers) {
433
+ requiredProviders[provider.providerType] = provider.requiredProvider;
434
+ }
435
+ if (record.hasExternalResources) {
436
+ requiredProviders['null'] = this.requiredProviders['null'] ?? { source: 'hashicorp/null' };
437
+ requiredProviders['external'] = this.requiredProviders['external'] ?? { source: 'hashicorp/external' };
438
+ }
439
+ const mainTf = [this.renderConfigBlock(requiredProviders, context), ...record.providers.map((p) => p.blockHcl)]
440
+ .filter(Boolean)
441
+ .join('\n\n');
442
+ return {
443
+ mainTf: mainTf + '\n',
444
+ outputsTf: '',
445
+ terragruntHcl: this.renderTerragrunt({
446
+ autoVariables: new Map(),
447
+ moduleDependencies: new Map(),
448
+ parentDependsOn: new Map(),
449
+ resolvedRefs: new Map(),
450
+ }) + '\n',
451
+ variablesTf: '',
452
+ };
453
+ }
454
+ /**
455
+ * Drops everything the last run rendered, keeping the registered providers and config.
456
+ */
457
+ reset() {
458
+ this.minTerraformVersion = '1.6.0';
459
+ this.providers.clear();
460
+ this.requiredProviders = {};
461
+ this.resetTransactionState();
462
+ }
463
+ /**
464
+ * Clears what a transaction's sweep contributed (modules, resources, wiring), keeping the
465
+ * per-process registrations (terraform config and providers) intact. Test harnesses that run
466
+ * multiple transactions in one process call this between them; a real octo process runs one
467
+ * transaction, so production code never resets.
468
+ */
469
+ resetTransactionState() {
470
+ this.modules.clear();
471
+ this.pendingExternalResourcesInputWiring.length = 0;
472
+ this.resourceRegistry.clear();
473
+ this.sanitizedResourceIds.clear();
474
+ }
475
+ /**
476
+ * The {@link TerraformModuleScope} handed to a resource's `toHCL()`, bound to one module folder.
477
+ */
478
+ scope(moduleId) {
479
+ return {
480
+ addOctoTerraformExternalResource: (octoResource) => this.addOctoTerraformExternalResource(moduleId, octoResource),
481
+ addOctoTerraformResource: (octoResource, options = {}) => this.addOctoTerraformResource(moduleId, octoResource, options),
482
+ addTerraformData: (type, name, spec = {}) => this.addTerraformData(moduleId, type, name, spec),
483
+ getProviderAliasRef: (accountId, regionId) => this.getProviderAliasRef(moduleId, accountId, regionId),
484
+ getRef: ((resource, key) => this.getRef(resource, key)),
485
+ interpolate: (...parts) => this.interpolate(parts),
486
+ jsonencode: (subject) => this.jsonencode(subject),
487
+ localPath: (filePath) => this.localPath(moduleId, filePath),
488
+ mapAttr: (value) => this.mapAttr(value),
489
+ moduleId,
490
+ raw: (value) => this.raw(value),
491
+ type: (schema) => this.type(schema),
492
+ variable: (name, typeExpression, options) => this.variable(moduleId, name, typeExpression, options),
493
+ };
494
+ }
495
+ /**
496
+ * Generates the `null_resource` + `data "external"` wrapper for an external resource.
497
+ *
498
+ * Only the parts that depend on no other resource are built here:
499
+ * - the `null_resource` shell (its triggers and `local-exec` provisioners are filled in later),
500
+ * - the `data "external"` read of the response octo recorded, which terraform re-runs on every plan
501
+ * and every apply, and defers to apply time only while the `null_resource` is planned to change,
502
+ * - the single whole-result output (`data.external.<name>.result`) that carries the entire response.
503
+ *
504
+ * The external resource is never enumerated per response key: its response shape is unknown at
505
+ * generation time (the action has not run) and is, by the `data "external"` protocol, a flat map of
506
+ * strings. Refs to it (`getRef`) resolve by indexing this one expression; commit reads the whole map back.
507
+ *
508
+ * The parent-input wiring (triggers + create/destroy commands) reads every parent's outputs, which
509
+ * only all exist once registration is complete. It is therefore deferred to
510
+ * {@link wireExternalResourceInputs} (run at the start of {@link computeWiring}). This deferral is
511
+ * what lets the generate sweep contribute resources in any order, rather than parents-first.
512
+ */
513
+ addOctoTerraformExternalResource(moduleId, octoResource) {
514
+ const resourceId = octoResource.resourceId;
515
+ const name = StringUtility.sanitizeForIdentifier(resourceId);
516
+ const parents = (octoResource.parents || []).map((p) => p instanceof MatchingResource ? p.getActual() : p);
517
+ const octoTerraformResource = this.addOctoTerraformResource(moduleId, octoResource, {
518
+ explicitParents: parents,
519
+ externalResultExpression: `data.external.${name}.result`,
520
+ });
521
+ this.module(moduleId).hasExternalResources = true;
522
+ // The null_resource shell. Its triggers and provisioner are added by wireExternalResourceInputs(),
523
+ // once every parent's outputs are known.
524
+ const nullResource = octoTerraformResource.addTerraformResource('null_resource', name);
525
+ const dataExternal = this.addTerraformData(moduleId, 'external', name, {
526
+ program: [...this.octoCommandPrefix.split(' '), 'get-response', `--resourceId=${resourceId}`],
527
+ });
528
+ dataExternal.attribute('depends_on', new ExpressionHclNode(`[null_resource.${name}]`));
529
+ this.pendingExternalResourcesInputWiring.push({ nullResource, octoResource, resourceId });
530
+ return octoTerraformResource;
531
+ }
532
+ /**
533
+ * @internal
534
+ */
535
+ addOctoTerraformResource(moduleId, octoResource, options = {}) {
536
+ const explicitParentResourceIds = (options.explicitParents || []).map((p) => p instanceof MatchingResource ? p.getSchemaInstanceInResourceAction().resourceId : p.resourceId);
537
+ // Distinct resource ids must stay distinct after sanitization, or else the generated output names,
538
+ // identifiers, and variable names would silently collide. Environment-variable sanitization
539
+ // merges strictly more characters than identifier sanitization, so this one check covers both.
540
+ const sanitizedResourceId = StringUtility.sanitizeForEnvironmentVariable(octoResource.resourceId);
541
+ const existingResourceId = this.sanitizedResourceIds.get(sanitizedResourceId);
542
+ if (existingResourceId !== undefined) {
543
+ if (existingResourceId === octoResource.resourceId) {
544
+ throw new Error(`Resource id "${octoResource.resourceId}" is already registered!`);
545
+ }
546
+ throw new Error(`Resource id "${octoResource.resourceId}" collides with resource id "${existingResourceId}" after sanitization!`);
547
+ }
548
+ this.sanitizedResourceIds.set(sanitizedResourceId, octoResource.resourceId);
549
+ const module = this.module(moduleId);
550
+ let providerRef;
551
+ if (options.provider) {
552
+ const { alias, key, providerType } = this.findProvider(options.provider.accountId, options.provider.regionId);
553
+ providerRef = `${providerType}.${alias}`;
554
+ module.providerKeys.add(key);
555
+ }
556
+ // Seed mock outputs from the producer schema's declared response defaults — never the live
557
+ // response — so a regenerate after a commit cannot inline real (possibly secret) applied values.
558
+ // Test resources carry a non-constructable NODE_SCHEMA; they fall back to synthetic placeholders.
559
+ const schemaClass = octoResource.constructor
560
+ .NODE_SCHEMA;
561
+ const responseDefaults = typeof schemaClass === 'function' ? (getSchemaDefaults(schemaClass).response ?? {}) : {};
562
+ const newResource = new OctoTerraformResource(octoResource.resourceId, octoResource.getContext(), moduleId, octoResource.constructor.DESTROY_LOSES_DATA ?? false, explicitParentResourceIds, providerRef, options.externalResultExpression, responseDefaults);
563
+ module.resources.push(newResource);
564
+ this.resourceRegistry.set(octoResource.resourceId, newResource);
565
+ return newResource;
566
+ }
567
+ /**
568
+ * @internal
569
+ */
570
+ addTerraformData(moduleId, type, name, spec = {}) {
571
+ const dataSource = new TerraformData(type, StringUtility.sanitizeForIdentifier(name));
572
+ addTerraformResourceSpec(dataSource, spec);
573
+ this.module(moduleId).dataSources.push(dataSource);
574
+ return dataSource;
575
+ }
576
+ /**
577
+ * Phase 1 of rendering: resolve every {@link RefHclNode} and compute each folder's wiring.
578
+ *
579
+ * For every folder: collect all refs made from it, then resolve each one —
580
+ * - producer in the same folder → the inline terraform expression,
581
+ * - producer in another folder → a `var.*` name, plus three recorded consequences: declare the
582
+ * variable, depend on the producer's folder, wire the input in terragrunt.hcl.
583
+ *
584
+ * Explicit parents resolve similarly: same folder → `depends_on` addresses; other folder → a
585
+ * terragrunt dependency. All ref errors and cross-folder cycles surface here — phase 2 cannot
586
+ * fail.
587
+ */
588
+ computeWiring() {
589
+ // Resolve deferred external-resource input wiring before collecting refs, so the triggers and
590
+ // commands it produces are part of this pass.
591
+ this.wireExternalResourceInputs();
592
+ const wiring = new Map();
593
+ const wiringOf = (moduleId) => {
594
+ if (!wiring.has(moduleId)) {
595
+ wiring.set(moduleId, {
596
+ autoVariables: new Map(),
597
+ moduleDependencies: new Map(),
598
+ parentDependsOn: new Map(),
599
+ resolvedRefs: new Map(),
600
+ });
601
+ }
602
+ return wiring.get(moduleId);
603
+ };
604
+ const addModuleDependency = (moduleWiring, producerModuleId, reason) => {
605
+ if (!moduleWiring.moduleDependencies.has(producerModuleId)) {
606
+ moduleWiring.moduleDependencies.set(producerModuleId, new Set());
607
+ }
608
+ moduleWiring.moduleDependencies.get(producerModuleId).add(reason);
609
+ };
610
+ // Producer output expressions render without a consumer context; a ref inside an output
611
+ // expression would require recursive resolution and is not supported.
612
+ const renderInlineContext = {
613
+ resolveRef: () => {
614
+ throw new Error('Nested resource references are not supported in output expressions!');
615
+ },
616
+ step: this.step,
617
+ };
618
+ for (const module of this.modules.values()) {
619
+ const moduleWiring = wiringOf(module.moduleId);
620
+ // Collect every reference made from this module.
621
+ const refs = [];
622
+ module.resources.forEach((r) => r.collectRefs(refs));
623
+ module.dataSources.forEach((d) => d.collectRefs(refs));
624
+ module.variables.forEach((v) => v.collectRefs(refs));
625
+ for (const ref of refs) {
626
+ const producer = this.resourceRegistry.get(ref.resourceId);
627
+ if (!producer) {
628
+ throw new Error(`Resource "${ref.resourceId}" not found in Octo Terraform!`);
629
+ }
630
+ if (producer.externalResultExpression !== undefined) {
631
+ if (producer.moduleId === module.moduleId) {
632
+ moduleWiring.resolvedRefs.set(`${ref.resourceId}.${ref.key}`, ref.entireResponse
633
+ ? producer.externalResultExpression
634
+ : `${producer.externalResultExpression}.${ref.key}`);
635
+ }
636
+ else {
637
+ const variableName = StringUtility.sanitizeForEnvironmentVariable(ref.resourceId);
638
+ const entireResponseOutput = producer.outputs.get(producer.resourceId);
639
+ // The variable holds the producer's entire response object; collect the keys consumed
640
+ // across all refs so the mock can be shaped as an object (see `objectKeys`).
641
+ const objectKeys = moduleWiring.autoVariables.get(variableName)?.objectKeys ?? new Set();
642
+ if (!ref.entireResponse) {
643
+ objectKeys.add(ref.key);
644
+ }
645
+ moduleWiring.autoVariables.set(variableName, {
646
+ objectKeys,
647
+ outputName: entireResponseOutput.name,
648
+ producerModuleId: producer.moduleId,
649
+ producerResourceId: producer.resourceId,
650
+ });
651
+ addModuleDependency(moduleWiring, producer.moduleId, `ref "${ref.resourceId}.${ref.key}"`);
652
+ moduleWiring.resolvedRefs.set(`${ref.resourceId}.${ref.key}`, ref.entireResponse ? `var.${variableName}` : `var.${variableName}.${ref.key}`);
653
+ }
654
+ continue;
655
+ }
656
+ const output = producer.outputs.get(ref.key);
657
+ if (!output) {
658
+ throw new Error(`Ref "${ref.key}" not registered for resource "${ref.resourceId}" in Octo Terraform!`);
659
+ }
660
+ if (producer.moduleId === module.moduleId) {
661
+ moduleWiring.resolvedRefs.set(`${ref.resourceId}.${ref.key}`, output.value.render('', renderInlineContext));
662
+ }
663
+ else {
664
+ const variableName = StringUtility.sanitizeForEnvironmentVariable(`${ref.resourceId}_${ref.key}`);
665
+ const existingVariable = moduleWiring.autoVariables.get(variableName);
666
+ if (existingVariable && existingVariable.outputName !== output.name) {
667
+ throw new Error(`Variable "${variableName}" maps to both outputs "${existingVariable.outputName}" and ` +
668
+ `"${output.name}"! Rename one of the resources or response keys.`);
669
+ }
670
+ moduleWiring.autoVariables.set(variableName, {
671
+ outputName: output.name,
672
+ producerModuleId: producer.moduleId,
673
+ producerResourceId: producer.resourceId,
674
+ responseKey: ref.key,
675
+ });
676
+ addModuleDependency(moduleWiring, producer.moduleId, `ref "${ref.resourceId}.${ref.key}"`);
677
+ moduleWiring.resolvedRefs.set(`${ref.resourceId}.${ref.key}`, `var.${variableName}`);
678
+ }
679
+ }
680
+ // Explicit parents: same module → depends_on; cross module → terragrunt ordering.
681
+ for (const resource of module.resources) {
682
+ const addresses = [];
683
+ for (const parentResourceId of resource.explicitParentResourceIds) {
684
+ const parent = this.resourceRegistry.get(parentResourceId);
685
+ if (!parent) {
686
+ throw new Error(`Explicit parent "${parentResourceId}" of resource "${resource.resourceId}" is not registered in Terraform!`);
687
+ }
688
+ if (parent.moduleId === module.moduleId) {
689
+ addresses.push(...parent.terraformResources.map((t) => t.address));
690
+ }
691
+ else {
692
+ addModuleDependency(moduleWiring, parent.moduleId, `parent "${parentResourceId}" of "${resource.resourceId}"`);
693
+ }
694
+ }
695
+ moduleWiring.parentDependsOn.set(resource, addresses);
696
+ }
697
+ }
698
+ this.detectModuleCycles(wiring);
699
+ return wiring;
700
+ }
701
+ /**
702
+ * Depth-first search over folder dependencies; throws on the first cycle, naming its path and
703
+ * the refs/explicit parents forming each edge.
704
+ */
705
+ detectModuleCycles(wiring) {
706
+ const visited = new Set();
707
+ const inStack = new Set();
708
+ const visit = (moduleId, path) => {
709
+ if (inStack.has(moduleId)) {
710
+ const cycleStart = path.indexOf(moduleId);
711
+ const cycle = [...path.slice(cycleStart), moduleId];
712
+ const edges = cycle.slice(0, -1).map((from, i) => {
713
+ const to = cycle[i + 1];
714
+ const reasons = [...(wiring.get(from)?.moduleDependencies.get(to) ?? [])];
715
+ return `"${from}" -> "${to}" via ${reasons.join(', ')}`;
716
+ });
717
+ throw new Error(`Found cross-module dependency cycle: ${cycle.join(' -> ')}! Edges: ${edges.join('; ')}.`);
718
+ }
719
+ if (visited.has(moduleId)) {
720
+ return;
721
+ }
722
+ inStack.add(moduleId);
723
+ for (const dependency of wiring.get(moduleId)?.moduleDependencies.keys() ?? []) {
724
+ visit(dependency, [...path, moduleId]);
725
+ }
726
+ inStack.delete(moduleId);
727
+ visited.add(moduleId);
728
+ };
729
+ for (const moduleId of wiring.keys()) {
730
+ visit(moduleId, []);
731
+ }
732
+ }
733
+ /**
734
+ * Derives the registered provider for a deployment target using only `accountId` (+ `regionId`).
735
+ * The provider type was decided during provider registration.
736
+ *
737
+ * Without a `regionId` (global resources), any provider of the account matches — the first
738
+ * registered wins, since the alias only routes the region.
739
+ */
740
+ findProvider(accountId, regionId) {
741
+ const matches = [...this.providers.entries()].filter(([, p]) => p.accountId === accountId && (regionId === undefined || p.regionId === regionId));
742
+ const target = regionId === undefined ? `account "${accountId}"` : `account "${accountId}" and region "${regionId}"`;
743
+ if (matches.length === 0) {
744
+ throw new Error(`No provider registered for ${target}!`);
745
+ }
746
+ // A resource names its provider by account and region only, never by provider type, so that pair
747
+ // has to identify one provider. Deliberate: outside aws the account id is expected to be what tells
748
+ // them apart, which keeps the pair unique in practice, and threading a provider type through
749
+ // every resource binding costs more than the ambiguity is worth. Hence a hint rather than a fix.
750
+ const providerTypes = new Set(matches.map(([, p]) => p.providerType));
751
+ if (regionId !== undefined && providerTypes.size > 1) {
752
+ throw new Error(`Multiple providers registered for ${target}! Cannot derive a unique provider. ` +
753
+ `Give each provider type its own accountId, since a resource selects its provider by ` +
754
+ `account and region alone. Registered here: ${[...providerTypes].sort().join(', ')}.`);
755
+ }
756
+ const [key, provider] = matches[0];
757
+ return { alias: provider.alias, key, providerType: provider.providerType };
758
+ }
759
+ /**
760
+ * The moves of a folder's record: the resources whose state moves in from another folder, with the
761
+ * outputs their consumers in other folders read.
762
+ */
763
+ getFolderMoves(module) {
764
+ const moves = module.moves
765
+ .filter(({ fromModuleId }) => fromModuleId !== module.moduleId)
766
+ .map(({ fromModuleId, fromResourceId, toResourceId }) => {
767
+ const fromName = StringUtility.sanitizeForIdentifier(fromResourceId);
768
+ const toName = StringUtility.sanitizeForIdentifier(toResourceId);
769
+ return {
770
+ addresses: this.getMoveAddresses(fromResourceId, toResourceId),
771
+ fromModuleId,
772
+ outputs: [...this.resourceRegistry.get(toResourceId).outputs.values()]
773
+ .filter(({ name }) => name.startsWith(toName))
774
+ .map(({ name }) => ({ from: `${fromName}${name.slice(toName.length)}`, to: name })),
775
+ resourceId: toResourceId,
776
+ };
777
+ });
778
+ return moves.length > 0 ? { moves } : {};
779
+ }
780
+ /**
781
+ * The addresses a resource's state moves between. A block named `<resourceId><suffix>` moves from the
782
+ * block the old resource named `<its resourceId><suffix>`; a `from` the state does not hold moves nothing.
783
+ */
784
+ getMoveAddresses(fromResourceId, toResourceId) {
785
+ const fromName = StringUtility.sanitizeForIdentifier(fromResourceId);
786
+ const toName = StringUtility.sanitizeForIdentifier(toResourceId);
787
+ const addresses = [];
788
+ for (const { address } of this.resourceRegistry.get(toResourceId).terraformResources) {
789
+ const typeLength = address.indexOf('.') + 1;
790
+ const name = address.slice(typeLength);
791
+ if (name.startsWith(toName)) {
792
+ addresses.push({ from: `${address.slice(0, typeLength)}${fromName}${name.slice(toName.length)}`, to: address });
793
+ }
794
+ }
795
+ return addresses;
796
+ }
797
+ /**
798
+ * The `moved` addresses of a folder, for the resources taking over a state the same folder holds.
799
+ */
800
+ getMovedAddresses(module) {
801
+ return module.moves
802
+ .filter(({ fromModuleId }) => fromModuleId === module.moduleId)
803
+ .flatMap(({ fromResourceId, toResourceId }) => this.getMoveAddresses(fromResourceId, toResourceId));
804
+ }
805
+ /**
806
+ * @internal
807
+ */
808
+ getProviderAliasRef(moduleId, accountId, regionId) {
809
+ const { alias, key, providerType } = this.findProvider(accountId, regionId);
810
+ this.module(moduleId).providerKeys.add(key);
811
+ return new ExpressionHclNode(`${providerType}.${alias}`);
812
+ }
813
+ /**
814
+ * @internal
815
+ */
816
+ getRef(resource, key) {
817
+ const resourceId = resource instanceof MatchingResource
818
+ ? resource.getSchemaInstanceInResourceAction().resourceId
819
+ : resource.resourceId;
820
+ return new RefHclNode(resourceId, key);
821
+ }
822
+ /**
823
+ * @internal
824
+ */
825
+ interpolate(parts) {
826
+ return new InterpolatedStringHclNode(parts);
827
+ }
828
+ /**
829
+ * @internal
830
+ */
831
+ jsonencode(subject) {
832
+ return new JsonEncodeHclNode(subject);
833
+ }
834
+ /**
835
+ * Renders a local file path as `"${var.OCTO_PROJECT_ROOT}/<path from the project root>"`, and
836
+ * marks this folder as needing that variable declared.
837
+ *
838
+ * Separators are normalized to `/`, so a folder generated on Windows reads the same as one
839
+ * generated anywhere else.
840
+ */
841
+ localPath(moduleId, filePath) {
842
+ this.module(moduleId).needsProjectRoot = true;
843
+ const pathFromProjectRoot = relative(process.cwd(), resolve(filePath)).split(sep).join('/');
844
+ return `"\${var.${PROJECT_ROOT_VARIABLE_NAME}}/${pathFromProjectRoot}"`;
845
+ }
846
+ /**
847
+ * @internal
848
+ */
849
+ mapAttr(value) {
850
+ return new MapHclNode(Object.entries(value).map(([k, v]) => [k, toHclNode(v)]));
851
+ }
852
+ /**
853
+ * @internal
854
+ */
855
+ module(moduleId) {
856
+ if (!this.modules.has(moduleId)) {
857
+ this.modules.set(moduleId, new TerraformModule(moduleId));
858
+ }
859
+ return this.modules.get(moduleId);
860
+ }
861
+ /**
862
+ * @internal
863
+ */
864
+ providerKey(providerType, accountId, regionId) {
865
+ return `${providerType}:${accountId}:${regionId}`;
866
+ }
867
+ /**
868
+ * @internal
869
+ */
870
+ raw(value) {
871
+ return new ExpressionHclNode(String(value));
872
+ }
873
+ /**
874
+ * Prints one folder's `terraform { required_version, required_providers }` config block.
875
+ */
876
+ renderConfigBlock(requiredProviders, context) {
877
+ const configBlock = new TerraformBlock('terraform');
878
+ configBlock.attribute('required_version', `>= ${this.minTerraformVersion}`);
879
+ const providerTypes = Object.keys(requiredProviders).sort();
880
+ if (providerTypes.length > 0) {
881
+ const requiredProvidersBlock = configBlock.block('required_providers');
882
+ for (const providerType of providerTypes) {
883
+ const entry = requiredProviders[providerType];
884
+ const lines = [`source = "${entry.source}"`];
885
+ if (entry.minVersion) {
886
+ lines.push(`version = ">= ${entry.minVersion}"`);
887
+ }
888
+ requiredProvidersBlock.attribute(providerType, new MapHclNodeRaw(lines));
889
+ }
890
+ }
891
+ return configBlock.render('', context);
892
+ }
893
+ /**
894
+ * Prints one folder's `terragrunt.hcl`: a `dependency` block per producer folder (with
895
+ * `mock_outputs`, shallow-merged over any applied state, so `run --all -- plan` works both before the
896
+ * first apply and when intent renames a producer resource), and the `inputs` map wiring each auto
897
+ * variable to the producer's output.
898
+ */
899
+ renderTerragrunt(moduleWiring) {
900
+ const step = this.step;
901
+ const sections = [];
902
+ // Pin the (local) backend state to a stable absolute path in the module folder. Terragrunt
903
+ // copies each module into a per-invocation `.terragrunt-cache` working directory; without this,
904
+ // local state lands inside that throwaway copy, so a later `terragrunt output` (run from a fresh
905
+ // copy) cannot see the state a prior `terragrunt apply` wrote. Generating a `backend.tf` that
906
+ // points at `${get_terragrunt_dir()}/terraform.tfstate` keeps the state in one fixed place that
907
+ // every terragrunt command shares. Users who want a remote backend override this block.
908
+ sections.push([
909
+ 'remote_state {',
910
+ `${step}backend = "local"`,
911
+ `${step}generate = {`,
912
+ `${step}${step}path = "backend.tf"`,
913
+ `${step}${step}if_exists = "overwrite_terragrunt"`,
914
+ `${step}}`,
915
+ `${step}config = {`,
916
+ `${step}${step}path = "\${get_terragrunt_dir()}/terraform.tfstate"`,
917
+ `${step}}`,
918
+ '}',
919
+ ].join('\n'));
920
+ // Dependency blocks, with mock outputs so `run --all -- plan` works before first apply.
921
+ for (const producerModuleId of [...moduleWiring.moduleDependencies.keys()].sort()) {
922
+ const dependencyName = StringUtility.sanitizeForIdentifier(producerModuleId);
923
+ // Map each consumed output name to its mock shape: a flat string for per-key scalar outputs,
924
+ // or an object (keyed by the accessed fields) for an external producer's entire-response output.
925
+ const mockOutputs = new Map();
926
+ for (const v of moduleWiring.autoVariables.values()) {
927
+ if (v.producerModuleId !== producerModuleId) {
928
+ continue;
929
+ }
930
+ if (v.objectKeys) {
931
+ const responseKeys = mockOutputs.get(v.outputName)?.responseKeys ?? new Set();
932
+ for (const key of v.objectKeys) {
933
+ responseKeys.add(key);
934
+ }
935
+ mockOutputs.set(v.outputName, { producerResourceId: v.producerResourceId, responseKeys });
936
+ }
937
+ else if (!mockOutputs.has(v.outputName)) {
938
+ mockOutputs.set(v.outputName, { producerResourceId: v.producerResourceId, responseKey: v.responseKey });
939
+ }
940
+ }
941
+ // Prefer the producer schema's declared default value (an author-written, non-secret
942
+ // placeholder, e.g. a syntactically valid ARN) over a synthetic placeholder, falling back to
943
+ // `mock-*` when the schema declares no default for that response key.
944
+ const mockValue = (producerResourceId, responseKey, fallback) => {
945
+ const value = this.resourceRegistry.get(producerResourceId)?.responseDefaults[responseKey];
946
+ return typeof value === 'string' && value.length > 0 ? value : fallback;
947
+ };
948
+ const lines = [`dependency "${dependencyName}" {`, `${step}config_path = "../${producerModuleId}"`];
949
+ if (mockOutputs.size > 0) {
950
+ lines.push('');
951
+ lines.push(`${step}mock_outputs = {`);
952
+ for (const outputName of [...mockOutputs.keys()].sort()) {
953
+ const { producerResourceId, responseKey, responseKeys } = mockOutputs.get(outputName);
954
+ if (responseKeys) {
955
+ const fields = [...responseKeys]
956
+ .sort()
957
+ .map((key) => `${key} = "${mockValue(producerResourceId, key, `mock-${outputName}-${key}`)}"`);
958
+ lines.push(`${step}${step}"${outputName}" = { ${fields.join(', ')} }`);
959
+ }
960
+ else {
961
+ lines.push(`${step}${step}"${outputName}" = "${mockValue(producerResourceId, responseKey, `mock-${outputName}`)}"`);
962
+ }
963
+ }
964
+ lines.push(`${step}}`);
965
+ lines.push(`${step}mock_outputs_allowed_terraform_commands = ["init", "plan", "show", "validate"]`);
966
+ lines.push(`${step}mock_outputs_merge_strategy_with_state = "shallow"`);
967
+ }
968
+ lines.push('}');
969
+ sections.push(lines.join('\n'));
970
+ }
971
+ // Inputs wiring.
972
+ const inputEntries = [...moduleWiring.autoVariables.entries()].sort(([a], [b]) => a.localeCompare(b));
973
+ if (inputEntries.length > 0) {
974
+ const lines = ['inputs = {'];
975
+ for (const [variableName, { outputName, producerModuleId }] of inputEntries) {
976
+ lines.push(`${step}${variableName} = dependency.${StringUtility.sanitizeForIdentifier(producerModuleId)}.outputs["${outputName}"]`);
977
+ }
978
+ lines.push('}');
979
+ sections.push(lines.join('\n'));
980
+ }
981
+ return sections.join('\n\n');
982
+ }
983
+ /**
984
+ * @internal
985
+ */
986
+ type(schema) {
987
+ return new TypeHclNode(schema);
988
+ }
989
+ /**
990
+ * @internal
991
+ */
992
+ variable(moduleId, name, typeExpression, options) {
993
+ const resolvedType = typeof typeExpression === 'string' && !['any', 'bool', 'number', 'string'].includes(typeExpression)
994
+ ? new ExpressionHclNode(typeExpression)
995
+ : new TypeHclNode(typeExpression);
996
+ const variable = new TerraformVariable(name, resolvedType, options);
997
+ this.module(moduleId).variables.push(variable);
998
+ return variable;
999
+ }
1000
+ /**
1001
+ * Resolves the deferred parent-input wiring for every external resource: for each parent, expands
1002
+ * its declared outputs into `--input` arguments on the `local-exec` create/destroy commands and
1003
+ * into individual triggers (so the `when = destroy` provisioner can reach the raw values via
1004
+ * `self.triggers`). Runs once all resources are registered, so it is independent of the order in
1005
+ * which resources were contributed. Idempotent: clears the queue, so a repeat call is a no-op.
1006
+ */
1007
+ wireExternalResourceInputs() {
1008
+ for (const { nullResource, octoResource, resourceId } of this.pendingExternalResourcesInputWiring) {
1009
+ const parents = (octoResource.parents || []).map((p) => p instanceof MatchingResource ? p.getActual() : p);
1010
+ // A native parent contributes one input per output key; an external parent its whole result as a single input.
1011
+ const inputs = [];
1012
+ for (const parent of parents) {
1013
+ const parentOctoResource = this.resourceRegistry.get(parent.resourceId);
1014
+ if (!parentOctoResource) {
1015
+ throw new Error(`Parent resource "${parent.resourceId}" of external resource "${resourceId}" is not registered with Terraform!`);
1016
+ }
1017
+ if (parentOctoResource.externalResultExpression !== undefined) {
1018
+ inputs.push({
1019
+ argKey: parent.resourceId,
1020
+ ref: new RefHclNode(parent.resourceId, '', true),
1021
+ triggerKey: StringUtility.sanitizeForEnvironmentVariable(`input_${parent.resourceId}`),
1022
+ });
1023
+ }
1024
+ else {
1025
+ for (const key of parentOctoResource.outputs.keys()) {
1026
+ inputs.push({
1027
+ argKey: `${parent.resourceId}.${key}`,
1028
+ ref: new RefHclNode(parent.resourceId, key),
1029
+ triggerKey: StringUtility.sanitizeForEnvironmentVariable(`input_${parent.resourceId}_${key}`),
1030
+ });
1031
+ }
1032
+ }
1033
+ }
1034
+ const createCommand = [
1035
+ `${this.octoCommandPrefix} run-action --resourceId=${resourceId}`,
1036
+ ];
1037
+ const destroyCommand = [
1038
+ `${this.octoCommandPrefix} run-action --resourceId=${resourceId}`,
1039
+ ];
1040
+ for (const input of inputs) {
1041
+ createCommand.push(` --input ${input.argKey}=`, input.ref);
1042
+ destroyCommand.push(` --input ${input.argKey}=\${self.triggers.${input.triggerKey}}`);
1043
+ }
1044
+ const triggers = [
1045
+ [
1046
+ 'octo_properties_hash',
1047
+ new LiteralHclNode(StringUtility.deterministicHash(octoResource.getExternalTriggerProperties())),
1048
+ ],
1049
+ ...inputs.map((i) => [i.triggerKey, new InterpolatedStringHclNode([i.ref])]),
1050
+ ];
1051
+ nullResource.attribute('triggers', new MapHclNode(triggers));
1052
+ const createProvisioner = nullResource.block('provisioner "local-exec"');
1053
+ createProvisioner.attribute('command', new InterpolatedStringHclNode(createCommand));
1054
+ const destroyProvisioner = nullResource.block('provisioner "local-exec"');
1055
+ destroyProvisioner.attribute('when', new ExpressionHclNode('destroy'));
1056
+ destroyProvisioner.attribute('command', new InterpolatedStringHclNode(destroyCommand));
1057
+ }
1058
+ this.pendingExternalResourcesInputWiring.length = 0;
1059
+ }
1060
+ }
1061
+ /**
1062
+ * @internal
1063
+ */
1064
+ let TerraformServiceFactory = class TerraformServiceFactory {
1065
+ /**
1066
+ * @internal
1067
+ */
1068
+ static async create({ octoCommandPrefix = 'octo', terraformFileIndentLength = 2, } = {}, forceNew = false) {
1069
+ if (!this.instance) {
1070
+ this.instance = new TerraformService(octoCommandPrefix, terraformFileIndentLength);
1071
+ }
1072
+ if (forceNew) {
1073
+ this.instance.reset();
1074
+ }
1075
+ return this.instance;
1076
+ }
1077
+ };
1078
+ TerraformServiceFactory = __decorate([
1079
+ Factory(TerraformService)
1080
+ ], TerraformServiceFactory);
1081
+ export { TerraformServiceFactory };
1082
+ //# sourceMappingURL=terraform.service.js.map