@geekmidas/cli 10.0.0-alpha.123 → 10.0.0-alpha.125

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 (265) hide show
  1. package/dist/{EndpointGenerator-BDa6pPgE.cjs → EndpointGenerator-BGxf9jWE.cjs} +8 -4
  2. package/dist/{EndpointGenerator-BDa6pPgE.cjs.map → EndpointGenerator-BGxf9jWE.cjs.map} +1 -1
  3. package/dist/{EndpointGenerator-CP_z_E3e.mjs → EndpointGenerator-errMeldx.mjs} +8 -4
  4. package/dist/{EndpointGenerator-CP_z_E3e.mjs.map → EndpointGenerator-errMeldx.mjs.map} +1 -1
  5. package/dist/{GoDaddyProvider-BlB5nJky.mjs → GoDaddyProvider-BN1dODm9.mjs} +3 -3
  6. package/dist/{GoDaddyProvider-BlB5nJky.mjs.map → GoDaddyProvider-BN1dODm9.mjs.map} +1 -1
  7. package/dist/{GoDaddyProvider-Dubq-pCc.cjs → GoDaddyProvider-BTdtqhI_.cjs} +3 -3
  8. package/dist/{GoDaddyProvider-Dubq-pCc.cjs.map → GoDaddyProvider-BTdtqhI_.cjs.map} +1 -1
  9. package/dist/{HostingerProvider-BNpS9aRJ.mjs → HostingerProvider-Cjh_2Ml6.mjs} +2 -2
  10. package/dist/{HostingerProvider-BNpS9aRJ.mjs.map → HostingerProvider-Cjh_2Ml6.mjs.map} +1 -1
  11. package/dist/{HostingerProvider-DR1iws1P.cjs → HostingerProvider-OdQmKXE3.cjs} +2 -2
  12. package/dist/{HostingerProvider-DR1iws1P.cjs.map → HostingerProvider-OdQmKXE3.cjs.map} +1 -1
  13. package/dist/{LegacyStateStore-ClvUC8qy.cjs → LegacyStateStore-C2S8TGwx.cjs} +3 -3
  14. package/dist/{LegacyStateStore-ClvUC8qy.cjs.map → LegacyStateStore-C2S8TGwx.cjs.map} +1 -1
  15. package/dist/{LegacyStateStore-DGALQGZu.mjs → LegacyStateStore-DgXmuG87.mjs} +3 -3
  16. package/dist/{LegacyStateStore-DGALQGZu.mjs.map → LegacyStateStore-DgXmuG87.mjs.map} +1 -1
  17. package/dist/{LocalStateStore-43kSYNpw.cjs → LocalStateStore-CaJw9H-3.cjs} +2 -2
  18. package/dist/{LocalStateStore-43kSYNpw.cjs.map → LocalStateStore-CaJw9H-3.cjs.map} +1 -1
  19. package/dist/{LocalStateStore-CBYUzVR5.mjs → LocalStateStore-ojY_BiMK.mjs} +2 -2
  20. package/dist/{LocalStateStore-CBYUzVR5.mjs.map → LocalStateStore-ojY_BiMK.mjs.map} +1 -1
  21. package/dist/{OpenApiTsGenerator-CsrukCE-.mjs → OpenApiTsGenerator-W_HRXH1X.mjs} +2 -2
  22. package/dist/{OpenApiTsGenerator-CsrukCE-.mjs.map → OpenApiTsGenerator-W_HRXH1X.mjs.map} +1 -1
  23. package/dist/{OpenApiTsGenerator-FNhY7Ssz.cjs → OpenApiTsGenerator-cQgtxWQo.cjs} +2 -2
  24. package/dist/{OpenApiTsGenerator-FNhY7Ssz.cjs.map → OpenApiTsGenerator-cQgtxWQo.cjs.map} +1 -1
  25. package/dist/{S3StateStore-DLHjDYYP.mjs → S3StateStore-BSPkxhrx.mjs} +2 -2
  26. package/dist/{S3StateStore-DLHjDYYP.mjs.map → S3StateStore-BSPkxhrx.mjs.map} +1 -1
  27. package/dist/{S3StateStore-ahE7hSNK.cjs → S3StateStore-BqugXIcb.cjs} +2 -2
  28. package/dist/{S3StateStore-ahE7hSNK.cjs.map → S3StateStore-BqugXIcb.cjs.map} +1 -1
  29. package/dist/{SSMStateStore-qIzIuazA.cjs → SSMStateStore-B0OsuT_k.cjs} +2 -2
  30. package/dist/{SSMStateStore-qIzIuazA.cjs.map → SSMStateStore-B0OsuT_k.cjs.map} +1 -1
  31. package/dist/{SSMStateStore-j5RuMkch.mjs → SSMStateStore-DM85OiH0.mjs} +2 -2
  32. package/dist/{SSMStateStore-j5RuMkch.mjs.map → SSMStateStore-DM85OiH0.mjs.map} +1 -1
  33. package/dist/{SubscriberGenerator-C8CPxrCK.cjs → SubscriberGenerator-BDUp7OeY.cjs} +237 -54
  34. package/dist/SubscriberGenerator-BDUp7OeY.cjs.map +1 -0
  35. package/dist/{SubscriberGenerator-y3pncae_.mjs → SubscriberGenerator-ocQ2yj0K.mjs} +227 -56
  36. package/dist/SubscriberGenerator-ocQ2yj0K.mjs.map +1 -0
  37. package/dist/{add-Bm-C56lH.mjs → add-C7ZBXIzM.mjs} +6 -6
  38. package/dist/{add-Bm-C56lH.mjs.map → add-C7ZBXIzM.mjs.map} +1 -1
  39. package/dist/{add-C2CvXrYw.cjs → add-CrxvmSpq.cjs} +7 -7
  40. package/dist/{add-C2CvXrYw.cjs.map → add-CrxvmSpq.cjs.map} +1 -1
  41. package/dist/{bundler-Dk7tAvAX.cjs → bundler-DVUXW95p.cjs} +5 -5
  42. package/dist/{bundler-Dk7tAvAX.cjs.map → bundler-DVUXW95p.cjs.map} +1 -1
  43. package/dist/{bundler-DnkFt4mn.mjs → bundler-DpZ9uBGN.mjs} +5 -5
  44. package/dist/{bundler-DnkFt4mn.mjs.map → bundler-DpZ9uBGN.mjs.map} +1 -1
  45. package/dist/{cli-Di464j1O.cjs → cli-BMRhE6cZ.cjs} +4 -4
  46. package/dist/{cli-Di464j1O.cjs.map → cli-BMRhE6cZ.cjs.map} +1 -1
  47. package/dist/{cli-CVeHFXNE.mjs → cli-BWfYyy1k.mjs} +4 -4
  48. package/dist/{cli-CVeHFXNE.mjs.map → cli-BWfYyy1k.mjs.map} +1 -1
  49. package/dist/{compose-cHkF---f.mjs → compose-BFmdLig3.mjs} +525 -55
  50. package/dist/compose-BFmdLig3.mjs.map +1 -0
  51. package/dist/{compose-ChVvb3xM.cjs → compose-Bqcy0lqo.cjs} +554 -84
  52. package/dist/compose-Bqcy0lqo.cjs.map +1 -0
  53. package/dist/{config-DrOX29sT.d.mts → config-CdytFgSR.d.cts} +3 -4
  54. package/dist/config-CdytFgSR.d.cts.map +1 -0
  55. package/dist/{config-B3iz9BtK.d.cts → config-_y1FGBWR.d.mts} +3 -4
  56. package/dist/config-_y1FGBWR.d.mts.map +1 -0
  57. package/dist/config.cjs +2 -2
  58. package/dist/config.d.cts +3 -3
  59. package/dist/config.d.mts +3 -3
  60. package/dist/config.mjs +2 -2
  61. package/dist/{credentialSchemas-B67WJOOm.cjs → credentialSchemas-DFxvGEwa.cjs} +2 -2
  62. package/dist/{credentialSchemas-B67WJOOm.cjs.map → credentialSchemas-DFxvGEwa.cjs.map} +1 -1
  63. package/dist/{credentialSchemas-DDOLRCLF.mjs → credentialSchemas-DaMpt-Dx.mjs} +2 -2
  64. package/dist/{credentialSchemas-DDOLRCLF.mjs.map → credentialSchemas-DaMpt-Dx.mjs.map} +1 -1
  65. package/dist/{credentials-Zqr3QQYB.cjs → credentials-D7XJypNd.cjs} +173 -3
  66. package/dist/credentials-D7XJypNd.cjs.map +1 -0
  67. package/dist/{credentials-B5N5yKk6.mjs → credentials-DpfnUvIR.mjs} +132 -4
  68. package/dist/credentials-DpfnUvIR.mjs.map +1 -0
  69. package/dist/deploy/public.cjs +16 -17
  70. package/dist/deploy/public.cjs.map +1 -1
  71. package/dist/deploy/public.d.cts +3 -5
  72. package/dist/deploy/public.d.cts.map +1 -1
  73. package/dist/deploy/public.d.mts +3 -5
  74. package/dist/deploy/public.d.mts.map +1 -1
  75. package/dist/deploy/public.mjs +11 -12
  76. package/dist/deploy/public.mjs.map +1 -1
  77. package/dist/{deploy-DO4djCHb.mjs → deploy-D8pUA9Y0.mjs} +1240 -130
  78. package/dist/deploy-D8pUA9Y0.mjs.map +1 -0
  79. package/dist/{deploy-BffR_uEN.cjs → deploy-DVrNjX1X.cjs} +1261 -169
  80. package/dist/deploy-DVrNjX1X.cjs.map +1 -0
  81. package/dist/{discover-C5roJuc9.mjs → discover-CzGM5P3p.mjs} +136 -14
  82. package/dist/discover-CzGM5P3p.mjs.map +1 -0
  83. package/dist/{discover-DozUKs8f.cjs → discover-DL6h-J97.cjs} +158 -12
  84. package/dist/discover-DL6h-J97.cjs.map +1 -0
  85. package/dist/{dokploy-BcZm93Cc.mjs → dokploy-CzDGNB6A.mjs} +2 -2
  86. package/dist/{dokploy-BcZm93Cc.mjs.map → dokploy-CzDGNB6A.mjs.map} +1 -1
  87. package/dist/{dokploy-CsdvfH3t.cjs → dokploy-TtK6Zyl3.cjs} +2 -2
  88. package/dist/{dokploy-CsdvfH3t.cjs.map → dokploy-TtK6Zyl3.cjs.map} +1 -1
  89. package/dist/{drivers-BjGDYfnC.mjs → drivers-C-VqMtld.mjs} +2 -2
  90. package/dist/{drivers-BjGDYfnC.mjs.map → drivers-C-VqMtld.mjs.map} +1 -1
  91. package/dist/{drivers-DbjiYpW6.cjs → drivers-L29WvE95.cjs} +2 -2
  92. package/dist/{drivers-DbjiYpW6.cjs.map → drivers-L29WvE95.cjs.map} +1 -1
  93. package/dist/{eas-CN_cUXth.cjs → eas-D3clzkni.cjs} +2 -2
  94. package/dist/{eas-CN_cUXth.cjs.map → eas-D3clzkni.cjs.map} +1 -1
  95. package/dist/{eas-CBShP20v.mjs → eas-DaobzDks.mjs} +2 -2
  96. package/dist/{eas-CBShP20v.mjs.map → eas-DaobzDks.mjs.map} +1 -1
  97. package/dist/{emit-Dmc1nvdS.cjs → emit-CYloptRS.cjs} +34 -1
  98. package/dist/emit-CYloptRS.cjs.map +1 -0
  99. package/dist/{emit-C1e8qD08.mjs → emit-CiJGdy_f.mjs} +34 -1
  100. package/dist/emit-CiJGdy_f.mjs.map +1 -0
  101. package/dist/{encryption-CPDM49Hi.cjs → encryption-DBkIFiR2.cjs} +3 -3
  102. package/dist/{encryption-CPDM49Hi.cjs.map → encryption-DBkIFiR2.cjs.map} +1 -1
  103. package/dist/{encryption-CITa6dXL.mjs → encryption-DV31gEY8.mjs} +3 -3
  104. package/dist/{encryption-CITa6dXL.mjs.map → encryption-DV31gEY8.mjs.map} +1 -1
  105. package/dist/{engine-CTnHmHjz.mjs → engine-CFOR9h6X.mjs} +140 -16
  106. package/dist/engine-CFOR9h6X.mjs.map +1 -0
  107. package/dist/{engine-D-jfMVj-.cjs → engine-CuZUGbEh.cjs} +143 -19
  108. package/dist/engine-CuZUGbEh.cjs.map +1 -0
  109. package/dist/{github-D56PSK39.cjs → github-CsvtWSBW.cjs} +115 -47
  110. package/dist/github-CsvtWSBW.cjs.map +1 -0
  111. package/dist/{github-9RlA7EjN.mjs → github-Nep8pI4_.mjs} +113 -45
  112. package/dist/github-Nep8pI4_.mjs.map +1 -0
  113. package/dist/{googlePlay-SX_jQeJf.cjs → googlePlay-C-UfBXJU.cjs} +5 -5
  114. package/dist/{googlePlay-SX_jQeJf.cjs.map → googlePlay-C-UfBXJU.cjs.map} +1 -1
  115. package/dist/{googlePlay-COi5LXON.mjs → googlePlay-lF7Atm_N.mjs} +4 -4
  116. package/dist/{googlePlay-COi5LXON.mjs.map → googlePlay-lF7Atm_N.mjs.map} +1 -1
  117. package/dist/{index-DcyRA5Rf.d.mts → index-DrkV7N5w.d.mts} +2 -2
  118. package/dist/{index-DcyRA5Rf.d.mts.map → index-DrkV7N5w.d.mts.map} +1 -1
  119. package/dist/{index-DjLSMgAL.d.cts → index-yaXZ9m-J.d.cts} +2 -2
  120. package/dist/{index-DjLSMgAL.d.cts.map → index-yaXZ9m-J.d.cts.map} +1 -1
  121. package/dist/index.cjs +166 -85
  122. package/dist/index.cjs.map +1 -1
  123. package/dist/index.mjs +149 -68
  124. package/dist/index.mjs.map +1 -1
  125. package/dist/{local-BwC2v2Y8.mjs → local-B7Zv1NoA.mjs} +2 -2
  126. package/dist/{local-BwC2v2Y8.mjs.map → local-B7Zv1NoA.mjs.map} +1 -1
  127. package/dist/{local-DNhnkugT.cjs → local-OPC9BCZt.cjs} +3 -3
  128. package/dist/{local-DNhnkugT.cjs.map → local-OPC9BCZt.cjs.map} +1 -1
  129. package/dist/{migrate-CrtxXeGY.cjs → migrate-Dp3GhFnf.cjs} +4 -4
  130. package/dist/{migrate-CrtxXeGY.cjs.map → migrate-Dp3GhFnf.cjs.map} +1 -1
  131. package/dist/{migrate-0J6gsfrQ.mjs → migrate-NfbPVhi9.mjs} +4 -4
  132. package/dist/{migrate-0J6gsfrQ.mjs.map → migrate-NfbPVhi9.mjs.map} +1 -1
  133. package/dist/{migrate-DJa_Dlsl.mjs → migrate-_sBX0pve.mjs} +3 -3
  134. package/dist/{migrate-DJa_Dlsl.mjs.map → migrate-_sBX0pve.mjs.map} +1 -1
  135. package/dist/{migrate-BCz1lkh_.cjs → migrate-hlNKNWmg.cjs} +3 -3
  136. package/dist/{migrate-BCz1lkh_.cjs.map → migrate-hlNKNWmg.cjs.map} +1 -1
  137. package/dist/openapi.cjs +3 -3
  138. package/dist/openapi.d.cts +1 -1
  139. package/dist/openapi.d.mts +1 -1
  140. package/dist/openapi.mjs +3 -3
  141. package/dist/{publish-CuQlOW5p.mjs → publish-BuUVaZVy.mjs} +2 -2
  142. package/dist/{publish-CuQlOW5p.mjs.map → publish-BuUVaZVy.mjs.map} +1 -1
  143. package/dist/{publish-qSpnvmdX.cjs → publish-ZOo4xEuZ.cjs} +2 -2
  144. package/dist/{publish-qSpnvmdX.cjs.map → publish-ZOo4xEuZ.cjs.map} +1 -1
  145. package/dist/{ready-BOfogqzr.mjs → ready-Ca883OvY.mjs} +6 -6
  146. package/dist/{ready-BOfogqzr.mjs.map → ready-Ca883OvY.mjs.map} +1 -1
  147. package/dist/{ready-BFFP838V.cjs → ready-qnqp6JAY.cjs} +6 -6
  148. package/dist/{ready-BFFP838V.cjs.map → ready-qnqp6JAY.cjs.map} +1 -1
  149. package/dist/reconcile/public.cjs +2 -2
  150. package/dist/reconcile/public.d.cts +1 -2
  151. package/dist/reconcile/public.d.cts.map +1 -1
  152. package/dist/reconcile/public.d.mts +1 -2
  153. package/dist/reconcile/public.d.mts.map +1 -1
  154. package/dist/reconcile/public.mjs +2 -2
  155. package/dist/{resolve-j8hUcbGz.mjs → resolve-BCU9pYDx.mjs} +4 -4
  156. package/dist/{resolve-j8hUcbGz.mjs.map → resolve-BCU9pYDx.mjs.map} +1 -1
  157. package/dist/{resolve-AsfohXd1.cjs → resolve-C9QqOl5t.cjs} +4 -4
  158. package/dist/{resolve-AsfohXd1.cjs.map → resolve-C9QqOl5t.cjs.map} +1 -1
  159. package/dist/{rollback-Bxq_URgL.cjs → rollback-CTWdDBgB.cjs} +7 -7
  160. package/dist/{rollback-Bxq_URgL.cjs.map → rollback-CTWdDBgB.cjs.map} +1 -1
  161. package/dist/{rollback-JgbAY43C.mjs → rollback-CWyyTQ0B.mjs} +7 -7
  162. package/dist/{rollback-JgbAY43C.mjs.map → rollback-CWyyTQ0B.mjs.map} +1 -1
  163. package/dist/{runtime-DIZTeGJV.d.cts → runtime-DCkfI2E7.d.mts} +2 -2
  164. package/dist/{runtime-DIZTeGJV.d.cts.map → runtime-DCkfI2E7.d.mts.map} +1 -1
  165. package/dist/{runtime-WMLJY-db.d.mts → runtime-Ie4znWIB.d.cts} +2 -2
  166. package/dist/{runtime-WMLJY-db.d.mts.map → runtime-Ie4znWIB.d.cts.map} +1 -1
  167. package/dist/{s3-B4FTQQkV.mjs → s3-BZzSnker.mjs} +2 -2
  168. package/dist/{s3-B4FTQQkV.mjs.map → s3-BZzSnker.mjs.map} +1 -1
  169. package/dist/{s3-DwKLXwUT.cjs → s3-pqgmCTOq.cjs} +2 -2
  170. package/dist/{s3-DwKLXwUT.cjs.map → s3-pqgmCTOq.cjs.map} +1 -1
  171. package/dist/sandbox/credentials-worker.cjs +2 -2
  172. package/dist/sandbox/credentials-worker.mjs +2 -2
  173. package/dist/sandbox/discover-worker.cjs +1 -1
  174. package/dist/sandbox/discover-worker.mjs +1 -1
  175. package/dist/sandbox/migrate-worker.cjs +1 -1
  176. package/dist/sandbox/migrate-worker.mjs +1 -1
  177. package/dist/{seeds-Dv78P9LS.mjs → seeds-CK7w79VC.mjs} +848 -61
  178. package/dist/seeds-CK7w79VC.mjs.map +1 -0
  179. package/dist/{seeds-BWC-qjSI.cjs → seeds-CPEcmoPn.cjs} +953 -64
  180. package/dist/seeds-CPEcmoPn.cjs.map +1 -0
  181. package/dist/{serverSetup-DB6sKwAy.mjs → serverSetup-BAPNuNEX.mjs} +6 -6
  182. package/dist/{serverSetup-DB6sKwAy.mjs.map → serverSetup-BAPNuNEX.mjs.map} +1 -1
  183. package/dist/{serverSetup-CtV80a5M.cjs → serverSetup-C6t6Vtt7.cjs} +6 -6
  184. package/dist/{serverSetup-CtV80a5M.cjs.map → serverSetup-C6t6Vtt7.cjs.map} +1 -1
  185. package/dist/{sniffer-ctpwBBkT.cjs → sniffer-DdX_WN_-.cjs} +9 -9
  186. package/dist/{sniffer-ctpwBBkT.cjs.map → sniffer-DdX_WN_-.cjs.map} +1 -1
  187. package/dist/{sniffer-qDFUQmWP.mjs → sniffer-g9v7cqDN.mjs} +9 -9
  188. package/dist/{sniffer-qDFUQmWP.mjs.map → sniffer-g9v7cqDN.mjs.map} +1 -1
  189. package/dist/{dns-Bmnuttja.cjs → snsSubscriptions-5Cqww8S7.cjs} +351 -3
  190. package/dist/snsSubscriptions-5Cqww8S7.cjs.map +1 -0
  191. package/dist/{dns-DFvO5a53.mjs → snsSubscriptions-BmYeeTH8.mjs} +280 -4
  192. package/dist/snsSubscriptions-BmYeeTH8.mjs.map +1 -0
  193. package/dist/{sst-C1pxbFEd.mjs → sst-CTqZZl1_.mjs} +3 -4
  194. package/dist/{sst-C1pxbFEd.mjs.map → sst-CTqZZl1_.mjs.map} +1 -1
  195. package/dist/{sst-B-Uz4cvm.cjs → sst-lEkBwzGc.cjs} +4 -5
  196. package/dist/{sst-B-Uz4cvm.cjs.map → sst-lEkBwzGc.cjs.map} +1 -1
  197. package/dist/{stage-C7lf36az.mjs → stage-C-I2dVw1.mjs} +6 -6
  198. package/dist/{stage-C7lf36az.mjs.map → stage-C-I2dVw1.mjs.map} +1 -1
  199. package/dist/{stage-DzALYUwY.cjs → stage-CUE5pMmw.cjs} +7 -7
  200. package/dist/{stage-DzALYUwY.cjs.map → stage-CUE5pMmw.cjs.map} +1 -1
  201. package/dist/{stages-B7mCWI_R.cjs → stages-3yUZaZeA.cjs} +2 -2
  202. package/dist/{stages-B7mCWI_R.cjs.map → stages-3yUZaZeA.cjs.map} +1 -1
  203. package/dist/{stages-DSltTW2d.mjs → stages-DTTv0NZW.mjs} +2 -2
  204. package/dist/{stages-DSltTW2d.mjs.map → stages-DTTv0NZW.mjs.map} +1 -1
  205. package/dist/target/public.cjs +1 -1
  206. package/dist/target/public.d.cts +2 -2
  207. package/dist/target/public.d.mts +2 -2
  208. package/dist/target/public.mjs +1 -1
  209. package/dist/{terminal-DJlqcW_U.cjs → terminal-PKhPAajN.cjs} +3 -3
  210. package/dist/{terminal-DJlqcW_U.cjs.map → terminal-PKhPAajN.cjs.map} +1 -1
  211. package/dist/{terminal-CW_dWeTe.mjs → terminal-bqY33lpN.mjs} +3 -3
  212. package/dist/{terminal-CW_dWeTe.mjs.map → terminal-bqY33lpN.mjs.map} +1 -1
  213. package/dist/{types-Q2SC_ly2.d.cts → types-Bmjs4jZV.d.mts} +248 -4
  214. package/dist/types-Bmjs4jZV.d.mts.map +1 -0
  215. package/dist/{types-C8gXOLWL.d.mts → types-D0IsQxES.d.cts} +249 -3
  216. package/dist/types-D0IsQxES.d.cts.map +1 -0
  217. package/dist/vitest.cjs +1 -1
  218. package/dist/vitest.mjs +1 -1
  219. package/dist/workspace/index.cjs +1 -1
  220. package/dist/workspace/index.d.cts +2 -2
  221. package/dist/workspace/index.d.mts +2 -2
  222. package/dist/workspace/index.mjs +1 -1
  223. package/dist/{workspace-DUnnVjqC.mjs → workspace-DBHF9Gba.mjs} +5 -3
  224. package/dist/workspace-DBHF9Gba.mjs.map +1 -0
  225. package/dist/{workspace-C82GLPnf.cjs → workspace-Du4Afb03.cjs} +5 -3
  226. package/dist/workspace-Du4Afb03.cjs.map +1 -0
  227. package/package.json +17 -15
  228. package/dist/SubscriberGenerator-C8CPxrCK.cjs.map +0 -1
  229. package/dist/SubscriberGenerator-y3pncae_.mjs.map +0 -1
  230. package/dist/compose-ChVvb3xM.cjs.map +0 -1
  231. package/dist/compose-cHkF---f.mjs.map +0 -1
  232. package/dist/config-B3iz9BtK.d.cts.map +0 -1
  233. package/dist/config-DrOX29sT.d.mts.map +0 -1
  234. package/dist/credentials-B5N5yKk6.mjs.map +0 -1
  235. package/dist/credentials-Zqr3QQYB.cjs.map +0 -1
  236. package/dist/deploy-BffR_uEN.cjs.map +0 -1
  237. package/dist/deploy-DO4djCHb.mjs.map +0 -1
  238. package/dist/discover-C08meHhu.d.mts +0 -125
  239. package/dist/discover-C08meHhu.d.mts.map +0 -1
  240. package/dist/discover-C5roJuc9.mjs.map +0 -1
  241. package/dist/discover-DipvfWpK.d.cts +0 -125
  242. package/dist/discover-DipvfWpK.d.cts.map +0 -1
  243. package/dist/discover-DozUKs8f.cjs.map +0 -1
  244. package/dist/dns-Bmnuttja.cjs.map +0 -1
  245. package/dist/dns-DFvO5a53.mjs.map +0 -1
  246. package/dist/emit-C1e8qD08.mjs.map +0 -1
  247. package/dist/emit-Dmc1nvdS.cjs.map +0 -1
  248. package/dist/engine-CTnHmHjz.mjs.map +0 -1
  249. package/dist/engine-D-jfMVj-.cjs.map +0 -1
  250. package/dist/github-9RlA7EjN.mjs.map +0 -1
  251. package/dist/github-D56PSK39.cjs.map +0 -1
  252. package/dist/run-BJLIerMM.mjs +0 -133
  253. package/dist/run-BJLIerMM.mjs.map +0 -1
  254. package/dist/run-fVZ-rLnc.cjs +0 -174
  255. package/dist/run-fVZ-rLnc.cjs.map +0 -1
  256. package/dist/sandbox-Co1y2CmF.d.mts +0 -104
  257. package/dist/sandbox-Co1y2CmF.d.mts.map +0 -1
  258. package/dist/sandbox-DvNqr6od.d.cts +0 -104
  259. package/dist/sandbox-DvNqr6od.d.cts.map +0 -1
  260. package/dist/seeds-BWC-qjSI.cjs.map +0 -1
  261. package/dist/seeds-Dv78P9LS.mjs.map +0 -1
  262. package/dist/types-C8gXOLWL.d.mts.map +0 -1
  263. package/dist/types-Q2SC_ly2.d.cts.map +0 -1
  264. package/dist/workspace-C82GLPnf.cjs.map +0 -1
  265. package/dist/workspace-DUnnVjqC.mjs.map +0 -1
@@ -1,97 +1,24 @@
1
1
  import { t as GkmError } from "./errors-D8Gwp5kU.mjs";
2
- import { s as gkmHome } from "./run-BJLIerMM.mjs";
3
- import { r as storedCredentials } from "./credentials-B5N5yKk6.mjs";
4
- import { $ as IamUserNotOwned, G as checkS3Config, J as BucketNameUnavailable, K as AccessKeyLimit, L as resolveStageTelemetry, N as assertDeployedStage, Q as BucketRegionUnknown, U as stageProvider, W as S3_PROVISIONING, X as BucketProbeFailed, Y as BucketOwnedByAnotherProject, Z as BucketRegionMismatch, et as ProvisionedBucketUnreachable, ht as isReservedStageKey, nt as RotationInProgress, q as BucketKeySetElsewhere, rt as S3RegionRequired, tt as RecordedBucketUnreachable, u as getAppBuildOrder } from "./workspace-DUnnVjqC.mjs";
5
- import { D as deploysWithCompose, E as stageBackups, S as backupExpiries, c as stageDnsDomains, g as BACKUPS_URL_KEY, h as BACKUPS_SERVICE, n as dnsCredentials, u as assertSitePasswordTarget, w as backupsPrefix } from "./dns-DFvO5a53.mjs";
2
+ import { r as storedCredentials, x as gkmHome } from "./credentials-DpfnUvIR.mjs";
3
+ import { $ as IamUserNotOwned, G as checkS3Config, J as BucketNameUnavailable, K as AccessKeyLimit, L as resolveStageTelemetry, N as assertDeployedStage, Q as BucketRegionUnknown, U as stageProvider, W as S3_PROVISIONING, X as BucketProbeFailed, Y as BucketOwnedByAnotherProject, Z as BucketRegionMismatch, et as ProvisionedBucketUnreachable, ht as isReservedStageKey, nt as RotationInProgress, q as BucketKeySetElsewhere, rt as S3RegionRequired, tt as RecordedBucketUnreachable, u as getAppBuildOrder } from "./workspace-DBHF9Gba.mjs";
4
+ import { D as BACKUPS_SERVICE, I as backupsPrefix, O as BACKUPS_URL_KEY, P as backupExpiries, R as stageBackups, S as assertSitePasswordTarget, _ as stageDnsDomains, a as subscriptionKey, b as awsEndpoint, d as recordTtl, l as dnsCredentials, m as settleStackDns, o as subscriptionResource, x as awsProvisioningCredentials, y as awsClientConfig, z as deploysWithCompose } from "./snsSubscriptions-BmYeeTH8.mjs";
6
5
  import { o as builtinTarget } from "./define-6poNqm5O.mjs";
7
- import { Bt as initStageSecrets, Lt as secretsStoreFor, N as stageProviderNotes, P as PROVIDER_KINDS, X as DevServicesNeedServerTarget, Z as ExternalServicesNotConfigured, d as DeployJournal, dn as mergeLifecycle, et as devServicesUsed, ft as constructGlobs, it as stageServiceDeclarations, j as assertStageProvidersEnabled, nr as isMainFrontendApp, nt as reportDevServices, ot as appEnvKeys, rr as resolveHost, tr as NoDomainForStage, tt as externalServices, v as createComposeState, y as createEmptyState, zn as usesTelemetry } from "./seeds-Dv78P9LS.mjs";
6
+ import { $ as ORIGIN_VERIFY_HEADER, En as mergeLifecycle, N as stageProviderNotes, P as PROVIDER_KINDS, Qt as initStageSecrets, Yt as secretsStoreFor, _t as appEnvKeys, br as resolveHost, ct as ExternalServicesNotConfigured, d as DeployJournal, dt as devServicesUsed, er as usesTelemetry, f as extractSubdomain, ft as externalServices, ht as stageServiceDeclarations, j as assertStageProvidersEnabled, m as normalizeDnsConfig, nt as originSecretKey, p as findRootDomain, pt as reportDevServices, rt as processorOriginHost, st as DevServicesNeedServerTarget, tt as newOriginSecret, v as createComposeState, vr as NoDomainForStage, wt as constructGlobs, y as createEmptyState, yr as isMainFrontendApp } from "./seeds-CK7w79VC.mjs";
8
7
  import { n as derivedApps, t as appKey } from "./derive-B4vDpzgP.mjs";
8
+ import { s as subscriberRoute } from "./SubscriberGenerator-ocQ2yj0K.mjs";
9
9
  import { n as deployIdentity, t as applicationName } from "./identity-CBXr4wNi.mjs";
10
- import { i as discover, p as withOutput } from "./discover-C5roJuc9.mjs";
10
+ import { i as discover, m as eventsEdge, p as withOutput } from "./discover-CzGM5P3p.mjs";
11
11
  import { c as withSandbox } from "./sandbox-CbVB7uOD.mjs";
12
12
  import { findWorkspaceRoot, loadWorkspaceConfig } from "./config.mjs";
13
- import { a as parseTargetOptions, o as resolveTarget } from "./resolve-j8hUcbGz.mjs";
14
- import { t as LocalSandbox } from "./local-BwC2v2Y8.mjs";
15
- import { E as eventError, S as createStateStore, v as assertStateOutlivesRun, y as assertStateRoom } from "./sniffer-qDFUQmWP.mjs";
13
+ import { a as parseTargetOptions, o as resolveTarget } from "./resolve-BCU9pYDx.mjs";
14
+ import { t as LocalSandbox } from "./local-B7Zv1NoA.mjs";
15
+ import { E as eventError, S as createStateStore, v as assertStateOutlivesRun, y as assertStateRoom } from "./sniffer-g9v7cqDN.mjs";
16
16
  import { a as findProjectBucket, i as ensureProjectBucket, o as projectBucketName, r as callerAccountId, s as reconcileProjectBucketLifecycle } from "./projectBucket-C_DN4WzF.mjs";
17
- import { n as assertStageCredentials } from "./credentialSchemas-DDOLRCLF.mjs";
17
+ import { n as assertStageCredentials } from "./credentialSchemas-DaMpt-Dx.mjs";
18
18
  import { resolve } from "node:path";
19
19
  import { randomInt } from "node:crypto";
20
- import { dependentsOf, kebabCase, provideKey, providedKeyFor } from "@geekmidas/manifest";
20
+ import { bucketExpiry, dependentsOf, kebabCase, provideKey, providedKeyFor } from "@geekmidas/manifest";
21
21
  import * as s3Url from "@geekmidas/storage/s3-url";
22
- //#region src/providers/aws.ts
23
- /**
24
- * The AWS account a provider provisions in — the stage's, found the way the
25
- * SSM and Secrets Manager stores find it.
26
- *
27
- * 1. `--profile`, resolved on its own: exported keys left over from another
28
- * account never win over a profile someone named.
29
- * 2. `AWS_PROFILE`, else `AWS_ACCESS_KEY_ID`/`AWS_SECRET_ACCESS_KEY` — the
30
- * deploy's own `aws` credential kind (`storedCredentials`).
31
- * 3. The SDK's default chain: `~/.aws` default profile, SSO, a role. In CI,
32
- * whatever `aws-actions/configure-aws-credentials` exported.
33
- *
34
- * None of them: the provider has no provisioning credentials, and the stage
35
- * is treated as `external`.
36
- */
37
- async function awsProvisioningCredentials(lookup) {
38
- const region = lookup.env.AWS_REGION ?? lookup.env.AWS_DEFAULT_REGION;
39
- const where = region ? { region } : {};
40
- if (lookup.profile) return {
41
- profile: lookup.profile,
42
- ...where
43
- };
44
- const stored = await storedCredentials({
45
- env: lookup.env,
46
- ...lookup.home ? { home: lookup.home } : {}
47
- }).get({
48
- kind: "aws",
49
- stage: lookup.stage
50
- });
51
- if (stored) return stored;
52
- try {
53
- const { fromNodeProviderChain } = await import("@aws-sdk/credential-providers");
54
- await fromNodeProviderChain({
55
- timeout: 1e3,
56
- maxRetries: 0
57
- })();
58
- return {
59
- chain: true,
60
- ...where
61
- };
62
- } catch {
63
- return;
64
- }
65
- }
66
- /**
67
- * An S3-compatible endpoint the environment points the SDK at — the AWS
68
- * emulator in tests. The SDK reads it itself; it is read here too, because
69
- * an emulator takes path-style requests, and a key written for one has to
70
- * say where it is.
71
- */
72
- function awsEndpoint(env, service) {
73
- return env[`AWS_ENDPOINT_URL_${service}`] ?? env.AWS_ENDPOINT_URL;
74
- }
75
- /** An SDK client's config for a provisioning credential. */
76
- async function awsClientConfig(credential, region) {
77
- if ("profile" in credential) {
78
- const { fromIni } = await import("@aws-sdk/credential-providers");
79
- return {
80
- region,
81
- credentials: fromIni({ profile: credential.profile })
82
- };
83
- }
84
- if ("accessKeyId" in credential) return {
85
- region,
86
- credentials: {
87
- accessKeyId: credential.accessKeyId,
88
- secretAccessKey: credential.secretAccessKey,
89
- ...credential.sessionToken ? { sessionToken: credential.sessionToken } : {}
90
- }
91
- };
92
- return { region };
93
- }
94
- //#endregion
95
22
  //#region src/target/secrets.ts
96
23
  /**
97
24
  * A stage's secrets as a target sees them: read and written through the
@@ -247,28 +174,37 @@ const bucketArn = (bucket) => `arn:aws:s3:::${bucket}`;
247
174
  function userPolicy(bucket, options = {}) {
248
175
  return {
249
176
  Version: "2012-10-17",
250
- Statement: [{
251
- Sid: "GkmObjects",
252
- Effect: "Allow",
253
- Action: [
254
- "s3:GetObject",
255
- "s3:PutObject",
256
- "s3:DeleteObject",
257
- "s3:AbortMultipartUpload",
258
- "s3:ListMultipartUploadParts",
259
- ...options.versioning ? ["s3:GetObjectVersion"] : []
260
- ],
261
- Resource: `${bucketArn(bucket)}/*`
262
- }, {
263
- Sid: "GkmBucket",
264
- Effect: "Allow",
265
- Action: [
266
- "s3:ListBucket",
267
- "s3:GetBucketLocation",
268
- "s3:ListBucketMultipartUploads"
269
- ],
270
- Resource: bucketArn(bucket)
271
- }]
177
+ Statement: [
178
+ {
179
+ Sid: "GkmObjects",
180
+ Effect: "Allow",
181
+ Action: [
182
+ "s3:GetObject",
183
+ "s3:PutObject",
184
+ "s3:DeleteObject",
185
+ "s3:AbortMultipartUpload",
186
+ "s3:ListMultipartUploadParts",
187
+ ...options.versioning ? ["s3:GetObjectVersion"] : []
188
+ ],
189
+ Resource: `${bucketArn(bucket)}/*`
190
+ },
191
+ {
192
+ Sid: "GkmBucket",
193
+ Effect: "Allow",
194
+ Action: [
195
+ "s3:ListBucket",
196
+ "s3:GetBucketLocation",
197
+ "s3:ListBucketMultipartUploads"
198
+ ],
199
+ Resource: bucketArn(bucket)
200
+ },
201
+ ...options.eventsTopicArn ? [{
202
+ Sid: "GkmEvents",
203
+ Effect: "Allow",
204
+ Action: "sns:ConfirmSubscription",
205
+ Resource: options.eventsTopicArn
206
+ }] : []
207
+ ]
272
208
  };
273
209
  }
274
210
  /**
@@ -283,10 +219,13 @@ function openResource(bucket, pattern) {
283
219
  }
284
220
  /**
285
221
  * The bucket's own policy: every request refused unless it came over TLS,
286
- * and — only when a file server opens paths on it — anonymous `s3:GetObject`
287
- * on exactly those prefixes.
222
+ * and — only when a file server opens paths on it — `s3:GetObject` on
223
+ * exactly those prefixes: to anyone, or, when a CDN serves them, to
224
+ * CloudFront alone, for exactly the stage's distributions. Behind a CDN the
225
+ * bucket is never public: a key outside the open prefixes is refused to
226
+ * CloudFront too, so the edge answers it 403.
288
227
  */
289
- function bucketPolicy(bucket, open = []) {
228
+ function bucketPolicy(bucket, open = [], options = {}) {
290
229
  const statements = [{
291
230
  Sid: "GkmDenyInsecureTransport",
292
231
  Effect: "Deny",
@@ -295,12 +234,21 @@ function bucketPolicy(bucket, open = []) {
295
234
  Resource: [bucketArn(bucket), `${bucketArn(bucket)}/*`],
296
235
  Condition: { Bool: { "aws:SecureTransport": "false" } }
297
236
  }];
298
- if (open.length > 0) statements.push({
237
+ const resources = [...new Set(open.map((p) => openResource(bucket, p)))].sort();
238
+ if (open.length > 0 && options.cdn) statements.push({
239
+ Sid: "GkmCdnRead",
240
+ Effect: "Allow",
241
+ Principal: { Service: "cloudfront.amazonaws.com" },
242
+ Action: "s3:GetObject",
243
+ Resource: resources,
244
+ Condition: { StringEquals: { "AWS:SourceArn": [...new Set(options.cdn.distributionArns)].sort() } }
245
+ });
246
+ else if (open.length > 0) statements.push({
299
247
  Sid: "GkmOpenPaths",
300
248
  Effect: "Allow",
301
249
  Principal: "*",
302
250
  Action: "s3:GetObject",
303
- Resource: [...new Set(open.map((p) => openResource(bucket, p)))].sort()
251
+ Resource: resources
304
252
  });
305
253
  return {
306
254
  Version: "2012-10-17",
@@ -349,7 +297,7 @@ function canonical(value, key) {
349
297
  }
350
298
  if (value && typeof value === "object") return Object.fromEntries(Object.keys(value).sort().map((k) => {
351
299
  const v = value[k];
352
- return [k, canonical(k === "Action" || k === "Resource" || k === "AWS" ? Array.isArray(v) ? v : [v] : v, k)];
300
+ return [k, canonical(k === "Action" || k === "Resource" || k === "AWS" || k === "AWS:SourceArn" ? Array.isArray(v) ? v : [v] : v, k)];
353
301
  }));
354
302
  return value;
355
303
  }
@@ -395,7 +343,7 @@ function identityTags(ctx, id) {
395
343
  ];
396
344
  }
397
345
  /** A read whose "there is none" answer is one of `missing`. */
398
- async function readOr$1(read, missing) {
346
+ async function readOr$3(read, missing) {
399
347
  try {
400
348
  return await read();
401
349
  } catch (error) {
@@ -425,7 +373,7 @@ async function ensureIamUser(ctx, clients, input) {
425
373
  key: `iam-user:${id}`,
426
374
  type: "iam-user"
427
375
  };
428
- const found = await readOr$1(() => iam.send(new IAM.GetUserCommand({ UserName: user })), ["NoSuchEntity", "NoSuchEntityException"]);
376
+ const found = await readOr$3(() => iam.send(new IAM.GetUserCommand({ UserName: user })), ["NoSuchEntity", "NoSuchEntityException"]);
429
377
  if (found?.User) {
430
378
  const tags = await iam.send(new IAM.ListUserTagsCommand({ UserName: user }));
431
379
  const tag = (key) => tags.Tags?.find((t) => t.Key === key)?.Value;
@@ -445,7 +393,7 @@ async function ensureIamUser(ctx, clients, input) {
445
393
  await ctx.state.ready(entry, user);
446
394
  });
447
395
  const exists = Boolean(found?.User) || !ctx.dryRun;
448
- const current = exists ? await readOr$1(() => iam.send(new IAM.GetUserPolicyCommand({
396
+ const current = exists ? await readOr$3(() => iam.send(new IAM.GetUserPolicyCommand({
449
397
  UserName: user,
450
398
  PolicyName: input.policyName
451
399
  })), ["NoSuchEntity", "NoSuchEntityException"]) : void 0;
@@ -859,6 +807,1108 @@ async function verifyStageBackups(input) {
859
807
  }
860
808
  }
861
809
  //#endregion
810
+ //#region src/providers/dnsWriter.ts
811
+ /**
812
+ * The DNS records a provider asks for — a certificate's validation record, a
813
+ * CDN's name — written through the stage's own DNS providers.
814
+ *
815
+ * The same machinery the deploy's DNS step uses for the stack's hosts
816
+ * (`settleStackDns`), handed a plan of CNAMEs instead of the server's
817
+ * addresses: a record holding its value is left alone, one with another value
818
+ * is replaced and printed, and what is written is read back. A host under no
819
+ * domain in `dns`, or under a `manual` one, is printed for the person to
820
+ * create and reported back, so the provider can say what it is waiting on.
821
+ */
822
+ async function writeProviderDns(records, options) {
823
+ const { log } = options;
824
+ const dns = options.workspace.dns ? normalizeDnsConfig(options.workspace.dns) : {};
825
+ const manual = [];
826
+ const domains = /* @__PURE__ */ new Map();
827
+ for (const record of records) {
828
+ const host = record.host.toLowerCase().replace(/\.$/, "");
829
+ const value = record.value.replace(/\.$/, "");
830
+ const domain = findRootDomain(host, dns);
831
+ if (!domain) {
832
+ log(` create at its DNS host: ${host} CNAME ${value}`);
833
+ manual.push(host);
834
+ continue;
835
+ }
836
+ const config = dns[domain];
837
+ const plan = domains.get(domain) ?? {
838
+ domain,
839
+ provider: typeof config.provider === "string" ? config.provider : "custom",
840
+ config,
841
+ records: []
842
+ };
843
+ const planned = {
844
+ host,
845
+ domain,
846
+ name: extractSubdomain(host, domain),
847
+ type: record.type,
848
+ value,
849
+ ttl: recordTtl(config)
850
+ };
851
+ plan.records.push(planned);
852
+ domains.set(domain, plan);
853
+ }
854
+ if (domains.size === 0) return { manual };
855
+ const plans = [...domains.values()];
856
+ const tokens = await dnsCredentials(options.stage, plans.map(({ domain, config }) => ({
857
+ domain,
858
+ config
859
+ })), options.credentials);
860
+ const applied = await settleStackDns({
861
+ stage: options.stage,
862
+ server: { ipv4: "" },
863
+ domains: plans,
864
+ uncovered: []
865
+ }, {
866
+ ...options.dryRun ? { dryRun: true } : { confirm: true },
867
+ log,
868
+ credentials: {
869
+ ...tokens.godaddy ? { credential: tokens.godaddy } : {},
870
+ log
871
+ },
872
+ ...tokens.hostinger ? { hostinger: tokens.hostinger } : {},
873
+ ...options.providerFor ? { providerFor: options.providerFor } : {}
874
+ });
875
+ for (const change of applied.changes) {
876
+ if (change.action !== "manual") continue;
877
+ const host = plans.find((p) => p.domain === change.domain)?.records.find((r) => r.name === change.name)?.host;
878
+ if (host) manual.push(host);
879
+ }
880
+ return { manual: [...new Set(manual)].sort() };
881
+ }
882
+ //#endregion
883
+ //#region src/providers/s3/cdn.ts
884
+ /** AWS's managed `CachingOptimized` cache policy: path-keyed, gzip and brotli. */
885
+ const CACHING_OPTIMIZED = "658327ea-f89d-4fab-a63d-7e88639e58f6";
886
+ /** What an immutable path is served with. */
887
+ const IMMUTABLE_CACHE_CONTROL = "public, max-age=31536000, immutable";
888
+ /** The only region CloudFront reads a certificate from. */
889
+ const CDN_REGION = "us-east-1";
890
+ /** The distribution's bucket origin. */
891
+ const ORIGIN_ID = "bucket";
892
+ /** Its processors' origin: the stack's processor unit, at a host of its own. */
893
+ const PROCESSORS_ORIGIN_ID = "processors";
894
+ /**
895
+ * AWS's managed `CachingOptimizedForUncompressedObjects`: keyed on the path
896
+ * alone, and nothing re-compressed — a derivative is an image, already
897
+ * compressed, and its width is in the path, so nothing else may fragment it.
898
+ */
899
+ const CACHING_OPTIMIZED_UNCOMPRESSED = "b2884449-e4de-46a7-ac36-70bc7f1ddd6d";
900
+ /** A CDN is a name on the stage's domain, and the stage has none. */
901
+ var FileServerCdnNeedsDomain = class extends GkmError {
902
+ server;
903
+ stage;
904
+ constructor(server, stage) {
905
+ super(`'${server}' is served through a CDN on '${stage}', which answers on <fileserver>.<domain> — and '${stage}' has no domain. Set domains.${stage} in gkm.config.ts, or set deploy.objects.${stage}.cdn to false to serve it from the bucket's own endpoint.`);
906
+ this.server = server;
907
+ this.stage = stage;
908
+ this.name = "FileServerCdnNeedsDomain";
909
+ }
910
+ };
911
+ /**
912
+ * The certificate is waiting on its validation record: one the person has to
913
+ * create, or one that has not propagated yet. Everything up to it is
914
+ * recorded, so the next deploy carries on from here.
915
+ */
916
+ var CdnCertificatePending = class extends GkmError {
917
+ server;
918
+ host;
919
+ record;
920
+ reason;
921
+ constructor(server, host, record, reason) {
922
+ super((reason === "manual" ? `The certificate for ${host} ('${server}') is validated by a DNS record no DNS provider in gkm.config.ts can write. Create it at the domain's DNS host:
923
+ ` : `The certificate for ${host} ('${server}') is not issued yet: ACM has not seen its validation record. It is written; DNS can take a while to answer with it. Check it resolves:
924
+ `) + ` ${record.name} CNAME ${record.value}\nthen deploy again — the certificate and everything before it are recorded, and the deploy carries on from there.`);
925
+ this.server = server;
926
+ this.host = host;
927
+ this.record = record;
928
+ this.reason = reason;
929
+ this.name = "CdnCertificatePending";
930
+ }
931
+ };
932
+ /** ACM gave up on the certificate. */
933
+ var CdnCertificateFailed = class extends GkmError {
934
+ server;
935
+ host;
936
+ status;
937
+ reason;
938
+ constructor(server, host, status, reason) {
939
+ super(`ACM did not issue the certificate for ${host} ('${server}'): ${status}` + (reason ? ` (${reason})` : "") + ". A CAA record on the domain that does not allow amazon.com, or a validation record removed before it was checked, are the usual causes. Fix it and deploy again: a new certificate is requested.");
940
+ this.server = server;
941
+ this.host = host;
942
+ this.status = status;
943
+ this.reason = reason;
944
+ this.name = "CdnCertificateFailed";
945
+ }
946
+ };
947
+ /** Where a file server answers: `<fileserver>.<domain>`. */
948
+ function cdnHost(serverId, domain) {
949
+ return `${appKey(serverId)}.${domain}`.toLowerCase();
950
+ }
951
+ /**
952
+ * The name the stage's CDN resources go by, within CloudFront's limits:
953
+ * letters, digits, `-` and `_`, and at most 64 characters (an origin access
954
+ * control's). Kept whole where it fits; otherwise shortened with a hash of
955
+ * the whole, so two long names never meet.
956
+ */
957
+ function cdnName(scope, stage, serverId) {
958
+ const whole = `gkm-${scope}-${stage}-${appKey(serverId)}`.replace(/[^A-Za-z0-9_-]/g, "-");
959
+ if (whole.length <= 56) return whole;
960
+ return `${whole.slice(0, 47)}-${hash(whole)}`;
961
+ }
962
+ function hash(value) {
963
+ let h = 2166136261;
964
+ for (let i = 0; i < value.length; i++) {
965
+ h ^= value.charCodeAt(i);
966
+ h = Math.imul(h, 16777619);
967
+ }
968
+ return (h >>> 0).toString(36).padStart(8, "0").slice(0, 8);
969
+ }
970
+ /**
971
+ * An open or immutable glob as a CloudFront path pattern. CloudFront's `*`
972
+ * crosses `/`, so a single star is as wide as a double one — the bucket
973
+ * policy reads it the same way, and the construct's own check is the
974
+ * stricter of the three.
975
+ */
976
+ function pathPattern(glob) {
977
+ return glob.replace(/^\/+/, "").replace(/\*\*/g, "*");
978
+ }
979
+ /** A CloudFront list: its `Quantity` and, when it has some, its `Items`. */
980
+ function list(items) {
981
+ return items.length > 0 ? {
982
+ Quantity: items.length,
983
+ Items: [...items]
984
+ } : { Quantity: 0 };
985
+ }
986
+ /** One of the stage's response headers policies. */
987
+ function headersPolicyConfig(input) {
988
+ return {
989
+ Name: input.name,
990
+ Comment: input.comment,
991
+ ...input.origins.length > 0 ? { CorsConfig: {
992
+ AccessControlAllowOrigins: list([...input.origins].sort()),
993
+ AccessControlAllowHeaders: {
994
+ Quantity: 1,
995
+ Items: ["*"]
996
+ },
997
+ AccessControlAllowMethods: {
998
+ Quantity: 3,
999
+ Items: [
1000
+ "GET",
1001
+ "HEAD",
1002
+ "OPTIONS"
1003
+ ]
1004
+ },
1005
+ AccessControlAllowCredentials: false,
1006
+ AccessControlMaxAgeSec: 86400,
1007
+ OriginOverride: false
1008
+ } } : {},
1009
+ ...input.immutable ? { CustomHeadersConfig: {
1010
+ Quantity: 1,
1011
+ Items: [{
1012
+ Header: "Cache-Control",
1013
+ Value: IMMUTABLE_CACHE_CONTROL,
1014
+ Override: true
1015
+ }]
1016
+ } } : {}
1017
+ };
1018
+ }
1019
+ /** The parts of a distribution's config this provider owns. */
1020
+ function distributionConfig(input) {
1021
+ const behaviour = (policy) => ({
1022
+ TargetOriginId: ORIGIN_ID,
1023
+ ViewerProtocolPolicy: "redirect-to-https",
1024
+ AllowedMethods: {
1025
+ Quantity: 3,
1026
+ Items: [
1027
+ "GET",
1028
+ "HEAD",
1029
+ "OPTIONS"
1030
+ ],
1031
+ CachedMethods: {
1032
+ Quantity: 2,
1033
+ Items: ["GET", "HEAD"]
1034
+ }
1035
+ },
1036
+ Compress: true,
1037
+ CachePolicyId: CACHING_OPTIMIZED,
1038
+ ...policy ? { ResponseHeadersPolicyId: policy } : {}
1039
+ });
1040
+ const patterns = [...new Set(input.immutable.map(pathPattern))].sort();
1041
+ const processors = input.processors;
1042
+ const processorPatterns = processors ? [...new Set(processors.prefixes.map((p) => `${pathPattern(p)}*`))].sort() : [];
1043
+ return {
1044
+ CallerReference: input.callerReference,
1045
+ Comment: input.comment,
1046
+ Enabled: true,
1047
+ Aliases: {
1048
+ Quantity: 1,
1049
+ Items: [input.host]
1050
+ },
1051
+ Origins: list([{
1052
+ Id: ORIGIN_ID,
1053
+ DomainName: input.bucketDomain,
1054
+ OriginPath: "",
1055
+ S3OriginConfig: { OriginAccessIdentity: "" },
1056
+ OriginAccessControlId: input.originAccessControlId
1057
+ }, ...processors ? [{
1058
+ Id: PROCESSORS_ORIGIN_ID,
1059
+ DomainName: processors.host,
1060
+ OriginPath: "",
1061
+ CustomOriginConfig: {
1062
+ HTTPPort: 80,
1063
+ HTTPSPort: 443,
1064
+ OriginProtocolPolicy: "https-only",
1065
+ OriginSslProtocols: {
1066
+ Quantity: 1,
1067
+ Items: ["TLSv1.2"]
1068
+ },
1069
+ OriginReadTimeout: 30,
1070
+ OriginKeepaliveTimeout: 5
1071
+ },
1072
+ CustomHeaders: {
1073
+ Quantity: 1,
1074
+ Items: [{
1075
+ HeaderName: ORIGIN_VERIFY_HEADER,
1076
+ HeaderValue: processors.secret
1077
+ }]
1078
+ }
1079
+ }] : []]),
1080
+ DefaultCacheBehavior: behaviour(input.corsPolicyId),
1081
+ CacheBehaviors: list([...processorPatterns.map((PathPattern) => ({
1082
+ ...behaviour(input.corsPolicyId),
1083
+ TargetOriginId: PROCESSORS_ORIGIN_ID,
1084
+ Compress: false,
1085
+ CachePolicyId: CACHING_OPTIMIZED_UNCOMPRESSED,
1086
+ PathPattern
1087
+ })), ...patterns.map((PathPattern) => ({
1088
+ ...behaviour(input.immutablePolicyId),
1089
+ PathPattern
1090
+ }))]),
1091
+ ViewerCertificate: {
1092
+ ACMCertificateArn: input.certificateArn,
1093
+ SSLSupportMethod: "sni-only",
1094
+ MinimumProtocolVersion: "TLSv1.2_2021",
1095
+ CloudFrontDefaultCertificate: false
1096
+ },
1097
+ HttpVersion: "http2and3",
1098
+ IsIPV6Enabled: true,
1099
+ DefaultRootObject: ""
1100
+ };
1101
+ }
1102
+ /**
1103
+ * Whether `current` already holds everything in `wanted`: each key `wanted`
1104
+ * names, recursively, with lists compared as sets. What else `current` holds
1105
+ * — logging, a WAF, geo restrictions set by hand — is left alone, and a
1106
+ * `CallerReference`, fixed at creation, is never compared.
1107
+ */
1108
+ function holds(current, wanted, key) {
1109
+ if (key === "CallerReference") return true;
1110
+ if (Array.isArray(wanted)) return Array.isArray(current) && current.length === wanted.length && wanted.every((w) => current.some((c) => holds(c, w)));
1111
+ if (wanted && typeof wanted === "object") {
1112
+ if (!current || typeof current !== "object") return false;
1113
+ return Object.entries(wanted).every(([k, v]) => holds(current[k], v, k));
1114
+ }
1115
+ return current === wanted;
1116
+ }
1117
+ /** Header configs a policy may hold that this provider never sets. */
1118
+ const FOREIGN_HEADER_CONFIGS = [
1119
+ "CorsConfig",
1120
+ "CustomHeadersConfig",
1121
+ "SecurityHeadersConfig",
1122
+ "RemoveHeadersConfig",
1123
+ "ServerTimingHeadersConfig"
1124
+ ];
1125
+ /** Whether a policy is exactly what is wanted: nothing missing, nothing more. */
1126
+ function sameHeadersPolicy(current, wanted) {
1127
+ if (!current || !holds(current, wanted)) return false;
1128
+ return FOREIGN_HEADER_CONFIGS.every((k) => {
1129
+ if (k in wanted) return true;
1130
+ const extra = current[k];
1131
+ return !extra || extra.Quantity === 0;
1132
+ });
1133
+ }
1134
+ const defaultCdnClients = async (credential) => {
1135
+ const CF = await import("@aws-sdk/client-cloudfront");
1136
+ const ACM = await import("@aws-sdk/client-acm");
1137
+ const config = await awsClientConfig(credential, CDN_REGION);
1138
+ const cloudfront = awsEndpoint(process.env, "CLOUDFRONT");
1139
+ const acm = awsEndpoint(process.env, "ACM");
1140
+ return {
1141
+ CF,
1142
+ ACM,
1143
+ cloudfront: new CF.CloudFrontClient({
1144
+ ...config,
1145
+ ...cloudfront ? { endpoint: cloudfront } : {}
1146
+ }),
1147
+ acm: new ACM.ACMClient({
1148
+ ...config,
1149
+ ...acm ? { endpoint: acm } : {}
1150
+ }),
1151
+ wait: {
1152
+ timeoutMs: 9e5,
1153
+ intervalMs: 1e4
1154
+ }
1155
+ };
1156
+ };
1157
+ let makeCdnClients = defaultCdnClients;
1158
+ /** What an AWS SDK error is called, whichever field carries it. */
1159
+ function errorName$2(error) {
1160
+ const e = error;
1161
+ return e?.Code ?? e?.name ?? "";
1162
+ }
1163
+ async function readOr$2(read, missing) {
1164
+ try {
1165
+ return await read();
1166
+ } catch (error) {
1167
+ if (missing.includes(errorName$2(error))) return void 0;
1168
+ throw error;
1169
+ }
1170
+ }
1171
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
1172
+ /** What stands in for an id a dry run would create. */
1173
+ const PLANNED = "(created by this deploy)";
1174
+ /** Each file server over a bucket, behind its own distribution. */
1175
+ async function ensureBucketCdn(ctx, input) {
1176
+ const domain = ctx.workspace.domains?.[ctx.stage];
1177
+ if (!domain) throw new FileServerCdnNeedsDomain(input.servers[0].id, ctx.stage);
1178
+ const clients = await makeCdnClients(ctx.credential);
1179
+ const out = {
1180
+ distributionArns: [],
1181
+ urls: {}
1182
+ };
1183
+ for (const server of input.servers) {
1184
+ const { arn, url } = await ensureServerCdn(ctx, clients, {
1185
+ ...input,
1186
+ server,
1187
+ host: cdnHost(server.id, domain),
1188
+ processorHost: processorOriginHost(server.id, domain)
1189
+ });
1190
+ out.distributionArns.push(arn);
1191
+ out.urls[server.id] = url;
1192
+ }
1193
+ return out;
1194
+ }
1195
+ async function ensureServerCdn(ctx, clients, input) {
1196
+ const { server, host } = input;
1197
+ const name = cdnName(ctx.identity.scope, ctx.stage, server.id);
1198
+ const comment = `gkm ${ctx.identity.key} ${ctx.stage} ${server.id}`.slice(0, 128);
1199
+ const certificateArn = await ensureCertificate(ctx, clients, {
1200
+ id: server.id,
1201
+ host,
1202
+ name
1203
+ });
1204
+ const originAccessControlId = await ensureOriginAccessControl(ctx, clients, {
1205
+ id: server.id,
1206
+ name,
1207
+ bucket: input.bucket
1208
+ });
1209
+ const corsPolicyId = input.origins.length > 0 ? await ensureHeadersPolicy(ctx, clients, {
1210
+ id: server.id,
1211
+ kind: "cors",
1212
+ config: headersPolicyConfig({
1213
+ name: `${name}-cors`,
1214
+ comment: `${comment}: CORS`,
1215
+ origins: input.origins,
1216
+ immutable: false
1217
+ })
1218
+ }) : void 0;
1219
+ const immutablePolicyId = server.immutable.length > 0 ? await ensureHeadersPolicy(ctx, clients, {
1220
+ id: server.id,
1221
+ kind: "immutable",
1222
+ config: headersPolicyConfig({
1223
+ name: `${name}-immutable`,
1224
+ comment: `${comment}: CORS and Cache-Control immutable`,
1225
+ origins: input.origins,
1226
+ immutable: true
1227
+ })
1228
+ }) : void 0;
1229
+ const processors = server.processors?.length ? {
1230
+ prefixes: server.processors,
1231
+ host: input.processorHost,
1232
+ secret: await ensureOriginSecret(ctx, server.id)
1233
+ } : void 0;
1234
+ const distribution = await ensureDistribution(ctx, clients, {
1235
+ id: server.id,
1236
+ comment,
1237
+ config: distributionConfig({
1238
+ comment,
1239
+ callerReference: name,
1240
+ host,
1241
+ certificateArn,
1242
+ bucketDomain: `${input.bucket}.s3.${input.region}.amazonaws.com`,
1243
+ originAccessControlId,
1244
+ ...corsPolicyId ? { corsPolicyId } : {},
1245
+ ...immutablePolicyId ? { immutablePolicyId } : {},
1246
+ immutable: server.immutable,
1247
+ ...processors ? { processors } : {}
1248
+ })
1249
+ });
1250
+ if (distribution.domainName !== PLANNED) {
1251
+ if ((await ctx.writeDns([{
1252
+ host,
1253
+ type: "CNAME",
1254
+ value: distribution.domainName
1255
+ }])).manual.includes(host)) ctx.log(` ${host} answers once that record exists — ${server.id}'s URL is https://${host}`);
1256
+ }
1257
+ return {
1258
+ arn: distribution.arn,
1259
+ url: `https://${host}`
1260
+ };
1261
+ }
1262
+ /**
1263
+ * The secret the CDN sends a file server's processors: the one in the stage's
1264
+ * secrets, or a new one written there — read by the stack, whose edge checks
1265
+ * it, when it composes.
1266
+ */
1267
+ async function ensureOriginSecret(ctx, serverId) {
1268
+ const key = originSecretKey(serverId);
1269
+ const kept = ctx.secrets[key];
1270
+ if (kept) return kept;
1271
+ const secret = newOriginSecret();
1272
+ ctx.log(` ${serverId}: a new processor origin secret, kept as ${key}`);
1273
+ await ctx.writeSecrets({ [key]: secret });
1274
+ return secret;
1275
+ }
1276
+ /** The certificate for `host`: recorded, adopted, or requested — then issued. */
1277
+ async function ensureCertificate(ctx, clients, input) {
1278
+ const { ACM, acm } = clients;
1279
+ const { id, host } = input;
1280
+ const entry = {
1281
+ key: `acm-certificate:${id}`,
1282
+ type: "acm-certificate",
1283
+ data: { host }
1284
+ };
1285
+ const describe = (arn) => readOr$2(() => acm.send(new ACM.DescribeCertificateCommand({ CertificateArn: arn })), ["ResourceNotFoundException"]).then((out) => out?.Certificate);
1286
+ let arn;
1287
+ const recorded = ctx.state.record(entry.key);
1288
+ if (recorded?.id && recorded.data?.host === host) {
1289
+ const found = await describe(recorded.id);
1290
+ if (found && found.Status !== "FAILED" && found.Status !== "VALIDATION_TIMED_OUT" && found.Status !== "REVOKED" && found.Status !== "EXPIRED") arn = recorded.id;
1291
+ }
1292
+ if (!arn) {
1293
+ arn = await findCertificate(clients, host);
1294
+ if (arn) ctx.log(` adopting the certificate for ${host} — ${arn}`);
1295
+ }
1296
+ if (!arn) {
1297
+ if (ctx.dryRun) {
1298
+ await ctx.change({
1299
+ construct: id,
1300
+ resource: `certificate ${host}`,
1301
+ change: "request (ACM, us-east-1, DNS-validated)"
1302
+ }, async () => {});
1303
+ return PLANNED;
1304
+ }
1305
+ await ctx.state.pending(entry);
1306
+ arn = (await acm.send(new ACM.RequestCertificateCommand({
1307
+ DomainName: host,
1308
+ ValidationMethod: "DNS",
1309
+ IdempotencyToken: input.name.replace(/[^A-Za-z0-9]/g, "").slice(0, 32),
1310
+ Tags: identityTags(ctx, id).map((t) => ({
1311
+ Key: t.Key,
1312
+ Value: t.Value
1313
+ }))
1314
+ }))).CertificateArn;
1315
+ await ctx.change({
1316
+ construct: id,
1317
+ resource: `certificate ${host}`,
1318
+ change: "request (ACM, us-east-1, DNS-validated)"
1319
+ }, async () => {});
1320
+ }
1321
+ if (!ctx.dryRun) await ctx.state.ready(entry, arn);
1322
+ let certificate = await describe(arn);
1323
+ let record = certificate?.DomainValidationOptions?.[0]?.ResourceRecord;
1324
+ for (let i = 0; !record && i < 15; i++) {
1325
+ await sleep(Math.min(clients.wait.intervalMs, 2e3));
1326
+ certificate = await describe(arn);
1327
+ record = certificate?.DomainValidationOptions?.[0]?.ResourceRecord;
1328
+ }
1329
+ const validation = record ? {
1330
+ name: record.Name.replace(/\.$/, ""),
1331
+ value: record.Value.replace(/\.$/, "")
1332
+ } : void 0;
1333
+ const written = validation ? await ctx.writeDns([{
1334
+ host: validation.name,
1335
+ type: "CNAME",
1336
+ value: validation.value
1337
+ }]) : { manual: [] };
1338
+ if (certificate?.Status === "ISSUED" || ctx.dryRun) return arn;
1339
+ if (validation && written.manual.includes(validation.name.toLowerCase())) throw new CdnCertificatePending(id, host, validation, "manual");
1340
+ ctx.log(` waiting for ACM to issue the certificate for ${host}…`);
1341
+ const deadline = Date.now() + clients.wait.timeoutMs;
1342
+ while (certificate?.Status === "PENDING_VALIDATION" && Date.now() < deadline) {
1343
+ await sleep(clients.wait.intervalMs);
1344
+ certificate = await describe(arn);
1345
+ }
1346
+ if (certificate?.Status === "ISSUED") return arn;
1347
+ if (certificate?.Status === "PENDING_VALIDATION" || !certificate) throw new CdnCertificatePending(id, host, validation ?? {
1348
+ name: `(not published yet) ${host}`,
1349
+ value: ""
1350
+ }, "timeout");
1351
+ throw new CdnCertificateFailed(id, host, certificate.Status ?? "unknown", certificate.FailureReason);
1352
+ }
1353
+ /** An issued or pending certificate for exactly `host`, if the account has one. */
1354
+ async function findCertificate(clients, host) {
1355
+ const { ACM, acm } = clients;
1356
+ let NextToken;
1357
+ do {
1358
+ const page = await acm.send(new ACM.ListCertificatesCommand({
1359
+ CertificateStatuses: ["ISSUED", "PENDING_VALIDATION"],
1360
+ ...NextToken ? { NextToken } : {}
1361
+ }));
1362
+ const match = page.CertificateSummaryList?.find((c) => c.DomainName?.toLowerCase() === host);
1363
+ if (match?.CertificateArn) return match.CertificateArn;
1364
+ NextToken = page.NextToken;
1365
+ } while (NextToken);
1366
+ }
1367
+ /** The origin access control CloudFront signs its bucket requests with. */
1368
+ async function ensureOriginAccessControl(ctx, clients, input) {
1369
+ const { CF, cloudfront } = clients;
1370
+ const entry = {
1371
+ key: `cloudfront-oac:${input.id}`,
1372
+ type: "cloudfront-oac",
1373
+ data: { name: input.name }
1374
+ };
1375
+ const recorded = ctx.state.record(entry.key)?.id;
1376
+ if (recorded) {
1377
+ if (await readOr$2(() => cloudfront.send(new CF.GetOriginAccessControlCommand({ Id: recorded })), ["NoSuchOriginAccessControl"])) return recorded;
1378
+ }
1379
+ let Marker;
1380
+ do {
1381
+ const page = await cloudfront.send(new CF.ListOriginAccessControlsCommand({ ...Marker ? { Marker } : {} }));
1382
+ const match = page.OriginAccessControlList?.Items?.find((o) => o.Name === input.name);
1383
+ if (match?.Id) {
1384
+ if (!ctx.dryRun) await ctx.state.ready(entry, match.Id);
1385
+ return match.Id;
1386
+ }
1387
+ Marker = page.OriginAccessControlList?.NextMarker;
1388
+ } while (Marker);
1389
+ let id = PLANNED;
1390
+ await ctx.change({
1391
+ construct: input.id,
1392
+ resource: `origin access control ${input.name}`,
1393
+ change: `create (CloudFront signs its reads of ${input.bucket})`
1394
+ }, async () => {
1395
+ await ctx.state.pending(entry);
1396
+ id = (await cloudfront.send(new CF.CreateOriginAccessControlCommand({ OriginAccessControlConfig: {
1397
+ Name: input.name,
1398
+ Description: `gkm: CloudFront's reads of ${input.bucket}`,
1399
+ OriginAccessControlOriginType: "s3",
1400
+ SigningBehavior: "always",
1401
+ SigningProtocol: "sigv4"
1402
+ } }))).OriginAccessControl.Id;
1403
+ await ctx.state.ready(entry, id);
1404
+ });
1405
+ return id;
1406
+ }
1407
+ /** One response headers policy, as wanted. */
1408
+ async function ensureHeadersPolicy(ctx, clients, input) {
1409
+ const { CF, cloudfront } = clients;
1410
+ const { config } = input;
1411
+ const entry = {
1412
+ key: `cloudfront-headers:${input.id}:${input.kind}`,
1413
+ type: "cloudfront-headers",
1414
+ data: { name: config.Name }
1415
+ };
1416
+ const read = (Id) => readOr$2(() => cloudfront.send(new CF.GetResponseHeadersPolicyConfigCommand({ Id })), ["NoSuchResponseHeadersPolicy"]);
1417
+ let id = ctx.state.record(entry.key)?.id;
1418
+ let current = id ? await read(id) : void 0;
1419
+ if (!current) {
1420
+ id = void 0;
1421
+ let Marker;
1422
+ do {
1423
+ const page = await cloudfront.send(new CF.ListResponseHeadersPoliciesCommand({
1424
+ Type: "custom",
1425
+ ...Marker ? { Marker } : {}
1426
+ }));
1427
+ const match = page.ResponseHeadersPolicyList?.Items?.find((p) => p.ResponseHeadersPolicy?.ResponseHeadersPolicyConfig?.Name === config.Name);
1428
+ if (match?.ResponseHeadersPolicy?.Id) {
1429
+ id = match.ResponseHeadersPolicy.Id;
1430
+ current = await read(id);
1431
+ }
1432
+ Marker = page.ResponseHeadersPolicyList?.NextMarker;
1433
+ } while (!id && Marker);
1434
+ }
1435
+ if (id && current) {
1436
+ if (!ctx.dryRun) await ctx.state.ready(entry, id);
1437
+ if (sameHeadersPolicy(current.ResponseHeadersPolicyConfig, config)) return id;
1438
+ const existing = id;
1439
+ await ctx.change({
1440
+ construct: input.id,
1441
+ resource: `response headers policy ${config.Name}`,
1442
+ change: "put back what drifted"
1443
+ }, async () => {
1444
+ await cloudfront.send(new CF.UpdateResponseHeadersPolicyCommand({
1445
+ Id: existing,
1446
+ IfMatch: current.ETag,
1447
+ ResponseHeadersPolicyConfig: config
1448
+ }));
1449
+ });
1450
+ return existing;
1451
+ }
1452
+ let created = PLANNED;
1453
+ await ctx.change({
1454
+ construct: input.id,
1455
+ resource: `response headers policy ${config.Name}`,
1456
+ change: "create"
1457
+ }, async () => {
1458
+ await ctx.state.pending(entry);
1459
+ created = (await cloudfront.send(new CF.CreateResponseHeadersPolicyCommand({ ResponseHeadersPolicyConfig: config }))).ResponseHeadersPolicy.Id;
1460
+ await ctx.state.ready(entry, created);
1461
+ });
1462
+ return created;
1463
+ }
1464
+ /** The distribution: recorded, found by its comment, or created — as wanted. */
1465
+ async function ensureDistribution(ctx, clients, input) {
1466
+ const { CF, cloudfront } = clients;
1467
+ const entry = {
1468
+ key: `cloudfront-distribution:${input.id}`,
1469
+ type: "cloudfront-distribution"
1470
+ };
1471
+ const get = (Id) => readOr$2(() => cloudfront.send(new CF.GetDistributionCommand({ Id })), ["NoSuchDistribution"]);
1472
+ let found = ctx.state.record(entry.key)?.id ? await get(ctx.state.record(entry.key).id) : void 0;
1473
+ if (!found) {
1474
+ let Marker;
1475
+ do {
1476
+ const page = await cloudfront.send(new CF.ListDistributionsCommand({ ...Marker ? { Marker } : {} }));
1477
+ const match = page.DistributionList?.Items?.find((d) => d.Comment === input.comment);
1478
+ if (match?.Id) found = await get(match.Id);
1479
+ Marker = page.DistributionList?.NextMarker;
1480
+ } while (!found && Marker);
1481
+ }
1482
+ if (found?.Distribution) {
1483
+ const distribution = found.Distribution;
1484
+ const id = distribution.Id;
1485
+ const located = {
1486
+ arn: distribution.ARN,
1487
+ domainName: distribution.DomainName
1488
+ };
1489
+ if (!ctx.dryRun) await ctx.state.ready({
1490
+ ...entry,
1491
+ data: located
1492
+ }, id);
1493
+ const current = await cloudfront.send(new CF.GetDistributionConfigCommand({ Id: id }));
1494
+ if (holds(current.DistributionConfig, input.config)) return located;
1495
+ await ctx.change({
1496
+ construct: input.id,
1497
+ resource: `distribution ${id}`,
1498
+ change: "put back what drifted"
1499
+ }, async () => {
1500
+ await cloudfront.send(new CF.UpdateDistributionCommand({
1501
+ Id: id,
1502
+ IfMatch: current.ETag,
1503
+ DistributionConfig: {
1504
+ ...current.DistributionConfig,
1505
+ ...input.config,
1506
+ CallerReference: current.DistributionConfig.CallerReference
1507
+ }
1508
+ }));
1509
+ });
1510
+ return located;
1511
+ }
1512
+ let created = {
1513
+ arn: `arn:aws:cloudfront::${PLANNED}`,
1514
+ domainName: PLANNED
1515
+ };
1516
+ const host = input.config.Aliases?.Items?.[0] ?? input.id;
1517
+ await ctx.change({
1518
+ construct: input.id,
1519
+ resource: `distribution ${host}`,
1520
+ change: "create (CloudFront, takes minutes to reach every edge)"
1521
+ }, async () => {
1522
+ await ctx.state.pending(entry);
1523
+ const out = await cloudfront.send(new CF.CreateDistributionWithTagsCommand({ DistributionConfigWithTags: {
1524
+ DistributionConfig: input.config,
1525
+ Tags: { Items: identityTags(ctx, input.id).map((t) => ({
1526
+ Key: t.Key,
1527
+ Value: t.Value
1528
+ })) }
1529
+ } }));
1530
+ created = {
1531
+ arn: out.Distribution.ARN,
1532
+ domainName: out.Distribution.DomainName
1533
+ };
1534
+ await ctx.state.ready({
1535
+ ...entry,
1536
+ data: created
1537
+ }, out.Distribution.Id);
1538
+ });
1539
+ return created;
1540
+ }
1541
+ //#endregion
1542
+ //#region src/providers/s3/events.ts
1543
+ /** What S3 tells the topic about: every object written, and every removed. */
1544
+ const BUCKET_EVENTS = ["s3:ObjectCreated:*", "s3:ObjectRemoved:*"];
1545
+ /**
1546
+ * Each bucket a subscriber watches, with the subscribers discovery could name.
1547
+ *
1548
+ * A bucket is watched when a process consumes its events — discovery records
1549
+ * the edge, and the subscriber by its export — or when a build put a
1550
+ * subscriber in its declaration. Only a named subscriber can be subscribed: a
1551
+ * route is named after its export.
1552
+ */
1553
+ function bucketWatchers(manifest, runnables, subscribers = {}) {
1554
+ const watched = /* @__PURE__ */ new Map();
1555
+ for (const [id, declaration] of Object.entries(manifest)) {
1556
+ if (declaration.kind !== "objects") continue;
1557
+ const key = provideKey(id, "eventsConnectionString");
1558
+ const named = Object.entries(subscribers).flatMap(([worker, bound]) => bound.filter((s) => s.connectionKey === key).map((s) => ({
1559
+ worker,
1560
+ name: s.name
1561
+ }))).sort((a, b) => `${a.worker}/${a.name}`.localeCompare(`${b.worker}/${b.name}`));
1562
+ const edged = Object.values(runnables).some((edges) => edges.includes(eventsEdge(id)));
1563
+ const declared = (declaration.subscribers?.length ?? 0) > 0;
1564
+ if (named.length > 0 || edged || declared) watched.set(id, named);
1565
+ }
1566
+ return watched;
1567
+ }
1568
+ /** The topic a bucket's changes are published to. */
1569
+ function eventsTopicName(bucket) {
1570
+ return `${bucket.replaceAll(".", "-")}-events`;
1571
+ }
1572
+ /** What SNS gives up delivering, kept. */
1573
+ function eventsDeadLetterName(bucket) {
1574
+ return `${eventsTopicName(bucket)}-dlq`;
1575
+ }
1576
+ /** Only this bucket, in this account, may publish to its topic. */
1577
+ function eventsTopicPolicy(topicArn, bucket, account) {
1578
+ return {
1579
+ Version: "2012-10-17",
1580
+ Statement: [{
1581
+ Sid: "GkmBucketPublishes",
1582
+ Effect: "Allow",
1583
+ Principal: { Service: "s3.amazonaws.com" },
1584
+ Action: "sns:Publish",
1585
+ Resource: topicArn,
1586
+ Condition: {
1587
+ ArnLike: { "aws:SourceArn": `arn:aws:s3:::${bucket}` },
1588
+ StringEquals: { "aws:SourceAccount": account }
1589
+ }
1590
+ }]
1591
+ };
1592
+ }
1593
+ /** Only the bucket's topic may send to its dead-letter queue. */
1594
+ function eventsDeadLetterPolicy(queueArn, topicArn) {
1595
+ return {
1596
+ Version: "2012-10-17",
1597
+ Statement: [{
1598
+ Sid: "GkmTopicDeadLetters",
1599
+ Effect: "Allow",
1600
+ Principal: { Service: "sns.amazonaws.com" },
1601
+ Action: "sqs:SendMessage",
1602
+ Resource: queueArn,
1603
+ Condition: { ArnEquals: { "aws:SourceArn": topicArn } }
1604
+ }]
1605
+ };
1606
+ }
1607
+ /** The id this provider's notification entry goes under. */
1608
+ const notificationId = (id) => `gkm-${id}`;
1609
+ /**
1610
+ * The bucket's notifications with this provider's entry put back, and whether
1611
+ * that changed anything. Every other entry — a queue, a function, a topic
1612
+ * somebody added by hand — is kept exactly as it was.
1613
+ */
1614
+ function mergedNotifications(current, id, topicArn) {
1615
+ const own = notificationId(id);
1616
+ const wanted = {
1617
+ Id: own,
1618
+ TopicArn: topicArn,
1619
+ Events: [...BUCKET_EVENTS]
1620
+ };
1621
+ const topics = current.TopicConfigurations ?? [];
1622
+ const existing = topics.find((t) => t.Id === own);
1623
+ const same = existing !== void 0 && existing.TopicArn === topicArn && JSON.stringify([...existing.Events ?? []].sort()) === JSON.stringify([...BUCKET_EVENTS].sort()) && existing.Filter === void 0;
1624
+ const { ResponseMetadata: _, ...rest } = current;
1625
+ return {
1626
+ config: {
1627
+ ...rest,
1628
+ TopicConfigurations: [...topics.filter((t) => t.Id !== own), wanted]
1629
+ },
1630
+ changed: !same
1631
+ };
1632
+ }
1633
+ /** S3 refused the entry because another one already covers these events. */
1634
+ var BucketNotificationsOverlap = class extends GkmError {
1635
+ bucket;
1636
+ detail;
1637
+ constructor(bucket, detail) {
1638
+ super(`'${bucket}' already sends object creates or deletes somewhere else, and S3 sends each event to one place only, so the deploy cannot add its own entry. Remove the other notification from the bucket, or give it a prefix, and deploy again. S3 said: ${detail}`);
1639
+ this.bucket = bucket;
1640
+ this.detail = detail;
1641
+ this.name = "BucketNotificationsOverlap";
1642
+ }
1643
+ };
1644
+ const defaultEventsClients = async (credential, region) => {
1645
+ const SNS = await import("@aws-sdk/client-sns");
1646
+ const SQS = await import("@aws-sdk/client-sqs");
1647
+ const { STSClient, GetCallerIdentityCommand } = await import("@aws-sdk/client-sts");
1648
+ const config = await awsClientConfig(credential, region);
1649
+ const endpoint = awsEndpoint(process.env, "SNS");
1650
+ const sts = new STSClient({
1651
+ ...config,
1652
+ ...awsEndpoint(process.env, "STS") ? { endpoint: awsEndpoint(process.env, "STS") } : {}
1653
+ });
1654
+ const { Account } = await sts.send(new GetCallerIdentityCommand({}));
1655
+ sts.destroy();
1656
+ return {
1657
+ SNS,
1658
+ SQS,
1659
+ sns: new SNS.SNSClient({
1660
+ ...config,
1661
+ ...endpoint ? { endpoint } : {}
1662
+ }),
1663
+ sqs: new SQS.SQSClient({
1664
+ ...config,
1665
+ ...awsEndpoint(process.env, "SQS") ? { endpoint: awsEndpoint(process.env, "SQS") } : {}
1666
+ }),
1667
+ account: Account,
1668
+ region,
1669
+ ...endpoint ? { endpoint } : {}
1670
+ };
1671
+ };
1672
+ let makeEventsClients = defaultEventsClients;
1673
+ function errorName$1(error) {
1674
+ const e = error;
1675
+ return e?.name ?? e?.Code ?? e?.code ?? "";
1676
+ }
1677
+ async function readOr$1(read, missing) {
1678
+ try {
1679
+ return await read();
1680
+ } catch (error) {
1681
+ if (missing.includes(errorName$1(error))) return void 0;
1682
+ throw error;
1683
+ }
1684
+ }
1685
+ /**
1686
+ * The topic, its policy, the dead-letter queue and the bucket's notification
1687
+ * entry for one watched bucket — created where missing, put back where they
1688
+ * drifted.
1689
+ */
1690
+ async function ensureBucketEvents(ctx, s3, input) {
1691
+ const { id, bucket, region } = input;
1692
+ const { SNS, SQS, sns, sqs, account } = await makeEventsClients(ctx.credential, region);
1693
+ const topicName = eventsTopicName(bucket);
1694
+ const topicArn = `arn:aws:sns:${region}:${account}:${topicName}`;
1695
+ const topicEntry = {
1696
+ key: `sns-topic:${id}`,
1697
+ type: "sns-topic"
1698
+ };
1699
+ const topic = await readOr$1(() => sns.send(new SNS.GetTopicAttributesCommand({ TopicArn: topicArn })), ["NotFound", "NotFoundException"]);
1700
+ if (topic) await ctx.state.ready(topicEntry, topicArn);
1701
+ else await ctx.change({
1702
+ construct: id,
1703
+ resource: `SNS topic ${topicName}`,
1704
+ change: "create"
1705
+ }, async () => {
1706
+ await ctx.state.pending(topicEntry);
1707
+ await sns.send(new SNS.CreateTopicCommand({
1708
+ Name: topicName,
1709
+ Tags: identityTags(ctx, id)
1710
+ }));
1711
+ await ctx.state.ready(topicEntry, topicArn);
1712
+ });
1713
+ const topicPolicy = eventsTopicPolicy(topicArn, bucket, account);
1714
+ const currentTopicPolicy = topic?.Attributes?.Policy;
1715
+ if (!currentTopicPolicy || canonicalPolicy(JSON.parse(currentTopicPolicy)) !== canonicalPolicy(topicPolicy)) await ctx.change({
1716
+ construct: id,
1717
+ resource: `SNS topic ${topicName}`,
1718
+ change: `publishable by '${bucket}' only`
1719
+ }, async () => {
1720
+ await sns.send(new SNS.SetTopicAttributesCommand({
1721
+ TopicArn: topicArn,
1722
+ AttributeName: "Policy",
1723
+ AttributeValue: JSON.stringify(topicPolicy)
1724
+ }));
1725
+ });
1726
+ const queueName = eventsDeadLetterName(bucket);
1727
+ const deadLetterArn = `arn:aws:sqs:${region}:${account}:${queueName}`;
1728
+ const queueEntry = {
1729
+ key: `sqs-queue:${id}-events-dlq`,
1730
+ type: "sqs-queue"
1731
+ };
1732
+ const queue = await readOr$1(() => sqs.send(new SQS.GetQueueUrlCommand({ QueueName: queueName })), ["QueueDoesNotExist", "AWS.SimpleQueueService.NonExistentQueue"]);
1733
+ let queueUrl = queue?.QueueUrl;
1734
+ if (queueUrl) await ctx.state.ready(queueEntry, deadLetterArn);
1735
+ else await ctx.change({
1736
+ construct: id,
1737
+ resource: `SQS queue ${queueName}`,
1738
+ change: "create"
1739
+ }, async () => {
1740
+ await ctx.state.pending(queueEntry);
1741
+ queueUrl = (await sqs.send(new SQS.CreateQueueCommand({
1742
+ QueueName: queueName,
1743
+ Attributes: { MessageRetentionPeriod: "1209600" },
1744
+ tags: Object.fromEntries(identityTags(ctx, id).map((t) => [t.Key, t.Value]))
1745
+ }))).QueueUrl;
1746
+ await ctx.state.ready(queueEntry, deadLetterArn);
1747
+ });
1748
+ const queuePolicy = eventsDeadLetterPolicy(deadLetterArn, topicArn);
1749
+ const currentQueuePolicy = queue?.QueueUrl ? (await sqs.send(new SQS.GetQueueAttributesCommand({
1750
+ QueueUrl: queue.QueueUrl,
1751
+ AttributeNames: ["Policy"]
1752
+ }))).Attributes?.Policy : void 0;
1753
+ if (!currentQueuePolicy || canonicalPolicy(JSON.parse(currentQueuePolicy)) !== canonicalPolicy(queuePolicy)) await ctx.change({
1754
+ construct: id,
1755
+ resource: `SQS queue ${queueName}`,
1756
+ change: `receives what ${topicName} cannot deliver`
1757
+ }, async () => {
1758
+ await sqs.send(new SQS.SetQueueAttributesCommand({
1759
+ QueueUrl: queueUrl,
1760
+ Attributes: { Policy: JSON.stringify(queuePolicy) }
1761
+ }));
1762
+ });
1763
+ const merged = mergedNotifications(await readOr$1(() => s3.s3.send(new s3.S3.GetBucketNotificationConfigurationCommand({ Bucket: bucket })), ["NoSuchBucket"]) ?? {}, id, topicArn);
1764
+ if (merged.changed) await ctx.change({
1765
+ construct: id,
1766
+ resource: `bucket ${bucket}`,
1767
+ change: `send object creates and deletes to ${topicName}`
1768
+ }, async () => {
1769
+ try {
1770
+ await s3.s3.send(new s3.S3.PutBucketNotificationConfigurationCommand({
1771
+ Bucket: bucket,
1772
+ NotificationConfiguration: merged.config
1773
+ }));
1774
+ } catch (error) {
1775
+ const message = error.message ?? "";
1776
+ if (/overlap/i.test(message)) throw new BucketNotificationsOverlap(bucket, message);
1777
+ throw error;
1778
+ }
1779
+ });
1780
+ sns.destroy();
1781
+ sqs.destroy();
1782
+ return {
1783
+ topicArn,
1784
+ deadLetterArn
1785
+ };
1786
+ }
1787
+ /**
1788
+ * The events key: the topic's address, signed by the bucket's own access key
1789
+ * — read off the bucket's URL, so a rotated key rotates this one with it.
1790
+ */
1791
+ async function ensureEventsKey(ctx, input) {
1792
+ const { id, topicArn, region } = input;
1793
+ const key = provideKey(id, "eventsConnectionString");
1794
+ const url = ctx.secrets[provideKey(id, "url")];
1795
+ let parsed;
1796
+ try {
1797
+ parsed = url ? s3Url.parse(url) : void 0;
1798
+ } catch {
1799
+ parsed = void 0;
1800
+ }
1801
+ if (!parsed?.accessKeyId || !parsed.secretAccessKey) {
1802
+ await ctx.change({
1803
+ construct: id,
1804
+ resource: key,
1805
+ change: "set to the topic’s address"
1806
+ }, async () => {});
1807
+ return;
1808
+ }
1809
+ const { snsUrl } = await import("@geekmidas/events/sns");
1810
+ const endpoint = awsEndpoint(process.env, "SNS");
1811
+ const address = new URL(snsUrl.build({
1812
+ topicArn,
1813
+ region,
1814
+ ...endpoint ? { endpoint } : {}
1815
+ }));
1816
+ address.searchParams.set("accessKeyId", parsed.accessKeyId);
1817
+ address.searchParams.set("secretAccessKey", parsed.secretAccessKey);
1818
+ const value = address.toString();
1819
+ const entry = {
1820
+ key: `events-key:${id}`,
1821
+ type: "url"
1822
+ };
1823
+ if (ctx.secrets[key] === value) {
1824
+ await ctx.state.ready(entry, topicArn);
1825
+ return;
1826
+ }
1827
+ await ctx.change({
1828
+ construct: id,
1829
+ resource: key,
1830
+ change: `set to ${topicArn}`
1831
+ }, async () => {
1832
+ await ctx.writeSecrets({ [key]: value });
1833
+ await ctx.state.ready(entry, topicArn);
1834
+ });
1835
+ }
1836
+ /** Where SNS pushes to a worker: its own host on the stage's domain. */
1837
+ function workerPushHost(worker, domain) {
1838
+ return `${appKey(worker)}.${domain}`;
1839
+ }
1840
+ /**
1841
+ * One HTTP subscription per subscriber to the bucket's topic, at its route on
1842
+ * its worker's host, with the bucket's dead-letter queue — and each ARN
1843
+ * recorded, which is what the worker accepts deliveries under.
1844
+ *
1845
+ * Unfiltered: a bucket's notifications carry no `type` attribute, and the
1846
+ * subscriber's own filter — events, prefix, suffix — is what decides. Only a
1847
+ * compose stage pushes to a worker; any other is told so and left alone.
1848
+ */
1849
+ async function ensurePushSubscriptions(ctx, input) {
1850
+ const { id, events, region, watchers } = input;
1851
+ if (!deploysWithCompose(ctx.workspace)) {
1852
+ ctx.log(` '${id}' is watched, and only a compose stage pushes a bucket's changes to a worker: its subscribers receive nothing on '${ctx.stage}'`);
1853
+ return;
1854
+ }
1855
+ if (watchers.length === 0) return;
1856
+ const domain = ctx.workspace.domains?.[ctx.stage];
1857
+ if (!domain) throw new NoDomainForStage(ctx.stage);
1858
+ const { SNSConnection, reconcileHttpSubscriptions } = await import("@geekmidas/events/sns");
1859
+ const { credentials } = await awsClientConfig(ctx.credential, region);
1860
+ const endpoint = awsEndpoint(process.env, "SNS");
1861
+ const byWorker = /* @__PURE__ */ new Map();
1862
+ for (const watcher of watchers) byWorker.set(watcher.worker, [...byWorker.get(watcher.worker) ?? [], watcher]);
1863
+ for (const [worker, own] of byWorker) {
1864
+ const host = workerPushHost(worker, domain);
1865
+ const endpoints = own.map(({ name }) => ({
1866
+ name,
1867
+ endpoint: `https://${host}${subscriberRoute(name)}`,
1868
+ deadLetterQueueArn: events.deadLetterArn
1869
+ }));
1870
+ const unrecorded = own.filter(({ name }) => ctx.state.record(subscriptionKey(worker, name))?.status !== "ready");
1871
+ if (ctx.dryRun) {
1872
+ if (unrecorded.length > 0) await ctx.change({
1873
+ construct: id,
1874
+ resource: `SNS subscriptions on ${host}`,
1875
+ change: `subscribe ${unrecorded.map((w) => w.name).join(", ")}`
1876
+ }, async () => {});
1877
+ continue;
1878
+ }
1879
+ const connection = new SNSConnection({
1880
+ topicArn: events.topicArn,
1881
+ region,
1882
+ ...endpoint ? { endpoint } : {},
1883
+ ...credentials ? { credentials } : {}
1884
+ });
1885
+ const result = await reconcileHttpSubscriptions(connection, {
1886
+ endpoints,
1887
+ ownedHost: host
1888
+ });
1889
+ await connection.close();
1890
+ for (const spec of endpoints) {
1891
+ const arn = result.subscriptions[spec.name];
1892
+ const pending = result.pending.includes(spec.name);
1893
+ if (ctx.state.record(subscriptionKey(worker, spec.name))?.id !== arn) ctx.log(` ✓ '${spec.name}' subscribed to ${events.topicArn.split(":").pop()} at ${spec.endpoint}`);
1894
+ const { key, type, data } = subscriptionResource({
1895
+ worker,
1896
+ name: spec.name,
1897
+ topicArn: events.topicArn,
1898
+ endpoint: spec.endpoint,
1899
+ deadLetterQueueArn: events.deadLetterArn,
1900
+ pending
1901
+ }, arn);
1902
+ await ctx.state.ready({
1903
+ key,
1904
+ type,
1905
+ ...data ? { data } : {}
1906
+ }, arn);
1907
+ }
1908
+ for (const arn of result.removed) ctx.log(` – unsubscribed ${arn}: no subscriber wants it any more`);
1909
+ }
1910
+ }
1911
+ //#endregion
862
1912
  //#region src/providers/s3/origins.ts
863
1913
  /**
864
1914
  * Which browser origins may call a bucket: the stage's sites that depend on
@@ -917,10 +1967,12 @@ function bucketConstructs(manifest) {
917
1967
  return Object.entries(manifest).filter(([, d]) => d.kind === "objects").map(([id, d]) => ({
918
1968
  id,
919
1969
  versioned: d.versioned === true,
920
- ...d.expire ? { expire: d.expire } : {},
1970
+ ...bucketExpiry(manifest, id) ? { expire: bucketExpiry(manifest, id) } : {},
921
1971
  servers: Object.entries(manifest).filter(([, s]) => s.kind === "file-server" && s.of === id).map(([sid, s]) => ({
922
1972
  id: sid,
923
- open: s.open ?? []
1973
+ open: s.open ?? [],
1974
+ immutable: s.immutable ?? [],
1975
+ ...s.processors?.length ? { processors: [...new Set(s.processors.map((p) => p.prefix))].sort() } : {}
924
1976
  })).sort((a, b) => a.id.localeCompare(b.id))
925
1977
  })).sort((a, b) => a.id.localeCompare(b.id));
926
1978
  }
@@ -1052,9 +2104,10 @@ let makeClients = defaultClients;
1052
2104
  async function ensureS3(ctx) {
1053
2105
  const region = s3Region(ctx);
1054
2106
  const clients = await makeClients(ctx.credential, region);
1055
- for (const construct of bucketConstructs(ctx.manifest)) await ensureBucket(ctx, clients, construct);
2107
+ const watched = bucketWatchers(ctx.manifest, ctx.runnables, ctx.subscribers);
2108
+ for (const construct of bucketConstructs(ctx.manifest)) await ensureBucket(ctx, clients, construct, watched.get(construct.id));
1056
2109
  }
1057
- async function ensureBucket(ctx, stageClients, construct) {
2110
+ async function ensureBucket(ctx, stageClients, construct, watchers) {
1058
2111
  const { id } = construct;
1059
2112
  const nameInput = {
1060
2113
  scope: ctx.identity.scope,
@@ -1070,19 +2123,40 @@ async function ensureBucket(ctx, stageClients, construct) {
1070
2123
  clients = await makeClients(ctx.credential, located.region);
1071
2124
  ctx.log(` '${bucket}' is in ${located.region}, not ${stageClients.region}: '${id}' is reached and addressed there`);
1072
2125
  }
2126
+ const cdn = ctx.config.cdn !== false && construct.servers.length > 0 ? await ensureBucketCdn(ctx, {
2127
+ bucket,
2128
+ region: clients.region,
2129
+ servers: construct.servers,
2130
+ origins: bucketOrigins({
2131
+ workspace: ctx.workspace,
2132
+ manifest: ctx.manifest,
2133
+ runnables: ctx.runnables,
2134
+ stage: ctx.stage,
2135
+ bucket: id
2136
+ }).origins
2137
+ }) : void 0;
1073
2138
  await ensureBucketSettings(ctx, clients, {
1074
2139
  id,
1075
2140
  bucket,
1076
2141
  exists,
1077
2142
  versioning,
1078
2143
  open,
2144
+ ...cdn ? { cdn } : {},
1079
2145
  ...construct.expire ? { expire: construct.expire } : {}
1080
2146
  });
2147
+ const events = watchers ? await ensureBucketEvents(ctx, clients, {
2148
+ id,
2149
+ bucket,
2150
+ region: clients.region
2151
+ }) : void 0;
1081
2152
  const user = await ensureIamUser(ctx, clients, {
1082
2153
  id,
1083
2154
  user: iamUserName(nameInput),
1084
2155
  policyName: IAM_POLICY_NAME,
1085
- policy: userPolicy(bucket, { versioning }),
2156
+ policy: userPolicy(bucket, {
2157
+ versioning,
2158
+ ...events ? { eventsTopicArn: events.topicArn } : {}
2159
+ }),
1086
2160
  describe: "this bucket only"
1087
2161
  });
1088
2162
  await ensureAccessKey(ctx, clients, {
@@ -1091,8 +2165,22 @@ async function ensureBucket(ctx, stageClients, construct) {
1091
2165
  urlKey: provideKey(id, "url"),
1092
2166
  ...user
1093
2167
  });
1094
- const publicUrl = bucketPublicUrl(bucket, clients.region, clients.endpoint);
2168
+ if (events && watchers) {
2169
+ await ensureEventsKey(ctx, {
2170
+ id,
2171
+ topicArn: events.topicArn,
2172
+ region: clients.region
2173
+ });
2174
+ await ensurePushSubscriptions(ctx, {
2175
+ id,
2176
+ events,
2177
+ region: clients.region,
2178
+ watchers
2179
+ });
2180
+ }
2181
+ const bucketUrl = bucketPublicUrl(bucket, clients.region, clients.endpoint);
1095
2182
  for (const server of construct.servers) {
2183
+ const publicUrl = cdn?.urls[server.id] ?? bucketUrl;
1096
2184
  const key = provideKey(server.id, "url");
1097
2185
  const entry = {
1098
2186
  key: `file-server:${server.id}`,
@@ -1250,7 +2338,7 @@ async function ensureBucketSettings(ctx, clients, input) {
1250
2338
  Value: t.Value
1251
2339
  })) }
1252
2340
  })));
1253
- const isOpen = input.open.length > 0;
2341
+ const isOpen = input.open.length > 0 && !input.cdn;
1254
2342
  const block = publicAccessBlock(isOpen);
1255
2343
  const ensureBlock = async () => {
1256
2344
  const now = (await read(() => s3.send(new S3.GetPublicAccessBlockCommand({ Bucket })), ["NoSuchPublicAccessBlockConfiguration"]))?.PublicAccessBlockConfiguration;
@@ -1259,10 +2347,10 @@ async function ensureBucketSettings(ctx, clients, input) {
1259
2347
  PublicAccessBlockConfiguration: block
1260
2348
  })));
1261
2349
  };
1262
- const policy = bucketPolicy(bucket, input.open);
2350
+ const policy = bucketPolicy(bucket, input.open, input.cdn ? { cdn: input.cdn } : {});
1263
2351
  const ensurePolicy = async () => {
1264
2352
  const current = await read(() => s3.send(new S3.GetBucketPolicyCommand({ Bucket })), ["NoSuchBucketPolicy"]);
1265
- if (!(current?.Policy !== void 0 && canonicalPolicy(JSON.parse(current.Policy)) === canonicalPolicy(policy))) await set(isOpen ? `policy: TLS only, public read on ${input.open.join(", ")}` : "policy: TLS only", () => s3.send(new S3.PutBucketPolicyCommand({
2353
+ if (!(current?.Policy !== void 0 && canonicalPolicy(JSON.parse(current.Policy)) === canonicalPolicy(policy))) await set(isOpen ? `policy: TLS only, public read on ${input.open.join(", ")}` : input.cdn && input.open.length > 0 ? `policy: TLS only, CloudFront reads ${input.open.join(", ")}` : "policy: TLS only", () => s3.send(new S3.PutBucketPolicyCommand({
1266
2354
  Bucket,
1267
2355
  Policy: JSON.stringify(policy)
1268
2356
  })));
@@ -1393,8 +2481,13 @@ const BUILTIN_PROVIDERS = { objects: { s3: {
1393
2481
  check: checkS3Config,
1394
2482
  provisioning: S3_PROVISIONING,
1395
2483
  credentials: awsProvisioningCredentials,
1396
- runtimeKeys(manifest) {
1397
- return bucketConstructs(manifest).flatMap((b) => [provideKey(b.id, "url"), ...b.servers.map((s) => provideKey(s.id, "url"))]);
2484
+ runtimeKeys(manifest, runnables = {}) {
2485
+ const watched = bucketWatchers(manifest, runnables);
2486
+ return bucketConstructs(manifest).flatMap((b) => [
2487
+ provideKey(b.id, "url"),
2488
+ ...watched.has(b.id) ? [provideKey(b.id, "eventsConnectionString")] : [],
2489
+ ...b.servers.map((s) => provideKey(s.id, "url"))
2490
+ ]);
1398
2491
  },
1399
2492
  ensure: ensureS3,
1400
2493
  verify: verifyS3
@@ -1519,7 +2612,7 @@ async function provisionStage(options) {
1519
2612
  }
1520
2613
  const identity = deployIdentity(workspace, stage);
1521
2614
  let secrets = (await options.secrets.read())?.custom ?? {};
1522
- for (const entry of configured) entry.report.planned = entry.provider.runtimeKeys(manifest).filter((key) => secrets[key] === void 0);
2615
+ for (const entry of configured) entry.report.planned = entry.provider.runtimeKeys(manifest, runnables).filter((key) => secrets[key] === void 0);
1523
2616
  const state = dryRun ? await readOnlyState(options.state, stage) : journalState(await DeployJournal.open(options.state, stage, () => deploysWithCompose(workspace) ? createComposeState(stage) : createEmptyState(stage, "", "")));
1524
2617
  const writeSecrets = async (values) => {
1525
2618
  if (dryRun) return;
@@ -1541,6 +2634,7 @@ async function provisionStage(options) {
1541
2634
  workspace,
1542
2635
  manifest,
1543
2636
  runnables,
2637
+ ...options.subscribers ? { subscribers: options.subscribers } : {},
1544
2638
  stage,
1545
2639
  identity,
1546
2640
  config: entry.config,
@@ -1558,6 +2652,17 @@ async function provisionStage(options) {
1558
2652
  log(` ${dryRun ? "would " : ""}${action.change} — ${action.resource}`);
1559
2653
  },
1560
2654
  writeSecrets,
2655
+ writeDns: (records) => writeProviderDns(records, {
2656
+ workspace,
2657
+ stage,
2658
+ dryRun,
2659
+ credentials: options.credentials ?? storedCredentials({
2660
+ env,
2661
+ ...options.home ? { home: options.home } : {}
2662
+ }),
2663
+ ...options.dnsProviderFor ? { providerFor: options.dnsProviderFor } : {},
2664
+ log
2665
+ }),
1561
2666
  log
1562
2667
  });
1563
2668
  if (entry.report.actions.length === 0) log(" up to date");
@@ -1993,12 +3098,14 @@ async function provisionResources(phaseCtx, request, ctx, store, emit) {
1993
3098
  stage: phaseCtx.stage,
1994
3099
  manifest: phaseCtx.manifest,
1995
3100
  runnables: phaseCtx.runnables ?? {},
3101
+ ...phaseCtx.subscribers ? { subscribers: phaseCtx.subscribers } : {},
1996
3102
  secrets: phaseCtx.secrets,
1997
3103
  state: store,
1998
3104
  dryRun: ctx.dryRun,
1999
3105
  ...request.rotateKeys ? { rotateKeys: true } : {},
2000
3106
  ...request.retireOldKeys ? { retireOldKeys: true } : {},
2001
3107
  ...ctx.home ? { home: ctx.home } : {},
3108
+ credentials: ctx.credentials,
2002
3109
  log: (line) => phaseCtx.logger.info(line)
2003
3110
  });
2004
3111
  if (!ctx.dryRun) return;
@@ -2087,11 +3194,13 @@ async function prepare(source, request, ctx, emit, redactor) {
2087
3194
  assertSitePasswordTarget(configured, stage, resolved.target.name);
2088
3195
  const runnables = {};
2089
3196
  const background = {};
3197
+ const subscribers = {};
2090
3198
  const manifest = await discover({
2091
3199
  patterns: constructGlobs(configured),
2092
3200
  cwd: configured.root,
2093
3201
  runnables,
2094
- background
3202
+ background,
3203
+ subscribers
2095
3204
  });
2096
3205
  resolveStageTelemetry({
2097
3206
  ...configured.deploy?.telemetry ? { telemetry: configured.deploy.telemetry } : {},
@@ -2172,6 +3281,7 @@ async function prepare(source, request, ctx, emit, redactor) {
2172
3281
  manifest,
2173
3282
  runnables,
2174
3283
  background,
3284
+ subscribers,
2175
3285
  options,
2176
3286
  dryRun: ctx.dryRun,
2177
3287
  atomic: request.atomic ?? false,
@@ -2324,6 +3434,6 @@ async function load(input, sandbox) {
2324
3434
  return workspace;
2325
3435
  }
2326
3436
  //#endregion
2327
- export { UnknownDeployApps as a, bucketName as c, awsEndpoint as d, awsProvisioningCredentials as f, TargetBuildsNothing as i, iamUserName as l, NoDeployableApps as n, backupsUserName as o, RollbackFailed as r, IAM_USER_PATH as s, deploy as t, awsClientConfig as u };
3437
+ export { UnknownDeployApps as a, bucketName as c, TargetBuildsNothing as i, iamUserName as l, NoDeployableApps as n, backupsUserName as o, RollbackFailed as r, IAM_USER_PATH as s, deploy as t };
2328
3438
 
2329
- //# sourceMappingURL=deploy-DO4djCHb.mjs.map
3439
+ //# sourceMappingURL=deploy-D8pUA9Y0.mjs.map