@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.
- package/dist/{EndpointGenerator-BDa6pPgE.cjs → EndpointGenerator-BGxf9jWE.cjs} +8 -4
- package/dist/{EndpointGenerator-BDa6pPgE.cjs.map → EndpointGenerator-BGxf9jWE.cjs.map} +1 -1
- package/dist/{EndpointGenerator-CP_z_E3e.mjs → EndpointGenerator-errMeldx.mjs} +8 -4
- package/dist/{EndpointGenerator-CP_z_E3e.mjs.map → EndpointGenerator-errMeldx.mjs.map} +1 -1
- package/dist/{GoDaddyProvider-BlB5nJky.mjs → GoDaddyProvider-BN1dODm9.mjs} +3 -3
- package/dist/{GoDaddyProvider-BlB5nJky.mjs.map → GoDaddyProvider-BN1dODm9.mjs.map} +1 -1
- package/dist/{GoDaddyProvider-Dubq-pCc.cjs → GoDaddyProvider-BTdtqhI_.cjs} +3 -3
- package/dist/{GoDaddyProvider-Dubq-pCc.cjs.map → GoDaddyProvider-BTdtqhI_.cjs.map} +1 -1
- package/dist/{HostingerProvider-BNpS9aRJ.mjs → HostingerProvider-Cjh_2Ml6.mjs} +2 -2
- package/dist/{HostingerProvider-BNpS9aRJ.mjs.map → HostingerProvider-Cjh_2Ml6.mjs.map} +1 -1
- package/dist/{HostingerProvider-DR1iws1P.cjs → HostingerProvider-OdQmKXE3.cjs} +2 -2
- package/dist/{HostingerProvider-DR1iws1P.cjs.map → HostingerProvider-OdQmKXE3.cjs.map} +1 -1
- package/dist/{LegacyStateStore-ClvUC8qy.cjs → LegacyStateStore-C2S8TGwx.cjs} +3 -3
- package/dist/{LegacyStateStore-ClvUC8qy.cjs.map → LegacyStateStore-C2S8TGwx.cjs.map} +1 -1
- package/dist/{LegacyStateStore-DGALQGZu.mjs → LegacyStateStore-DgXmuG87.mjs} +3 -3
- package/dist/{LegacyStateStore-DGALQGZu.mjs.map → LegacyStateStore-DgXmuG87.mjs.map} +1 -1
- package/dist/{LocalStateStore-43kSYNpw.cjs → LocalStateStore-CaJw9H-3.cjs} +2 -2
- package/dist/{LocalStateStore-43kSYNpw.cjs.map → LocalStateStore-CaJw9H-3.cjs.map} +1 -1
- package/dist/{LocalStateStore-CBYUzVR5.mjs → LocalStateStore-ojY_BiMK.mjs} +2 -2
- package/dist/{LocalStateStore-CBYUzVR5.mjs.map → LocalStateStore-ojY_BiMK.mjs.map} +1 -1
- package/dist/{OpenApiTsGenerator-CsrukCE-.mjs → OpenApiTsGenerator-W_HRXH1X.mjs} +2 -2
- package/dist/{OpenApiTsGenerator-CsrukCE-.mjs.map → OpenApiTsGenerator-W_HRXH1X.mjs.map} +1 -1
- package/dist/{OpenApiTsGenerator-FNhY7Ssz.cjs → OpenApiTsGenerator-cQgtxWQo.cjs} +2 -2
- package/dist/{OpenApiTsGenerator-FNhY7Ssz.cjs.map → OpenApiTsGenerator-cQgtxWQo.cjs.map} +1 -1
- package/dist/{S3StateStore-DLHjDYYP.mjs → S3StateStore-BSPkxhrx.mjs} +2 -2
- package/dist/{S3StateStore-DLHjDYYP.mjs.map → S3StateStore-BSPkxhrx.mjs.map} +1 -1
- package/dist/{S3StateStore-ahE7hSNK.cjs → S3StateStore-BqugXIcb.cjs} +2 -2
- package/dist/{S3StateStore-ahE7hSNK.cjs.map → S3StateStore-BqugXIcb.cjs.map} +1 -1
- package/dist/{SSMStateStore-qIzIuazA.cjs → SSMStateStore-B0OsuT_k.cjs} +2 -2
- package/dist/{SSMStateStore-qIzIuazA.cjs.map → SSMStateStore-B0OsuT_k.cjs.map} +1 -1
- package/dist/{SSMStateStore-j5RuMkch.mjs → SSMStateStore-DM85OiH0.mjs} +2 -2
- package/dist/{SSMStateStore-j5RuMkch.mjs.map → SSMStateStore-DM85OiH0.mjs.map} +1 -1
- package/dist/{SubscriberGenerator-C8CPxrCK.cjs → SubscriberGenerator-BDUp7OeY.cjs} +237 -54
- package/dist/SubscriberGenerator-BDUp7OeY.cjs.map +1 -0
- package/dist/{SubscriberGenerator-y3pncae_.mjs → SubscriberGenerator-ocQ2yj0K.mjs} +227 -56
- package/dist/SubscriberGenerator-ocQ2yj0K.mjs.map +1 -0
- package/dist/{add-Bm-C56lH.mjs → add-C7ZBXIzM.mjs} +6 -6
- package/dist/{add-Bm-C56lH.mjs.map → add-C7ZBXIzM.mjs.map} +1 -1
- package/dist/{add-C2CvXrYw.cjs → add-CrxvmSpq.cjs} +7 -7
- package/dist/{add-C2CvXrYw.cjs.map → add-CrxvmSpq.cjs.map} +1 -1
- package/dist/{bundler-Dk7tAvAX.cjs → bundler-DVUXW95p.cjs} +5 -5
- package/dist/{bundler-Dk7tAvAX.cjs.map → bundler-DVUXW95p.cjs.map} +1 -1
- package/dist/{bundler-DnkFt4mn.mjs → bundler-DpZ9uBGN.mjs} +5 -5
- package/dist/{bundler-DnkFt4mn.mjs.map → bundler-DpZ9uBGN.mjs.map} +1 -1
- package/dist/{cli-Di464j1O.cjs → cli-BMRhE6cZ.cjs} +4 -4
- package/dist/{cli-Di464j1O.cjs.map → cli-BMRhE6cZ.cjs.map} +1 -1
- package/dist/{cli-CVeHFXNE.mjs → cli-BWfYyy1k.mjs} +4 -4
- package/dist/{cli-CVeHFXNE.mjs.map → cli-BWfYyy1k.mjs.map} +1 -1
- package/dist/{compose-cHkF---f.mjs → compose-BFmdLig3.mjs} +525 -55
- package/dist/compose-BFmdLig3.mjs.map +1 -0
- package/dist/{compose-ChVvb3xM.cjs → compose-Bqcy0lqo.cjs} +554 -84
- package/dist/compose-Bqcy0lqo.cjs.map +1 -0
- package/dist/{config-DrOX29sT.d.mts → config-CdytFgSR.d.cts} +3 -4
- package/dist/config-CdytFgSR.d.cts.map +1 -0
- package/dist/{config-B3iz9BtK.d.cts → config-_y1FGBWR.d.mts} +3 -4
- package/dist/config-_y1FGBWR.d.mts.map +1 -0
- package/dist/config.cjs +2 -2
- package/dist/config.d.cts +3 -3
- package/dist/config.d.mts +3 -3
- package/dist/config.mjs +2 -2
- package/dist/{credentialSchemas-B67WJOOm.cjs → credentialSchemas-DFxvGEwa.cjs} +2 -2
- package/dist/{credentialSchemas-B67WJOOm.cjs.map → credentialSchemas-DFxvGEwa.cjs.map} +1 -1
- package/dist/{credentialSchemas-DDOLRCLF.mjs → credentialSchemas-DaMpt-Dx.mjs} +2 -2
- package/dist/{credentialSchemas-DDOLRCLF.mjs.map → credentialSchemas-DaMpt-Dx.mjs.map} +1 -1
- package/dist/{credentials-Zqr3QQYB.cjs → credentials-D7XJypNd.cjs} +173 -3
- package/dist/credentials-D7XJypNd.cjs.map +1 -0
- package/dist/{credentials-B5N5yKk6.mjs → credentials-DpfnUvIR.mjs} +132 -4
- package/dist/credentials-DpfnUvIR.mjs.map +1 -0
- package/dist/deploy/public.cjs +16 -17
- package/dist/deploy/public.cjs.map +1 -1
- package/dist/deploy/public.d.cts +3 -5
- package/dist/deploy/public.d.cts.map +1 -1
- package/dist/deploy/public.d.mts +3 -5
- package/dist/deploy/public.d.mts.map +1 -1
- package/dist/deploy/public.mjs +11 -12
- package/dist/deploy/public.mjs.map +1 -1
- package/dist/{deploy-DO4djCHb.mjs → deploy-D8pUA9Y0.mjs} +1240 -130
- package/dist/deploy-D8pUA9Y0.mjs.map +1 -0
- package/dist/{deploy-BffR_uEN.cjs → deploy-DVrNjX1X.cjs} +1261 -169
- package/dist/deploy-DVrNjX1X.cjs.map +1 -0
- package/dist/{discover-C5roJuc9.mjs → discover-CzGM5P3p.mjs} +136 -14
- package/dist/discover-CzGM5P3p.mjs.map +1 -0
- package/dist/{discover-DozUKs8f.cjs → discover-DL6h-J97.cjs} +158 -12
- package/dist/discover-DL6h-J97.cjs.map +1 -0
- package/dist/{dokploy-BcZm93Cc.mjs → dokploy-CzDGNB6A.mjs} +2 -2
- package/dist/{dokploy-BcZm93Cc.mjs.map → dokploy-CzDGNB6A.mjs.map} +1 -1
- package/dist/{dokploy-CsdvfH3t.cjs → dokploy-TtK6Zyl3.cjs} +2 -2
- package/dist/{dokploy-CsdvfH3t.cjs.map → dokploy-TtK6Zyl3.cjs.map} +1 -1
- package/dist/{drivers-BjGDYfnC.mjs → drivers-C-VqMtld.mjs} +2 -2
- package/dist/{drivers-BjGDYfnC.mjs.map → drivers-C-VqMtld.mjs.map} +1 -1
- package/dist/{drivers-DbjiYpW6.cjs → drivers-L29WvE95.cjs} +2 -2
- package/dist/{drivers-DbjiYpW6.cjs.map → drivers-L29WvE95.cjs.map} +1 -1
- package/dist/{eas-CN_cUXth.cjs → eas-D3clzkni.cjs} +2 -2
- package/dist/{eas-CN_cUXth.cjs.map → eas-D3clzkni.cjs.map} +1 -1
- package/dist/{eas-CBShP20v.mjs → eas-DaobzDks.mjs} +2 -2
- package/dist/{eas-CBShP20v.mjs.map → eas-DaobzDks.mjs.map} +1 -1
- package/dist/{emit-Dmc1nvdS.cjs → emit-CYloptRS.cjs} +34 -1
- package/dist/emit-CYloptRS.cjs.map +1 -0
- package/dist/{emit-C1e8qD08.mjs → emit-CiJGdy_f.mjs} +34 -1
- package/dist/emit-CiJGdy_f.mjs.map +1 -0
- package/dist/{encryption-CPDM49Hi.cjs → encryption-DBkIFiR2.cjs} +3 -3
- package/dist/{encryption-CPDM49Hi.cjs.map → encryption-DBkIFiR2.cjs.map} +1 -1
- package/dist/{encryption-CITa6dXL.mjs → encryption-DV31gEY8.mjs} +3 -3
- package/dist/{encryption-CITa6dXL.mjs.map → encryption-DV31gEY8.mjs.map} +1 -1
- package/dist/{engine-CTnHmHjz.mjs → engine-CFOR9h6X.mjs} +140 -16
- package/dist/engine-CFOR9h6X.mjs.map +1 -0
- package/dist/{engine-D-jfMVj-.cjs → engine-CuZUGbEh.cjs} +143 -19
- package/dist/engine-CuZUGbEh.cjs.map +1 -0
- package/dist/{github-D56PSK39.cjs → github-CsvtWSBW.cjs} +115 -47
- package/dist/github-CsvtWSBW.cjs.map +1 -0
- package/dist/{github-9RlA7EjN.mjs → github-Nep8pI4_.mjs} +113 -45
- package/dist/github-Nep8pI4_.mjs.map +1 -0
- package/dist/{googlePlay-SX_jQeJf.cjs → googlePlay-C-UfBXJU.cjs} +5 -5
- package/dist/{googlePlay-SX_jQeJf.cjs.map → googlePlay-C-UfBXJU.cjs.map} +1 -1
- package/dist/{googlePlay-COi5LXON.mjs → googlePlay-lF7Atm_N.mjs} +4 -4
- package/dist/{googlePlay-COi5LXON.mjs.map → googlePlay-lF7Atm_N.mjs.map} +1 -1
- package/dist/{index-DcyRA5Rf.d.mts → index-DrkV7N5w.d.mts} +2 -2
- package/dist/{index-DcyRA5Rf.d.mts.map → index-DrkV7N5w.d.mts.map} +1 -1
- package/dist/{index-DjLSMgAL.d.cts → index-yaXZ9m-J.d.cts} +2 -2
- package/dist/{index-DjLSMgAL.d.cts.map → index-yaXZ9m-J.d.cts.map} +1 -1
- package/dist/index.cjs +166 -85
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +149 -68
- package/dist/index.mjs.map +1 -1
- package/dist/{local-BwC2v2Y8.mjs → local-B7Zv1NoA.mjs} +2 -2
- package/dist/{local-BwC2v2Y8.mjs.map → local-B7Zv1NoA.mjs.map} +1 -1
- package/dist/{local-DNhnkugT.cjs → local-OPC9BCZt.cjs} +3 -3
- package/dist/{local-DNhnkugT.cjs.map → local-OPC9BCZt.cjs.map} +1 -1
- package/dist/{migrate-CrtxXeGY.cjs → migrate-Dp3GhFnf.cjs} +4 -4
- package/dist/{migrate-CrtxXeGY.cjs.map → migrate-Dp3GhFnf.cjs.map} +1 -1
- package/dist/{migrate-0J6gsfrQ.mjs → migrate-NfbPVhi9.mjs} +4 -4
- package/dist/{migrate-0J6gsfrQ.mjs.map → migrate-NfbPVhi9.mjs.map} +1 -1
- package/dist/{migrate-DJa_Dlsl.mjs → migrate-_sBX0pve.mjs} +3 -3
- package/dist/{migrate-DJa_Dlsl.mjs.map → migrate-_sBX0pve.mjs.map} +1 -1
- package/dist/{migrate-BCz1lkh_.cjs → migrate-hlNKNWmg.cjs} +3 -3
- package/dist/{migrate-BCz1lkh_.cjs.map → migrate-hlNKNWmg.cjs.map} +1 -1
- package/dist/openapi.cjs +3 -3
- package/dist/openapi.d.cts +1 -1
- package/dist/openapi.d.mts +1 -1
- package/dist/openapi.mjs +3 -3
- package/dist/{publish-CuQlOW5p.mjs → publish-BuUVaZVy.mjs} +2 -2
- package/dist/{publish-CuQlOW5p.mjs.map → publish-BuUVaZVy.mjs.map} +1 -1
- package/dist/{publish-qSpnvmdX.cjs → publish-ZOo4xEuZ.cjs} +2 -2
- package/dist/{publish-qSpnvmdX.cjs.map → publish-ZOo4xEuZ.cjs.map} +1 -1
- package/dist/{ready-BOfogqzr.mjs → ready-Ca883OvY.mjs} +6 -6
- package/dist/{ready-BOfogqzr.mjs.map → ready-Ca883OvY.mjs.map} +1 -1
- package/dist/{ready-BFFP838V.cjs → ready-qnqp6JAY.cjs} +6 -6
- package/dist/{ready-BFFP838V.cjs.map → ready-qnqp6JAY.cjs.map} +1 -1
- package/dist/reconcile/public.cjs +2 -2
- package/dist/reconcile/public.d.cts +1 -2
- package/dist/reconcile/public.d.cts.map +1 -1
- package/dist/reconcile/public.d.mts +1 -2
- package/dist/reconcile/public.d.mts.map +1 -1
- package/dist/reconcile/public.mjs +2 -2
- package/dist/{resolve-j8hUcbGz.mjs → resolve-BCU9pYDx.mjs} +4 -4
- package/dist/{resolve-j8hUcbGz.mjs.map → resolve-BCU9pYDx.mjs.map} +1 -1
- package/dist/{resolve-AsfohXd1.cjs → resolve-C9QqOl5t.cjs} +4 -4
- package/dist/{resolve-AsfohXd1.cjs.map → resolve-C9QqOl5t.cjs.map} +1 -1
- package/dist/{rollback-Bxq_URgL.cjs → rollback-CTWdDBgB.cjs} +7 -7
- package/dist/{rollback-Bxq_URgL.cjs.map → rollback-CTWdDBgB.cjs.map} +1 -1
- package/dist/{rollback-JgbAY43C.mjs → rollback-CWyyTQ0B.mjs} +7 -7
- package/dist/{rollback-JgbAY43C.mjs.map → rollback-CWyyTQ0B.mjs.map} +1 -1
- package/dist/{runtime-DIZTeGJV.d.cts → runtime-DCkfI2E7.d.mts} +2 -2
- package/dist/{runtime-DIZTeGJV.d.cts.map → runtime-DCkfI2E7.d.mts.map} +1 -1
- package/dist/{runtime-WMLJY-db.d.mts → runtime-Ie4znWIB.d.cts} +2 -2
- package/dist/{runtime-WMLJY-db.d.mts.map → runtime-Ie4znWIB.d.cts.map} +1 -1
- package/dist/{s3-B4FTQQkV.mjs → s3-BZzSnker.mjs} +2 -2
- package/dist/{s3-B4FTQQkV.mjs.map → s3-BZzSnker.mjs.map} +1 -1
- package/dist/{s3-DwKLXwUT.cjs → s3-pqgmCTOq.cjs} +2 -2
- package/dist/{s3-DwKLXwUT.cjs.map → s3-pqgmCTOq.cjs.map} +1 -1
- package/dist/sandbox/credentials-worker.cjs +2 -2
- package/dist/sandbox/credentials-worker.mjs +2 -2
- package/dist/sandbox/discover-worker.cjs +1 -1
- package/dist/sandbox/discover-worker.mjs +1 -1
- package/dist/sandbox/migrate-worker.cjs +1 -1
- package/dist/sandbox/migrate-worker.mjs +1 -1
- package/dist/{seeds-Dv78P9LS.mjs → seeds-CK7w79VC.mjs} +848 -61
- package/dist/seeds-CK7w79VC.mjs.map +1 -0
- package/dist/{seeds-BWC-qjSI.cjs → seeds-CPEcmoPn.cjs} +953 -64
- package/dist/seeds-CPEcmoPn.cjs.map +1 -0
- package/dist/{serverSetup-DB6sKwAy.mjs → serverSetup-BAPNuNEX.mjs} +6 -6
- package/dist/{serverSetup-DB6sKwAy.mjs.map → serverSetup-BAPNuNEX.mjs.map} +1 -1
- package/dist/{serverSetup-CtV80a5M.cjs → serverSetup-C6t6Vtt7.cjs} +6 -6
- package/dist/{serverSetup-CtV80a5M.cjs.map → serverSetup-C6t6Vtt7.cjs.map} +1 -1
- package/dist/{sniffer-ctpwBBkT.cjs → sniffer-DdX_WN_-.cjs} +9 -9
- package/dist/{sniffer-ctpwBBkT.cjs.map → sniffer-DdX_WN_-.cjs.map} +1 -1
- package/dist/{sniffer-qDFUQmWP.mjs → sniffer-g9v7cqDN.mjs} +9 -9
- package/dist/{sniffer-qDFUQmWP.mjs.map → sniffer-g9v7cqDN.mjs.map} +1 -1
- package/dist/{dns-Bmnuttja.cjs → snsSubscriptions-5Cqww8S7.cjs} +351 -3
- package/dist/snsSubscriptions-5Cqww8S7.cjs.map +1 -0
- package/dist/{dns-DFvO5a53.mjs → snsSubscriptions-BmYeeTH8.mjs} +280 -4
- package/dist/snsSubscriptions-BmYeeTH8.mjs.map +1 -0
- package/dist/{sst-C1pxbFEd.mjs → sst-CTqZZl1_.mjs} +3 -4
- package/dist/{sst-C1pxbFEd.mjs.map → sst-CTqZZl1_.mjs.map} +1 -1
- package/dist/{sst-B-Uz4cvm.cjs → sst-lEkBwzGc.cjs} +4 -5
- package/dist/{sst-B-Uz4cvm.cjs.map → sst-lEkBwzGc.cjs.map} +1 -1
- package/dist/{stage-C7lf36az.mjs → stage-C-I2dVw1.mjs} +6 -6
- package/dist/{stage-C7lf36az.mjs.map → stage-C-I2dVw1.mjs.map} +1 -1
- package/dist/{stage-DzALYUwY.cjs → stage-CUE5pMmw.cjs} +7 -7
- package/dist/{stage-DzALYUwY.cjs.map → stage-CUE5pMmw.cjs.map} +1 -1
- package/dist/{stages-B7mCWI_R.cjs → stages-3yUZaZeA.cjs} +2 -2
- package/dist/{stages-B7mCWI_R.cjs.map → stages-3yUZaZeA.cjs.map} +1 -1
- package/dist/{stages-DSltTW2d.mjs → stages-DTTv0NZW.mjs} +2 -2
- package/dist/{stages-DSltTW2d.mjs.map → stages-DTTv0NZW.mjs.map} +1 -1
- package/dist/target/public.cjs +1 -1
- package/dist/target/public.d.cts +2 -2
- package/dist/target/public.d.mts +2 -2
- package/dist/target/public.mjs +1 -1
- package/dist/{terminal-DJlqcW_U.cjs → terminal-PKhPAajN.cjs} +3 -3
- package/dist/{terminal-DJlqcW_U.cjs.map → terminal-PKhPAajN.cjs.map} +1 -1
- package/dist/{terminal-CW_dWeTe.mjs → terminal-bqY33lpN.mjs} +3 -3
- package/dist/{terminal-CW_dWeTe.mjs.map → terminal-bqY33lpN.mjs.map} +1 -1
- package/dist/{types-Q2SC_ly2.d.cts → types-Bmjs4jZV.d.mts} +248 -4
- package/dist/types-Bmjs4jZV.d.mts.map +1 -0
- package/dist/{types-C8gXOLWL.d.mts → types-D0IsQxES.d.cts} +249 -3
- package/dist/types-D0IsQxES.d.cts.map +1 -0
- package/dist/vitest.cjs +1 -1
- package/dist/vitest.mjs +1 -1
- package/dist/workspace/index.cjs +1 -1
- package/dist/workspace/index.d.cts +2 -2
- package/dist/workspace/index.d.mts +2 -2
- package/dist/workspace/index.mjs +1 -1
- package/dist/{workspace-DUnnVjqC.mjs → workspace-DBHF9Gba.mjs} +5 -3
- package/dist/workspace-DBHF9Gba.mjs.map +1 -0
- package/dist/{workspace-C82GLPnf.cjs → workspace-Du4Afb03.cjs} +5 -3
- package/dist/workspace-Du4Afb03.cjs.map +1 -0
- package/package.json +17 -15
- package/dist/SubscriberGenerator-C8CPxrCK.cjs.map +0 -1
- package/dist/SubscriberGenerator-y3pncae_.mjs.map +0 -1
- package/dist/compose-ChVvb3xM.cjs.map +0 -1
- package/dist/compose-cHkF---f.mjs.map +0 -1
- package/dist/config-B3iz9BtK.d.cts.map +0 -1
- package/dist/config-DrOX29sT.d.mts.map +0 -1
- package/dist/credentials-B5N5yKk6.mjs.map +0 -1
- package/dist/credentials-Zqr3QQYB.cjs.map +0 -1
- package/dist/deploy-BffR_uEN.cjs.map +0 -1
- package/dist/deploy-DO4djCHb.mjs.map +0 -1
- package/dist/discover-C08meHhu.d.mts +0 -125
- package/dist/discover-C08meHhu.d.mts.map +0 -1
- package/dist/discover-C5roJuc9.mjs.map +0 -1
- package/dist/discover-DipvfWpK.d.cts +0 -125
- package/dist/discover-DipvfWpK.d.cts.map +0 -1
- package/dist/discover-DozUKs8f.cjs.map +0 -1
- package/dist/dns-Bmnuttja.cjs.map +0 -1
- package/dist/dns-DFvO5a53.mjs.map +0 -1
- package/dist/emit-C1e8qD08.mjs.map +0 -1
- package/dist/emit-Dmc1nvdS.cjs.map +0 -1
- package/dist/engine-CTnHmHjz.mjs.map +0 -1
- package/dist/engine-D-jfMVj-.cjs.map +0 -1
- package/dist/github-9RlA7EjN.mjs.map +0 -1
- package/dist/github-D56PSK39.cjs.map +0 -1
- package/dist/run-BJLIerMM.mjs +0 -133
- package/dist/run-BJLIerMM.mjs.map +0 -1
- package/dist/run-fVZ-rLnc.cjs +0 -174
- package/dist/run-fVZ-rLnc.cjs.map +0 -1
- package/dist/sandbox-Co1y2CmF.d.mts +0 -104
- package/dist/sandbox-Co1y2CmF.d.mts.map +0 -1
- package/dist/sandbox-DvNqr6od.d.cts +0 -104
- package/dist/sandbox-DvNqr6od.d.cts.map +0 -1
- package/dist/seeds-BWC-qjSI.cjs.map +0 -1
- package/dist/seeds-Dv78P9LS.mjs.map +0 -1
- package/dist/types-C8gXOLWL.d.mts.map +0 -1
- package/dist/types-Q2SC_ly2.d.cts.map +0 -1
- package/dist/workspace-C82GLPnf.cjs.map +0 -1
- package/dist/workspace-DUnnVjqC.mjs.map +0 -1
|
@@ -1,97 +1,24 @@
|
|
|
1
1
|
import { t as GkmError } from "./errors-D8Gwp5kU.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
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 {
|
|
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-
|
|
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-
|
|
14
|
-
import { t as LocalSandbox } from "./local-
|
|
15
|
-
import { E as eventError, S as createStateStore, v as assertStateOutlivesRun, y as assertStateRoom } from "./sniffer-
|
|
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-
|
|
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
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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 —
|
|
287
|
-
*
|
|
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
|
-
|
|
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:
|
|
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$
|
|
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$
|
|
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$
|
|
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
|
-
...
|
|
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
|
-
|
|
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, {
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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-
|
|
3439
|
+
//# sourceMappingURL=deploy-D8pUA9Y0.mjs.map
|