@geekmidas/cli 10.0.0-alpha.9 → 10.0.0-alpha.91
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/README.md +315 -414
- package/bin/adjacent-tsconfig.mjs +46 -8
- package/bin/gkm.mjs +11 -3
- package/bin/owning-tsconfig-jsx-register.mjs +18 -0
- package/bin/owning-tsconfig-jsx.mjs +108 -0
- package/dist/EndpointGenerator-CVc6Oi1Y.mjs +1021 -0
- package/dist/EndpointGenerator-CVc6Oi1Y.mjs.map +1 -0
- package/dist/EndpointGenerator-CwLjBCFE.cjs +1080 -0
- package/dist/EndpointGenerator-CwLjBCFE.cjs.map +1 -0
- package/dist/GoDaddyProvider-DSStJVAv.mjs +387 -0
- package/dist/GoDaddyProvider-DSStJVAv.mjs.map +1 -0
- package/dist/GoDaddyProvider-DqZ1qD_F.cjs +387 -0
- package/dist/GoDaddyProvider-DqZ1qD_F.cjs.map +1 -0
- package/dist/{HostingerProvider-Cft7lEYr.mjs → HostingerProvider-BI6J7oJr.mjs} +22 -23
- package/dist/HostingerProvider-BI6J7oJr.mjs.map +1 -0
- package/dist/{HostingerProvider-h3EbEp-Z.cjs → HostingerProvider-CyTpG_jy.cjs} +22 -23
- package/dist/HostingerProvider-CyTpG_jy.cjs.map +1 -0
- package/dist/LegacyStateStore-DXmNA7hy.cjs +89 -0
- package/dist/LegacyStateStore-DXmNA7hy.cjs.map +1 -0
- package/dist/LegacyStateStore-ZkdmiUFZ.mjs +89 -0
- package/dist/LegacyStateStore-ZkdmiUFZ.mjs.map +1 -0
- package/dist/LocalStateStore-BBeTlRTH.cjs +178 -0
- package/dist/LocalStateStore-BBeTlRTH.cjs.map +1 -0
- package/dist/LocalStateStore-CU-O0SBp.mjs +167 -0
- package/dist/LocalStateStore-CU-O0SBp.mjs.map +1 -0
- package/dist/OpenApiTsGenerator-B--HV-sW.cjs +541 -0
- package/dist/OpenApiTsGenerator-B--HV-sW.cjs.map +1 -0
- package/dist/OpenApiTsGenerator-CkvsPdgg.mjs +536 -0
- package/dist/OpenApiTsGenerator-CkvsPdgg.mjs.map +1 -0
- package/dist/{Route53Provider-di2fMM5N.cjs → Route53Provider-Bllus2NR.cjs} +21 -22
- package/dist/Route53Provider-Bllus2NR.cjs.map +1 -0
- package/dist/{Route53Provider-RdpUDJQk.mjs → Route53Provider-QXxjkZO1.mjs} +13 -13
- package/dist/Route53Provider-QXxjkZO1.mjs.map +1 -0
- package/dist/S3StateStore-Br8VMY22.cjs +160 -0
- package/dist/S3StateStore-Br8VMY22.cjs.map +1 -0
- package/dist/S3StateStore-bwxcI_wQ.mjs +161 -0
- package/dist/S3StateStore-bwxcI_wQ.mjs.map +1 -0
- package/dist/SSMStateStore-CsOS0W1y.mjs +154 -0
- package/dist/SSMStateStore-CsOS0W1y.mjs.map +1 -0
- package/dist/SSMStateStore-DoZE1Wi1.cjs +153 -0
- package/dist/SSMStateStore-DoZE1Wi1.cjs.map +1 -0
- package/dist/SubscriberGenerator-BPqQf8fB.mjs +448 -0
- package/dist/SubscriberGenerator-BPqQf8fB.mjs.map +1 -0
- package/dist/SubscriberGenerator-BVn7cQ-2.cjs +489 -0
- package/dist/SubscriberGenerator-BVn7cQ-2.cjs.map +1 -0
- package/dist/add-BSHk5Ngn.mjs +747 -0
- package/dist/add-BSHk5Ngn.mjs.map +1 -0
- package/dist/add-DmmBDV2e.cjs +749 -0
- package/dist/add-DmmBDV2e.cjs.map +1 -0
- package/dist/appPorts-C9z9Pzn9.mjs +223 -0
- package/dist/appPorts-C9z9Pzn9.mjs.map +1 -0
- package/dist/appPorts-Cu79hJF6.cjs +264 -0
- package/dist/appPorts-Cu79hJF6.cjs.map +1 -0
- package/dist/aws-B-6uEhgP.mjs +71 -0
- package/dist/aws-B-6uEhgP.mjs.map +1 -0
- package/dist/aws-BTawF0F-.cjs +82 -0
- package/dist/aws-BTawF0F-.cjs.map +1 -0
- package/dist/awsStore-7VmS_cZR.cjs +54 -0
- package/dist/awsStore-7VmS_cZR.cjs.map +1 -0
- package/dist/awsStore-B529csle.mjs +43 -0
- package/dist/awsStore-B529csle.mjs.map +1 -0
- package/dist/{backup-provisioner-Bgj5Vi_L.mjs → backup-provisioner-C8DnCmxK.mjs} +11 -8
- package/dist/backup-provisioner-C8DnCmxK.mjs.map +1 -0
- package/dist/{backup-provisioner-BrsHwYG0.cjs → backup-provisioner-oDEsPj02.cjs} +22 -20
- package/dist/backup-provisioner-oDEsPj02.cjs.map +1 -0
- package/dist/{bundler-BY1OmV1e.cjs → bundler-BQJiB7Ub.cjs} +38 -35
- package/dist/bundler-BQJiB7Ub.cjs.map +1 -0
- package/dist/{bundler-Be8Zg4Wd.mjs → bundler-D6eBfVkl.mjs} +36 -32
- package/dist/bundler-D6eBfVkl.mjs.map +1 -0
- package/dist/cli-BE9mqeX8.cjs +69 -0
- package/dist/cli-BE9mqeX8.cjs.map +1 -0
- package/dist/cli-DD-Yk-I5.mjs +69 -0
- package/dist/cli-DD-Yk-I5.mjs.map +1 -0
- package/dist/compose-Bhmf0n4o.mjs +3140 -0
- package/dist/compose-Bhmf0n4o.mjs.map +1 -0
- package/dist/compose-CvpU6ooi.cjs +3265 -0
- package/dist/compose-CvpU6ooi.cjs.map +1 -0
- package/dist/config-CdEcTrjG.d.mts +269 -0
- package/dist/config-CdEcTrjG.d.mts.map +1 -0
- package/dist/config-pST7E0CS.d.cts +269 -0
- package/dist/config-pST7E0CS.d.cts.map +1 -0
- package/dist/config.cjs +420 -11
- package/dist/config.cjs.map +1 -0
- package/dist/config.d.cts +4 -120
- package/dist/config.d.mts +4 -120
- package/dist/config.mjs +404 -4
- package/dist/config.mjs.map +1 -0
- package/dist/credentialSchemas-BJqRJNK4.mjs +236 -0
- package/dist/credentialSchemas-BJqRJNK4.mjs.map +1 -0
- package/dist/credentialSchemas-DCR4Wd1W.cjs +271 -0
- package/dist/credentialSchemas-DCR4Wd1W.cjs.map +1 -0
- package/dist/credentials-BKfIugu2.cjs +513 -0
- package/dist/credentials-BKfIugu2.cjs.map +1 -0
- package/dist/credentials-CnVqoXMb.mjs +388 -0
- package/dist/credentials-CnVqoXMb.mjs.map +1 -0
- package/dist/databases-BY-pEImp.cjs +597 -0
- package/dist/databases-BY-pEImp.cjs.map +1 -0
- package/dist/databases-CzszQV0N.mjs +494 -0
- package/dist/databases-CzszQV0N.mjs.map +1 -0
- package/dist/define-DR8xGYfL.mjs +97 -0
- package/dist/define-DR8xGYfL.mjs.map +1 -0
- package/dist/define-DzRz8FgH.cjs +144 -0
- package/dist/define-DzRz8FgH.cjs.map +1 -0
- package/dist/deploy/public.cjs +208 -0
- package/dist/deploy/public.cjs.map +1 -0
- package/dist/deploy/public.d.cts +649 -0
- package/dist/deploy/public.d.cts.map +1 -0
- package/dist/deploy/public.d.mts +649 -0
- package/dist/deploy/public.d.mts.map +1 -0
- package/dist/deploy/public.mjs +142 -0
- package/dist/deploy/public.mjs.map +1 -0
- package/dist/deploy/sniffer-envkit-patch.cjs +18 -10
- package/dist/deploy/sniffer-envkit-patch.cjs.map +1 -1
- package/dist/deploy/sniffer-envkit-patch.d.cts +0 -2
- package/dist/deploy/sniffer-envkit-patch.d.cts.map +1 -1
- package/dist/deploy/sniffer-envkit-patch.d.mts +0 -2
- package/dist/deploy/sniffer-envkit-patch.d.mts.map +1 -1
- package/dist/deploy/sniffer-envkit-patch.mjs +10 -2
- package/dist/deploy/sniffer-envkit-patch.mjs.map +1 -1
- package/dist/deploy/sniffer-envparser-worker.cjs +54 -0
- package/dist/deploy/sniffer-envparser-worker.cjs.map +1 -0
- package/dist/deploy/sniffer-envparser-worker.d.cts +1 -0
- package/dist/deploy/sniffer-envparser-worker.d.mts +1 -0
- package/dist/deploy/sniffer-envparser-worker.mjs +55 -0
- package/dist/deploy/sniffer-envparser-worker.mjs.map +1 -0
- package/dist/deploy/sniffer-hooks.cjs +13 -7
- package/dist/deploy/sniffer-hooks.cjs.map +1 -1
- package/dist/deploy/sniffer-hooks.d.cts +1 -2
- package/dist/deploy/sniffer-hooks.d.cts.map +1 -1
- package/dist/deploy/sniffer-hooks.d.mts +1 -2
- package/dist/deploy/sniffer-hooks.d.mts.map +1 -1
- package/dist/deploy/sniffer-hooks.mjs +10 -3
- package/dist/deploy/sniffer-hooks.mjs.map +1 -1
- package/dist/deploy/sniffer-loader.cjs +16 -7
- package/dist/deploy/sniffer-loader.cjs.map +1 -1
- package/dist/deploy/sniffer-loader.d.cts +1 -1
- package/dist/deploy/sniffer-loader.d.mts +1 -1
- package/dist/deploy/sniffer-loader.mjs +14 -3
- package/dist/deploy/sniffer-loader.mjs.map +1 -1
- package/dist/deploy/sniffer-routes-worker.cjs +11 -11
- package/dist/deploy/sniffer-routes-worker.cjs.map +1 -1
- package/dist/deploy/sniffer-routes-worker.d.cts +1 -1
- package/dist/deploy/sniffer-routes-worker.d.mts +1 -1
- package/dist/deploy/sniffer-routes-worker.mjs +4 -4
- package/dist/deploy/sniffer-routes-worker.mjs.map +1 -1
- package/dist/deploy/sniffer-worker.cjs +15 -6
- package/dist/deploy/sniffer-worker.cjs.map +1 -1
- package/dist/deploy/sniffer-worker.d.cts +1 -3
- package/dist/deploy/sniffer-worker.d.cts.map +1 -1
- package/dist/deploy/sniffer-worker.d.mts +1 -3
- package/dist/deploy/sniffer-worker.d.mts.map +1 -1
- package/dist/deploy/sniffer-worker.mjs +15 -4
- package/dist/deploy/sniffer-worker.mjs.map +1 -1
- package/dist/deploy-CuHasEA4.cjs +554 -0
- package/dist/deploy-CuHasEA4.cjs.map +1 -0
- package/dist/deploy-QNtx_eWR.mjs +525 -0
- package/dist/deploy-QNtx_eWR.mjs.map +1 -0
- package/dist/derive-B4vDpzgP.mjs +150 -0
- package/dist/derive-B4vDpzgP.mjs.map +1 -0
- package/dist/derive-DAbo49sm.cjs +161 -0
- package/dist/derive-DAbo49sm.cjs.map +1 -0
- package/dist/discover-BPelzeGW.mjs +526 -0
- package/dist/discover-BPelzeGW.mjs.map +1 -0
- package/dist/discover-CU0paUyH.d.cts +108 -0
- package/dist/discover-CU0paUyH.d.cts.map +1 -0
- package/dist/discover-D1It5r_h.cjs +599 -0
- package/dist/discover-D1It5r_h.cjs.map +1 -0
- package/dist/discover-DFiJxwfv.d.mts +108 -0
- package/dist/discover-DFiJxwfv.d.mts.map +1 -0
- package/dist/dokploy-CYcMLh9-.mjs +38 -0
- package/dist/dokploy-CYcMLh9-.mjs.map +1 -0
- package/dist/dokploy-DIowwyIB.cjs +38 -0
- package/dist/dokploy-DIowwyIB.cjs.map +1 -0
- package/dist/drivers-DTZzxeIl.mjs +177 -0
- package/dist/drivers-DTZzxeIl.mjs.map +1 -0
- package/dist/drivers-LYSdQDmd.cjs +200 -0
- package/dist/drivers-LYSdQDmd.cjs.map +1 -0
- package/dist/{emit-CiZS_5Ub.mjs → emit-C1e8qD08.mjs} +13 -26
- package/dist/emit-C1e8qD08.mjs.map +1 -0
- package/dist/{emit-CbcRolsa.cjs → emit-Dmc1nvdS.cjs} +31 -57
- package/dist/emit-Dmc1nvdS.cjs.map +1 -0
- package/dist/encryption-BrEef7Jk.mjs +88 -0
- package/dist/encryption-BrEef7Jk.mjs.map +1 -0
- package/dist/encryption-CjYdUb2s.cjs +89 -0
- package/dist/encryption-CjYdUb2s.cjs.map +1 -0
- package/dist/engine-2h4k5ICq.mjs +3085 -0
- package/dist/engine-2h4k5ICq.mjs.map +1 -0
- package/dist/engine-BhJ7ZP9K.cjs +3198 -0
- package/dist/engine-BhJ7ZP9K.cjs.map +1 -0
- package/dist/github-B5Bw4HIu.cjs +669 -0
- package/dist/github-B5Bw4HIu.cjs.map +1 -0
- package/dist/github-BCDF6xVH.mjs +669 -0
- package/dist/github-BCDF6xVH.mjs.map +1 -0
- package/dist/{index-C6e-ULLx.d.mts → index-D-F_hTDz.d.cts} +39 -24
- package/dist/index-D-F_hTDz.d.cts.map +1 -0
- package/dist/{index-CR5urhWX.d.cts → index-YFNxR5Ns.d.mts} +39 -24
- package/dist/index-YFNxR5Ns.d.mts.map +1 -0
- package/dist/index.cjs +9487 -12080
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +9420 -12026
- package/dist/index.mjs.map +1 -1
- package/dist/local-BjxDfPvy.cjs +241 -0
- package/dist/local-BjxDfPvy.cjs.map +1 -0
- package/dist/local-IuoIF3CT.mjs +224 -0
- package/dist/local-IuoIF3CT.mjs.map +1 -0
- package/dist/migrate-CGaancHY.cjs +93 -0
- package/dist/migrate-CGaancHY.cjs.map +1 -0
- package/dist/migrate-DWZz9kah.mjs +93 -0
- package/dist/migrate-DWZz9kah.mjs.map +1 -0
- package/dist/migrate-DbtcuOr_.mjs +265 -0
- package/dist/migrate-DbtcuOr_.mjs.map +1 -0
- package/dist/migrate-q2YYLJLE.cjs +270 -0
- package/dist/migrate-q2YYLJLE.cjs.map +1 -0
- package/dist/openapi.cjs +214 -10
- package/dist/openapi.cjs.map +1 -0
- package/dist/openapi.d.cts +47 -17
- package/dist/openapi.d.cts.map +1 -1
- package/dist/openapi.d.mts +47 -17
- package/dist/openapi.d.mts.map +1 -1
- package/dist/openapi.mjs +208 -5
- package/dist/openapi.mjs.map +1 -0
- package/dist/provider-C9_-45d5.cjs +56 -0
- package/dist/provider-C9_-45d5.cjs.map +1 -0
- package/dist/provider-q4lsYzYG.mjs +45 -0
- package/dist/provider-q4lsYzYG.mjs.map +1 -0
- package/dist/ready-BpNbORRB.cjs +402 -0
- package/dist/ready-BpNbORRB.cjs.map +1 -0
- package/dist/ready-b6vm0-W-.mjs +373 -0
- package/dist/ready-b6vm0-W-.mjs.map +1 -0
- package/dist/reconcile/public.cjs +3 -5
- package/dist/reconcile/public.d.cts +11 -87
- package/dist/reconcile/public.d.cts.map +1 -1
- package/dist/reconcile/public.d.mts +11 -87
- package/dist/reconcile/public.d.mts.map +1 -1
- package/dist/reconcile/public.mjs +3 -4
- package/dist/{reconcile-D0pimNiy.cjs → reconcile-CuCQBa5-.cjs} +5 -8
- package/dist/reconcile-CuCQBa5-.cjs.map +1 -0
- package/dist/{reconcile-CFs8C_1a.mjs → reconcile-Dh1Jk7WE.mjs} +4 -7
- package/dist/reconcile-Dh1Jk7WE.mjs.map +1 -0
- package/dist/resolve-Cns0v6dN.cjs +315 -0
- package/dist/resolve-Cns0v6dN.cjs.map +1 -0
- package/dist/resolve-DNg0icnh.mjs +262 -0
- package/dist/resolve-DNg0icnh.mjs.map +1 -0
- package/dist/rollback-CFrNLmxs.mjs +132 -0
- package/dist/rollback-CFrNLmxs.mjs.map +1 -0
- package/dist/rollback-UIjS25F1.cjs +161 -0
- package/dist/rollback-UIjS25F1.cjs.map +1 -0
- package/dist/rolldown-runtime-BhDjJH2R.mjs +15 -0
- package/dist/{chunk-CUT6urMc.cjs → rolldown-runtime-C0BPl7ul.cjs} +23 -10
- package/dist/runtime-CN_kmqxo.d.cts +90 -0
- package/dist/runtime-CN_kmqxo.d.cts.map +1 -0
- package/dist/runtime-Cjvon3oj.d.mts +90 -0
- package/dist/runtime-Cjvon3oj.d.mts.map +1 -0
- package/dist/sandbox/config-worker.cjs +43 -0
- package/dist/sandbox/config-worker.cjs.map +1 -0
- package/dist/sandbox/config-worker.d.cts +1 -0
- package/dist/sandbox/config-worker.d.mts +1 -0
- package/dist/sandbox/config-worker.mjs +44 -0
- package/dist/sandbox/config-worker.mjs.map +1 -0
- package/dist/sandbox/credentials-worker.cjs +54 -0
- package/dist/sandbox/credentials-worker.cjs.map +1 -0
- package/dist/sandbox/credentials-worker.d.cts +1 -0
- package/dist/sandbox/credentials-worker.d.mts +1 -0
- package/dist/sandbox/credentials-worker.mjs +55 -0
- package/dist/sandbox/credentials-worker.mjs.map +1 -0
- package/dist/sandbox/discover-worker.cjs +47 -0
- package/dist/sandbox/discover-worker.cjs.map +1 -0
- package/dist/sandbox/discover-worker.d.cts +1 -0
- package/dist/sandbox/discover-worker.d.mts +1 -0
- package/dist/sandbox/discover-worker.mjs +48 -0
- package/dist/sandbox/discover-worker.mjs.map +1 -0
- package/dist/sandbox/migrate-worker.cjs +75 -0
- package/dist/sandbox/migrate-worker.cjs.map +1 -0
- package/dist/sandbox/migrate-worker.d.cts +1 -0
- package/dist/sandbox/migrate-worker.d.mts +1 -0
- package/dist/sandbox/migrate-worker.mjs +76 -0
- package/dist/sandbox/migrate-worker.mjs.map +1 -0
- package/dist/sandbox-CL--ZDui.cjs +164 -0
- package/dist/sandbox-CL--ZDui.cjs.map +1 -0
- package/dist/sandbox-Dm247NGx.mjs +117 -0
- package/dist/sandbox-Dm247NGx.mjs.map +1 -0
- package/dist/sandbox-M8UnLhXE.d.cts +126 -0
- package/dist/sandbox-M8UnLhXE.d.cts.map +1 -0
- package/dist/sandbox-M8UnLhXE.d.mts +126 -0
- package/dist/sandbox-M8UnLhXE.d.mts.map +1 -0
- package/dist/{workspace-zoqRbihZ.mjs → schemas-BObwxB_t.cjs} +998 -1659
- package/dist/schemas-BObwxB_t.cjs.map +1 -0
- package/dist/{workspace-C6VJQVtG.cjs → schemas-D-xeKJDD.mjs} +913 -1842
- package/dist/schemas-D-xeKJDD.mjs.map +1 -0
- package/dist/secretsManager-Chs9VV2r.mjs +76 -0
- package/dist/secretsManager-Chs9VV2r.mjs.map +1 -0
- package/dist/secretsManager-DcMlHnRC.cjs +87 -0
- package/dist/secretsManager-DcMlHnRC.cjs.map +1 -0
- package/dist/seeds-CkIPFGNO.cjs +10560 -0
- package/dist/seeds-CkIPFGNO.cjs.map +1 -0
- package/dist/seeds-odfgSCmu.mjs +9538 -0
- package/dist/seeds-odfgSCmu.mjs.map +1 -0
- package/dist/sniffer-BTtX3NUM.mjs +1262 -0
- package/dist/sniffer-BTtX3NUM.mjs.map +1 -0
- package/dist/sniffer-IAQEpsQb.cjs +1333 -0
- package/dist/sniffer-IAQEpsQb.cjs.map +1 -0
- package/dist/sst-BgcOsaBi.cjs +453 -0
- package/dist/sst-BgcOsaBi.cjs.map +1 -0
- package/dist/sst-CJzvyrEz.mjs +430 -0
- package/dist/sst-CJzvyrEz.mjs.map +1 -0
- package/dist/stages-CUn6gvXL.cjs +174 -0
- package/dist/stages-CUn6gvXL.cjs.map +1 -0
- package/dist/stages-CnZRYmkI.mjs +174 -0
- package/dist/stages-CnZRYmkI.mjs.map +1 -0
- package/dist/target/public.cjs +15 -0
- package/dist/target/public.d.cts +28 -0
- package/dist/target/public.d.cts.map +1 -0
- package/dist/target/public.d.mts +28 -0
- package/dist/target/public.d.mts.map +1 -0
- package/dist/target/public.mjs +4 -0
- package/dist/terminal-D8TPK7o9.mjs +84 -0
- package/dist/terminal-D8TPK7o9.mjs.map +1 -0
- package/dist/terminal-DuapRxh6.cjs +84 -0
- package/dist/terminal-DuapRxh6.cjs.map +1 -0
- package/dist/types-NqX1Hz4P.d.cts +7454 -0
- package/dist/types-NqX1Hz4P.d.cts.map +1 -0
- package/dist/types-ZHuZ89hv.d.mts +7453 -0
- package/dist/types-ZHuZ89hv.d.mts.map +1 -0
- package/dist/utils-D2Nk1Nv2.cjs +205 -0
- package/dist/utils-D2Nk1Nv2.cjs.map +1 -0
- package/dist/utils-D_fxUqvU.mjs +156 -0
- package/dist/utils-D_fxUqvU.mjs.map +1 -0
- package/dist/vitest.cjs +65 -0
- package/dist/vitest.cjs.map +1 -0
- package/dist/vitest.d.cts +29 -0
- package/dist/vitest.d.cts.map +1 -0
- package/dist/vitest.d.mts +29 -0
- package/dist/vitest.d.mts.map +1 -0
- package/dist/vitest.mjs +65 -0
- package/dist/vitest.mjs.map +1 -0
- package/dist/workerRuntime-BXfhbe13.cjs +108 -0
- package/dist/workerRuntime-BXfhbe13.cjs.map +1 -0
- package/dist/workerRuntime-CsOyBRx2.mjs +79 -0
- package/dist/workerRuntime-CsOyBRx2.mjs.map +1 -0
- package/dist/workspace/index.cjs +8 -4
- package/dist/workspace/index.d.cts +3 -3
- package/dist/workspace/index.d.mts +3 -3
- package/dist/workspace/index.mjs +2 -3
- package/dist/workspace-CJ0lKFq-.mjs +1979 -0
- package/dist/workspace-CJ0lKFq-.mjs.map +1 -0
- package/dist/workspace-CwW3Gkru.cjs +2355 -0
- package/dist/workspace-CwW3Gkru.cjs.map +1 -0
- package/package.json +53 -22
- package/dist/CachedStateProvider-BLo7zv7S.cjs +0 -3
- package/dist/CachedStateProvider-BxoAm-B2.mjs +0 -3
- package/dist/CachedStateProvider-CqPdW7CB.cjs +0 -60
- package/dist/CachedStateProvider-CqPdW7CB.cjs.map +0 -1
- package/dist/CachedStateProvider-DTniehMP.mjs +0 -54
- package/dist/CachedStateProvider-DTniehMP.mjs.map +0 -1
- package/dist/HostingerProvider-Cft7lEYr.mjs.map +0 -1
- package/dist/HostingerProvider-h3EbEp-Z.cjs.map +0 -1
- package/dist/LocalStateProvider-Bn6tS-oL.mjs +0 -42
- package/dist/LocalStateProvider-Bn6tS-oL.mjs.map +0 -1
- package/dist/LocalStateProvider-Db9SEjyH.cjs +0 -43
- package/dist/LocalStateProvider-Db9SEjyH.cjs.map +0 -1
- package/dist/Route53Provider-RdpUDJQk.mjs.map +0 -1
- package/dist/Route53Provider-di2fMM5N.cjs.map +0 -1
- package/dist/SSMStateProvider-BRt2py-A.mjs +0 -66
- package/dist/SSMStateProvider-BRt2py-A.mjs.map +0 -1
- package/dist/SSMStateProvider-DyX8s9HI.cjs +0 -66
- package/dist/SSMStateProvider-DyX8s9HI.cjs.map +0 -1
- package/dist/backup-provisioner-Bgj5Vi_L.mjs.map +0 -1
- package/dist/backup-provisioner-BrsHwYG0.cjs.map +0 -1
- package/dist/bundler-BY1OmV1e.cjs.map +0 -1
- package/dist/bundler-Be8Zg4Wd.mjs.map +0 -1
- package/dist/chunk-Duj1WY3L.mjs +0 -7
- package/dist/config-BZ1F0WuA.cjs +0 -462
- package/dist/config-BZ1F0WuA.cjs.map +0 -1
- package/dist/config-DVVph9yz.mjs +0 -402
- package/dist/config-DVVph9yz.mjs.map +0 -1
- package/dist/config.d.cts.map +0 -1
- package/dist/config.d.mts.map +0 -1
- package/dist/credentials-B5zRHdAl.mjs +0 -141
- package/dist/credentials-B5zRHdAl.mjs.map +0 -1
- package/dist/credentials-BapzjyV5.cjs +0 -195
- package/dist/credentials-BapzjyV5.cjs.map +0 -1
- package/dist/credentials-Xamqe_NJ.cjs +0 -2681
- package/dist/credentials-Xamqe_NJ.cjs.map +0 -1
- package/dist/credentials-dP8vRVRv.mjs +0 -2549
- package/dist/credentials-dP8vRVRv.mjs.map +0 -1
- package/dist/discover-CAlFgYec.mjs +0 -229
- package/dist/discover-CAlFgYec.mjs.map +0 -1
- package/dist/discover-DTmFeloL.cjs +0 -271
- package/dist/discover-DTmFeloL.cjs.map +0 -1
- package/dist/dokploy-api-BS2xnJLZ.mjs +0 -3
- package/dist/dokploy-api-BsVH0y_s.cjs +0 -718
- package/dist/dokploy-api-BsVH0y_s.cjs.map +0 -1
- package/dist/dokploy-api-BuxDRo3l.cjs +0 -3
- package/dist/dokploy-api-aTFPz7_4.mjs +0 -706
- package/dist/dokploy-api-aTFPz7_4.mjs.map +0 -1
- package/dist/emit-CbcRolsa.cjs.map +0 -1
- package/dist/emit-CiZS_5Ub.mjs.map +0 -1
- package/dist/encryption-C6jaU0hE.cjs +0 -54
- package/dist/encryption-C6jaU0hE.cjs.map +0 -1
- package/dist/encryption-CHzzeR8a.cjs +0 -4
- package/dist/encryption-CQUncu7v.mjs +0 -3
- package/dist/encryption-D-abyhZx.mjs +0 -42
- package/dist/encryption-D-abyhZx.mjs.map +0 -1
- package/dist/fullstack-secrets-C-GGohke.cjs +0 -111
- package/dist/fullstack-secrets-C-GGohke.cjs.map +0 -1
- package/dist/fullstack-secrets-D1o9-G5l.mjs +0 -87
- package/dist/fullstack-secrets-D1o9-G5l.mjs.map +0 -1
- package/dist/generator-CNzBw7g5.cjs +0 -289
- package/dist/generator-CNzBw7g5.cjs.map +0 -1
- package/dist/generator-VE2JrFlY.mjs +0 -253
- package/dist/generator-VE2JrFlY.mjs.map +0 -1
- package/dist/index-C6e-ULLx.d.mts.map +0 -1
- package/dist/index-CR5urhWX.d.cts.map +0 -1
- package/dist/openapi-SjsgARie.cjs +0 -2075
- package/dist/openapi-SjsgARie.cjs.map +0 -1
- package/dist/openapi-Xx0A7Wg0.mjs +0 -2021
- package/dist/openapi-Xx0A7Wg0.mjs.map +0 -1
- package/dist/openapi-react-query-Byltvm_7.mjs +0 -167
- package/dist/openapi-react-query-Byltvm_7.mjs.map +0 -1
- package/dist/openapi-react-query-CmJ1xDbn.cjs +0 -173
- package/dist/openapi-react-query-CmJ1xDbn.cjs.map +0 -1
- package/dist/openapi-react-query.cjs +0 -4
- package/dist/openapi-react-query.d.cts +0 -11
- package/dist/openapi-react-query.d.cts.map +0 -1
- package/dist/openapi-react-query.d.mts +0 -11
- package/dist/openapi-react-query.d.mts.map +0 -1
- package/dist/openapi-react-query.mjs +0 -4
- package/dist/reconcile-CFs8C_1a.mjs.map +0 -1
- package/dist/reconcile-D0pimNiy.cjs.map +0 -1
- package/dist/setup-BA8ciTPU.mjs +0 -11
- package/dist/setup-BsZvZP6f.cjs +0 -11
- package/dist/setup-C8KOx8_8.mjs +0 -443
- package/dist/setup-C8KOx8_8.mjs.map +0 -1
- package/dist/setup-hC6hI86f.cjs +0 -473
- package/dist/setup-hC6hI86f.cjs.map +0 -1
- package/dist/storage-BJmzYxt8.cjs +0 -363
- package/dist/storage-BJmzYxt8.cjs.map +0 -1
- package/dist/storage-BM9hGpgv.cjs +0 -7
- package/dist/storage-Bi24WfoW.mjs +0 -297
- package/dist/storage-Bi24WfoW.mjs.map +0 -1
- package/dist/storage-CYlGwWTj.mjs +0 -3
- package/dist/sync-CmEuNZsB.mjs +0 -76
- package/dist/sync-CmEuNZsB.mjs.map +0 -1
- package/dist/sync-D306yOWu.cjs +0 -4
- package/dist/sync-SizaChmC.cjs +0 -93
- package/dist/sync-SizaChmC.cjs.map +0 -1
- package/dist/sync-hOqAWHR6.mjs +0 -3
- package/dist/types-6pkp2RRj.d.mts +0 -2220
- package/dist/types-6pkp2RRj.d.mts.map +0 -1
- package/dist/types-Y3fKQSAK.d.cts +0 -2220
- package/dist/types-Y3fKQSAK.d.cts.map +0 -1
- package/dist/workspace-BzrG_2SI.mjs +0 -8
- package/dist/workspace-C6VJQVtG.cjs.map +0 -1
- package/dist/workspace-XXtNhqJl.cjs +0 -9
- package/dist/workspace-zoqRbihZ.mjs.map +0 -1
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
let _aws_sdk_credential_providers = require("@aws-sdk/credential-providers");
|
|
2
|
+
let _aws_sdk_client_route_53 = require("@aws-sdk/client-route-53");
|
|
3
|
+
//#region src/target/dokploy/dns/Route53Provider.ts
|
|
4
|
+
/**
|
|
5
|
+
* Route53 DNS Provider
|
|
6
|
+
*
|
|
7
|
+
* Implements DnsProvider interface using AWS Route53.
|
|
8
|
+
*/
|
|
6
9
|
/**
|
|
7
10
|
* Route53 DNS provider implementation.
|
|
8
11
|
*
|
|
@@ -17,10 +20,10 @@ var Route53Provider = class {
|
|
|
17
20
|
hostedZoneCache = /* @__PURE__ */ new Map();
|
|
18
21
|
constructor(options = {}) {
|
|
19
22
|
const region = options.region ?? process.env.AWS_REGION ?? "us-east-1";
|
|
20
|
-
this.client = new
|
|
23
|
+
this.client = new _aws_sdk_client_route_53.Route53Client({
|
|
21
24
|
region,
|
|
22
25
|
...options.endpoint && { endpoint: options.endpoint },
|
|
23
|
-
...options.profile && { credentials: (0,
|
|
26
|
+
...options.profile && { credentials: (0, _aws_sdk_credential_providers.fromIni)({ profile: options.profile }) }
|
|
24
27
|
});
|
|
25
28
|
this.hostedZoneId = options.hostedZoneId;
|
|
26
29
|
}
|
|
@@ -31,12 +34,11 @@ var Route53Provider = class {
|
|
|
31
34
|
async getHostedZoneId(domain) {
|
|
32
35
|
if (this.hostedZoneId) return this.hostedZoneId;
|
|
33
36
|
if (this.hostedZoneCache.has(domain)) return this.hostedZoneCache.get(domain);
|
|
34
|
-
const command = new
|
|
37
|
+
const command = new _aws_sdk_client_route_53.ListHostedZonesByNameCommand({
|
|
35
38
|
DNSName: domain,
|
|
36
39
|
MaxItems: 1
|
|
37
40
|
});
|
|
38
|
-
const
|
|
39
|
-
const zones = response.HostedZones ?? [];
|
|
41
|
+
const zones = (await this.client.send(command)).HostedZones ?? [];
|
|
40
42
|
const normalizedDomain = domain.endsWith(".") ? domain : `${domain}.`;
|
|
41
43
|
const zone = zones.find((z) => z.Name === normalizedDomain);
|
|
42
44
|
if (!zone?.Id) throw new Error(`No hosted zone found for domain: ${domain}. Create one in Route53 or provide hostedZoneId in config.`);
|
|
@@ -49,9 +51,8 @@ var Route53Provider = class {
|
|
|
49
51
|
* Excludes NS and SOA which are auto-managed by Route53 for the zone.
|
|
50
52
|
*/
|
|
51
53
|
toRecordType(type) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const validTypes = [
|
|
54
|
+
if (["NS", "SOA"].includes(type)) return null;
|
|
55
|
+
return [
|
|
55
56
|
"A",
|
|
56
57
|
"AAAA",
|
|
57
58
|
"CNAME",
|
|
@@ -59,8 +60,7 @@ var Route53Provider = class {
|
|
|
59
60
|
"TXT",
|
|
60
61
|
"SRV",
|
|
61
62
|
"CAA"
|
|
62
|
-
];
|
|
63
|
-
return validTypes.includes(type) ? type : null;
|
|
63
|
+
].includes(type) ? type : null;
|
|
64
64
|
}
|
|
65
65
|
/**
|
|
66
66
|
* Extract subdomain from full record name relative to domain.
|
|
@@ -69,8 +69,7 @@ var Route53Provider = class {
|
|
|
69
69
|
const normalizedDomain = domain.endsWith(".") ? domain : `${domain}.`;
|
|
70
70
|
const normalizedName = recordName.endsWith(".") ? recordName : `${recordName}.`;
|
|
71
71
|
if (normalizedName === normalizedDomain) return "@";
|
|
72
|
-
|
|
73
|
-
return subdomain.replace(/\.$/, "");
|
|
72
|
+
return normalizedName.replace(`.${normalizedDomain}`, "").replace(/\.$/, "");
|
|
74
73
|
}
|
|
75
74
|
async getRecords(domain) {
|
|
76
75
|
const zoneId = await this.getHostedZoneId(domain);
|
|
@@ -78,7 +77,7 @@ var Route53Provider = class {
|
|
|
78
77
|
let nextRecordName;
|
|
79
78
|
let nextRecordType;
|
|
80
79
|
do {
|
|
81
|
-
const command = new
|
|
80
|
+
const command = new _aws_sdk_client_route_53.ListResourceRecordSetsCommand({
|
|
82
81
|
HostedZoneId: zoneId,
|
|
83
82
|
StartRecordName: nextRecordName,
|
|
84
83
|
StartRecordType: nextRecordType,
|
|
@@ -139,7 +138,7 @@ var Route53Provider = class {
|
|
|
139
138
|
});
|
|
140
139
|
}
|
|
141
140
|
if (changes.length > 0) {
|
|
142
|
-
const command = new
|
|
141
|
+
const command = new _aws_sdk_client_route_53.ChangeResourceRecordSetsCommand({
|
|
143
142
|
HostedZoneId: zoneId,
|
|
144
143
|
ChangeBatch: {
|
|
145
144
|
Comment: "Upsert by gkm deploy",
|
|
@@ -186,7 +185,7 @@ var Route53Provider = class {
|
|
|
186
185
|
});
|
|
187
186
|
}
|
|
188
187
|
if (changes.length > 0) try {
|
|
189
|
-
const command = new
|
|
188
|
+
const command = new _aws_sdk_client_route_53.ChangeResourceRecordSetsCommand({
|
|
190
189
|
HostedZoneId: zoneId,
|
|
191
190
|
ChangeBatch: {
|
|
192
191
|
Comment: "Delete by gkm undeploy",
|
|
@@ -204,7 +203,7 @@ var Route53Provider = class {
|
|
|
204
203
|
return results;
|
|
205
204
|
}
|
|
206
205
|
};
|
|
207
|
-
|
|
208
206
|
//#endregion
|
|
209
207
|
exports.Route53Provider = Route53Provider;
|
|
210
|
-
|
|
208
|
+
|
|
209
|
+
//# sourceMappingURL=Route53Provider-Bllus2NR.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Route53Provider-Bllus2NR.cjs","names":["Route53Client","fromIni","ListHostedZonesByNameCommand","ListResourceRecordSetsCommand","ChangeResourceRecordSetsCommand"],"sources":["../src/target/dokploy/dns/Route53Provider.ts"],"sourcesContent":["/**\n * Route53 DNS Provider\n *\n * Implements DnsProvider interface using AWS Route53.\n */\n\nimport {\n\tChangeResourceRecordSetsCommand,\n\tListHostedZonesByNameCommand,\n\tListResourceRecordSetsCommand,\n\tRoute53Client,\n\ttype RRType,\n} from '@aws-sdk/client-route-53';\nimport { fromIni } from '@aws-sdk/credential-providers';\nimport type {\n\tDeleteDnsRecord,\n\tDeleteResult,\n\tDnsProvider,\n\tDnsRecord,\n\tDnsRecordType,\n\tUpsertDnsRecord,\n\tUpsertResult,\n} from './DnsProvider';\n\nexport interface Route53ProviderOptions {\n\t/** AWS region (optional - uses AWS_REGION env var if not provided) */\n\tregion?: string;\n\t/** AWS profile name (optional - uses default credential chain if not provided) */\n\tprofile?: string;\n\t/** Hosted zone ID (optional - auto-detected from domain if not provided) */\n\thostedZoneId?: string;\n\t/** Custom endpoint for testing with localstack */\n\tendpoint?: string;\n}\n\n/**\n * Route53 DNS provider implementation.\n *\n * Uses AWS default credential chain for authentication.\n * Region can be specified or will use AWS_REGION/AWS_DEFAULT_REGION env vars.\n * Profile can be specified to use a named profile from ~/.aws/credentials.\n */\nexport class Route53Provider implements DnsProvider {\n\treadonly name = 'route53';\n\tprivate client: Route53Client;\n\tprivate hostedZoneId?: string;\n\tprivate hostedZoneCache: Map<string, string> = new Map();\n\n\tconstructor(options: Route53ProviderOptions = {}) {\n\t\t// Route53 is a global service, default to us-east-1 if no region specified\n\t\tconst region = options.region ?? process.env.AWS_REGION ?? 'us-east-1';\n\n\t\tthis.client = new Route53Client({\n\t\t\tregion,\n\t\t\t...(options.endpoint && { endpoint: options.endpoint }),\n\t\t\t...(options.profile && {\n\t\t\t\tcredentials: fromIni({ profile: options.profile }),\n\t\t\t}),\n\t\t});\n\t\tthis.hostedZoneId = options.hostedZoneId;\n\t}\n\n\t/**\n\t * Get the hosted zone ID for a domain.\n\t * Uses cache to avoid repeated API calls.\n\t */\n\tprivate async getHostedZoneId(domain: string): Promise<string> {\n\t\t// Use configured zone ID if provided\n\t\tif (this.hostedZoneId) {\n\t\t\treturn this.hostedZoneId;\n\t\t}\n\n\t\t// Check cache\n\t\tif (this.hostedZoneCache.has(domain)) {\n\t\t\treturn this.hostedZoneCache.get(domain)!;\n\t\t}\n\n\t\t// Auto-detect from domain\n\t\tconst command = new ListHostedZonesByNameCommand({\n\t\t\tDNSName: domain,\n\t\t\tMaxItems: 1,\n\t\t});\n\n\t\tconst response = await this.client.send(command);\n\t\tconst zones = response.HostedZones ?? [];\n\n\t\t// Find exact match (domain with trailing dot)\n\t\tconst normalizedDomain = domain.endsWith('.') ? domain : `${domain}.`;\n\t\tconst zone = zones.find((z) => z.Name === normalizedDomain);\n\n\t\tif (!zone?.Id) {\n\t\t\tthrow new Error(\n\t\t\t\t`No hosted zone found for domain: ${domain}. Create one in Route53 or provide hostedZoneId in config.`,\n\t\t\t);\n\t\t}\n\n\t\t// Zone ID comes as \"/hostedzone/Z1234567890\" - extract just the ID\n\t\tconst zoneId = zone.Id.replace('/hostedzone/', '');\n\t\tthis.hostedZoneCache.set(domain, zoneId);\n\t\treturn zoneId;\n\t}\n\n\t/**\n\t * Convert Route53 record type to our DnsRecordType.\n\t * Excludes NS and SOA which are auto-managed by Route53 for the zone.\n\t */\n\tprivate toRecordType(type: string): DnsRecordType | null {\n\t\t// Exclude NS and SOA which are auto-managed zone records\n\t\tconst managedTypes = ['NS', 'SOA'];\n\t\tif (managedTypes.includes(type)) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst validTypes: DnsRecordType[] = [\n\t\t\t'A',\n\t\t\t'AAAA',\n\t\t\t'CNAME',\n\t\t\t'MX',\n\t\t\t'TXT',\n\t\t\t'SRV',\n\t\t\t'CAA',\n\t\t];\n\t\treturn validTypes.includes(type as DnsRecordType)\n\t\t\t? (type as DnsRecordType)\n\t\t\t: null;\n\t}\n\n\t/**\n\t * Extract subdomain from full record name relative to domain.\n\t */\n\tprivate extractSubdomain(recordName: string, domain: string): string {\n\t\tconst normalizedDomain = domain.endsWith('.') ? domain : `${domain}.`;\n\t\tconst normalizedName = recordName.endsWith('.')\n\t\t\t? recordName\n\t\t\t: `${recordName}.`;\n\n\t\tif (normalizedName === normalizedDomain) {\n\t\t\treturn '@';\n\t\t}\n\n\t\t// Remove the domain suffix\n\t\tconst subdomain = normalizedName.replace(`.${normalizedDomain}`, '');\n\t\treturn subdomain.replace(/\\.$/, ''); // Remove trailing dot if any\n\t}\n\n\tasync getRecords(domain: string): Promise<DnsRecord[]> {\n\t\tconst zoneId = await this.getHostedZoneId(domain);\n\t\tconst records: DnsRecord[] = [];\n\n\t\tlet nextRecordName: string | undefined;\n\t\tlet nextRecordType: RRType | undefined;\n\n\t\t// Paginate through all records\n\t\tdo {\n\t\t\tconst command = new ListResourceRecordSetsCommand({\n\t\t\t\tHostedZoneId: zoneId,\n\t\t\t\tStartRecordName: nextRecordName,\n\t\t\t\tStartRecordType: nextRecordType,\n\t\t\t\tMaxItems: 100,\n\t\t\t});\n\n\t\t\tconst response = await this.client.send(command);\n\n\t\t\tfor (const recordSet of response.ResourceRecordSets ?? []) {\n\t\t\t\tconst type = this.toRecordType(recordSet.Type ?? '');\n\t\t\t\tif (!type || !recordSet.Name) continue;\n\n\t\t\t\tconst values = (recordSet.ResourceRecords ?? [])\n\t\t\t\t\t.map((r) => r.Value)\n\t\t\t\t\t.filter((v): v is string => !!v);\n\n\t\t\t\trecords.push({\n\t\t\t\t\tname: this.extractSubdomain(recordSet.Name, domain),\n\t\t\t\t\ttype,\n\t\t\t\t\tttl: recordSet.TTL ?? 300,\n\t\t\t\t\tvalues,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (response.IsTruncated) {\n\t\t\t\tnextRecordName = response.NextRecordName;\n\t\t\t\tnextRecordType = response.NextRecordType;\n\t\t\t} else {\n\t\t\t\tnextRecordName = undefined;\n\t\t\t}\n\t\t} while (nextRecordName);\n\n\t\treturn records;\n\t}\n\n\tasync upsertRecords(\n\t\tdomain: string,\n\t\trecords: UpsertDnsRecord[],\n\t): Promise<UpsertResult[]> {\n\t\tconst zoneId = await this.getHostedZoneId(domain);\n\t\tconst results: UpsertResult[] = [];\n\n\t\t// Get existing records to determine if creating or updating\n\t\tconst existingRecords = await this.getRecords(domain);\n\n\t\t// Process records in batches (Route53 allows max 1000 changes per request)\n\t\tconst batchSize = 100;\n\t\tfor (let i = 0; i < records.length; i += batchSize) {\n\t\t\tconst batch = records.slice(i, i + batchSize);\n\t\t\tconst changes = [];\n\n\t\t\tfor (const record of batch) {\n\t\t\t\tconst existing = existingRecords.find(\n\t\t\t\t\t(r) => r.name === record.name && r.type === record.type,\n\t\t\t\t);\n\n\t\t\t\tconst existingValue = existing?.values?.[0];\n\n\t\t\t\tif (existing && existingValue === record.value) {\n\t\t\t\t\t// Record exists with same value - unchanged\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\trecord,\n\t\t\t\t\t\tcreated: false,\n\t\t\t\t\t\tunchanged: true,\n\t\t\t\t\t});\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Build full record name\n\t\t\t\tconst recordName =\n\t\t\t\t\trecord.name === '@' ? domain : `${record.name}.${domain}`;\n\n\t\t\t\tchanges.push({\n\t\t\t\t\tAction: 'UPSERT' as const,\n\t\t\t\t\tResourceRecordSet: {\n\t\t\t\t\t\tName: recordName,\n\t\t\t\t\t\tType: record.type,\n\t\t\t\t\t\tTTL: record.ttl,\n\t\t\t\t\t\tResourceRecords: [{ Value: record.value }],\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresults.push({\n\t\t\t\t\trecord,\n\t\t\t\t\tcreated: !existing,\n\t\t\t\t\tunchanged: false,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Execute batch if there are changes\n\t\t\tif (changes.length > 0) {\n\t\t\t\tconst command = new ChangeResourceRecordSetsCommand({\n\t\t\t\t\tHostedZoneId: zoneId,\n\t\t\t\t\tChangeBatch: {\n\t\t\t\t\t\tComment: 'Upsert by gkm deploy',\n\t\t\t\t\t\tChanges: changes,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tawait this.client.send(command);\n\t\t\t}\n\t\t}\n\n\t\treturn results;\n\t}\n\n\tasync deleteRecords(\n\t\tdomain: string,\n\t\trecords: DeleteDnsRecord[],\n\t): Promise<DeleteResult[]> {\n\t\tconst zoneId = await this.getHostedZoneId(domain);\n\t\tconst results: DeleteResult[] = [];\n\n\t\t// Get existing records to find the ones to delete\n\t\tconst existingRecords = await this.getRecords(domain);\n\n\t\t// Process records in batches (Route53 allows max 1000 changes per request)\n\t\tconst batchSize = 100;\n\t\tfor (let i = 0; i < records.length; i += batchSize) {\n\t\t\tconst batch = records.slice(i, i + batchSize);\n\t\t\tconst changes = [];\n\n\t\t\tfor (const record of batch) {\n\t\t\t\tconst existing = existingRecords.find(\n\t\t\t\t\t(r) => r.name === record.name && r.type === record.type,\n\t\t\t\t);\n\n\t\t\t\tif (!existing) {\n\t\t\t\t\t// Record doesn't exist - already deleted\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\trecord,\n\t\t\t\t\t\tdeleted: false,\n\t\t\t\t\t\tnotFound: true,\n\t\t\t\t\t});\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Build full record name\n\t\t\t\tconst recordName =\n\t\t\t\t\trecord.name === '@' ? domain : `${record.name}.${domain}`;\n\n\t\t\t\tchanges.push({\n\t\t\t\t\tAction: 'DELETE' as const,\n\t\t\t\t\tResourceRecordSet: {\n\t\t\t\t\t\tName: recordName,\n\t\t\t\t\t\tType: record.type,\n\t\t\t\t\t\tTTL: existing.ttl,\n\t\t\t\t\t\tResourceRecords: existing.values.map((v) => ({ Value: v })),\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresults.push({\n\t\t\t\t\trecord,\n\t\t\t\t\tdeleted: true,\n\t\t\t\t\tnotFound: false,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Execute batch if there are changes\n\t\t\tif (changes.length > 0) {\n\t\t\t\ttry {\n\t\t\t\t\tconst command = new ChangeResourceRecordSetsCommand({\n\t\t\t\t\t\tHostedZoneId: zoneId,\n\t\t\t\t\t\tChangeBatch: {\n\t\t\t\t\t\t\tComment: 'Delete by gkm undeploy',\n\t\t\t\t\t\t\tChanges: changes,\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\n\t\t\t\t\tawait this.client.send(command);\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// Mark all records in this batch as failed\n\t\t\t\t\tfor (const result of results.slice(-changes.length)) {\n\t\t\t\t\t\tif (result.deleted) {\n\t\t\t\t\t\t\tresult.deleted = false;\n\t\t\t\t\t\t\tresult.error = String(error);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn results;\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { fromIni } from "@aws-sdk/credential-providers";
|
|
2
2
|
import { ChangeResourceRecordSetsCommand, ListHostedZonesByNameCommand, ListResourceRecordSetsCommand, Route53Client } from "@aws-sdk/client-route-53";
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
//#region src/target/dokploy/dns/Route53Provider.ts
|
|
4
|
+
/**
|
|
5
|
+
* Route53 DNS Provider
|
|
6
|
+
*
|
|
7
|
+
* Implements DnsProvider interface using AWS Route53.
|
|
8
|
+
*/
|
|
5
9
|
/**
|
|
6
10
|
* Route53 DNS provider implementation.
|
|
7
11
|
*
|
|
@@ -34,8 +38,7 @@ var Route53Provider = class {
|
|
|
34
38
|
DNSName: domain,
|
|
35
39
|
MaxItems: 1
|
|
36
40
|
});
|
|
37
|
-
const
|
|
38
|
-
const zones = response.HostedZones ?? [];
|
|
41
|
+
const zones = (await this.client.send(command)).HostedZones ?? [];
|
|
39
42
|
const normalizedDomain = domain.endsWith(".") ? domain : `${domain}.`;
|
|
40
43
|
const zone = zones.find((z) => z.Name === normalizedDomain);
|
|
41
44
|
if (!zone?.Id) throw new Error(`No hosted zone found for domain: ${domain}. Create one in Route53 or provide hostedZoneId in config.`);
|
|
@@ -48,9 +51,8 @@ var Route53Provider = class {
|
|
|
48
51
|
* Excludes NS and SOA which are auto-managed by Route53 for the zone.
|
|
49
52
|
*/
|
|
50
53
|
toRecordType(type) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const validTypes = [
|
|
54
|
+
if (["NS", "SOA"].includes(type)) return null;
|
|
55
|
+
return [
|
|
54
56
|
"A",
|
|
55
57
|
"AAAA",
|
|
56
58
|
"CNAME",
|
|
@@ -58,8 +60,7 @@ var Route53Provider = class {
|
|
|
58
60
|
"TXT",
|
|
59
61
|
"SRV",
|
|
60
62
|
"CAA"
|
|
61
|
-
];
|
|
62
|
-
return validTypes.includes(type) ? type : null;
|
|
63
|
+
].includes(type) ? type : null;
|
|
63
64
|
}
|
|
64
65
|
/**
|
|
65
66
|
* Extract subdomain from full record name relative to domain.
|
|
@@ -68,8 +69,7 @@ var Route53Provider = class {
|
|
|
68
69
|
const normalizedDomain = domain.endsWith(".") ? domain : `${domain}.`;
|
|
69
70
|
const normalizedName = recordName.endsWith(".") ? recordName : `${recordName}.`;
|
|
70
71
|
if (normalizedName === normalizedDomain) return "@";
|
|
71
|
-
|
|
72
|
-
return subdomain.replace(/\.$/, "");
|
|
72
|
+
return normalizedName.replace(`.${normalizedDomain}`, "").replace(/\.$/, "");
|
|
73
73
|
}
|
|
74
74
|
async getRecords(domain) {
|
|
75
75
|
const zoneId = await this.getHostedZoneId(domain);
|
|
@@ -203,7 +203,7 @@ var Route53Provider = class {
|
|
|
203
203
|
return results;
|
|
204
204
|
}
|
|
205
205
|
};
|
|
206
|
-
|
|
207
206
|
//#endregion
|
|
208
207
|
export { Route53Provider };
|
|
209
|
-
|
|
208
|
+
|
|
209
|
+
//# sourceMappingURL=Route53Provider-QXxjkZO1.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Route53Provider-QXxjkZO1.mjs","names":[],"sources":["../src/target/dokploy/dns/Route53Provider.ts"],"sourcesContent":["/**\n * Route53 DNS Provider\n *\n * Implements DnsProvider interface using AWS Route53.\n */\n\nimport {\n\tChangeResourceRecordSetsCommand,\n\tListHostedZonesByNameCommand,\n\tListResourceRecordSetsCommand,\n\tRoute53Client,\n\ttype RRType,\n} from '@aws-sdk/client-route-53';\nimport { fromIni } from '@aws-sdk/credential-providers';\nimport type {\n\tDeleteDnsRecord,\n\tDeleteResult,\n\tDnsProvider,\n\tDnsRecord,\n\tDnsRecordType,\n\tUpsertDnsRecord,\n\tUpsertResult,\n} from './DnsProvider';\n\nexport interface Route53ProviderOptions {\n\t/** AWS region (optional - uses AWS_REGION env var if not provided) */\n\tregion?: string;\n\t/** AWS profile name (optional - uses default credential chain if not provided) */\n\tprofile?: string;\n\t/** Hosted zone ID (optional - auto-detected from domain if not provided) */\n\thostedZoneId?: string;\n\t/** Custom endpoint for testing with localstack */\n\tendpoint?: string;\n}\n\n/**\n * Route53 DNS provider implementation.\n *\n * Uses AWS default credential chain for authentication.\n * Region can be specified or will use AWS_REGION/AWS_DEFAULT_REGION env vars.\n * Profile can be specified to use a named profile from ~/.aws/credentials.\n */\nexport class Route53Provider implements DnsProvider {\n\treadonly name = 'route53';\n\tprivate client: Route53Client;\n\tprivate hostedZoneId?: string;\n\tprivate hostedZoneCache: Map<string, string> = new Map();\n\n\tconstructor(options: Route53ProviderOptions = {}) {\n\t\t// Route53 is a global service, default to us-east-1 if no region specified\n\t\tconst region = options.region ?? process.env.AWS_REGION ?? 'us-east-1';\n\n\t\tthis.client = new Route53Client({\n\t\t\tregion,\n\t\t\t...(options.endpoint && { endpoint: options.endpoint }),\n\t\t\t...(options.profile && {\n\t\t\t\tcredentials: fromIni({ profile: options.profile }),\n\t\t\t}),\n\t\t});\n\t\tthis.hostedZoneId = options.hostedZoneId;\n\t}\n\n\t/**\n\t * Get the hosted zone ID for a domain.\n\t * Uses cache to avoid repeated API calls.\n\t */\n\tprivate async getHostedZoneId(domain: string): Promise<string> {\n\t\t// Use configured zone ID if provided\n\t\tif (this.hostedZoneId) {\n\t\t\treturn this.hostedZoneId;\n\t\t}\n\n\t\t// Check cache\n\t\tif (this.hostedZoneCache.has(domain)) {\n\t\t\treturn this.hostedZoneCache.get(domain)!;\n\t\t}\n\n\t\t// Auto-detect from domain\n\t\tconst command = new ListHostedZonesByNameCommand({\n\t\t\tDNSName: domain,\n\t\t\tMaxItems: 1,\n\t\t});\n\n\t\tconst response = await this.client.send(command);\n\t\tconst zones = response.HostedZones ?? [];\n\n\t\t// Find exact match (domain with trailing dot)\n\t\tconst normalizedDomain = domain.endsWith('.') ? domain : `${domain}.`;\n\t\tconst zone = zones.find((z) => z.Name === normalizedDomain);\n\n\t\tif (!zone?.Id) {\n\t\t\tthrow new Error(\n\t\t\t\t`No hosted zone found for domain: ${domain}. Create one in Route53 or provide hostedZoneId in config.`,\n\t\t\t);\n\t\t}\n\n\t\t// Zone ID comes as \"/hostedzone/Z1234567890\" - extract just the ID\n\t\tconst zoneId = zone.Id.replace('/hostedzone/', '');\n\t\tthis.hostedZoneCache.set(domain, zoneId);\n\t\treturn zoneId;\n\t}\n\n\t/**\n\t * Convert Route53 record type to our DnsRecordType.\n\t * Excludes NS and SOA which are auto-managed by Route53 for the zone.\n\t */\n\tprivate toRecordType(type: string): DnsRecordType | null {\n\t\t// Exclude NS and SOA which are auto-managed zone records\n\t\tconst managedTypes = ['NS', 'SOA'];\n\t\tif (managedTypes.includes(type)) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst validTypes: DnsRecordType[] = [\n\t\t\t'A',\n\t\t\t'AAAA',\n\t\t\t'CNAME',\n\t\t\t'MX',\n\t\t\t'TXT',\n\t\t\t'SRV',\n\t\t\t'CAA',\n\t\t];\n\t\treturn validTypes.includes(type as DnsRecordType)\n\t\t\t? (type as DnsRecordType)\n\t\t\t: null;\n\t}\n\n\t/**\n\t * Extract subdomain from full record name relative to domain.\n\t */\n\tprivate extractSubdomain(recordName: string, domain: string): string {\n\t\tconst normalizedDomain = domain.endsWith('.') ? domain : `${domain}.`;\n\t\tconst normalizedName = recordName.endsWith('.')\n\t\t\t? recordName\n\t\t\t: `${recordName}.`;\n\n\t\tif (normalizedName === normalizedDomain) {\n\t\t\treturn '@';\n\t\t}\n\n\t\t// Remove the domain suffix\n\t\tconst subdomain = normalizedName.replace(`.${normalizedDomain}`, '');\n\t\treturn subdomain.replace(/\\.$/, ''); // Remove trailing dot if any\n\t}\n\n\tasync getRecords(domain: string): Promise<DnsRecord[]> {\n\t\tconst zoneId = await this.getHostedZoneId(domain);\n\t\tconst records: DnsRecord[] = [];\n\n\t\tlet nextRecordName: string | undefined;\n\t\tlet nextRecordType: RRType | undefined;\n\n\t\t// Paginate through all records\n\t\tdo {\n\t\t\tconst command = new ListResourceRecordSetsCommand({\n\t\t\t\tHostedZoneId: zoneId,\n\t\t\t\tStartRecordName: nextRecordName,\n\t\t\t\tStartRecordType: nextRecordType,\n\t\t\t\tMaxItems: 100,\n\t\t\t});\n\n\t\t\tconst response = await this.client.send(command);\n\n\t\t\tfor (const recordSet of response.ResourceRecordSets ?? []) {\n\t\t\t\tconst type = this.toRecordType(recordSet.Type ?? '');\n\t\t\t\tif (!type || !recordSet.Name) continue;\n\n\t\t\t\tconst values = (recordSet.ResourceRecords ?? [])\n\t\t\t\t\t.map((r) => r.Value)\n\t\t\t\t\t.filter((v): v is string => !!v);\n\n\t\t\t\trecords.push({\n\t\t\t\t\tname: this.extractSubdomain(recordSet.Name, domain),\n\t\t\t\t\ttype,\n\t\t\t\t\tttl: recordSet.TTL ?? 300,\n\t\t\t\t\tvalues,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (response.IsTruncated) {\n\t\t\t\tnextRecordName = response.NextRecordName;\n\t\t\t\tnextRecordType = response.NextRecordType;\n\t\t\t} else {\n\t\t\t\tnextRecordName = undefined;\n\t\t\t}\n\t\t} while (nextRecordName);\n\n\t\treturn records;\n\t}\n\n\tasync upsertRecords(\n\t\tdomain: string,\n\t\trecords: UpsertDnsRecord[],\n\t): Promise<UpsertResult[]> {\n\t\tconst zoneId = await this.getHostedZoneId(domain);\n\t\tconst results: UpsertResult[] = [];\n\n\t\t// Get existing records to determine if creating or updating\n\t\tconst existingRecords = await this.getRecords(domain);\n\n\t\t// Process records in batches (Route53 allows max 1000 changes per request)\n\t\tconst batchSize = 100;\n\t\tfor (let i = 0; i < records.length; i += batchSize) {\n\t\t\tconst batch = records.slice(i, i + batchSize);\n\t\t\tconst changes = [];\n\n\t\t\tfor (const record of batch) {\n\t\t\t\tconst existing = existingRecords.find(\n\t\t\t\t\t(r) => r.name === record.name && r.type === record.type,\n\t\t\t\t);\n\n\t\t\t\tconst existingValue = existing?.values?.[0];\n\n\t\t\t\tif (existing && existingValue === record.value) {\n\t\t\t\t\t// Record exists with same value - unchanged\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\trecord,\n\t\t\t\t\t\tcreated: false,\n\t\t\t\t\t\tunchanged: true,\n\t\t\t\t\t});\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Build full record name\n\t\t\t\tconst recordName =\n\t\t\t\t\trecord.name === '@' ? domain : `${record.name}.${domain}`;\n\n\t\t\t\tchanges.push({\n\t\t\t\t\tAction: 'UPSERT' as const,\n\t\t\t\t\tResourceRecordSet: {\n\t\t\t\t\t\tName: recordName,\n\t\t\t\t\t\tType: record.type,\n\t\t\t\t\t\tTTL: record.ttl,\n\t\t\t\t\t\tResourceRecords: [{ Value: record.value }],\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresults.push({\n\t\t\t\t\trecord,\n\t\t\t\t\tcreated: !existing,\n\t\t\t\t\tunchanged: false,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Execute batch if there are changes\n\t\t\tif (changes.length > 0) {\n\t\t\t\tconst command = new ChangeResourceRecordSetsCommand({\n\t\t\t\t\tHostedZoneId: zoneId,\n\t\t\t\t\tChangeBatch: {\n\t\t\t\t\t\tComment: 'Upsert by gkm deploy',\n\t\t\t\t\t\tChanges: changes,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tawait this.client.send(command);\n\t\t\t}\n\t\t}\n\n\t\treturn results;\n\t}\n\n\tasync deleteRecords(\n\t\tdomain: string,\n\t\trecords: DeleteDnsRecord[],\n\t): Promise<DeleteResult[]> {\n\t\tconst zoneId = await this.getHostedZoneId(domain);\n\t\tconst results: DeleteResult[] = [];\n\n\t\t// Get existing records to find the ones to delete\n\t\tconst existingRecords = await this.getRecords(domain);\n\n\t\t// Process records in batches (Route53 allows max 1000 changes per request)\n\t\tconst batchSize = 100;\n\t\tfor (let i = 0; i < records.length; i += batchSize) {\n\t\t\tconst batch = records.slice(i, i + batchSize);\n\t\t\tconst changes = [];\n\n\t\t\tfor (const record of batch) {\n\t\t\t\tconst existing = existingRecords.find(\n\t\t\t\t\t(r) => r.name === record.name && r.type === record.type,\n\t\t\t\t);\n\n\t\t\t\tif (!existing) {\n\t\t\t\t\t// Record doesn't exist - already deleted\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\trecord,\n\t\t\t\t\t\tdeleted: false,\n\t\t\t\t\t\tnotFound: true,\n\t\t\t\t\t});\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Build full record name\n\t\t\t\tconst recordName =\n\t\t\t\t\trecord.name === '@' ? domain : `${record.name}.${domain}`;\n\n\t\t\t\tchanges.push({\n\t\t\t\t\tAction: 'DELETE' as const,\n\t\t\t\t\tResourceRecordSet: {\n\t\t\t\t\t\tName: recordName,\n\t\t\t\t\t\tType: record.type,\n\t\t\t\t\t\tTTL: existing.ttl,\n\t\t\t\t\t\tResourceRecords: existing.values.map((v) => ({ Value: v })),\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresults.push({\n\t\t\t\t\trecord,\n\t\t\t\t\tdeleted: true,\n\t\t\t\t\tnotFound: false,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Execute batch if there are changes\n\t\t\tif (changes.length > 0) {\n\t\t\t\ttry {\n\t\t\t\t\tconst command = new ChangeResourceRecordSetsCommand({\n\t\t\t\t\t\tHostedZoneId: zoneId,\n\t\t\t\t\t\tChangeBatch: {\n\t\t\t\t\t\t\tComment: 'Delete by gkm undeploy',\n\t\t\t\t\t\t\tChanges: changes,\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\n\t\t\t\t\tawait this.client.send(command);\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// Mark all records in this batch as failed\n\t\t\t\t\tfor (const result of results.slice(-changes.length)) {\n\t\t\t\t\t\tif (result.deleted) {\n\t\t\t\t\t\t\tresult.deleted = false;\n\t\t\t\t\t\t\tresult.error = String(error);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn results;\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
const require_seeds = require("./seeds-CkIPFGNO.cjs");
|
|
2
|
+
let _aws_sdk_client_s3 = require("@aws-sdk/client-s3");
|
|
3
|
+
//#region src/deploy/S3StateStore.ts
|
|
4
|
+
/**
|
|
5
|
+
* Amazon S3 state store.
|
|
6
|
+
*
|
|
7
|
+
* - `<prefix>/<workspace>/<stage>/state.json`: the state.
|
|
8
|
+
* - `<prefix>/<workspace>/<stage>/lock.json`: the lock.
|
|
9
|
+
* - `<prefix>/<workspace>/<stage>/state.v1.json`: the v1 state, kept on
|
|
10
|
+
* migration.
|
|
11
|
+
*
|
|
12
|
+
* Every write is conditional on the server: `If-None-Match: *` to create,
|
|
13
|
+
* `If-Match: <etag>` to replace. S3 answers a lost race with 412 (or 409 when
|
|
14
|
+
* two conditional writes are in flight at once), so there is no window
|
|
15
|
+
* between a check and a write for another run to slip into.
|
|
16
|
+
*/
|
|
17
|
+
/** A conditional write that lost: the object is not what the write assumed. */
|
|
18
|
+
function lostRace(error) {
|
|
19
|
+
const { name, $metadata } = error ?? {};
|
|
20
|
+
return name === "PreconditionFailed" || name === "ConditionalRequestConflict" || $metadata?.httpStatusCode === 412 || $metadata?.httpStatusCode === 409;
|
|
21
|
+
}
|
|
22
|
+
function notFound(error) {
|
|
23
|
+
const { name, $metadata } = error ?? {};
|
|
24
|
+
return name === "NoSuchKey" || name === "NotFound" || $metadata?.httpStatusCode === 404;
|
|
25
|
+
}
|
|
26
|
+
var S3StateStore = class S3StateStore extends require_seeds.DocumentStateStore {
|
|
27
|
+
workspaceName;
|
|
28
|
+
bucket;
|
|
29
|
+
client;
|
|
30
|
+
prefix;
|
|
31
|
+
constructor(workspaceName, bucket, client, prefix = "gkm") {
|
|
32
|
+
super();
|
|
33
|
+
this.workspaceName = workspaceName;
|
|
34
|
+
this.bucket = bucket;
|
|
35
|
+
this.client = client;
|
|
36
|
+
this.prefix = prefix.replace(/\/+$/, "");
|
|
37
|
+
}
|
|
38
|
+
static create(options) {
|
|
39
|
+
const config = {
|
|
40
|
+
region: options.region,
|
|
41
|
+
endpoint: options.endpoint,
|
|
42
|
+
forcePathStyle: options.endpoint ? true : void 0
|
|
43
|
+
};
|
|
44
|
+
if (options.profile) {
|
|
45
|
+
const { fromIni } = require("@aws-sdk/credential-providers");
|
|
46
|
+
config.credentials = fromIni({ profile: options.profile });
|
|
47
|
+
} else if (options.credentials) config.credentials = options.credentials;
|
|
48
|
+
return new S3StateStore(options.workspaceName, options.bucket, new _aws_sdk_client_s3.S3Client(config), options.prefix);
|
|
49
|
+
}
|
|
50
|
+
key(stage, leaf) {
|
|
51
|
+
const path = `${this.workspaceName}/${stage}/${leaf}`;
|
|
52
|
+
return this.prefix ? `${this.prefix}/${path}` : path;
|
|
53
|
+
}
|
|
54
|
+
location(stage) {
|
|
55
|
+
return `s3://${this.bucket}/${this.key(stage, "state.json")}`;
|
|
56
|
+
}
|
|
57
|
+
async readRaw(stage) {
|
|
58
|
+
const object = await this.get(this.key(stage, "state.json"));
|
|
59
|
+
if (!object) return null;
|
|
60
|
+
return {
|
|
61
|
+
body: object.body,
|
|
62
|
+
version: object.etag
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
async writeRaw(stage, body, expectedVersion) {
|
|
66
|
+
const Key = this.key(stage, "state.json");
|
|
67
|
+
try {
|
|
68
|
+
const { ETag } = await this.client.send(new _aws_sdk_client_s3.PutObjectCommand({
|
|
69
|
+
Bucket: this.bucket,
|
|
70
|
+
Key,
|
|
71
|
+
Body: body,
|
|
72
|
+
ContentType: "application/json",
|
|
73
|
+
...expectedVersion === null ? { IfNoneMatch: "*" } : { IfMatch: expectedVersion }
|
|
74
|
+
}));
|
|
75
|
+
if (ETag) return ETag;
|
|
76
|
+
return (await this.get(Key))?.etag ?? "";
|
|
77
|
+
} catch (error) {
|
|
78
|
+
if (lostRace(error)) {
|
|
79
|
+
const current = await this.get(Key);
|
|
80
|
+
throw new require_seeds.StateVersionConflict(stage, expectedVersion, current?.etag ?? null);
|
|
81
|
+
}
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
async writeV1Backup(stage, body) {
|
|
86
|
+
try {
|
|
87
|
+
await this.client.send(new _aws_sdk_client_s3.PutObjectCommand({
|
|
88
|
+
Bucket: this.bucket,
|
|
89
|
+
Key: this.key(stage, "state.v1.json"),
|
|
90
|
+
Body: body,
|
|
91
|
+
ContentType: "application/json",
|
|
92
|
+
IfNoneMatch: "*"
|
|
93
|
+
}));
|
|
94
|
+
} catch (error) {
|
|
95
|
+
if (!lostRace(error)) throw error;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async createLock(stage, holder) {
|
|
99
|
+
const Key = this.key(stage, "lock.json");
|
|
100
|
+
try {
|
|
101
|
+
await this.client.send(new _aws_sdk_client_s3.PutObjectCommand({
|
|
102
|
+
Bucket: this.bucket,
|
|
103
|
+
Key,
|
|
104
|
+
Body: JSON.stringify(holder),
|
|
105
|
+
ContentType: "application/json",
|
|
106
|
+
IfNoneMatch: "*"
|
|
107
|
+
}));
|
|
108
|
+
} catch (error) {
|
|
109
|
+
if (lostRace(error)) throw new require_seeds.StateLocked(stage, await this.readLock(stage), `s3://${this.bucket}/${Key}`);
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
async readLock(stage) {
|
|
114
|
+
const object = await this.get(this.key(stage, "lock.json"));
|
|
115
|
+
if (!object) return null;
|
|
116
|
+
try {
|
|
117
|
+
return JSON.parse(object.body);
|
|
118
|
+
} catch {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async removeLock(stage, holder) {
|
|
123
|
+
const Key = this.key(stage, "lock.json");
|
|
124
|
+
const object = await this.get(Key);
|
|
125
|
+
if (!object) return;
|
|
126
|
+
if (holder) try {
|
|
127
|
+
if (JSON.parse(object.body).id !== holder.id) return;
|
|
128
|
+
} catch {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
await this.client.send(new _aws_sdk_client_s3.DeleteObjectCommand({
|
|
133
|
+
Bucket: this.bucket,
|
|
134
|
+
Key,
|
|
135
|
+
IfMatch: object.etag
|
|
136
|
+
}));
|
|
137
|
+
} catch (error) {
|
|
138
|
+
if (!lostRace(error) && !notFound(error)) throw error;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
async get(Key) {
|
|
142
|
+
try {
|
|
143
|
+
const response = await this.client.send(new _aws_sdk_client_s3.GetObjectCommand({
|
|
144
|
+
Bucket: this.bucket,
|
|
145
|
+
Key
|
|
146
|
+
}));
|
|
147
|
+
return {
|
|
148
|
+
body: await response.Body?.transformToString("utf-8") ?? "",
|
|
149
|
+
etag: response.ETag ?? ""
|
|
150
|
+
};
|
|
151
|
+
} catch (error) {
|
|
152
|
+
if (notFound(error)) return null;
|
|
153
|
+
throw error;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
//#endregion
|
|
158
|
+
exports.S3StateStore = S3StateStore;
|
|
159
|
+
|
|
160
|
+
//# sourceMappingURL=S3StateStore-Br8VMY22.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"S3StateStore-Br8VMY22.cjs","names":["DocumentStateStore","S3Client","PutObjectCommand","StateVersionConflict","StateLocked","DeleteObjectCommand","GetObjectCommand"],"sources":["../src/deploy/S3StateStore.ts"],"sourcesContent":["/**\n * Amazon S3 state store.\n *\n * - `<prefix>/<workspace>/<stage>/state.json`: the state.\n * - `<prefix>/<workspace>/<stage>/lock.json`: the lock.\n * - `<prefix>/<workspace>/<stage>/state.v1.json`: the v1 state, kept on\n * migration.\n *\n * Every write is conditional on the server: `If-None-Match: *` to create,\n * `If-Match: <etag>` to replace. S3 answers a lost race with 412 (or 409 when\n * two conditional writes are in flight at once), so there is no window\n * between a check and a write for another run to slip into.\n */\n\nimport {\n\tDeleteObjectCommand,\n\tGetObjectCommand,\n\tPutObjectCommand,\n\tS3Client,\n\ttype S3ClientConfig,\n} from '@aws-sdk/client-s3';\nimport type { AwsRegion } from './StateProvider';\nimport {\n\tDocumentStateStore,\n\ttype LockHolder,\n\ttype RawState,\n\tStateLocked,\n\ttype StateVersion,\n\tStateVersionConflict,\n} from './StateStore';\n\nexport interface S3StateStoreOptions {\n\tworkspaceName: string;\n\tbucket: string;\n\t/** Key prefix inside the bucket; `gkm` by default. */\n\tprefix?: string;\n\tregion?: AwsRegion;\n\t/** Uses the default credential chain when omitted. */\n\tprofile?: string;\n\tcredentials?: S3ClientConfig['credentials'];\n\t/** For the local AWS emulator. */\n\tendpoint?: string;\n}\n\ninterface S3Failure {\n\tname?: string;\n\t$metadata?: { httpStatusCode?: number };\n}\n\n/** A conditional write that lost: the object is not what the write assumed. */\nfunction lostRace(error: unknown): boolean {\n\tconst { name, $metadata } = (error ?? {}) as S3Failure;\n\treturn (\n\t\tname === 'PreconditionFailed' ||\n\t\tname === 'ConditionalRequestConflict' ||\n\t\t$metadata?.httpStatusCode === 412 ||\n\t\t$metadata?.httpStatusCode === 409\n\t);\n}\n\nfunction notFound(error: unknown): boolean {\n\tconst { name, $metadata } = (error ?? {}) as S3Failure;\n\treturn (\n\t\tname === 'NoSuchKey' ||\n\t\tname === 'NotFound' ||\n\t\t$metadata?.httpStatusCode === 404\n\t);\n}\n\nexport class S3StateStore extends DocumentStateStore {\n\tprivate readonly prefix: string;\n\n\tconstructor(\n\t\treadonly workspaceName: string,\n\t\treadonly bucket: string,\n\t\tprivate readonly client: S3Client,\n\t\tprefix = 'gkm',\n\t) {\n\t\tsuper();\n\t\tthis.prefix = prefix.replace(/\\/+$/, '');\n\t}\n\n\tstatic create(options: S3StateStoreOptions): S3StateStore {\n\t\tconst config: S3ClientConfig = {\n\t\t\tregion: options.region,\n\t\t\tendpoint: options.endpoint,\n\t\t\t// The emulator serves buckets by path, not by subdomain.\n\t\t\tforcePathStyle: options.endpoint ? true : undefined,\n\t\t};\n\t\tif (options.profile) {\n\t\t\t// Required lazily: only a profile needs the credential providers.\n\t\t\tconst { fromIni } = require('@aws-sdk/credential-providers');\n\t\t\tconfig.credentials = fromIni({ profile: options.profile });\n\t\t} else if (options.credentials) {\n\t\t\tconfig.credentials = options.credentials;\n\t\t}\n\t\treturn new S3StateStore(\n\t\t\toptions.workspaceName,\n\t\t\toptions.bucket,\n\t\t\tnew S3Client(config),\n\t\t\toptions.prefix,\n\t\t);\n\t}\n\n\tprivate key(stage: string, leaf: string): string {\n\t\tconst path = `${this.workspaceName}/${stage}/${leaf}`;\n\t\treturn this.prefix ? `${this.prefix}/${path}` : path;\n\t}\n\n\tprotected location(stage: string): string {\n\t\treturn `s3://${this.bucket}/${this.key(stage, 'state.json')}`;\n\t}\n\n\tprotected async readRaw(stage: string): Promise<RawState | null> {\n\t\tconst object = await this.get(this.key(stage, 'state.json'));\n\t\tif (!object) return null;\n\t\treturn { body: object.body, version: object.etag };\n\t}\n\n\tprotected async writeRaw(\n\t\tstage: string,\n\t\tbody: string,\n\t\texpectedVersion: StateVersion | null,\n\t): Promise<StateVersion> {\n\t\tconst Key = this.key(stage, 'state.json');\n\t\ttry {\n\t\t\tconst { ETag } = await this.client.send(\n\t\t\t\tnew PutObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey,\n\t\t\t\t\tBody: body,\n\t\t\t\t\tContentType: 'application/json',\n\t\t\t\t\t...(expectedVersion === null\n\t\t\t\t\t\t? { IfNoneMatch: '*' }\n\t\t\t\t\t\t: { IfMatch: expectedVersion }),\n\t\t\t\t}),\n\t\t\t);\n\t\t\tif (ETag) return ETag;\n\t\t\t// Some S3-compatible servers omit the ETag on a conditional put.\n\t\t\treturn (await this.get(Key))?.etag ?? '';\n\t\t} catch (error) {\n\t\t\tif (lostRace(error)) {\n\t\t\t\tconst current = await this.get(Key);\n\t\t\t\tthrow new StateVersionConflict(\n\t\t\t\t\tstage,\n\t\t\t\t\texpectedVersion,\n\t\t\t\t\tcurrent?.etag ?? null,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tprotected async writeV1Backup(stage: string, body: string): Promise<void> {\n\t\ttry {\n\t\t\tawait this.client.send(\n\t\t\t\tnew PutObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey: this.key(stage, 'state.v1.json'),\n\t\t\t\t\tBody: body,\n\t\t\t\t\tContentType: 'application/json',\n\t\t\t\t\tIfNoneMatch: '*',\n\t\t\t\t}),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\t// The first migration's backup is the one worth keeping.\n\t\t\tif (!lostRace(error)) throw error;\n\t\t}\n\t}\n\n\tprotected async createLock(stage: string, holder: LockHolder): Promise<void> {\n\t\tconst Key = this.key(stage, 'lock.json');\n\t\ttry {\n\t\t\tawait this.client.send(\n\t\t\t\tnew PutObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey,\n\t\t\t\t\tBody: JSON.stringify(holder),\n\t\t\t\t\tContentType: 'application/json',\n\t\t\t\t\tIfNoneMatch: '*',\n\t\t\t\t}),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tif (lostRace(error)) {\n\t\t\t\tthrow new StateLocked(\n\t\t\t\t\tstage,\n\t\t\t\t\tawait this.readLock(stage),\n\t\t\t\t\t`s3://${this.bucket}/${Key}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tprotected async readLock(stage: string): Promise<LockHolder | null> {\n\t\tconst object = await this.get(this.key(stage, 'lock.json'));\n\t\tif (!object) return null;\n\t\ttry {\n\t\t\treturn JSON.parse(object.body) as LockHolder;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tprotected async removeLock(\n\t\tstage: string,\n\t\tholder: LockHolder | null,\n\t): Promise<void> {\n\t\tconst Key = this.key(stage, 'lock.json');\n\t\tconst object = await this.get(Key);\n\t\tif (!object) return;\n\t\tif (holder) {\n\t\t\ttry {\n\t\t\t\tif ((JSON.parse(object.body) as LockHolder).id !== holder.id) return;\n\t\t\t} catch {\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\ttry {\n\t\t\t// Conditional on the lock read above, so a lock taken by another\n\t\t\t// run in the meantime survives.\n\t\t\tawait this.client.send(\n\t\t\t\tnew DeleteObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey,\n\t\t\t\t\tIfMatch: object.etag,\n\t\t\t\t}),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tif (!lostRace(error) && !notFound(error)) throw error;\n\t\t}\n\t}\n\n\tprivate async get(\n\t\tKey: string,\n\t): Promise<{ body: string; etag: string } | null> {\n\t\ttry {\n\t\t\tconst response = await this.client.send(\n\t\t\t\tnew GetObjectCommand({ Bucket: this.bucket, Key }),\n\t\t\t);\n\t\t\tconst body = (await response.Body?.transformToString('utf-8')) ?? '';\n\t\t\treturn { body, etag: response.ETag ?? '' };\n\t\t} catch (error) {\n\t\t\tif (notFound(error)) return null;\n\t\t\tthrow error;\n\t\t}\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { n as __require } from "./rolldown-runtime-BhDjJH2R.mjs";
|
|
2
|
+
import { I as DocumentStateStore, R as StateLocked, V as StateVersionConflict } from "./seeds-odfgSCmu.mjs";
|
|
3
|
+
import { DeleteObjectCommand, GetObjectCommand, PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
|
|
4
|
+
//#region src/deploy/S3StateStore.ts
|
|
5
|
+
/**
|
|
6
|
+
* Amazon S3 state store.
|
|
7
|
+
*
|
|
8
|
+
* - `<prefix>/<workspace>/<stage>/state.json`: the state.
|
|
9
|
+
* - `<prefix>/<workspace>/<stage>/lock.json`: the lock.
|
|
10
|
+
* - `<prefix>/<workspace>/<stage>/state.v1.json`: the v1 state, kept on
|
|
11
|
+
* migration.
|
|
12
|
+
*
|
|
13
|
+
* Every write is conditional on the server: `If-None-Match: *` to create,
|
|
14
|
+
* `If-Match: <etag>` to replace. S3 answers a lost race with 412 (or 409 when
|
|
15
|
+
* two conditional writes are in flight at once), so there is no window
|
|
16
|
+
* between a check and a write for another run to slip into.
|
|
17
|
+
*/
|
|
18
|
+
/** A conditional write that lost: the object is not what the write assumed. */
|
|
19
|
+
function lostRace(error) {
|
|
20
|
+
const { name, $metadata } = error ?? {};
|
|
21
|
+
return name === "PreconditionFailed" || name === "ConditionalRequestConflict" || $metadata?.httpStatusCode === 412 || $metadata?.httpStatusCode === 409;
|
|
22
|
+
}
|
|
23
|
+
function notFound(error) {
|
|
24
|
+
const { name, $metadata } = error ?? {};
|
|
25
|
+
return name === "NoSuchKey" || name === "NotFound" || $metadata?.httpStatusCode === 404;
|
|
26
|
+
}
|
|
27
|
+
var S3StateStore = class S3StateStore extends DocumentStateStore {
|
|
28
|
+
workspaceName;
|
|
29
|
+
bucket;
|
|
30
|
+
client;
|
|
31
|
+
prefix;
|
|
32
|
+
constructor(workspaceName, bucket, client, prefix = "gkm") {
|
|
33
|
+
super();
|
|
34
|
+
this.workspaceName = workspaceName;
|
|
35
|
+
this.bucket = bucket;
|
|
36
|
+
this.client = client;
|
|
37
|
+
this.prefix = prefix.replace(/\/+$/, "");
|
|
38
|
+
}
|
|
39
|
+
static create(options) {
|
|
40
|
+
const config = {
|
|
41
|
+
region: options.region,
|
|
42
|
+
endpoint: options.endpoint,
|
|
43
|
+
forcePathStyle: options.endpoint ? true : void 0
|
|
44
|
+
};
|
|
45
|
+
if (options.profile) {
|
|
46
|
+
const { fromIni } = __require("@aws-sdk/credential-providers");
|
|
47
|
+
config.credentials = fromIni({ profile: options.profile });
|
|
48
|
+
} else if (options.credentials) config.credentials = options.credentials;
|
|
49
|
+
return new S3StateStore(options.workspaceName, options.bucket, new S3Client(config), options.prefix);
|
|
50
|
+
}
|
|
51
|
+
key(stage, leaf) {
|
|
52
|
+
const path = `${this.workspaceName}/${stage}/${leaf}`;
|
|
53
|
+
return this.prefix ? `${this.prefix}/${path}` : path;
|
|
54
|
+
}
|
|
55
|
+
location(stage) {
|
|
56
|
+
return `s3://${this.bucket}/${this.key(stage, "state.json")}`;
|
|
57
|
+
}
|
|
58
|
+
async readRaw(stage) {
|
|
59
|
+
const object = await this.get(this.key(stage, "state.json"));
|
|
60
|
+
if (!object) return null;
|
|
61
|
+
return {
|
|
62
|
+
body: object.body,
|
|
63
|
+
version: object.etag
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
async writeRaw(stage, body, expectedVersion) {
|
|
67
|
+
const Key = this.key(stage, "state.json");
|
|
68
|
+
try {
|
|
69
|
+
const { ETag } = await this.client.send(new PutObjectCommand({
|
|
70
|
+
Bucket: this.bucket,
|
|
71
|
+
Key,
|
|
72
|
+
Body: body,
|
|
73
|
+
ContentType: "application/json",
|
|
74
|
+
...expectedVersion === null ? { IfNoneMatch: "*" } : { IfMatch: expectedVersion }
|
|
75
|
+
}));
|
|
76
|
+
if (ETag) return ETag;
|
|
77
|
+
return (await this.get(Key))?.etag ?? "";
|
|
78
|
+
} catch (error) {
|
|
79
|
+
if (lostRace(error)) {
|
|
80
|
+
const current = await this.get(Key);
|
|
81
|
+
throw new StateVersionConflict(stage, expectedVersion, current?.etag ?? null);
|
|
82
|
+
}
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async writeV1Backup(stage, body) {
|
|
87
|
+
try {
|
|
88
|
+
await this.client.send(new PutObjectCommand({
|
|
89
|
+
Bucket: this.bucket,
|
|
90
|
+
Key: this.key(stage, "state.v1.json"),
|
|
91
|
+
Body: body,
|
|
92
|
+
ContentType: "application/json",
|
|
93
|
+
IfNoneMatch: "*"
|
|
94
|
+
}));
|
|
95
|
+
} catch (error) {
|
|
96
|
+
if (!lostRace(error)) throw error;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
async createLock(stage, holder) {
|
|
100
|
+
const Key = this.key(stage, "lock.json");
|
|
101
|
+
try {
|
|
102
|
+
await this.client.send(new PutObjectCommand({
|
|
103
|
+
Bucket: this.bucket,
|
|
104
|
+
Key,
|
|
105
|
+
Body: JSON.stringify(holder),
|
|
106
|
+
ContentType: "application/json",
|
|
107
|
+
IfNoneMatch: "*"
|
|
108
|
+
}));
|
|
109
|
+
} catch (error) {
|
|
110
|
+
if (lostRace(error)) throw new StateLocked(stage, await this.readLock(stage), `s3://${this.bucket}/${Key}`);
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async readLock(stage) {
|
|
115
|
+
const object = await this.get(this.key(stage, "lock.json"));
|
|
116
|
+
if (!object) return null;
|
|
117
|
+
try {
|
|
118
|
+
return JSON.parse(object.body);
|
|
119
|
+
} catch {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
async removeLock(stage, holder) {
|
|
124
|
+
const Key = this.key(stage, "lock.json");
|
|
125
|
+
const object = await this.get(Key);
|
|
126
|
+
if (!object) return;
|
|
127
|
+
if (holder) try {
|
|
128
|
+
if (JSON.parse(object.body).id !== holder.id) return;
|
|
129
|
+
} catch {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
await this.client.send(new DeleteObjectCommand({
|
|
134
|
+
Bucket: this.bucket,
|
|
135
|
+
Key,
|
|
136
|
+
IfMatch: object.etag
|
|
137
|
+
}));
|
|
138
|
+
} catch (error) {
|
|
139
|
+
if (!lostRace(error) && !notFound(error)) throw error;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
async get(Key) {
|
|
143
|
+
try {
|
|
144
|
+
const response = await this.client.send(new GetObjectCommand({
|
|
145
|
+
Bucket: this.bucket,
|
|
146
|
+
Key
|
|
147
|
+
}));
|
|
148
|
+
return {
|
|
149
|
+
body: await response.Body?.transformToString("utf-8") ?? "",
|
|
150
|
+
etag: response.ETag ?? ""
|
|
151
|
+
};
|
|
152
|
+
} catch (error) {
|
|
153
|
+
if (notFound(error)) return null;
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
//#endregion
|
|
159
|
+
export { S3StateStore };
|
|
160
|
+
|
|
161
|
+
//# sourceMappingURL=S3StateStore-bwxcI_wQ.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"S3StateStore-bwxcI_wQ.mjs","names":[],"sources":["../src/deploy/S3StateStore.ts"],"sourcesContent":["/**\n * Amazon S3 state store.\n *\n * - `<prefix>/<workspace>/<stage>/state.json`: the state.\n * - `<prefix>/<workspace>/<stage>/lock.json`: the lock.\n * - `<prefix>/<workspace>/<stage>/state.v1.json`: the v1 state, kept on\n * migration.\n *\n * Every write is conditional on the server: `If-None-Match: *` to create,\n * `If-Match: <etag>` to replace. S3 answers a lost race with 412 (or 409 when\n * two conditional writes are in flight at once), so there is no window\n * between a check and a write for another run to slip into.\n */\n\nimport {\n\tDeleteObjectCommand,\n\tGetObjectCommand,\n\tPutObjectCommand,\n\tS3Client,\n\ttype S3ClientConfig,\n} from '@aws-sdk/client-s3';\nimport type { AwsRegion } from './StateProvider';\nimport {\n\tDocumentStateStore,\n\ttype LockHolder,\n\ttype RawState,\n\tStateLocked,\n\ttype StateVersion,\n\tStateVersionConflict,\n} from './StateStore';\n\nexport interface S3StateStoreOptions {\n\tworkspaceName: string;\n\tbucket: string;\n\t/** Key prefix inside the bucket; `gkm` by default. */\n\tprefix?: string;\n\tregion?: AwsRegion;\n\t/** Uses the default credential chain when omitted. */\n\tprofile?: string;\n\tcredentials?: S3ClientConfig['credentials'];\n\t/** For the local AWS emulator. */\n\tendpoint?: string;\n}\n\ninterface S3Failure {\n\tname?: string;\n\t$metadata?: { httpStatusCode?: number };\n}\n\n/** A conditional write that lost: the object is not what the write assumed. */\nfunction lostRace(error: unknown): boolean {\n\tconst { name, $metadata } = (error ?? {}) as S3Failure;\n\treturn (\n\t\tname === 'PreconditionFailed' ||\n\t\tname === 'ConditionalRequestConflict' ||\n\t\t$metadata?.httpStatusCode === 412 ||\n\t\t$metadata?.httpStatusCode === 409\n\t);\n}\n\nfunction notFound(error: unknown): boolean {\n\tconst { name, $metadata } = (error ?? {}) as S3Failure;\n\treturn (\n\t\tname === 'NoSuchKey' ||\n\t\tname === 'NotFound' ||\n\t\t$metadata?.httpStatusCode === 404\n\t);\n}\n\nexport class S3StateStore extends DocumentStateStore {\n\tprivate readonly prefix: string;\n\n\tconstructor(\n\t\treadonly workspaceName: string,\n\t\treadonly bucket: string,\n\t\tprivate readonly client: S3Client,\n\t\tprefix = 'gkm',\n\t) {\n\t\tsuper();\n\t\tthis.prefix = prefix.replace(/\\/+$/, '');\n\t}\n\n\tstatic create(options: S3StateStoreOptions): S3StateStore {\n\t\tconst config: S3ClientConfig = {\n\t\t\tregion: options.region,\n\t\t\tendpoint: options.endpoint,\n\t\t\t// The emulator serves buckets by path, not by subdomain.\n\t\t\tforcePathStyle: options.endpoint ? true : undefined,\n\t\t};\n\t\tif (options.profile) {\n\t\t\t// Required lazily: only a profile needs the credential providers.\n\t\t\tconst { fromIni } = require('@aws-sdk/credential-providers');\n\t\t\tconfig.credentials = fromIni({ profile: options.profile });\n\t\t} else if (options.credentials) {\n\t\t\tconfig.credentials = options.credentials;\n\t\t}\n\t\treturn new S3StateStore(\n\t\t\toptions.workspaceName,\n\t\t\toptions.bucket,\n\t\t\tnew S3Client(config),\n\t\t\toptions.prefix,\n\t\t);\n\t}\n\n\tprivate key(stage: string, leaf: string): string {\n\t\tconst path = `${this.workspaceName}/${stage}/${leaf}`;\n\t\treturn this.prefix ? `${this.prefix}/${path}` : path;\n\t}\n\n\tprotected location(stage: string): string {\n\t\treturn `s3://${this.bucket}/${this.key(stage, 'state.json')}`;\n\t}\n\n\tprotected async readRaw(stage: string): Promise<RawState | null> {\n\t\tconst object = await this.get(this.key(stage, 'state.json'));\n\t\tif (!object) return null;\n\t\treturn { body: object.body, version: object.etag };\n\t}\n\n\tprotected async writeRaw(\n\t\tstage: string,\n\t\tbody: string,\n\t\texpectedVersion: StateVersion | null,\n\t): Promise<StateVersion> {\n\t\tconst Key = this.key(stage, 'state.json');\n\t\ttry {\n\t\t\tconst { ETag } = await this.client.send(\n\t\t\t\tnew PutObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey,\n\t\t\t\t\tBody: body,\n\t\t\t\t\tContentType: 'application/json',\n\t\t\t\t\t...(expectedVersion === null\n\t\t\t\t\t\t? { IfNoneMatch: '*' }\n\t\t\t\t\t\t: { IfMatch: expectedVersion }),\n\t\t\t\t}),\n\t\t\t);\n\t\t\tif (ETag) return ETag;\n\t\t\t// Some S3-compatible servers omit the ETag on a conditional put.\n\t\t\treturn (await this.get(Key))?.etag ?? '';\n\t\t} catch (error) {\n\t\t\tif (lostRace(error)) {\n\t\t\t\tconst current = await this.get(Key);\n\t\t\t\tthrow new StateVersionConflict(\n\t\t\t\t\tstage,\n\t\t\t\t\texpectedVersion,\n\t\t\t\t\tcurrent?.etag ?? null,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tprotected async writeV1Backup(stage: string, body: string): Promise<void> {\n\t\ttry {\n\t\t\tawait this.client.send(\n\t\t\t\tnew PutObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey: this.key(stage, 'state.v1.json'),\n\t\t\t\t\tBody: body,\n\t\t\t\t\tContentType: 'application/json',\n\t\t\t\t\tIfNoneMatch: '*',\n\t\t\t\t}),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\t// The first migration's backup is the one worth keeping.\n\t\t\tif (!lostRace(error)) throw error;\n\t\t}\n\t}\n\n\tprotected async createLock(stage: string, holder: LockHolder): Promise<void> {\n\t\tconst Key = this.key(stage, 'lock.json');\n\t\ttry {\n\t\t\tawait this.client.send(\n\t\t\t\tnew PutObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey,\n\t\t\t\t\tBody: JSON.stringify(holder),\n\t\t\t\t\tContentType: 'application/json',\n\t\t\t\t\tIfNoneMatch: '*',\n\t\t\t\t}),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tif (lostRace(error)) {\n\t\t\t\tthrow new StateLocked(\n\t\t\t\t\tstage,\n\t\t\t\t\tawait this.readLock(stage),\n\t\t\t\t\t`s3://${this.bucket}/${Key}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tprotected async readLock(stage: string): Promise<LockHolder | null> {\n\t\tconst object = await this.get(this.key(stage, 'lock.json'));\n\t\tif (!object) return null;\n\t\ttry {\n\t\t\treturn JSON.parse(object.body) as LockHolder;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tprotected async removeLock(\n\t\tstage: string,\n\t\tholder: LockHolder | null,\n\t): Promise<void> {\n\t\tconst Key = this.key(stage, 'lock.json');\n\t\tconst object = await this.get(Key);\n\t\tif (!object) return;\n\t\tif (holder) {\n\t\t\ttry {\n\t\t\t\tif ((JSON.parse(object.body) as LockHolder).id !== holder.id) return;\n\t\t\t} catch {\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\ttry {\n\t\t\t// Conditional on the lock read above, so a lock taken by another\n\t\t\t// run in the meantime survives.\n\t\t\tawait this.client.send(\n\t\t\t\tnew DeleteObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey,\n\t\t\t\t\tIfMatch: object.etag,\n\t\t\t\t}),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tif (!lostRace(error) && !notFound(error)) throw error;\n\t\t}\n\t}\n\n\tprivate async get(\n\t\tKey: string,\n\t): Promise<{ body: string; etag: string } | null> {\n\t\ttry {\n\t\t\tconst response = await this.client.send(\n\t\t\t\tnew GetObjectCommand({ Bucket: this.bucket, Key }),\n\t\t\t);\n\t\t\tconst body = (await response.Body?.transformToString('utf-8')) ?? '';\n\t\t\treturn { body, etag: response.ETag ?? '' };\n\t\t} catch (error) {\n\t\t\tif (notFound(error)) return null;\n\t\t\tthrow error;\n\t\t}\n\t}\n}\n"],"mappings":"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"}
|