@geekmidas/cli 10.0.0-alpha.12 → 10.0.0-alpha.120
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 +310 -377
- 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-BDa6pPgE.cjs +1090 -0
- package/dist/EndpointGenerator-BDa6pPgE.cjs.map +1 -0
- package/dist/EndpointGenerator-CP_z_E3e.mjs +1031 -0
- package/dist/EndpointGenerator-CP_z_E3e.mjs.map +1 -0
- package/dist/GoDaddyProvider-3C8U_tVu.cjs +388 -0
- package/dist/GoDaddyProvider-3C8U_tVu.cjs.map +1 -0
- package/dist/GoDaddyProvider-D0MQoOM4.mjs +388 -0
- package/dist/GoDaddyProvider-D0MQoOM4.mjs.map +1 -0
- package/dist/{HostingerProvider-BcyFRbqV.mjs → HostingerProvider-BNpS9aRJ.mjs} +29 -25
- package/dist/HostingerProvider-BNpS9aRJ.mjs.map +1 -0
- package/dist/{HostingerProvider-BOvsgATO.cjs → HostingerProvider-DR1iws1P.cjs} +29 -25
- package/dist/HostingerProvider-DR1iws1P.cjs.map +1 -0
- package/dist/LegacyStateStore-CRi7TosX.mjs +124 -0
- package/dist/LegacyStateStore-CRi7TosX.mjs.map +1 -0
- package/dist/LegacyStateStore-DHsoQGOo.cjs +124 -0
- package/dist/LegacyStateStore-DHsoQGOo.cjs.map +1 -0
- package/dist/LocalStateStore-BQXTkeoS.cjs +178 -0
- package/dist/LocalStateStore-BQXTkeoS.cjs.map +1 -0
- package/dist/LocalStateStore-Dkth0jXX.mjs +167 -0
- package/dist/LocalStateStore-Dkth0jXX.mjs.map +1 -0
- package/dist/OpenApiTsGenerator-CsrukCE-.mjs +536 -0
- package/dist/OpenApiTsGenerator-CsrukCE-.mjs.map +1 -0
- package/dist/OpenApiTsGenerator-FNhY7Ssz.cjs +541 -0
- package/dist/OpenApiTsGenerator-FNhY7Ssz.cjs.map +1 -0
- package/dist/{Route53Provider-CH9XH7bz.cjs → Route53Provider-Bllus2NR.cjs} +21 -22
- package/dist/Route53Provider-Bllus2NR.cjs.map +1 -0
- package/dist/{Route53Provider-C0xmer8U.mjs → Route53Provider-QXxjkZO1.mjs} +13 -13
- package/dist/Route53Provider-QXxjkZO1.mjs.map +1 -0
- package/dist/S3StateStore-DGKZgyKz.cjs +222 -0
- package/dist/S3StateStore-DGKZgyKz.cjs.map +1 -0
- package/dist/S3StateStore-pZZnLLBd.mjs +223 -0
- package/dist/S3StateStore-pZZnLLBd.mjs.map +1 -0
- package/dist/SSMStateStore-CZRMERDA.mjs +205 -0
- package/dist/SSMStateStore-CZRMERDA.mjs.map +1 -0
- package/dist/SSMStateStore-DzkyjQ5D.cjs +204 -0
- package/dist/SSMStateStore-DzkyjQ5D.cjs.map +1 -0
- package/dist/SubscriberGenerator-C8CPxrCK.cjs +592 -0
- package/dist/SubscriberGenerator-C8CPxrCK.cjs.map +1 -0
- package/dist/SubscriberGenerator-y3pncae_.mjs +533 -0
- package/dist/SubscriberGenerator-y3pncae_.mjs.map +1 -0
- package/dist/add-C0kXOsWS.mjs +717 -0
- package/dist/add-C0kXOsWS.mjs.map +1 -0
- package/dist/add-Df8cmz0H.cjs +719 -0
- package/dist/add-Df8cmz0H.cjs.map +1 -0
- package/dist/appPorts-BoXMwolc.mjs +249 -0
- package/dist/appPorts-BoXMwolc.mjs.map +1 -0
- package/dist/appPorts-IW-OZmRi.cjs +296 -0
- package/dist/appPorts-IW-OZmRi.cjs.map +1 -0
- package/dist/aws-8QQ8n-t6.mjs +89 -0
- package/dist/aws-8QQ8n-t6.mjs.map +1 -0
- package/dist/aws-DY9NqQIT.cjs +100 -0
- package/dist/aws-DY9NqQIT.cjs.map +1 -0
- package/dist/awsStore-Bx5nCFnS.mjs +121 -0
- package/dist/awsStore-Bx5nCFnS.mjs.map +1 -0
- package/dist/awsStore-u3ehhMoY.cjs +138 -0
- package/dist/awsStore-u3ehhMoY.cjs.map +1 -0
- package/dist/{backup-provisioner-DXXg6FIL.mjs → backup-provisioner-C8DnCmxK.mjs} +11 -8
- package/dist/backup-provisioner-C8DnCmxK.mjs.map +1 -0
- package/dist/{backup-provisioner-cE2PuCTb.cjs → backup-provisioner-oDEsPj02.cjs} +22 -20
- package/dist/backup-provisioner-oDEsPj02.cjs.map +1 -0
- package/dist/backups/runner.cjs +519 -0
- package/dist/backups/runner.cjs.map +1 -0
- package/dist/backups/runner.d.cts +102 -0
- package/dist/backups/runner.d.cts.map +1 -0
- package/dist/backups/runner.d.mts +102 -0
- package/dist/backups/runner.d.mts.map +1 -0
- package/dist/backups/runner.mjs +506 -0
- package/dist/backups/runner.mjs.map +1 -0
- package/dist/{bundler-Bux2iF-v.mjs → bundler-B-I4Du99.mjs} +35 -16
- package/dist/bundler-B-I4Du99.mjs.map +1 -0
- package/dist/{bundler-BMekgx7a.cjs → bundler-Dx_grUmc.cjs} +37 -19
- package/dist/bundler-Dx_grUmc.cjs.map +1 -0
- package/dist/cli-Cd9exatJ.cjs +71 -0
- package/dist/cli-Cd9exatJ.cjs.map +1 -0
- package/dist/cli-DL7GxGAe.mjs +71 -0
- package/dist/cli-DL7GxGAe.mjs.map +1 -0
- package/dist/compose-C43c03WZ.mjs +3284 -0
- package/dist/compose-C43c03WZ.mjs.map +1 -0
- package/dist/compose-Mdgea4Yt.cjs +3427 -0
- package/dist/compose-Mdgea4Yt.cjs.map +1 -0
- package/dist/config-D48erO2j.d.mts +269 -0
- package/dist/config-D48erO2j.d.mts.map +1 -0
- package/dist/config-DHDtsQrB.d.cts +269 -0
- package/dist/config-DHDtsQrB.d.cts.map +1 -0
- package/dist/config.cjs +421 -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 +405 -4
- package/dist/config.mjs.map +1 -0
- package/dist/credentialSchemas-B67WJOOm.cjs +272 -0
- package/dist/credentialSchemas-B67WJOOm.cjs.map +1 -0
- package/dist/credentialSchemas-DDOLRCLF.mjs +237 -0
- package/dist/credentialSchemas-DDOLRCLF.mjs.map +1 -0
- package/dist/credentials-B5N5yKk6.mjs +267 -0
- package/dist/credentials-B5N5yKk6.mjs.map +1 -0
- package/dist/credentials-Zqr3QQYB.cjs +350 -0
- package/dist/credentials-Zqr3QQYB.cjs.map +1 -0
- package/dist/databases-BPOgTn1u.mjs +522 -0
- package/dist/databases-BPOgTn1u.mjs.map +1 -0
- package/dist/databases-fOaoBInm.cjs +625 -0
- package/dist/databases-fOaoBInm.cjs.map +1 -0
- package/dist/define-6poNqm5O.mjs +106 -0
- package/dist/define-6poNqm5O.mjs.map +1 -0
- package/dist/define-crmMcXFS.cjs +153 -0
- package/dist/define-crmMcXFS.cjs.map +1 -0
- package/dist/deploy/public.cjs +212 -0
- package/dist/deploy/public.cjs.map +1 -0
- package/dist/deploy/public.d.cts +654 -0
- package/dist/deploy/public.d.cts.map +1 -0
- package/dist/deploy/public.d.mts +654 -0
- package/dist/deploy/public.d.mts.map +1 -0
- package/dist/deploy/public.mjs +145 -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-BMM2hx-K.cjs +2401 -0
- package/dist/deploy-BMM2hx-K.cjs.map +1 -0
- package/dist/deploy-D5YhwDng.mjs +2328 -0
- package/dist/deploy-D5YhwDng.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-C5roJuc9.mjs +560 -0
- package/dist/discover-C5roJuc9.mjs.map +1 -0
- package/dist/discover-CSW_BMWa.d.cts +125 -0
- package/dist/discover-CSW_BMWa.d.cts.map +1 -0
- package/dist/discover-DozUKs8f.cjs +639 -0
- package/dist/discover-DozUKs8f.cjs.map +1 -0
- package/dist/discover-xtT7fgWg.d.mts +125 -0
- package/dist/discover-xtT7fgWg.d.mts.map +1 -0
- package/dist/dns-BIYf20p7.cjs +1057 -0
- package/dist/dns-BIYf20p7.cjs.map +1 -0
- package/dist/dns-agf2VCdV.mjs +926 -0
- package/dist/dns-agf2VCdV.mjs.map +1 -0
- package/dist/dokploy-2p4PK6ej.mjs +38 -0
- package/dist/dokploy-2p4PK6ej.mjs.map +1 -0
- package/dist/dokploy-BANMTroy.cjs +38 -0
- package/dist/dokploy-BANMTroy.cjs.map +1 -0
- package/dist/drivers-BjGDYfnC.mjs +195 -0
- package/dist/drivers-BjGDYfnC.mjs.map +1 -0
- package/dist/drivers-DbjiYpW6.cjs +224 -0
- package/dist/drivers-DbjiYpW6.cjs.map +1 -0
- package/dist/eas-CNi16SoO.cjs +337 -0
- package/dist/eas-CNi16SoO.cjs.map +1 -0
- package/dist/eas-iT2JDHCh.mjs +334 -0
- package/dist/eas-iT2JDHCh.mjs.map +1 -0
- package/dist/{emit-CIlvnDvV.mjs → emit-C1e8qD08.mjs} +13 -26
- package/dist/emit-C1e8qD08.mjs.map +1 -0
- package/dist/{emit-CUD1BWls.cjs → emit-Dmc1nvdS.cjs} +31 -57
- package/dist/emit-Dmc1nvdS.cjs.map +1 -0
- package/dist/encryption-DkXVbyZc.mjs +89 -0
- package/dist/encryption-DkXVbyZc.mjs.map +1 -0
- package/dist/encryption-d2lLtkYx.cjs +90 -0
- package/dist/encryption-d2lLtkYx.cjs.map +1 -0
- package/dist/engine-CatKW6E-.mjs +3089 -0
- package/dist/engine-CatKW6E-.mjs.map +1 -0
- package/dist/engine-DlEHexED.cjs +3202 -0
- package/dist/engine-DlEHexED.cjs.map +1 -0
- package/dist/errors-D8Gwp5kU.mjs +23 -0
- package/dist/errors-D8Gwp5kU.mjs.map +1 -0
- package/dist/errors-DMC4Umsb.cjs +34 -0
- package/dist/errors-DMC4Umsb.cjs.map +1 -0
- package/dist/github-BAeaC1Pl.cjs +833 -0
- package/dist/github-BAeaC1Pl.cjs.map +1 -0
- package/dist/github-DErsqKGN.mjs +832 -0
- package/dist/github-DErsqKGN.mjs.map +1 -0
- package/dist/googlePlay-BVcfFWRO.cjs +931 -0
- package/dist/googlePlay-BVcfFWRO.cjs.map +1 -0
- package/dist/googlePlay-BXJ5ZjMK.mjs +931 -0
- package/dist/googlePlay-BXJ5ZjMK.mjs.map +1 -0
- package/dist/identity-CBXr4wNi.mjs +129 -0
- package/dist/identity-CBXr4wNi.mjs.map +1 -0
- package/dist/identity-CbLfgQNT.cjs +182 -0
- package/dist/identity-CbLfgQNT.cjs.map +1 -0
- package/dist/{index-BzJ7QJ9c.d.mts → index-8OfpOtSI.d.cts} +39 -20
- package/dist/index-8OfpOtSI.d.cts.map +1 -0
- package/dist/{index-CfMjtdVC.d.cts → index-Dpji6Bxq.d.mts} +39 -20
- package/dist/index-Dpji6Bxq.d.mts.map +1 -0
- package/dist/index.cjs +14291 -11898
- 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 +13938 -11560
- package/dist/index.mjs.map +1 -1
- package/dist/local-BwC2v2Y8.mjs +224 -0
- package/dist/local-BwC2v2Y8.mjs.map +1 -0
- package/dist/local-DNhnkugT.cjs +241 -0
- package/dist/local-DNhnkugT.cjs.map +1 -0
- package/dist/migrate-Bb1MmGHl.mjs +181 -0
- package/dist/migrate-Bb1MmGHl.mjs.map +1 -0
- package/dist/migrate-D79GI0XM.cjs +271 -0
- package/dist/migrate-D79GI0XM.cjs.map +1 -0
- package/dist/migrate-L4a273dY.cjs +181 -0
- package/dist/migrate-L4a273dY.cjs.map +1 -0
- package/dist/migrate-s4QizIoM.mjs +266 -0
- package/dist/migrate-s4QizIoM.mjs.map +1 -0
- package/dist/openapi.cjs +214 -10
- package/dist/openapi.cjs.map +1 -0
- package/dist/openapi.d.cts +38 -17
- package/dist/openapi.d.cts.map +1 -1
- package/dist/openapi.d.mts +38 -17
- package/dist/openapi.d.mts.map +1 -1
- package/dist/openapi.mjs +208 -5
- package/dist/openapi.mjs.map +1 -0
- package/dist/projectBucket-BBbQ1lHF.cjs +263 -0
- package/dist/projectBucket-BBbQ1lHF.cjs.map +1 -0
- package/dist/projectBucket-C_DN4WzF.mjs +222 -0
- package/dist/projectBucket-C_DN4WzF.mjs.map +1 -0
- package/dist/provider-BqpjG9QM.cjs +57 -0
- package/dist/provider-BqpjG9QM.cjs.map +1 -0
- package/dist/provider-CEOKk8NS.mjs +46 -0
- package/dist/provider-CEOKk8NS.mjs.map +1 -0
- package/dist/publish-CcTlmJT6.mjs +123 -0
- package/dist/publish-CcTlmJT6.mjs.map +1 -0
- package/dist/publish-ydscCYsW.cjs +124 -0
- package/dist/publish-ydscCYsW.cjs.map +1 -0
- package/dist/ready-BFFP838V.cjs +404 -0
- package/dist/ready-BFFP838V.cjs.map +1 -0
- package/dist/ready-BOfogqzr.mjs +375 -0
- package/dist/ready-BOfogqzr.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-BI0tWkwz.mjs → reconcile--I7V4Klj.mjs} +4 -7
- package/dist/reconcile--I7V4Klj.mjs.map +1 -0
- package/dist/{reconcile-Bhw5fmKu.cjs → reconcile-CuCQBa5-.cjs} +5 -8
- package/dist/reconcile-CuCQBa5-.cjs.map +1 -0
- package/dist/resolve-BLp2trep.mjs +263 -0
- package/dist/resolve-BLp2trep.mjs.map +1 -0
- package/dist/resolve-BgL-brF9.cjs +316 -0
- package/dist/resolve-BgL-brF9.cjs.map +1 -0
- package/dist/rollback-2cyLXEH1.cjs +165 -0
- package/dist/rollback-2cyLXEH1.cjs.map +1 -0
- package/dist/rollback-DwqZ8zzj.mjs +136 -0
- package/dist/rollback-DwqZ8zzj.mjs.map +1 -0
- package/dist/rolldown-runtime-BhDjJH2R.mjs +15 -0
- package/dist/{chunk-CUT6urMc.cjs → rolldown-runtime-C0BPl7ul.cjs} +23 -10
- package/dist/run-BJLIerMM.mjs +133 -0
- package/dist/run-BJLIerMM.mjs.map +1 -0
- package/dist/run-fVZ-rLnc.cjs +174 -0
- package/dist/run-fVZ-rLnc.cjs.map +1 -0
- package/dist/runtime-ACdfK59n.d.cts +82 -0
- package/dist/runtime-ACdfK59n.d.cts.map +1 -0
- package/dist/runtime-YyQuekxi.d.mts +82 -0
- package/dist/runtime-YyQuekxi.d.mts.map +1 -0
- package/dist/s3-CrPoA89T.cjs +215 -0
- package/dist/s3-CrPoA89T.cjs.map +1 -0
- package/dist/s3-DQXZdmCM.mjs +215 -0
- package/dist/s3-DQXZdmCM.mjs.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 +50 -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 +51 -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-C4g1eG31.d.mts +104 -0
- package/dist/sandbox-C4g1eG31.d.mts.map +1 -0
- package/dist/sandbox-CHv3OYXg.cjs +165 -0
- package/dist/sandbox-CHv3OYXg.cjs.map +1 -0
- package/dist/sandbox-CbVB7uOD.mjs +118 -0
- package/dist/sandbox-CbVB7uOD.mjs.map +1 -0
- package/dist/sandbox-CdKgQ3XU.d.cts +104 -0
- package/dist/sandbox-CdKgQ3XU.d.cts.map +1 -0
- package/dist/{workspace-DPV7FtAv.cjs → schemas-BCwKKA42.mjs} +958 -1816
- package/dist/schemas-BCwKKA42.mjs.map +1 -0
- package/dist/{workspace-CtnZnWsa.mjs → schemas-BQ_v0lEX.cjs} +1048 -1626
- package/dist/schemas-BQ_v0lEX.cjs.map +1 -0
- package/dist/secretsManager-CQORubp9.mjs +97 -0
- package/dist/secretsManager-CQORubp9.mjs.map +1 -0
- package/dist/secretsManager-RAGcEDX1.cjs +108 -0
- package/dist/secretsManager-RAGcEDX1.cjs.map +1 -0
- package/dist/seeds-C2w4UqVP.cjs +9347 -0
- package/dist/seeds-C2w4UqVP.cjs.map +1 -0
- package/dist/seeds-Cwtv-U9-.mjs +8350 -0
- package/dist/seeds-Cwtv-U9-.mjs.map +1 -0
- package/dist/serverSetup-CKs4K9gD.cjs +630 -0
- package/dist/serverSetup-CKs4K9gD.cjs.map +1 -0
- package/dist/serverSetup-P68H5Lal.mjs +630 -0
- package/dist/serverSetup-P68H5Lal.mjs.map +1 -0
- package/dist/sniffer-8C4q6Szd.mjs +1825 -0
- package/dist/sniffer-8C4q6Szd.mjs.map +1 -0
- package/dist/sniffer-CF7wNxMo.cjs +1998 -0
- package/dist/sniffer-CF7wNxMo.cjs.map +1 -0
- package/dist/sst-B-Uz4cvm.cjs +464 -0
- package/dist/sst-B-Uz4cvm.cjs.map +1 -0
- package/dist/sst-C1pxbFEd.mjs +441 -0
- package/dist/sst-C1pxbFEd.mjs.map +1 -0
- package/dist/stage-BvM_u2z4.mjs +302 -0
- package/dist/stage-BvM_u2z4.mjs.map +1 -0
- package/dist/stage-DEQJGW3r.cjs +313 -0
- package/dist/stage-DEQJGW3r.cjs.map +1 -0
- package/dist/stages-Cb9rqRbN.mjs +190 -0
- package/dist/stages-Cb9rqRbN.mjs.map +1 -0
- package/dist/stages-D4SnG5yl.cjs +190 -0
- package/dist/stages-D4SnG5yl.cjs.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-CW_dWeTe.mjs +85 -0
- package/dist/terminal-CW_dWeTe.mjs.map +1 -0
- package/dist/terminal-DJlqcW_U.cjs +85 -0
- package/dist/terminal-DJlqcW_U.cjs.map +1 -0
- package/dist/types-B6UXlEkf.d.cts +7650 -0
- package/dist/types-B6UXlEkf.d.cts.map +1 -0
- package/dist/types-HqVPddRK.d.mts +7649 -0
- package/dist/types-HqVPddRK.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-BwqQnool.cjs +2806 -0
- package/dist/workspace-BwqQnool.cjs.map +1 -0
- package/dist/workspace-D1QbNd55.mjs +2394 -0
- package/dist/workspace-D1QbNd55.mjs.map +1 -0
- package/package.json +54 -22
- package/dist/CachedStateProvider-BEBl2wQT.cjs +0 -60
- package/dist/CachedStateProvider-BEBl2wQT.cjs.map +0 -1
- package/dist/CachedStateProvider-C-8wtlto.mjs +0 -3
- package/dist/CachedStateProvider-Co4n5Wvs.mjs +0 -54
- package/dist/CachedStateProvider-Co4n5Wvs.mjs.map +0 -1
- package/dist/CachedStateProvider-DqhGpzsm.cjs +0 -3
- package/dist/HostingerProvider-BOvsgATO.cjs.map +0 -1
- package/dist/HostingerProvider-BcyFRbqV.mjs.map +0 -1
- package/dist/LocalStateProvider-Bq-0C0sg.mjs +0 -42
- package/dist/LocalStateProvider-Bq-0C0sg.mjs.map +0 -1
- package/dist/LocalStateProvider-yjXZXZDM.cjs +0 -43
- package/dist/LocalStateProvider-yjXZXZDM.cjs.map +0 -1
- package/dist/Route53Provider-C0xmer8U.mjs.map +0 -1
- package/dist/Route53Provider-CH9XH7bz.cjs.map +0 -1
- package/dist/SSMStateProvider-CXtNgPfy.cjs +0 -66
- package/dist/SSMStateProvider-CXtNgPfy.cjs.map +0 -1
- package/dist/SSMStateProvider-DOZifZfE.mjs +0 -66
- package/dist/SSMStateProvider-DOZifZfE.mjs.map +0 -1
- package/dist/backup-provisioner-DXXg6FIL.mjs.map +0 -1
- package/dist/backup-provisioner-cE2PuCTb.cjs.map +0 -1
- package/dist/bundler-BMekgx7a.cjs.map +0 -1
- package/dist/bundler-Bux2iF-v.mjs.map +0 -1
- package/dist/chunk-Duj1WY3L.mjs +0 -7
- package/dist/config-DTsVjKZD.mjs +0 -402
- package/dist/config-DTsVjKZD.mjs.map +0 -1
- package/dist/config-RYR9j6EF.cjs +0 -462
- package/dist/config-RYR9j6EF.cjs.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-DQRvPz2S.cjs +0 -2769
- package/dist/credentials-DQRvPz2S.cjs.map +0 -1
- package/dist/credentials-jMJC63qs.mjs +0 -2625
- package/dist/credentials-jMJC63qs.mjs.map +0 -1
- package/dist/discover-BYHQwQqu.mjs +0 -242
- package/dist/discover-BYHQwQqu.mjs.map +0 -1
- package/dist/discover-DmW9Bscc.cjs +0 -290
- package/dist/discover-DmW9Bscc.cjs.map +0 -1
- package/dist/dokploy-api-5cK2InBm.cjs +0 -3
- package/dist/dokploy-api-D2Ny4Jrc.mjs +0 -3
- package/dist/dokploy-api-pdaBryoY.mjs +0 -706
- package/dist/dokploy-api-pdaBryoY.mjs.map +0 -1
- package/dist/dokploy-api-zqKe9lHH.cjs +0 -718
- package/dist/dokploy-api-zqKe9lHH.cjs.map +0 -1
- package/dist/emit-CIlvnDvV.mjs.map +0 -1
- package/dist/emit-CUD1BWls.cjs.map +0 -1
- package/dist/encryption-D2pL3tHH.cjs +0 -54
- package/dist/encryption-D2pL3tHH.cjs.map +0 -1
- package/dist/encryption-YDsXXi6w.mjs +0 -3
- package/dist/encryption-_4zwjOAR.mjs +0 -42
- package/dist/encryption-_4zwjOAR.mjs.map +0 -1
- package/dist/encryption-sQ2Oxx0d.cjs +0 -4
- package/dist/fullstack-secrets-B6KBHf-g.mjs +0 -87
- package/dist/fullstack-secrets-B6KBHf-g.mjs.map +0 -1
- package/dist/fullstack-secrets-DL8v0y4D.cjs +0 -99
- package/dist/fullstack-secrets-DL8v0y4D.cjs.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/github-BThvbf2W.cjs +0 -197
- package/dist/github-BThvbf2W.cjs.map +0 -1
- package/dist/github-DpMZurEK.mjs +0 -196
- package/dist/github-DpMZurEK.mjs.map +0 -1
- package/dist/index-BzJ7QJ9c.d.mts.map +0 -1
- package/dist/index-CfMjtdVC.d.cts.map +0 -1
- package/dist/keystore-C9rint2E.cjs +0 -101
- package/dist/keystore-C9rint2E.cjs.map +0 -1
- package/dist/keystore-TxeYM7an.mjs +0 -83
- package/dist/keystore-TxeYM7an.mjs.map +0 -1
- package/dist/openapi-Bs3-5q_r.mjs +0 -2021
- package/dist/openapi-Bs3-5q_r.mjs.map +0 -1
- package/dist/openapi-DWLTsYLL.cjs +0 -2075
- package/dist/openapi-DWLTsYLL.cjs.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-BI0tWkwz.mjs.map +0 -1
- package/dist/reconcile-Bhw5fmKu.cjs.map +0 -1
- package/dist/setup-BmLoNrDX.mjs +0 -12
- package/dist/setup-BrR1_xGy.cjs +0 -12
- package/dist/setup-BsNR2LBG.cjs +0 -467
- package/dist/setup-BsNR2LBG.cjs.map +0 -1
- package/dist/setup-D5-JMknk.mjs +0 -437
- package/dist/setup-D5-JMknk.mjs.map +0 -1
- package/dist/storage-B020InPW.mjs +0 -222
- package/dist/storage-B020InPW.mjs.map +0 -1
- package/dist/storage-CSNplaJn.cjs +0 -282
- package/dist/storage-CSNplaJn.cjs.map +0 -1
- package/dist/storage-DGBzMLZa.mjs +0 -4
- package/dist/storage-DlVol_oj.cjs +0 -8
- package/dist/sync-0ILCb-1S.cjs +0 -93
- package/dist/sync-0ILCb-1S.cjs.map +0 -1
- package/dist/sync-CzHXiR9c.mjs +0 -76
- package/dist/sync-CzHXiR9c.mjs.map +0 -1
- package/dist/sync-DYENfOt4.cjs +0 -4
- package/dist/sync-HAau6h_R.mjs +0 -3
- package/dist/types-DVIx8I1N.d.mts +0 -1950
- package/dist/types-DVIx8I1N.d.mts.map +0 -1
- package/dist/types-PMP_qXRY.d.cts +0 -1950
- package/dist/types-PMP_qXRY.d.cts.map +0 -1
- package/dist/workspace-Cji6lE8E.cjs +0 -10
- package/dist/workspace-CkJAtRIX.mjs +0 -9
- package/dist/workspace-CtnZnWsa.mjs.map +0 -1
- package/dist/workspace-DPV7FtAv.cjs.map +0 -1
package/dist/openapi.mjs
CHANGED
|
@@ -1,7 +1,210 @@
|
|
|
1
1
|
#!/usr/bin/env -S npx tsx
|
|
2
|
-
import "./
|
|
3
|
-
import "./
|
|
4
|
-
import "./
|
|
5
|
-
import {
|
|
2
|
+
import { o as appConstructGlobs } from "./workspace-D1QbNd55.mjs";
|
|
3
|
+
import { t as appKey } from "./derive-B4vDpzgP.mjs";
|
|
4
|
+
import { c as parseTelemetryFlag, t as EndpointGenerator, u as telemetryArgs } from "./EndpointGenerator-CP_z_E3e.mjs";
|
|
5
|
+
import { loadWorkspaceConfig } from "./config.mjs";
|
|
6
|
+
import { t as OpenApiTsGenerator } from "./OpenApiTsGenerator-CsrukCE-.mjs";
|
|
7
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
8
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
9
|
+
import { spawn } from "node:child_process";
|
|
10
|
+
import { kebabCase } from "@geekmidas/manifest";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
//#region src/openapi.ts
|
|
13
|
+
/**
|
|
14
|
+
* Where a surface's generated client lands, under the workspace root.
|
|
15
|
+
*
|
|
16
|
+
* Named from the surface, so `new RestApi('Webhooks')` writes
|
|
17
|
+
* `.gkm/client/webhooks.ts` — the same kebab form that gives it its container
|
|
18
|
+
* name and its `WEBHOOKS_URL` — and a site imports it as
|
|
19
|
+
* `@<name>/client/webhooks`, through the alias its tsconfig maps.
|
|
20
|
+
*/
|
|
21
|
+
function openApiPathFor(surfaceId) {
|
|
22
|
+
return `./.gkm/client/${kebabCase(surfaceId)}.ts`;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Resolve OpenAPI config from GkmConfig
|
|
26
|
+
*/
|
|
27
|
+
function resolveOpenApiConfig(config) {
|
|
28
|
+
if (config.openapi === false) return { enabled: false };
|
|
29
|
+
if (config.openapi === true || config.openapi === void 0) return {
|
|
30
|
+
enabled: true,
|
|
31
|
+
title: "API Documentation",
|
|
32
|
+
version: "1.0.0",
|
|
33
|
+
description: "Auto-generated API documentation from endpoints"
|
|
34
|
+
};
|
|
35
|
+
return {
|
|
36
|
+
enabled: config.openapi.enabled !== false,
|
|
37
|
+
title: config.openapi.title || "API Documentation",
|
|
38
|
+
version: config.openapi.version || "1.0.0",
|
|
39
|
+
description: config.openapi.description || "Auto-generated API documentation from endpoints"
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Load endpoints from a glob, then generate.
|
|
44
|
+
*
|
|
45
|
+
* For the callers that have no endpoints in hand — `gkm openapi`, and `gkm dev`
|
|
46
|
+
* regenerating on reload. The glob is the app's derived one — the conventional
|
|
47
|
+
* directories under it — not a `routes` field anybody wrote.
|
|
48
|
+
*/
|
|
49
|
+
async function generateOpenApiFrom(routes, options = {}) {
|
|
50
|
+
return generateOpenApi((await new EndpointGenerator().load(routes, void 0, options.bustCache)).map(({ construct }) => construct), options);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Generate an OpenAPI spec for each surface these endpoints name.
|
|
54
|
+
*
|
|
55
|
+
* Takes the endpoints, not a glob to find them with. The build has already
|
|
56
|
+
* loaded every one of them to generate handlers, so globbing again was a second
|
|
57
|
+
* discovery pass over the same files — and the reason this function needed a
|
|
58
|
+
* `routes` to be told where to look, which is how a retired config field stayed
|
|
59
|
+
* load-bearing.
|
|
60
|
+
*
|
|
61
|
+
* @returns What was written, or null if disabled or there were none
|
|
62
|
+
*/
|
|
63
|
+
async function generateOpenApi(endpoints, options = {}) {
|
|
64
|
+
const root = options.root ?? process.cwd();
|
|
65
|
+
const logger = options.silent ? { log: () => {} } : console;
|
|
66
|
+
const openApiConfig = resolveOpenApiConfig({ openapi: options.openapi });
|
|
67
|
+
if (!openApiConfig.enabled) return null;
|
|
68
|
+
if (endpoints.length === 0) {
|
|
69
|
+
logger.log("No valid endpoints found for OpenAPI generation");
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
const bySurface = /* @__PURE__ */ new Map();
|
|
73
|
+
for (const endpoint of endpoints) {
|
|
74
|
+
const owner = endpoint.owner ?? endpoint.surface?.id;
|
|
75
|
+
if (!owner) continue;
|
|
76
|
+
const existing = bySurface.get(owner);
|
|
77
|
+
if (existing) existing.push(endpoint);
|
|
78
|
+
else bySurface.set(owner, [endpoint]);
|
|
79
|
+
}
|
|
80
|
+
if (bySurface.size === 0) {
|
|
81
|
+
logger.log("No endpoints named a surface, so no OpenAPI was generated");
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
const tsGenerator = new OpenApiTsGenerator();
|
|
85
|
+
const written = [];
|
|
86
|
+
for (const [surfaceId, surfaceEndpoints] of bySurface) {
|
|
87
|
+
const relative$1 = openApiPathFor(surfaceId);
|
|
88
|
+
const outputPath = join(root, relative$1);
|
|
89
|
+
await mkdir(dirname(outputPath), { recursive: true });
|
|
90
|
+
const tsContent = await tsGenerator.generate(surfaceEndpoints, {
|
|
91
|
+
title: openApiConfig.title ?? surfaceId,
|
|
92
|
+
version: openApiConfig.version,
|
|
93
|
+
description: openApiConfig.description,
|
|
94
|
+
...options.telemetry !== void 0 ? { telemetry: options.telemetry } : {}
|
|
95
|
+
});
|
|
96
|
+
await writeFile(outputPath, tsContent);
|
|
97
|
+
written.push(relative$1);
|
|
98
|
+
logger.log(`📄 OpenAPI client generated: ${relative(process.cwd(), outputPath)} (${surfaceEndpoints.length} endpoints)`);
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
outputPath: join(root, written[0]),
|
|
102
|
+
outputPaths: written.map((r) => join(root, r)),
|
|
103
|
+
endpointCount: endpoints.length
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
async function openapiCommand(options = {}) {
|
|
107
|
+
const logger = console;
|
|
108
|
+
try {
|
|
109
|
+
const loadedConfig = await loadWorkspaceConfig(options.cwd);
|
|
110
|
+
if (loadedConfig.type === "single") {
|
|
111
|
+
const config = loadedConfig.raw;
|
|
112
|
+
if (!config.openapi) config.openapi = { enabled: true };
|
|
113
|
+
const result = await generateOpenApiFrom(config.constructs, {
|
|
114
|
+
openapi: config.openapi,
|
|
115
|
+
root: loadedConfig.workspace.root,
|
|
116
|
+
...options.telemetry !== void 0 ? { telemetry: options.telemetry } : {}
|
|
117
|
+
});
|
|
118
|
+
if (result) logger.log(`Found ${result.endpointCount} endpoints`);
|
|
119
|
+
} else {
|
|
120
|
+
const { workspace } = loadedConfig;
|
|
121
|
+
const workspaceRoot = workspace.root;
|
|
122
|
+
const backendApps = Object.entries(workspace.apps).filter(([_, app]) => app.type === "backend" && app.openapi !== false && (typeof app.openapi !== "object" || app.openapi.enabled !== false));
|
|
123
|
+
if (backendApps.length === 0) {
|
|
124
|
+
logger.log("No backend apps with OpenAPI enabled found");
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (options.app) {
|
|
128
|
+
const entry = backendApps.find(([name]) => name === options.app);
|
|
129
|
+
if (!entry) throw new Error(`App "${options.app}" not found or has OpenAPI disabled`);
|
|
130
|
+
const [appName, app] = entry;
|
|
131
|
+
const result = await generateOpenApiForApp(workspace, appName, app, true, options.telemetry);
|
|
132
|
+
if (result) logger.log(`📄 [${appName}] Generated OpenAPI (${result.endpointCount} endpoints)`);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
for (const [appName, app] of backendApps) {
|
|
136
|
+
const appPath = join(workspaceRoot, app.path);
|
|
137
|
+
if (resolve(appPath) === resolve(workspaceRoot)) {
|
|
138
|
+
const result = await generateOpenApiForApp(workspace, appName, app, false, options.telemetry);
|
|
139
|
+
if (result) logger.log(`Found ${result.endpointCount} endpoints`);
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
await runOpenApiInSubprocess(appPath, appName, options.telemetry);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
} catch (error) {
|
|
146
|
+
throw new Error(`OpenAPI generation failed: ${error.message}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Generate OpenAPI for a single named app within a workspace.
|
|
151
|
+
* Runs in-process. The caller is responsible for ensuring `process.cwd()`
|
|
152
|
+
* is the app's directory so tsx loads the app's tsconfig path aliases.
|
|
153
|
+
*
|
|
154
|
+
* The endpoints are found the way the app's build finds them — through the
|
|
155
|
+
* workspace's constructs globs — never by importing every `.ts` under the app,
|
|
156
|
+
* which loaded its tests and gkm's own generated files with them.
|
|
157
|
+
*/
|
|
158
|
+
async function generateOpenApiForApp(workspace, appName, app, silent = true, telemetry) {
|
|
159
|
+
if (app.type !== "backend") return null;
|
|
160
|
+
return generateOpenApi((await new EndpointGenerator().load(appConstructGlobs(workspace, appName))).map(({ construct }) => construct).filter((endpoint) => {
|
|
161
|
+
const owner = endpoint.owner ?? endpoint.surface?.id;
|
|
162
|
+
if (!owner) return false;
|
|
163
|
+
const key = appKey(owner);
|
|
164
|
+
return key === appName || !workspace.apps[key];
|
|
165
|
+
}), {
|
|
166
|
+
openapi: app.openapi ?? { enabled: true },
|
|
167
|
+
silent,
|
|
168
|
+
root: workspace.root,
|
|
169
|
+
...telemetry !== void 0 ? { telemetry } : {}
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Resolve the gkm bin path. Tests can override via `GKM_BIN_PATH` env var
|
|
174
|
+
* to avoid depending on the built dist.
|
|
175
|
+
*/
|
|
176
|
+
function resolveGkmBinPath() {
|
|
177
|
+
if (process.env.GKM_BIN_PATH) return process.env.GKM_BIN_PATH;
|
|
178
|
+
return fileURLToPath(new URL("../bin/gkm.mjs", import.meta.url));
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Spawn a subprocess that runs `gkm openapi --app <name>` with `cwd` set to
|
|
182
|
+
* the app's directory. Inherits `NODE_OPTIONS` (which already contains
|
|
183
|
+
* `--import tsx`), so the child gets a fresh tsx instance whose tsconfig
|
|
184
|
+
* discovery picks up the app's tsconfig path aliases.
|
|
185
|
+
*/
|
|
186
|
+
async function runOpenApiInSubprocess(appCwd, appName, telemetry) {
|
|
187
|
+
const binPath = resolveGkmBinPath();
|
|
188
|
+
await new Promise((resolve, reject) => {
|
|
189
|
+
const child = spawn(process.execPath, [
|
|
190
|
+
binPath,
|
|
191
|
+
"openapi",
|
|
192
|
+
"--app",
|
|
193
|
+
appName,
|
|
194
|
+
...telemetryArgs(telemetry)
|
|
195
|
+
], {
|
|
196
|
+
cwd: appCwd,
|
|
197
|
+
stdio: "inherit",
|
|
198
|
+
env: process.env
|
|
199
|
+
});
|
|
200
|
+
child.on("error", reject);
|
|
201
|
+
child.on("close", (code) => {
|
|
202
|
+
if (code === 0) resolve();
|
|
203
|
+
else reject(/* @__PURE__ */ new Error(`OpenAPI generation for app "${appName}" exited with code ${code}`));
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
//#endregion
|
|
208
|
+
export { generateOpenApi, generateOpenApiFrom, openApiPathFor, openapiCommand, parseTelemetryFlag, resolveOpenApiConfig };
|
|
6
209
|
|
|
7
|
-
|
|
210
|
+
//# sourceMappingURL=openapi.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openapi.mjs","names":["relative","relativePath"],"sources":["../src/openapi.ts"],"sourcesContent":["#!/usr/bin/env -S npx tsx\n\nimport { spawn } from 'node:child_process';\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, join, relative as relativePath, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport type { Endpoint } from '@geekmidas/constructs/endpoints';\nimport { kebabCase } from '@geekmidas/manifest';\nimport { loadWorkspaceConfig } from './config.js';\nimport type { ClientTelemetryDefault } from './generators/clientTelemetry.js';\nimport { telemetryArgs } from './generators/clientTelemetry.js';\nimport { EndpointGenerator } from './generators/EndpointGenerator.js';\nimport { OpenApiTsGenerator } from './generators/OpenApiTsGenerator.js';\n\nexport { parseTelemetryFlag } from './generators/clientTelemetry.js';\n\nimport type { GkmConfig, OpenApiConfig, Routes } from './types.js';\nimport { appKey } from './workspace/derive.js';\nimport { appConstructGlobs } from './workspace/index.js';\nimport type {\n\tNormalizedAppConfig,\n\tNormalizedWorkspace,\n} from './workspace/types.js';\n\ninterface OpenAPIOptions {\n\tcwd?: string;\n\t/**\n\t * Workspace mode: generate for a single named backend app. When set,\n\t * generation runs in-process against the current CWD. When unset,\n\t * workspace mode spawns one subprocess per backend app so each gets a\n\t * fresh tsx that loads the app's own tsconfig (path aliases included).\n\t */\n\tapp?: string;\n\t/**\n\t * Trace propagation in the clients written, off when absent — see\n\t * {@link ClientTelemetryDefault}. `gkm openapi --telemetry [rate]`.\n\t */\n\ttelemetry?: ClientTelemetryDefault;\n}\n\n/**\n * Where a surface's generated client lands, under the workspace root.\n *\n * Named from the surface, so `new RestApi('Webhooks')` writes\n * `.gkm/client/webhooks.ts` — the same kebab form that gives it its container\n * name and its `WEBHOOKS_URL` — and a site imports it as\n * `@<name>/client/webhooks`, through the alias its tsconfig maps.\n */\nexport function openApiPathFor(surfaceId: string): string {\n\treturn `./.gkm/client/${kebabCase(surfaceId)}.ts`;\n}\n\n/**\n * Resolve OpenAPI config from GkmConfig\n */\nexport function resolveOpenApiConfig(config: {\n\topenapi?: boolean | OpenApiConfig;\n}): OpenApiConfig & { enabled: boolean } {\n\tif (config.openapi === false) {\n\t\treturn { enabled: false };\n\t}\n\n\tif (config.openapi === true || config.openapi === undefined) {\n\t\t// Enable by default when not explicitly set (undefined) or explicitly true\n\t\treturn {\n\t\t\tenabled: true,\n\t\t\ttitle: 'API Documentation',\n\t\t\tversion: '1.0.0',\n\t\t\tdescription: 'Auto-generated API documentation from endpoints',\n\t\t};\n\t}\n\n\treturn {\n\t\tenabled: config.openapi.enabled !== false,\n\t\ttitle: config.openapi.title || 'API Documentation',\n\t\tversion: config.openapi.version || '1.0.0',\n\t\tdescription:\n\t\t\tconfig.openapi.description ||\n\t\t\t'Auto-generated API documentation from endpoints',\n\t};\n}\n\n/** What a generation run wrote. */\nexport interface OpenApiResult {\n\t/** The first spec written — kept so existing callers still compile. */\n\toutputPath: string;\n\t/** Every spec written, one per surface. */\n\toutputPaths: string[];\n\t/** Endpoints across all of them. */\n\tendpointCount: number;\n}\n\n/**\n * Load endpoints from a glob, then generate.\n *\n * For the callers that have no endpoints in hand — `gkm openapi`, and `gkm dev`\n * regenerating on reload. The glob is the app's derived one — the conventional\n * directories under it — not a `routes` field anybody wrote.\n */\nexport async function generateOpenApiFrom(\n\troutes: Routes,\n\toptions: {\n\t\topenapi?: boolean | OpenApiConfig;\n\t\tsilent?: boolean;\n\t\tbustCache?: boolean;\n\t\troot?: string;\n\t\ttelemetry?: ClientTelemetryDefault;\n\t} = {},\n): Promise<OpenApiResult | null> {\n\tconst loaded = await new EndpointGenerator().load(\n\t\troutes,\n\t\tundefined,\n\t\toptions.bustCache,\n\t);\n\n\treturn generateOpenApi(\n\t\tloaded.map(({ construct }) => construct),\n\t\toptions,\n\t);\n}\n\n/**\n * Generate an OpenAPI spec for each surface these endpoints name.\n *\n * Takes the endpoints, not a glob to find them with. The build has already\n * loaded every one of them to generate handlers, so globbing again was a second\n * discovery pass over the same files — and the reason this function needed a\n * `routes` to be told where to look, which is how a retired config field stayed\n * load-bearing.\n *\n * @returns What was written, or null if disabled or there were none\n */\nexport async function generateOpenApi(\n\tendpoints: readonly Endpoint<any, any, any, any, any>[],\n\toptions: {\n\t\topenapi?: boolean | OpenApiConfig;\n\t\tsilent?: boolean;\n\t\t/**\n\t\t * The workspace root, whose `.gkm/client/` holds every surface's client.\n\t\t * The client is the application's, like its manifest — a site reaches it\n\t\t * as `@<name>/client/<surface>` — so it is written there whichever\n\t\t * directory the build happened to run in.\n\t\t */\n\t\troot?: string;\n\t\t/**\n\t\t * Whether the clients written propagate trace context by default. Off\n\t\t * when absent: the integration point for a site's edge to a\n\t\t * `Telemetry` construct, which decides it for the clients it imports.\n\t\t */\n\t\ttelemetry?: ClientTelemetryDefault;\n\t} = {},\n): Promise<OpenApiResult | null> {\n\tconst root = options.root ?? process.cwd();\n\tconst logger = options.silent ? { log: () => {} } : console;\n\tconst openApiConfig = resolveOpenApiConfig({ openapi: options.openapi });\n\n\tif (!openApiConfig.enabled) {\n\t\treturn null;\n\t}\n\n\tif (endpoints.length === 0) {\n\t\tlogger.log('No valid endpoints found for OpenAPI generation');\n\t\treturn null;\n\t}\n\n\t// One spec per surface, not one per glob.\n\t//\n\t// The glob is a directory, and a directory is not a surface: two `RestApi`s\n\t// whose endpoints live side by side produced a single spec describing both,\n\t// under a filename that named neither. Every endpoint carries the surface\n\t// that serves it, so that is what the spec is cut along — `Api` becomes\n\t// `api.ts`, `Webhooks` becomes `webhooks.ts`, by the same kebab rule that\n\t// gives the surface its container name and its environment keys.\n\tconst bySurface = new Map<string, Endpoint<any, any, any, any, any>[]>();\n\tfor (const endpoint of endpoints) {\n\t\tconst owner = endpoint.owner ?? endpoint.surface?.id;\n\t\tif (!owner) continue;\n\t\tconst existing = bySurface.get(owner);\n\t\tif (existing) existing.push(endpoint);\n\t\telse bySurface.set(owner, [endpoint]);\n\t}\n\n\tif (bySurface.size === 0) {\n\t\tlogger.log('No endpoints named a surface, so no OpenAPI was generated');\n\t\treturn null;\n\t}\n\n\tconst tsGenerator = new OpenApiTsGenerator();\n\tconst written: string[] = [];\n\n\tfor (const [surfaceId, surfaceEndpoints] of bySurface) {\n\t\tconst relative = openApiPathFor(surfaceId);\n\t\tconst outputPath = join(root, relative);\n\n\t\tawait mkdir(dirname(outputPath), { recursive: true });\n\n\t\tconst tsContent = await tsGenerator.generate(surfaceEndpoints, {\n\t\t\t// The surface's own name, so a spec says which API it describes\n\t\t\t// rather than repeating one title across all of them.\n\t\t\ttitle: openApiConfig.title ?? surfaceId,\n\t\t\tversion: openApiConfig.version!,\n\t\t\tdescription: openApiConfig.description!,\n\t\t\t...(options.telemetry !== undefined\n\t\t\t\t? { telemetry: options.telemetry }\n\t\t\t\t: {}),\n\t\t});\n\n\t\tawait writeFile(outputPath, tsContent);\n\t\twritten.push(relative);\n\t\tlogger.log(\n\t\t\t`📄 OpenAPI client generated: ${relativePath(process.cwd(), outputPath)} (${surfaceEndpoints.length} endpoints)`,\n\t\t);\n\t}\n\n\treturn {\n\t\t// The first, for a caller that wants one path. `outputPaths` has them all.\n\t\toutputPath: join(root, written[0]!),\n\t\toutputPaths: written.map((r) => join(root, r)),\n\t\tendpointCount: endpoints.length,\n\t};\n}\n\nexport async function openapiCommand(\n\toptions: OpenAPIOptions = {},\n): Promise<void> {\n\tconst logger = console;\n\n\ttry {\n\t\tconst loadedConfig = await loadWorkspaceConfig(options.cwd);\n\n\t\tif (loadedConfig.type === 'single') {\n\t\t\t// Single-app config - use existing behavior\n\t\t\tconst config = loadedConfig.raw as GkmConfig;\n\n\t\t\t// Enable openapi if not configured\n\t\t\tif (!config.openapi) {\n\t\t\t\tconfig.openapi = { enabled: true };\n\t\t\t}\n\n\t\t\tconst result = await generateOpenApiFrom(config.constructs, {\n\t\t\t\topenapi: config.openapi,\n\t\t\t\troot: loadedConfig.workspace.root,\n\t\t\t\t...(options.telemetry !== undefined\n\t\t\t\t\t? { telemetry: options.telemetry }\n\t\t\t\t\t: {}),\n\t\t\t});\n\n\t\t\tif (result) {\n\t\t\t\tlogger.log(`Found ${result.endpointCount} endpoints`);\n\t\t\t}\n\t\t} else {\n\t\t\t// Workspace config - generate for each backend app\n\t\t\tconst { workspace } = loadedConfig;\n\t\t\t// Always derive the workspace root from the loaded config rather than\n\t\t\t// CWD. Subprocesses run with CWD set to an app directory, and falling\n\t\t\t// back to CWD there causes `join(workspaceRoot, app.path)` to point at\n\t\t\t// a non-existent nested path (e.g. `apps/api/apps/api`).\n\t\t\tconst workspaceRoot = workspace.root;\n\n\t\t\t// Backend apps with openapi enabled — which is all of them that have\n\t\t\t// routes, unless one says otherwise.\n\t\t\t//\n\t\t\t// This used to require `openapi: true` written out, while the app's\n\t\t\t// own build generated a spec regardless. Two paths disagreeing about\n\t\t\t// the same flag: the app emitted `.gkm/openapi.ts` and this pass,\n\t\t\t// finding nothing \"enabled\", skipped copying it to the frontends that\n\t\t\t// consume it. A surface whose routes are declared rather than\n\t\t\t// discovered has no spec to generate, so it is not a candidate.\n\t\t\tconst backendApps = Object.entries(workspace.apps).filter(\n\t\t\t\t([_, app]) =>\n\t\t\t\t\tapp.type === 'backend' &&\n\t\t\t\t\tapp.openapi !== false &&\n\t\t\t\t\t(typeof app.openapi !== 'object' || app.openapi.enabled !== false),\n\t\t\t);\n\n\t\t\tif (backendApps.length === 0) {\n\t\t\t\tlogger.log('No backend apps with OpenAPI enabled found');\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Single-app mode: generate in-process. The caller is expected to\n\t\t\t// have CWD set to the app's directory (e.g., when invoked as a\n\t\t\t// subprocess), so tsx picks up the app's tsconfig path aliases.\n\t\t\tif (options.app) {\n\t\t\t\tconst entry = backendApps.find(([name]) => name === options.app);\n\t\t\t\tif (!entry) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`App \"${options.app}\" not found or has OpenAPI disabled`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tconst [appName, app] = entry;\n\t\t\t\tconst result = await generateOpenApiForApp(\n\t\t\t\t\tworkspace,\n\t\t\t\t\tappName,\n\t\t\t\t\tapp,\n\t\t\t\t\ttrue,\n\t\t\t\t\toptions.telemetry,\n\t\t\t\t);\n\t\t\t\tif (result) {\n\t\t\t\t\tlogger.log(\n\t\t\t\t\t\t`📄 [${appName}] Generated OpenAPI (${result.endpointCount} endpoints)`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Multi-app mode: spawn a subprocess per backend app. Each\n\t\t\t// subprocess starts with CWD at the app's directory so that\n\t\t\t// tsx's tsconfig discovery picks up the app's `paths` aliases\n\t\t\t// (e.g., `~/*`) instead of the workspace root's tsconfig.\n\t\t\tfor (const [appName, app] of backendApps) {\n\t\t\t\t// No `app.routes` check any more: the per-kind globs are gone, and\n\t\t\t\t// an app's code is found by one glob rather than by a field naming\n\t\t\t\t// endpoints. Guarding on `routes` here meant every app was skipped\n\t\t\t\t// the moment that field stopped existing — silently, because a loop\n\t\t\t\t// that runs zero times looks exactly like one with nothing to do.\n\t\t\t\tconst appPath = join(workspaceRoot, app.path);\n\n\t\t\t\t// A subprocess exists to put CWD inside the app, so tsx picks up\n\t\t\t\t// that app's tsconfig aliases instead of the workspace root's. When\n\t\t\t\t// the app *is* the root there is nothing to change: spawning would\n\t\t\t\t// re-enter the same directory, having paid for a process and a\n\t\t\t\t// module graph, and needing `tsx` resolvable from it.\n\t\t\t\tif (resolve(appPath) === resolve(workspaceRoot)) {\n\t\t\t\t\t// Not silent: silence is for a subprocess whose output the\n\t\t\t\t\t// parent relays, and there is no parent here to relay it. The\n\t\t\t\t\t// per-surface lines come from the generator; the count is this\n\t\t\t\t\t// command's own summary, as in the single-app path.\n\t\t\t\t\tconst result = await generateOpenApiForApp(\n\t\t\t\t\t\tworkspace,\n\t\t\t\t\t\tappName,\n\t\t\t\t\t\tapp,\n\t\t\t\t\t\tfalse,\n\t\t\t\t\t\toptions.telemetry,\n\t\t\t\t\t);\n\t\t\t\t\tif (result) {\n\t\t\t\t\t\tlogger.log(`Found ${result.endpointCount} endpoints`);\n\t\t\t\t\t}\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tawait runOpenApiInSubprocess(appPath, appName, options.telemetry);\n\t\t\t}\n\t\t}\n\t} catch (error) {\n\t\tthrow new Error(`OpenAPI generation failed: ${(error as Error).message}`);\n\t}\n}\n\n/**\n * Generate OpenAPI for a single named app within a workspace.\n * Runs in-process. The caller is responsible for ensuring `process.cwd()`\n * is the app's directory so tsx loads the app's tsconfig path aliases.\n *\n * The endpoints are found the way the app's build finds them — through the\n * workspace's constructs globs — never by importing every `.ts` under the app,\n * which loaded its tests and gkm's own generated files with them.\n */\nasync function generateOpenApiForApp(\n\tworkspace: NormalizedWorkspace,\n\tappName: string,\n\tapp: NormalizedAppConfig,\n\t/**\n\t * Quiet by default, because the usual caller is a subprocess whose output\n\t * the parent relays. Generating in-process there is no parent to relay to,\n\t * and silence would swallow the only report of what happened.\n\t */\n\tsilent = true,\n\ttelemetry?: ClientTelemetryDefault,\n): Promise<{ outputPath: string; endpointCount: number } | null> {\n\tif (app.type !== 'backend') {\n\t\treturn null;\n\t}\n\n\tconst loaded = await new EndpointGenerator().load(\n\t\tappConstructGlobs(workspace, appName),\n\t);\n\n\t// The workspace's globs reach every app's code, so each app writes its own\n\t// surfaces' clients and no other's. An endpoint whose surface is no app of\n\t// this workspace's has nowhere else to go, and stays.\n\tconst endpoints = loaded\n\t\t.map(({ construct }) => construct)\n\t\t.filter((endpoint) => {\n\t\t\tconst owner = endpoint.owner ?? endpoint.surface?.id;\n\t\t\tif (!owner) return false;\n\t\t\tconst key = appKey(owner);\n\t\t\treturn key === appName || !workspace.apps[key];\n\t\t});\n\n\treturn generateOpenApi(endpoints, {\n\t\t// Absent means enabled, which is what the caller's filter already decided\n\t\t// when it kept this app: it skips one that says `openapi: false` and keeps\n\t\t// every other. Passing `undefined` through let `resolveOpenApiConfig`\n\t\t// default it to *disabled*, so an app selected for generation generated\n\t\t// nothing and said nothing — two places disagreeing about one flag, which\n\t\t// is the failure the filter above was written to describe.\n\t\topenapi: app.openapi ?? { enabled: true },\n\t\tsilent,\n\t\troot: workspace.root,\n\t\t...(telemetry !== undefined ? { telemetry } : {}),\n\t});\n}\n\n/**\n * Resolve the gkm bin path. Tests can override via `GKM_BIN_PATH` env var\n * to avoid depending on the built dist.\n */\nfunction resolveGkmBinPath(): string {\n\tif (process.env.GKM_BIN_PATH) {\n\t\treturn process.env.GKM_BIN_PATH;\n\t}\n\treturn fileURLToPath(new URL('../bin/gkm.mjs', import.meta.url));\n}\n\n/**\n * Spawn a subprocess that runs `gkm openapi --app <name>` with `cwd` set to\n * the app's directory. Inherits `NODE_OPTIONS` (which already contains\n * `--import tsx`), so the child gets a fresh tsx instance whose tsconfig\n * discovery picks up the app's tsconfig path aliases.\n */\nasync function runOpenApiInSubprocess(\n\tappCwd: string,\n\tappName: string,\n\ttelemetry?: ClientTelemetryDefault,\n): Promise<void> {\n\tconst binPath = resolveGkmBinPath();\n\n\tawait new Promise<void>((resolve, reject) => {\n\t\tconst child = spawn(\n\t\t\tprocess.execPath,\n\t\t\t[binPath, 'openapi', '--app', appName, ...telemetryArgs(telemetry)],\n\t\t\t{\n\t\t\t\tcwd: appCwd,\n\t\t\t\tstdio: 'inherit',\n\t\t\t\tenv: process.env,\n\t\t\t},\n\t\t);\n\n\t\tchild.on('error', reject);\n\t\tchild.on('close', (code) => {\n\t\t\tif (code === 0) {\n\t\t\t\tresolve();\n\t\t\t} else {\n\t\t\t\treject(\n\t\t\t\t\tnew Error(\n\t\t\t\t\t\t`OpenAPI generation for app \"${appName}\" exited with code ${code}`,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\t});\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
const require_errors = require("./errors-DMC4Umsb.cjs");
|
|
2
|
+
require("./identity-CbLfgQNT.cjs");
|
|
3
|
+
let node_crypto = require("node:crypto");
|
|
4
|
+
//#region src/providers/projectBucket.ts
|
|
5
|
+
/**
|
|
6
|
+
* The project bucket: one S3 bucket per project and AWS account, created and
|
|
7
|
+
* owned by gkm, that holds what gkm keeps for every stage — the deploy state
|
|
8
|
+
* under `<prefix>/<project>/<stage>/`, and anything else gkm writes per stage
|
|
9
|
+
* beside it.
|
|
10
|
+
*
|
|
11
|
+
* Nothing can record its name — the deploy state lives in it — so the name is
|
|
12
|
+
* worked out the same way every time: `gkm-<scope>-<account id>`, the scope
|
|
13
|
+
* being the one the `s3` objects provider names its buckets with. The account
|
|
14
|
+
* id keeps it from clashing with another account's project of the same name
|
|
15
|
+
* without a random suffix; a clash that happens anyway (HeadBucket 403) is
|
|
16
|
+
* refused, never adopted.
|
|
17
|
+
*
|
|
18
|
+
* Created with versioning, SSE-S3, every public access blocked, ownership
|
|
19
|
+
* enforced for the bucket owner, a lifecycle that expires old versions, and
|
|
20
|
+
* the tag `gkm:project=<identity key>`.
|
|
21
|
+
*/
|
|
22
|
+
/** What the project part may take: 63 less `gkm-`, `-` and a 12-digit id. */
|
|
23
|
+
const SCOPE_MAX = 46;
|
|
24
|
+
/** The tag the bucket is created with, as the `s3` provider tags its own. */
|
|
25
|
+
const PROJECT_BUCKET_TAG = "gkm:project";
|
|
26
|
+
/** The bucket's name is held by another account, or cannot be reached. */
|
|
27
|
+
var ProjectBucketTaken = class extends require_errors.GkmError {
|
|
28
|
+
bucket;
|
|
29
|
+
status;
|
|
30
|
+
constructor(bucket, status) {
|
|
31
|
+
super(`The project bucket '${bucket}' ${status === 301 ? "is in another region than the one configured" : "exists and this account cannot reach it — another account holds the name"} (HTTP ${status}). Name a bucket you own instead — in gkm.config.ts: state: { provider: 's3', bucket: '<your bucket>', region: '<its region>' } — or run with the credentials of the account the name is for.`);
|
|
32
|
+
this.bucket = bucket;
|
|
33
|
+
this.status = status;
|
|
34
|
+
this.name = "ProjectBucketTaken";
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
/** The running credentials may not create the project bucket. */
|
|
38
|
+
var ProjectBucketNotCreatable = class extends require_errors.GkmError {
|
|
39
|
+
bucket;
|
|
40
|
+
reason;
|
|
41
|
+
constructor(bucket, reason) {
|
|
42
|
+
super(`The project bucket '${bucket}' does not exist yet, and these credentials cannot create it (${reason}). Run the first deploy with credentials allowed s3:CreateBucket and the bucket put-config actions on it — \`gkm deploy:github\` grants them to the CI role — or name an existing bucket: state: { provider: 's3', bucket, region }.`);
|
|
43
|
+
this.bucket = bucket;
|
|
44
|
+
this.reason = reason;
|
|
45
|
+
this.name = "ProjectBucketNotCreatable";
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
/** {@link projectBucket}, from a deploy identity's scope. */
|
|
49
|
+
function projectBucketName(scope, accountId) {
|
|
50
|
+
let part = scope.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
51
|
+
if (part.length > SCOPE_MAX) {
|
|
52
|
+
const hash = (0, node_crypto.createHash)("sha256").update(scope).digest("hex").slice(0, 6);
|
|
53
|
+
part = `${part.slice(0, 39).replace(/-+$/, "")}-${hash}`;
|
|
54
|
+
}
|
|
55
|
+
return `gkm-${part}-${accountId}`;
|
|
56
|
+
}
|
|
57
|
+
/** The account the credentials are in — what the bucket is named by. */
|
|
58
|
+
async function callerAccountId(sts) {
|
|
59
|
+
const { GetCallerIdentityCommand } = await import("@aws-sdk/client-sts");
|
|
60
|
+
const { Account } = await sts.send(new GetCallerIdentityCommand({}));
|
|
61
|
+
return Account ?? "";
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The bucket's whole lifecycle configuration. A put replaces every rule the
|
|
65
|
+
* bucket has, so this is the one place the set is built: the bucket's own
|
|
66
|
+
* noncurrent-version expiry, and any per-prefix expiry passed in.
|
|
67
|
+
*/
|
|
68
|
+
function projectBucketLifecycle(expiries = []) {
|
|
69
|
+
return [{
|
|
70
|
+
ID: "gkm-noncurrent-versions",
|
|
71
|
+
Status: "Enabled",
|
|
72
|
+
Filter: { Prefix: "" },
|
|
73
|
+
NoncurrentVersionExpiration: { NoncurrentDays: 90 },
|
|
74
|
+
Expiration: { ExpiredObjectDeleteMarker: true }
|
|
75
|
+
}, ...expiries.map((expiry) => ({
|
|
76
|
+
ID: expiry.id,
|
|
77
|
+
Status: "Enabled",
|
|
78
|
+
Filter: { Prefix: expiry.prefix },
|
|
79
|
+
Expiration: { Days: expiry.days },
|
|
80
|
+
...expiry.abortIncompleteDays ? { AbortIncompleteMultipartUpload: { DaysAfterInitiation: expiry.abortIncompleteDays } } : {}
|
|
81
|
+
}))];
|
|
82
|
+
}
|
|
83
|
+
/** A lifecycle rule reduced to what gkm sets, for comparing with what is there. */
|
|
84
|
+
function canonicalRule(rule) {
|
|
85
|
+
return JSON.stringify({
|
|
86
|
+
id: rule.ID ?? "",
|
|
87
|
+
status: rule.Status ?? "",
|
|
88
|
+
prefix: rule.Filter?.Prefix ?? rule.Prefix ?? "",
|
|
89
|
+
days: rule.Expiration?.Days ?? null,
|
|
90
|
+
markers: rule.Expiration?.ExpiredObjectDeleteMarker ?? null,
|
|
91
|
+
noncurrent: rule.NoncurrentVersionExpiration?.NoncurrentDays ?? null,
|
|
92
|
+
abort: rule.AbortIncompleteMultipartUpload?.DaysAfterInitiation ?? null
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
/** Whether two rule sets say the same thing, in any order. */
|
|
96
|
+
function sameLifecycle(a, b) {
|
|
97
|
+
const canonical = (rules) => rules.map(canonicalRule).sort().join("\n");
|
|
98
|
+
return canonical(a) === canonical(b);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* The project bucket's lifecycle put to {@link projectBucketLifecycle} of
|
|
102
|
+
* `expiries` — every stage's — when what it holds says anything else.
|
|
103
|
+
* Returns whether it was put; with `dryRun`, whether it would be.
|
|
104
|
+
*/
|
|
105
|
+
async function reconcileProjectBucketLifecycle(s3, bucket, expiries, options = {}) {
|
|
106
|
+
const S3 = await import("@aws-sdk/client-s3");
|
|
107
|
+
const rules = projectBucketLifecycle(expiries);
|
|
108
|
+
let current = [];
|
|
109
|
+
try {
|
|
110
|
+
current = (await s3.send(new S3.GetBucketLifecycleConfigurationCommand({ Bucket: bucket }))).Rules ?? [];
|
|
111
|
+
} catch (error) {
|
|
112
|
+
const failure = error;
|
|
113
|
+
if ((failure.Code ?? failure.name) !== "NoSuchLifecycleConfiguration") throw error;
|
|
114
|
+
}
|
|
115
|
+
if (sameLifecycle(current, rules)) return false;
|
|
116
|
+
if (!options.dryRun) await s3.send(new S3.PutBucketLifecycleConfigurationCommand({
|
|
117
|
+
Bucket: bucket,
|
|
118
|
+
LifecycleConfiguration: { Rules: rules }
|
|
119
|
+
}));
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Whether `bucket` is this account's (`ours`) or does not exist (`none`).
|
|
124
|
+
* Mapped by status: a HEAD has no body, so SDK v3 names a 301, 400 or 403
|
|
125
|
+
* `Unknown`.
|
|
126
|
+
*
|
|
127
|
+
* @throws {ProjectBucketTaken} on a 403 or 301
|
|
128
|
+
*/
|
|
129
|
+
async function findProjectBucket(s3, bucket) {
|
|
130
|
+
const { HeadBucketCommand } = await import("@aws-sdk/client-s3");
|
|
131
|
+
try {
|
|
132
|
+
await s3.send(new HeadBucketCommand({ Bucket: bucket }));
|
|
133
|
+
return "ours";
|
|
134
|
+
} catch (error) {
|
|
135
|
+
const status = error.$metadata?.httpStatusCode;
|
|
136
|
+
if (status === 404) return "none";
|
|
137
|
+
if (status === 403 || status === 301) throw new ProjectBucketTaken(bucket, status);
|
|
138
|
+
throw error;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* The project bucket, created and configured if it does not exist yet.
|
|
143
|
+
* Returns whether this call created it. One that exists is used as it is.
|
|
144
|
+
*
|
|
145
|
+
* @throws {ProjectBucketTaken} when another account holds the name
|
|
146
|
+
* @throws {ProjectBucketNotCreatable} when the credentials may not create it
|
|
147
|
+
*/
|
|
148
|
+
async function ensureProjectBucket(s3, options) {
|
|
149
|
+
const { bucket: Bucket, region } = options;
|
|
150
|
+
if (await findProjectBucket(s3, Bucket) === "ours") return { created: false };
|
|
151
|
+
const S3 = await import("@aws-sdk/client-s3");
|
|
152
|
+
try {
|
|
153
|
+
await s3.send(new S3.CreateBucketCommand({
|
|
154
|
+
Bucket,
|
|
155
|
+
ObjectOwnership: "BucketOwnerEnforced",
|
|
156
|
+
...region === "us-east-1" ? {} : { CreateBucketConfiguration: { LocationConstraint: region } }
|
|
157
|
+
}));
|
|
158
|
+
} catch (error) {
|
|
159
|
+
const failure = error;
|
|
160
|
+
const code = failure.Code ?? failure.name;
|
|
161
|
+
if (code !== "BucketAlreadyOwnedByYou") {
|
|
162
|
+
if (code === "BucketAlreadyExists") throw new ProjectBucketTaken(Bucket, 409);
|
|
163
|
+
if (code === "AccessDenied" || failure.$metadata?.httpStatusCode === 403) throw new ProjectBucketNotCreatable(Bucket, failure.message ?? "AccessDenied");
|
|
164
|
+
throw error;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
await s3.send(new S3.PutPublicAccessBlockCommand({
|
|
168
|
+
Bucket,
|
|
169
|
+
PublicAccessBlockConfiguration: {
|
|
170
|
+
BlockPublicAcls: true,
|
|
171
|
+
IgnorePublicAcls: true,
|
|
172
|
+
BlockPublicPolicy: true,
|
|
173
|
+
RestrictPublicBuckets: true
|
|
174
|
+
}
|
|
175
|
+
}));
|
|
176
|
+
await s3.send(new S3.PutBucketOwnershipControlsCommand({
|
|
177
|
+
Bucket,
|
|
178
|
+
OwnershipControls: { Rules: [{ ObjectOwnership: "BucketOwnerEnforced" }] }
|
|
179
|
+
}));
|
|
180
|
+
await s3.send(new S3.PutBucketEncryptionCommand({
|
|
181
|
+
Bucket,
|
|
182
|
+
ServerSideEncryptionConfiguration: { Rules: [{ ApplyServerSideEncryptionByDefault: { SSEAlgorithm: "AES256" } }] }
|
|
183
|
+
}));
|
|
184
|
+
await s3.send(new S3.PutBucketVersioningCommand({
|
|
185
|
+
Bucket,
|
|
186
|
+
VersioningConfiguration: { Status: "Enabled" }
|
|
187
|
+
}));
|
|
188
|
+
await s3.send(new S3.PutBucketLifecycleConfigurationCommand({
|
|
189
|
+
Bucket,
|
|
190
|
+
LifecycleConfiguration: { Rules: projectBucketLifecycle(options.expiries) }
|
|
191
|
+
}));
|
|
192
|
+
await s3.send(new S3.PutBucketTaggingCommand({
|
|
193
|
+
Bucket,
|
|
194
|
+
Tagging: { TagSet: [{
|
|
195
|
+
Key: PROJECT_BUCKET_TAG,
|
|
196
|
+
Value: options.identity.key
|
|
197
|
+
}] }
|
|
198
|
+
}));
|
|
199
|
+
return { created: true };
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* The IAM actions creating and configuring the project bucket takes — what a
|
|
203
|
+
* role that runs the first deploy needs on it, beside object access.
|
|
204
|
+
*/
|
|
205
|
+
const PROJECT_BUCKET_CREATE_ACTIONS = [
|
|
206
|
+
"s3:CreateBucket",
|
|
207
|
+
"s3:PutBucketVersioning",
|
|
208
|
+
"s3:PutEncryptionConfiguration",
|
|
209
|
+
"s3:PutBucketPublicAccessBlock",
|
|
210
|
+
"s3:PutBucketOwnershipControls",
|
|
211
|
+
"s3:PutLifecycleConfiguration",
|
|
212
|
+
"s3:PutBucketTagging"
|
|
213
|
+
];
|
|
214
|
+
/**
|
|
215
|
+
* What keeping the project bucket's lifecycle takes on a bucket that exists:
|
|
216
|
+
* reading it, to put it back only when it drifted.
|
|
217
|
+
*/
|
|
218
|
+
const PROJECT_BUCKET_LIFECYCLE_ACTIONS = ["s3:GetLifecycleConfiguration", "s3:PutLifecycleConfiguration"];
|
|
219
|
+
//#endregion
|
|
220
|
+
Object.defineProperty(exports, "PROJECT_BUCKET_CREATE_ACTIONS", {
|
|
221
|
+
enumerable: true,
|
|
222
|
+
get: function() {
|
|
223
|
+
return PROJECT_BUCKET_CREATE_ACTIONS;
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
Object.defineProperty(exports, "PROJECT_BUCKET_LIFECYCLE_ACTIONS", {
|
|
227
|
+
enumerable: true,
|
|
228
|
+
get: function() {
|
|
229
|
+
return PROJECT_BUCKET_LIFECYCLE_ACTIONS;
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
Object.defineProperty(exports, "callerAccountId", {
|
|
233
|
+
enumerable: true,
|
|
234
|
+
get: function() {
|
|
235
|
+
return callerAccountId;
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
Object.defineProperty(exports, "ensureProjectBucket", {
|
|
239
|
+
enumerable: true,
|
|
240
|
+
get: function() {
|
|
241
|
+
return ensureProjectBucket;
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
Object.defineProperty(exports, "findProjectBucket", {
|
|
245
|
+
enumerable: true,
|
|
246
|
+
get: function() {
|
|
247
|
+
return findProjectBucket;
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
Object.defineProperty(exports, "projectBucketName", {
|
|
251
|
+
enumerable: true,
|
|
252
|
+
get: function() {
|
|
253
|
+
return projectBucketName;
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
Object.defineProperty(exports, "reconcileProjectBucketLifecycle", {
|
|
257
|
+
enumerable: true,
|
|
258
|
+
get: function() {
|
|
259
|
+
return reconcileProjectBucketLifecycle;
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
//# sourceMappingURL=projectBucket-BBbQ1lHF.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projectBucket-BBbQ1lHF.cjs","names":["GkmError","createHash"],"sources":["../src/providers/projectBucket.ts"],"sourcesContent":["/**\n * The project bucket: one S3 bucket per project and AWS account, created and\n * owned by gkm, that holds what gkm keeps for every stage — the deploy state\n * under `<prefix>/<project>/<stage>/`, and anything else gkm writes per stage\n * beside it.\n *\n * Nothing can record its name — the deploy state lives in it — so the name is\n * worked out the same way every time: `gkm-<scope>-<account id>`, the scope\n * being the one the `s3` objects provider names its buckets with. The account\n * id keeps it from clashing with another account's project of the same name\n * without a random suffix; a clash that happens anyway (HeadBucket 403) is\n * refused, never adopted.\n *\n * Created with versioning, SSE-S3, every public access blocked, ownership\n * enforced for the bucket owner, a lifecycle that expires old versions, and\n * the tag `gkm:project=<identity key>`.\n */\n\nimport { createHash } from 'node:crypto';\nimport type {\n\tBucketLocationConstraint,\n\tLifecycleRule,\n\tS3Client,\n} from '@aws-sdk/client-s3';\nimport type { STSClient } from '@aws-sdk/client-sts';\nimport { type DeployIdentity, deployIdentity } from '../deploy/identity.js';\nimport { GkmError } from '../errors';\n\n/** S3's limit on a bucket name. */\nconst BUCKET_NAME_MAX = 63;\n/** What the project part may take: 63 less `gkm-`, `-` and a 12-digit id. */\nconst SCOPE_MAX = BUCKET_NAME_MAX - 'gkm-'.length - 1 - 12;\n\n/** The tag the bucket is created with, as the `s3` provider tags its own. */\nexport const PROJECT_BUCKET_TAG = 'gkm:project';\n\n/** How long a replaced or deleted object's old version is kept. */\nexport const NONCURRENT_VERSION_DAYS = 90;\n\n/** The bucket's name is held by another account, or cannot be reached. */\nexport class ProjectBucketTaken extends GkmError {\n\tconstructor(\n\t\treadonly bucket: string,\n\t\t/** HeadBucket's status: 403 another account's, 301 another region's. */\n\t\treadonly status: number,\n\t) {\n\t\tconst why =\n\t\t\tstatus === 301\n\t\t\t\t? 'is in another region than the one configured'\n\t\t\t\t: 'exists and this account cannot reach it — another account holds the name';\n\t\tsuper(\n\t\t\t`The project bucket '${bucket}' ${why} (HTTP ${status}). Name a bucket ` +\n\t\t\t\t\"you own instead — in gkm.config.ts: state: { provider: 's3', \" +\n\t\t\t\t\"bucket: '<your bucket>', region: '<its region>' } — or run with the \" +\n\t\t\t\t'credentials of the account the name is for.',\n\t\t);\n\t\tthis.name = 'ProjectBucketTaken';\n\t}\n}\n\n/** The running credentials may not create the project bucket. */\nexport class ProjectBucketNotCreatable extends GkmError {\n\tconstructor(\n\t\treadonly bucket: string,\n\t\treadonly reason: string,\n\t) {\n\t\tsuper(\n\t\t\t`The project bucket '${bucket}' does not exist yet, and these ` +\n\t\t\t\t`credentials cannot create it (${reason}). Run the first deploy with ` +\n\t\t\t\t'credentials allowed s3:CreateBucket and the bucket put-config ' +\n\t\t\t\t'actions on it — `gkm deploy:github` grants them to the CI role — ' +\n\t\t\t\t\"or name an existing bucket: state: { provider: 's3', bucket, region }.\",\n\t\t);\n\t\tthis.name = 'ProjectBucketNotCreatable';\n\t}\n}\n\n/**\n * The project bucket's name: `gkm-<scope>-<account id>`, lowercase and\n * DNS-safe. A scope too long to fit keeps its start and ends in a hash of\n * the whole, so two long scopes never share a name.\n */\nexport function projectBucket(\n\tworkspace: { name: string; deploy?: { namespace?: string } },\n\toptions: { accountId: string },\n): string {\n\tconst { scope } = deployIdentity(workspace, '');\n\treturn projectBucketName(scope, options.accountId);\n}\n\n/** {@link projectBucket}, from a deploy identity's scope. */\nexport function projectBucketName(scope: string, accountId: string): string {\n\tlet part = scope\n\t\t.toLowerCase()\n\t\t.replace(/[^a-z0-9]+/g, '-')\n\t\t.replace(/^-+|-+$/g, '');\n\tif (part.length > SCOPE_MAX) {\n\t\tconst hash = createHash('sha256').update(scope).digest('hex').slice(0, 6);\n\t\tpart = `${part.slice(0, SCOPE_MAX - 7).replace(/-+$/, '')}-${hash}`;\n\t}\n\treturn `gkm-${part}-${accountId}`;\n}\n\n/** The account the credentials are in — what the bucket is named by. */\nexport async function callerAccountId(sts: STSClient): Promise<string> {\n\tconst { GetCallerIdentityCommand } = await import('@aws-sdk/client-sts');\n\tconst { Account } = await sts.send(new GetCallerIdentityCommand({}));\n\treturn Account ?? '';\n}\n\n/** An expiry for one stage's prefix, added beside the bucket's own rule. */\nexport interface PrefixExpiry {\n\t/** Rule id, unique in the bucket — e.g. `backups-production`. */\n\tid: string;\n\t/** Key prefix it applies to — e.g. `gkm/acme/production/backups/`. */\n\tprefix: string;\n\t/** Days after which a current object expires. */\n\tdays: number;\n\t/**\n\t * Days after which a multipart upload under the prefix that never\n\t * completed is aborted — its parts are stored, and billed, until it is.\n\t */\n\tabortIncompleteDays?: number;\n}\n\n/**\n * The bucket's whole lifecycle configuration. A put replaces every rule the\n * bucket has, so this is the one place the set is built: the bucket's own\n * noncurrent-version expiry, and any per-prefix expiry passed in.\n */\nexport function projectBucketLifecycle(\n\texpiries: readonly PrefixExpiry[] = [],\n): LifecycleRule[] {\n\treturn [\n\t\t{\n\t\t\tID: 'gkm-noncurrent-versions',\n\t\t\tStatus: 'Enabled',\n\t\t\tFilter: { Prefix: '' },\n\t\t\tNoncurrentVersionExpiration: { NoncurrentDays: NONCURRENT_VERSION_DAYS },\n\t\t\t// A lock is deleted every run; its markers go once nothing is under them.\n\t\t\tExpiration: { ExpiredObjectDeleteMarker: true },\n\t\t},\n\t\t...expiries.map(\n\t\t\t(expiry): LifecycleRule => ({\n\t\t\t\tID: expiry.id,\n\t\t\t\tStatus: 'Enabled',\n\t\t\t\tFilter: { Prefix: expiry.prefix },\n\t\t\t\tExpiration: { Days: expiry.days },\n\t\t\t\t...(expiry.abortIncompleteDays\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tAbortIncompleteMultipartUpload: {\n\t\t\t\t\t\t\t\tDaysAfterInitiation: expiry.abortIncompleteDays,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}\n\t\t\t\t\t: {}),\n\t\t\t}),\n\t\t),\n\t];\n}\n\n/** A lifecycle rule reduced to what gkm sets, for comparing with what is there. */\nfunction canonicalRule(rule: LifecycleRule): string {\n\treturn JSON.stringify({\n\t\tid: rule.ID ?? '',\n\t\tstatus: rule.Status ?? '',\n\t\tprefix: rule.Filter?.Prefix ?? rule.Prefix ?? '',\n\t\tdays: rule.Expiration?.Days ?? null,\n\t\tmarkers: rule.Expiration?.ExpiredObjectDeleteMarker ?? null,\n\t\tnoncurrent: rule.NoncurrentVersionExpiration?.NoncurrentDays ?? null,\n\t\tabort: rule.AbortIncompleteMultipartUpload?.DaysAfterInitiation ?? null,\n\t});\n}\n\n/** Whether two rule sets say the same thing, in any order. */\nexport function sameLifecycle(\n\ta: readonly LifecycleRule[],\n\tb: readonly LifecycleRule[],\n): boolean {\n\tconst canonical = (rules: readonly LifecycleRule[]) =>\n\t\trules.map(canonicalRule).sort().join('\\n');\n\treturn canonical(a) === canonical(b);\n}\n\n/**\n * The project bucket's lifecycle put to {@link projectBucketLifecycle} of\n * `expiries` — every stage's — when what it holds says anything else.\n * Returns whether it was put; with `dryRun`, whether it would be.\n */\nexport async function reconcileProjectBucketLifecycle(\n\ts3: S3Client,\n\tbucket: string,\n\texpiries: readonly PrefixExpiry[],\n\toptions: { dryRun?: boolean } = {},\n): Promise<boolean> {\n\tconst S3 = await import('@aws-sdk/client-s3');\n\tconst rules = projectBucketLifecycle(expiries);\n\tlet current: LifecycleRule[] = [];\n\ttry {\n\t\tconst out = await s3.send(\n\t\t\tnew S3.GetBucketLifecycleConfigurationCommand({ Bucket: bucket }),\n\t\t);\n\t\tcurrent = out.Rules ?? [];\n\t} catch (error) {\n\t\tconst failure = error as S3Failure;\n\t\tconst code = failure.Code ?? failure.name;\n\t\tif (code !== 'NoSuchLifecycleConfiguration') throw error;\n\t}\n\tif (sameLifecycle(current, rules)) return false;\n\tif (!options.dryRun) {\n\t\tawait s3.send(\n\t\t\tnew S3.PutBucketLifecycleConfigurationCommand({\n\t\t\t\tBucket: bucket,\n\t\t\t\tLifecycleConfiguration: { Rules: rules },\n\t\t\t}),\n\t\t);\n\t}\n\treturn true;\n}\n\ninterface S3Failure {\n\tname?: string;\n\tCode?: string;\n\tmessage?: string;\n\t$metadata?: { httpStatusCode?: number };\n}\n\n/**\n * Whether `bucket` is this account's (`ours`) or does not exist (`none`).\n * Mapped by status: a HEAD has no body, so SDK v3 names a 301, 400 or 403\n * `Unknown`.\n *\n * @throws {ProjectBucketTaken} on a 403 or 301\n */\nexport async function findProjectBucket(\n\ts3: S3Client,\n\tbucket: string,\n): Promise<'ours' | 'none'> {\n\tconst { HeadBucketCommand } = await import('@aws-sdk/client-s3');\n\ttry {\n\t\tawait s3.send(new HeadBucketCommand({ Bucket: bucket }));\n\t\treturn 'ours';\n\t} catch (error) {\n\t\tconst status = (error as S3Failure).$metadata?.httpStatusCode;\n\t\tif (status === 404) return 'none';\n\t\tif (status === 403 || status === 301) {\n\t\t\tthrow new ProjectBucketTaken(bucket, status);\n\t\t}\n\t\tthrow error;\n\t}\n}\n\nexport interface EnsureProjectBucketOptions {\n\tbucket: string;\n\tregion: string;\n\t/** The deploy identity the bucket is tagged with. */\n\tidentity: Pick<DeployIdentity, 'key'>;\n\t/** Per-prefix expiries beside the bucket's own rule. */\n\texpiries?: readonly PrefixExpiry[];\n}\n\n/**\n * The project bucket, created and configured if it does not exist yet.\n * Returns whether this call created it. One that exists is used as it is.\n *\n * @throws {ProjectBucketTaken} when another account holds the name\n * @throws {ProjectBucketNotCreatable} when the credentials may not create it\n */\nexport async function ensureProjectBucket(\n\ts3: S3Client,\n\toptions: EnsureProjectBucketOptions,\n): Promise<{ created: boolean }> {\n\tconst { bucket: Bucket, region } = options;\n\tif ((await findProjectBucket(s3, Bucket)) === 'ours') {\n\t\treturn { created: false };\n\t}\n\n\tconst S3 = await import('@aws-sdk/client-s3');\n\ttry {\n\t\tawait s3.send(\n\t\t\tnew S3.CreateBucketCommand({\n\t\t\t\tBucket,\n\t\t\t\tObjectOwnership: 'BucketOwnerEnforced',\n\t\t\t\t// us-east-1 is the one region named by leaving it out.\n\t\t\t\t...(region === 'us-east-1'\n\t\t\t\t\t? {}\n\t\t\t\t\t: {\n\t\t\t\t\t\t\tCreateBucketConfiguration: {\n\t\t\t\t\t\t\t\tLocationConstraint: region as BucketLocationConstraint,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}),\n\t\t\t}),\n\t\t);\n\t} catch (error) {\n\t\tconst failure = error as S3Failure;\n\t\tconst code = failure.Code ?? failure.name;\n\t\t// Another run created it a moment ago: configure it all the same.\n\t\tif (code !== 'BucketAlreadyOwnedByYou') {\n\t\t\tif (code === 'BucketAlreadyExists') {\n\t\t\t\tthrow new ProjectBucketTaken(Bucket, 409);\n\t\t\t}\n\t\t\tif (\n\t\t\t\tcode === 'AccessDenied' ||\n\t\t\t\tfailure.$metadata?.httpStatusCode === 403\n\t\t\t) {\n\t\t\t\tthrow new ProjectBucketNotCreatable(\n\t\t\t\t\tBucket,\n\t\t\t\t\tfailure.message ?? 'AccessDenied',\n\t\t\t\t);\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tawait s3.send(\n\t\tnew S3.PutPublicAccessBlockCommand({\n\t\t\tBucket,\n\t\t\tPublicAccessBlockConfiguration: {\n\t\t\t\tBlockPublicAcls: true,\n\t\t\t\tIgnorePublicAcls: true,\n\t\t\t\tBlockPublicPolicy: true,\n\t\t\t\tRestrictPublicBuckets: true,\n\t\t\t},\n\t\t}),\n\t);\n\tawait s3.send(\n\t\tnew S3.PutBucketOwnershipControlsCommand({\n\t\t\tBucket,\n\t\t\tOwnershipControls: {\n\t\t\t\tRules: [{ ObjectOwnership: 'BucketOwnerEnforced' }],\n\t\t\t},\n\t\t}),\n\t);\n\tawait s3.send(\n\t\tnew S3.PutBucketEncryptionCommand({\n\t\t\tBucket,\n\t\t\tServerSideEncryptionConfiguration: {\n\t\t\t\tRules: [\n\t\t\t\t\t{ ApplyServerSideEncryptionByDefault: { SSEAlgorithm: 'AES256' } },\n\t\t\t\t],\n\t\t\t},\n\t\t}),\n\t);\n\tawait s3.send(\n\t\tnew S3.PutBucketVersioningCommand({\n\t\t\tBucket,\n\t\t\tVersioningConfiguration: { Status: 'Enabled' },\n\t\t}),\n\t);\n\tawait s3.send(\n\t\tnew S3.PutBucketLifecycleConfigurationCommand({\n\t\t\tBucket,\n\t\t\tLifecycleConfiguration: {\n\t\t\t\tRules: projectBucketLifecycle(options.expiries),\n\t\t\t},\n\t\t}),\n\t);\n\tawait s3.send(\n\t\tnew S3.PutBucketTaggingCommand({\n\t\t\tBucket,\n\t\t\tTagging: {\n\t\t\t\tTagSet: [{ Key: PROJECT_BUCKET_TAG, Value: options.identity.key }],\n\t\t\t},\n\t\t}),\n\t);\n\treturn { created: true };\n}\n\n/**\n * The IAM actions creating and configuring the project bucket takes — what a\n * role that runs the first deploy needs on it, beside object access.\n */\nexport const PROJECT_BUCKET_CREATE_ACTIONS = [\n\t's3:CreateBucket',\n\t's3:PutBucketVersioning',\n\t's3:PutEncryptionConfiguration',\n\t's3:PutBucketPublicAccessBlock',\n\t's3:PutBucketOwnershipControls',\n\t's3:PutLifecycleConfiguration',\n\t's3:PutBucketTagging',\n] as const;\n\n/**\n * What keeping the project bucket's lifecycle takes on a bucket that exists:\n * reading it, to put it back only when it drifted.\n */\nexport const PROJECT_BUCKET_LIFECYCLE_ACTIONS = [\n\t's3:GetLifecycleConfiguration',\n\t's3:PutLifecycleConfiguration',\n] as const;\n"],"mappings":"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"}
|