@geekmidas/cli 10.0.0-alpha.9 → 10.0.0-alpha.90
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-CNMF0Q3m.cjs +594 -0
- package/dist/add-CNMF0Q3m.cjs.map +1 -0
- package/dist/add-DHk1roh_.mjs +592 -0
- package/dist/add-DHk1roh_.mjs.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,2021 +0,0 @@
|
|
|
1
|
-
import { ConstructGenerator, isPartitionedRoutes } from "./discover-CAlFgYec.mjs";
|
|
2
|
-
import { loadWorkspaceConfig } from "./config-DVVph9yz.mjs";
|
|
3
|
-
import { dirname, join, relative, resolve } from "node:path";
|
|
4
|
-
import { kebabCase } from "@geekmidas/manifest";
|
|
5
|
-
import { mkdir, writeFile } from "node:fs/promises";
|
|
6
|
-
import { spawn } from "node:child_process";
|
|
7
|
-
import { Endpoint } from "@geekmidas/constructs/endpoints";
|
|
8
|
-
import { fileURLToPath } from "node:url";
|
|
9
|
-
import { StandardSchemaJsonSchema, getRegisteredZodJsonSchemas, getSchemaMetadata } from "@geekmidas/schema/conversion";
|
|
10
|
-
|
|
11
|
-
//#region src/build/endpoint-analyzer.ts
|
|
12
|
-
/**
|
|
13
|
-
* Analyze an endpoint to extract its features
|
|
14
|
-
*/
|
|
15
|
-
function analyzeEndpointFeatures(endpoint) {
|
|
16
|
-
return {
|
|
17
|
-
hasAuth: !!endpoint.authorizer,
|
|
18
|
-
hasServices: endpoint.services.length > 0,
|
|
19
|
-
hasDatabase: !!endpoint.databaseService,
|
|
20
|
-
hasBodyValidation: !!endpoint.input?.body,
|
|
21
|
-
hasQueryValidation: !!endpoint.input?.query,
|
|
22
|
-
hasParamValidation: !!endpoint.input?.params,
|
|
23
|
-
hasAudits: (endpoint.audits?.length ?? 0) > 0,
|
|
24
|
-
hasEvents: (endpoint.events?.length ?? 0) > 0,
|
|
25
|
-
hasRateLimit: !!endpoint.rateLimit,
|
|
26
|
-
hasRls: !!endpoint.rlsConfig && !endpoint.rlsBypass,
|
|
27
|
-
hasOutputValidation: !!endpoint.outputSchema
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Determine the optimization tier for an endpoint based on its features
|
|
32
|
-
*/
|
|
33
|
-
function determineEndpointTier(features) {
|
|
34
|
-
const { hasAuth, hasServices, hasDatabase, hasAudits, hasEvents, hasRateLimit, hasRls } = features;
|
|
35
|
-
if (!hasAuth && !hasServices && !hasDatabase && !hasAudits && !hasEvents && !hasRateLimit && !hasRls) return "minimal";
|
|
36
|
-
if (hasAudits || hasRls || hasRateLimit) return "full";
|
|
37
|
-
return "standard";
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Perform complete analysis of an endpoint
|
|
41
|
-
*/
|
|
42
|
-
function analyzeEndpoint(endpoint, exportName) {
|
|
43
|
-
const features = analyzeEndpointFeatures(endpoint);
|
|
44
|
-
const tier = determineEndpointTier(features);
|
|
45
|
-
return {
|
|
46
|
-
route: endpoint.route,
|
|
47
|
-
method: endpoint.method,
|
|
48
|
-
exportName,
|
|
49
|
-
features,
|
|
50
|
-
tier,
|
|
51
|
-
serviceNames: endpoint.services.map((s) => s.serviceName),
|
|
52
|
-
databaseServiceName: endpoint.databaseService?.serviceName
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Generate a summary of endpoint analysis for logging
|
|
57
|
-
*/
|
|
58
|
-
function summarizeAnalysis(analyses) {
|
|
59
|
-
const byTier = {
|
|
60
|
-
minimal: 0,
|
|
61
|
-
standard: 0,
|
|
62
|
-
full: 0
|
|
63
|
-
};
|
|
64
|
-
const byFeature = {
|
|
65
|
-
hasAuth: 0,
|
|
66
|
-
hasServices: 0,
|
|
67
|
-
hasDatabase: 0,
|
|
68
|
-
hasBodyValidation: 0,
|
|
69
|
-
hasQueryValidation: 0,
|
|
70
|
-
hasParamValidation: 0,
|
|
71
|
-
hasAudits: 0,
|
|
72
|
-
hasEvents: 0,
|
|
73
|
-
hasRateLimit: 0,
|
|
74
|
-
hasRls: 0,
|
|
75
|
-
hasOutputValidation: 0
|
|
76
|
-
};
|
|
77
|
-
for (const analysis of analyses) {
|
|
78
|
-
byTier[analysis.tier]++;
|
|
79
|
-
for (const [feature, enabled] of Object.entries(analysis.features)) if (enabled) byFeature[feature]++;
|
|
80
|
-
}
|
|
81
|
-
return {
|
|
82
|
-
total: analyses.length,
|
|
83
|
-
byTier,
|
|
84
|
-
byFeature
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
//#endregion
|
|
89
|
-
//#region src/build/handler-templates.ts
|
|
90
|
-
/**
|
|
91
|
-
* Generate validator references for an endpoint
|
|
92
|
-
*/
|
|
93
|
-
function generateValidators(exportName, features) {
|
|
94
|
-
const validators = [];
|
|
95
|
-
if (features.hasBodyValidation) validators.push(`validateBody(${exportName})`);
|
|
96
|
-
if (features.hasQueryValidation) validators.push(`validateQuery(${exportName})`);
|
|
97
|
-
if (features.hasParamValidation) validators.push(`validateParams(${exportName})`);
|
|
98
|
-
return validators.length > 0 ? `\n ${validators.join(",\n ")},` : "";
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* Generate a minimal handler (near-raw-Hono performance)
|
|
102
|
-
*
|
|
103
|
-
* Used for: Health checks, public endpoints with no services
|
|
104
|
-
*/
|
|
105
|
-
function generateMinimalHandler(analysis) {
|
|
106
|
-
const { exportName, features } = analysis;
|
|
107
|
-
const method = analysis.method.toLowerCase();
|
|
108
|
-
const validators = generateValidators(exportName, features);
|
|
109
|
-
const hasValidators = validators.length > 0;
|
|
110
|
-
if (!hasValidators && !features.hasOutputValidation) return `
|
|
111
|
-
// Minimal handler: ${analysis.route} (${analysis.method})
|
|
112
|
-
app.${method}('${analysis.route}', async (c) => {
|
|
113
|
-
const result = await ${exportName}.handler(
|
|
114
|
-
{
|
|
115
|
-
services: {},
|
|
116
|
-
logger,
|
|
117
|
-
body: undefined,
|
|
118
|
-
query: undefined,
|
|
119
|
-
params: undefined,
|
|
120
|
-
session: undefined,
|
|
121
|
-
header: Endpoint.createHeaders(c.req.header()),
|
|
122
|
-
cookie: Endpoint.createCookies(c.req.header().cookie),
|
|
123
|
-
auditor: undefined,
|
|
124
|
-
db: undefined,
|
|
125
|
-
} as any,
|
|
126
|
-
{ getMetadata: () => ({}) } as any,
|
|
127
|
-
);
|
|
128
|
-
return c.json(result, ${exportName}.status as any);
|
|
129
|
-
});`;
|
|
130
|
-
return `
|
|
131
|
-
// Minimal handler with validation: ${analysis.route} (${analysis.method})
|
|
132
|
-
app.${method}('${analysis.route}',${validators}
|
|
133
|
-
async (c) => {
|
|
134
|
-
const result = await ${exportName}.handler(
|
|
135
|
-
{
|
|
136
|
-
services: {},
|
|
137
|
-
logger,
|
|
138
|
-
body: ${features.hasBodyValidation ? "(c.req.valid as any)('json')" : "undefined"},
|
|
139
|
-
query: ${features.hasQueryValidation ? "(c.req.valid as any)('query')" : "undefined"},
|
|
140
|
-
params: ${features.hasParamValidation ? "(c.req.valid as any)('param')" : "undefined"},
|
|
141
|
-
session: undefined,
|
|
142
|
-
header: Endpoint.createHeaders(c.req.header()),
|
|
143
|
-
cookie: Endpoint.createCookies(c.req.header().cookie),
|
|
144
|
-
auditor: undefined,
|
|
145
|
-
db: undefined,
|
|
146
|
-
} as any,
|
|
147
|
-
{ getMetadata: () => ({}) } as any,
|
|
148
|
-
);
|
|
149
|
-
${features.hasOutputValidation ? `const output = await ${exportName}.parseOutput(result);
|
|
150
|
-
return c.json(output, ${exportName}.status as any);` : `return c.json(result, ${exportName}.status as any);`}
|
|
151
|
-
}
|
|
152
|
-
);`;
|
|
153
|
-
}
|
|
154
|
-
/**
|
|
155
|
-
* Generate a standard handler (auth and/or services)
|
|
156
|
-
*
|
|
157
|
-
* Used for: Authenticated endpoints, endpoints with services
|
|
158
|
-
*/
|
|
159
|
-
function generateStandardHandler(analysis) {
|
|
160
|
-
const { exportName, features } = analysis;
|
|
161
|
-
const method = analysis.method.toLowerCase();
|
|
162
|
-
const validators = generateValidators(exportName, features);
|
|
163
|
-
let serviceResolution = "";
|
|
164
|
-
if (features.hasServices || features.hasDatabase) serviceResolution = `
|
|
165
|
-
const services = await serviceDiscovery.register(${exportName}.services);
|
|
166
|
-
${features.hasDatabase ? `const db = ${exportName}.databaseService
|
|
167
|
-
? (await serviceDiscovery.register([${exportName}.databaseService]) as any)[${exportName}.databaseService.serviceName]
|
|
168
|
-
: undefined;` : "const db = undefined;"}`;
|
|
169
|
-
else serviceResolution = `
|
|
170
|
-
const services = {};
|
|
171
|
-
const db = undefined;`;
|
|
172
|
-
let authCode = "";
|
|
173
|
-
if (features.hasAuth) authCode = `
|
|
174
|
-
// Authentication
|
|
175
|
-
const session = await ${exportName}.getSession({
|
|
176
|
-
services,
|
|
177
|
-
logger,
|
|
178
|
-
header,
|
|
179
|
-
cookie,
|
|
180
|
-
...(db !== undefined && { db }),
|
|
181
|
-
} as any);
|
|
182
|
-
|
|
183
|
-
const isAuthorized = await ${exportName}.authorize({
|
|
184
|
-
header,
|
|
185
|
-
cookie,
|
|
186
|
-
services,
|
|
187
|
-
logger,
|
|
188
|
-
session,
|
|
189
|
-
} as any);
|
|
190
|
-
|
|
191
|
-
if (!isAuthorized) {
|
|
192
|
-
return c.json({ error: 'Unauthorized' }, 401);
|
|
193
|
-
}`;
|
|
194
|
-
let eventCode = "";
|
|
195
|
-
if (features.hasEvents) eventCode = `
|
|
196
|
-
// Publish events on success
|
|
197
|
-
if (Endpoint.isSuccessStatus(${exportName}.status)) {
|
|
198
|
-
await (publishConstructEvents as any)(
|
|
199
|
-
${exportName},
|
|
200
|
-
result,
|
|
201
|
-
serviceDiscovery,
|
|
202
|
-
logger,
|
|
203
|
-
);
|
|
204
|
-
}`;
|
|
205
|
-
return `
|
|
206
|
-
// Standard handler: ${analysis.route} (${analysis.method})
|
|
207
|
-
app.${method}('${analysis.route}',${validators}
|
|
208
|
-
async (c) => {
|
|
209
|
-
const headerValues = c.req.header();
|
|
210
|
-
const header = Endpoint.createHeaders(headerValues);
|
|
211
|
-
const cookie = Endpoint.createCookies(headerValues.cookie);
|
|
212
|
-
${serviceResolution}
|
|
213
|
-
${authCode}
|
|
214
|
-
|
|
215
|
-
const responseBuilder = new ResponseBuilder();
|
|
216
|
-
const result = await ${exportName}.handler(
|
|
217
|
-
{
|
|
218
|
-
services,
|
|
219
|
-
logger,
|
|
220
|
-
body: ${features.hasBodyValidation ? "(c.req.valid as any)('json')" : "undefined"},
|
|
221
|
-
query: ${features.hasQueryValidation ? "(c.req.valid as any)('query')" : "undefined"},
|
|
222
|
-
params: ${features.hasParamValidation ? "(c.req.valid as any)('param')" : "undefined"},
|
|
223
|
-
session: ${features.hasAuth ? "session" : "undefined"},
|
|
224
|
-
header,
|
|
225
|
-
cookie,
|
|
226
|
-
auditor: undefined,
|
|
227
|
-
db,
|
|
228
|
-
} as any,
|
|
229
|
-
responseBuilder,
|
|
230
|
-
);
|
|
231
|
-
|
|
232
|
-
let data = result;
|
|
233
|
-
let metadata = responseBuilder.getMetadata();
|
|
234
|
-
|
|
235
|
-
if (Endpoint.hasMetadata(result)) {
|
|
236
|
-
data = result.data;
|
|
237
|
-
metadata = result.metadata;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
${features.hasOutputValidation ? `const output = ${exportName}.outputSchema
|
|
241
|
-
? await ${exportName}.parseOutput(data)
|
|
242
|
-
: data;` : "const output = data;"}
|
|
243
|
-
${eventCode}
|
|
244
|
-
|
|
245
|
-
const status = (metadata.status ?? ${exportName}.status) as any;
|
|
246
|
-
return c.json(output, status);
|
|
247
|
-
}
|
|
248
|
-
);`;
|
|
249
|
-
}
|
|
250
|
-
/**
|
|
251
|
-
* Generate validators.ts - Shared validator middleware factories
|
|
252
|
-
*/
|
|
253
|
-
function generateValidatorsFile(analyses) {
|
|
254
|
-
const needsBody = analyses.some((a) => a.features.hasBodyValidation);
|
|
255
|
-
const needsQuery = analyses.some((a) => a.features.hasQueryValidation);
|
|
256
|
-
const needsParams = analyses.some((a) => a.features.hasParamValidation);
|
|
257
|
-
if (!needsBody && !needsQuery && !needsParams) return `// No validators needed for this build\nexport {};\n`;
|
|
258
|
-
const exports = [];
|
|
259
|
-
const factories = [];
|
|
260
|
-
if (needsBody) {
|
|
261
|
-
exports.push("validateBody");
|
|
262
|
-
factories.push(`
|
|
263
|
-
export const validateBody = (endpoint: any) =>
|
|
264
|
-
validator('json', async (value, c) => {
|
|
265
|
-
if (!endpoint.input?.body) return undefined;
|
|
266
|
-
const parsed = await Endpoint.validate(endpoint.input.body, value);
|
|
267
|
-
if (parsed.issues) return c.json(parsed.issues, 422);
|
|
268
|
-
return parsed.value;
|
|
269
|
-
});`);
|
|
270
|
-
}
|
|
271
|
-
if (needsQuery) {
|
|
272
|
-
exports.push("validateQuery");
|
|
273
|
-
factories.push(`
|
|
274
|
-
export const validateQuery = (endpoint: any) =>
|
|
275
|
-
validator('query', async (_, c) => {
|
|
276
|
-
if (!endpoint.input?.query) return undefined;
|
|
277
|
-
const rawQuery = Object.fromEntries(new URL(c.req.url).searchParams);
|
|
278
|
-
const parsed = await Endpoint.validate(endpoint.input.query, rawQuery);
|
|
279
|
-
if (parsed.issues) return c.json(parsed.issues, 422);
|
|
280
|
-
return parsed.value;
|
|
281
|
-
});`);
|
|
282
|
-
}
|
|
283
|
-
if (needsParams) {
|
|
284
|
-
exports.push("validateParams");
|
|
285
|
-
factories.push(`
|
|
286
|
-
export const validateParams = (endpoint: any) =>
|
|
287
|
-
validator('param', async (params, c) => {
|
|
288
|
-
if (!endpoint.input?.params) return undefined;
|
|
289
|
-
const parsed = await Endpoint.validate(endpoint.input.params, params);
|
|
290
|
-
if (parsed.issues) return c.json(parsed.issues, 422);
|
|
291
|
-
return parsed.value;
|
|
292
|
-
});`);
|
|
293
|
-
}
|
|
294
|
-
return `/**
|
|
295
|
-
* Generated validator middleware factories
|
|
296
|
-
* Shared across all endpoint tiers that need validation
|
|
297
|
-
*/
|
|
298
|
-
import { validator } from 'hono/validator';
|
|
299
|
-
import { Endpoint } from '@geekmidas/constructs/endpoints';
|
|
300
|
-
${factories.join("\n")}
|
|
301
|
-
`;
|
|
302
|
-
}
|
|
303
|
-
/**
|
|
304
|
-
* Generate a standalone minimal endpoint file
|
|
305
|
-
*/
|
|
306
|
-
function generateMinimalEndpointFile(analysis, endpointImport) {
|
|
307
|
-
const { exportName, features } = analysis;
|
|
308
|
-
const needsValidators = features.hasBodyValidation || features.hasQueryValidation || features.hasParamValidation;
|
|
309
|
-
const validatorImport = needsValidators ? generateValidatorImportsForEndpoint(analysis) : "";
|
|
310
|
-
const handler = generateMinimalHandler(analysis);
|
|
311
|
-
return `/**
|
|
312
|
-
* Minimal endpoint: ${analysis.route} (${analysis.method})
|
|
313
|
-
* Near-raw-Hono performance
|
|
314
|
-
*/
|
|
315
|
-
import type { Hono } from 'hono';
|
|
316
|
-
import type { Logger } from '@geekmidas/logger';
|
|
317
|
-
import { Endpoint } from '@geekmidas/constructs/endpoints';
|
|
318
|
-
${validatorImport}
|
|
319
|
-
import { ${exportName} } from '${endpointImport.importPath}';
|
|
320
|
-
|
|
321
|
-
export function setup${capitalize(exportName)}(
|
|
322
|
-
app: Hono,
|
|
323
|
-
logger: Logger,
|
|
324
|
-
): void {
|
|
325
|
-
${handler}
|
|
326
|
-
}
|
|
327
|
-
`;
|
|
328
|
-
}
|
|
329
|
-
/**
|
|
330
|
-
* Generate a standalone standard endpoint file
|
|
331
|
-
*/
|
|
332
|
-
function generateStandardEndpointFile(analysis, endpointImport) {
|
|
333
|
-
const { exportName, features } = analysis;
|
|
334
|
-
const needsValidators = features.hasBodyValidation || features.hasQueryValidation || features.hasParamValidation;
|
|
335
|
-
const validatorImport = needsValidators ? generateValidatorImportsForEndpoint(analysis) : "";
|
|
336
|
-
const eventsImport = features.hasEvents ? `import { publishConstructEvents } from '@geekmidas/constructs/endpoints';` : "";
|
|
337
|
-
const handler = generateStandardHandler(analysis);
|
|
338
|
-
return `/**
|
|
339
|
-
* Standard endpoint: ${analysis.route} (${analysis.method})
|
|
340
|
-
* Auth and/or services enabled
|
|
341
|
-
*/
|
|
342
|
-
import type { Hono } from 'hono';
|
|
343
|
-
import type { Logger } from '@geekmidas/logger';
|
|
344
|
-
import type { ServiceDiscovery } from '@geekmidas/services';
|
|
345
|
-
import { Endpoint, ResponseBuilder } from '@geekmidas/constructs/endpoints';
|
|
346
|
-
${eventsImport}
|
|
347
|
-
${validatorImport}
|
|
348
|
-
import { ${exportName} } from '${endpointImport.importPath}';
|
|
349
|
-
|
|
350
|
-
export function setup${capitalize(exportName)}(
|
|
351
|
-
app: Hono,
|
|
352
|
-
serviceDiscovery: ServiceDiscovery<any, any>,
|
|
353
|
-
logger: Logger,
|
|
354
|
-
): void {
|
|
355
|
-
${handler}
|
|
356
|
-
}
|
|
357
|
-
`;
|
|
358
|
-
}
|
|
359
|
-
/**
|
|
360
|
-
* Generate a standalone full endpoint file
|
|
361
|
-
*/
|
|
362
|
-
function generateFullEndpointFile(analysis, endpointImport) {
|
|
363
|
-
const { exportName } = analysis;
|
|
364
|
-
return `/**
|
|
365
|
-
* Full endpoint: ${analysis.route} (${analysis.method})
|
|
366
|
-
* Complex features: audits, RLS, rate limiting
|
|
367
|
-
*/
|
|
368
|
-
import type { Hono } from 'hono';
|
|
369
|
-
import type { ServiceDiscovery } from '@geekmidas/services';
|
|
370
|
-
import { HonoEndpoint } from '@geekmidas/constructs/hono';
|
|
371
|
-
import { ${exportName} } from '${endpointImport.importPath}';
|
|
372
|
-
|
|
373
|
-
export function setup${capitalize(exportName)}(
|
|
374
|
-
app: Hono,
|
|
375
|
-
serviceDiscovery: ServiceDiscovery<any, any>,
|
|
376
|
-
openApiOptions: any,
|
|
377
|
-
): void {
|
|
378
|
-
HonoEndpoint.addRoutes([${exportName}] as any, serviceDiscovery, app, openApiOptions);
|
|
379
|
-
}
|
|
380
|
-
`;
|
|
381
|
-
}
|
|
382
|
-
/**
|
|
383
|
-
* Generate tier index file that imports and calls all endpoint setup functions
|
|
384
|
-
*/
|
|
385
|
-
function generateTierIndexFile(tier, analyses) {
|
|
386
|
-
const tierEndpoints = analyses.filter((a) => a.tier === tier);
|
|
387
|
-
if (tierEndpoints.length === 0) {
|
|
388
|
-
if (tier === "minimal") return `// No minimal-tier endpoints in this build
|
|
389
|
-
import type { Hono } from 'hono';
|
|
390
|
-
import type { Logger } from '@geekmidas/logger';
|
|
391
|
-
|
|
392
|
-
export function setupMinimalEndpoints(
|
|
393
|
-
_app: Hono,
|
|
394
|
-
_logger: Logger,
|
|
395
|
-
): void {
|
|
396
|
-
// No minimal endpoints
|
|
397
|
-
}
|
|
398
|
-
`;
|
|
399
|
-
if (tier === "standard") return `// No standard-tier endpoints in this build
|
|
400
|
-
import type { Hono } from 'hono';
|
|
401
|
-
import type { Logger } from '@geekmidas/logger';
|
|
402
|
-
import type { ServiceDiscovery } from '@geekmidas/services';
|
|
403
|
-
|
|
404
|
-
export function setupStandardEndpoints(
|
|
405
|
-
_app: Hono,
|
|
406
|
-
_serviceDiscovery: ServiceDiscovery<any, any>,
|
|
407
|
-
_logger: Logger,
|
|
408
|
-
): void {
|
|
409
|
-
// No standard endpoints
|
|
410
|
-
}
|
|
411
|
-
`;
|
|
412
|
-
return `// No full-tier endpoints in this build
|
|
413
|
-
import type { Hono } from 'hono';
|
|
414
|
-
import type { ServiceDiscovery } from '@geekmidas/services';
|
|
415
|
-
|
|
416
|
-
export function setupFullEndpoints(
|
|
417
|
-
_app: Hono,
|
|
418
|
-
_serviceDiscovery: ServiceDiscovery<any, any>,
|
|
419
|
-
_enableOpenApi: boolean,
|
|
420
|
-
): void {
|
|
421
|
-
// No full endpoints
|
|
422
|
-
}
|
|
423
|
-
`;
|
|
424
|
-
}
|
|
425
|
-
const imports = tierEndpoints.map((a) => `import { setup${capitalize(a.exportName)} } from './${a.exportName}.js';`).join("\n");
|
|
426
|
-
if (tier === "minimal") {
|
|
427
|
-
const calls$1 = tierEndpoints.map((a) => ` setup${capitalize(a.exportName)}(app, logger);`).join("\n");
|
|
428
|
-
return `/**
|
|
429
|
-
* Minimal-tier endpoint index (${tierEndpoints.length} endpoints)
|
|
430
|
-
* Near-raw-Hono performance
|
|
431
|
-
*/
|
|
432
|
-
import type { Hono } from 'hono';
|
|
433
|
-
import type { Logger } from '@geekmidas/logger';
|
|
434
|
-
${imports}
|
|
435
|
-
|
|
436
|
-
export function setupMinimalEndpoints(
|
|
437
|
-
app: Hono,
|
|
438
|
-
logger: Logger,
|
|
439
|
-
): void {
|
|
440
|
-
${calls$1}
|
|
441
|
-
}
|
|
442
|
-
`;
|
|
443
|
-
}
|
|
444
|
-
if (tier === "standard") {
|
|
445
|
-
const calls$1 = tierEndpoints.map((a) => ` setup${capitalize(a.exportName)}(app, serviceDiscovery, logger);`).join("\n");
|
|
446
|
-
return `/**
|
|
447
|
-
* Standard-tier endpoint index (${tierEndpoints.length} endpoints)
|
|
448
|
-
* Auth and/or services enabled
|
|
449
|
-
*/
|
|
450
|
-
import type { Hono } from 'hono';
|
|
451
|
-
import type { Logger } from '@geekmidas/logger';
|
|
452
|
-
import type { ServiceDiscovery } from '@geekmidas/services';
|
|
453
|
-
${imports}
|
|
454
|
-
|
|
455
|
-
export function setupStandardEndpoints(
|
|
456
|
-
app: Hono,
|
|
457
|
-
serviceDiscovery: ServiceDiscovery<any, any>,
|
|
458
|
-
logger: Logger,
|
|
459
|
-
): void {
|
|
460
|
-
${calls$1}
|
|
461
|
-
}
|
|
462
|
-
`;
|
|
463
|
-
}
|
|
464
|
-
const calls = tierEndpoints.map((a) => ` setup${capitalize(a.exportName)}(app, serviceDiscovery, openApiOptions);`).join("\n");
|
|
465
|
-
return `/**
|
|
466
|
-
* Full-tier endpoint index (${tierEndpoints.length} endpoints)
|
|
467
|
-
* Complex features: audits, RLS, rate limiting
|
|
468
|
-
*/
|
|
469
|
-
import type { Hono } from 'hono';
|
|
470
|
-
import type { ServiceDiscovery } from '@geekmidas/services';
|
|
471
|
-
${imports}
|
|
472
|
-
|
|
473
|
-
export function setupFullEndpoints(
|
|
474
|
-
app: Hono,
|
|
475
|
-
serviceDiscovery: ServiceDiscovery<any, any>,
|
|
476
|
-
enableOpenApi: boolean,
|
|
477
|
-
): void {
|
|
478
|
-
const openApiOptions: any = enableOpenApi ? {
|
|
479
|
-
docsPath: '/__docs',
|
|
480
|
-
openApiOptions: {
|
|
481
|
-
title: 'API Documentation',
|
|
482
|
-
version: '1.0.0',
|
|
483
|
-
description: 'Generated API documentation'
|
|
484
|
-
}
|
|
485
|
-
} : { docsPath: false };
|
|
486
|
-
|
|
487
|
-
${calls}
|
|
488
|
-
}
|
|
489
|
-
`;
|
|
490
|
-
}
|
|
491
|
-
/**
|
|
492
|
-
* Generate validator imports for a single endpoint
|
|
493
|
-
*/
|
|
494
|
-
function generateValidatorImportsForEndpoint(analysis) {
|
|
495
|
-
const imports = [];
|
|
496
|
-
if (analysis.features.hasBodyValidation) imports.push("validateBody");
|
|
497
|
-
if (analysis.features.hasQueryValidation) imports.push("validateQuery");
|
|
498
|
-
if (analysis.features.hasParamValidation) imports.push("validateParams");
|
|
499
|
-
if (imports.length === 0) return "";
|
|
500
|
-
return `import { ${imports.join(", ")} } from '../validators.js';`;
|
|
501
|
-
}
|
|
502
|
-
/**
|
|
503
|
-
* Capitalize first letter
|
|
504
|
-
*/
|
|
505
|
-
function capitalize(str) {
|
|
506
|
-
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
507
|
-
}
|
|
508
|
-
/**
|
|
509
|
-
* Generate all endpoint files with nested folder structure (per-endpoint files)
|
|
510
|
-
*/
|
|
511
|
-
function generateEndpointFilesNested(analyses, endpointImports) {
|
|
512
|
-
const files = {
|
|
513
|
-
"validators.ts": generateValidatorsFile(analyses),
|
|
514
|
-
"minimal/index.ts": generateTierIndexFile("minimal", analyses),
|
|
515
|
-
"standard/index.ts": generateTierIndexFile("standard", analyses),
|
|
516
|
-
"full/index.ts": generateTierIndexFile("full", analyses),
|
|
517
|
-
"index.ts": generateNestedIndexFile(analyses)
|
|
518
|
-
};
|
|
519
|
-
for (const analysis of analyses) {
|
|
520
|
-
const endpointImport = endpointImports.find((i) => i.exportName === analysis.exportName);
|
|
521
|
-
if (!endpointImport) continue;
|
|
522
|
-
const fileName = `${analysis.tier}/${analysis.exportName}.ts`;
|
|
523
|
-
switch (analysis.tier) {
|
|
524
|
-
case "minimal":
|
|
525
|
-
files[fileName] = generateMinimalEndpointFile(analysis, endpointImport);
|
|
526
|
-
break;
|
|
527
|
-
case "standard":
|
|
528
|
-
files[fileName] = generateStandardEndpointFile(analysis, endpointImport);
|
|
529
|
-
break;
|
|
530
|
-
case "full":
|
|
531
|
-
files[fileName] = generateFullEndpointFile(analysis, endpointImport);
|
|
532
|
-
break;
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
return files;
|
|
536
|
-
}
|
|
537
|
-
/**
|
|
538
|
-
* Generate index.ts for nested structure
|
|
539
|
-
*/
|
|
540
|
-
function generateNestedIndexFile(analyses) {
|
|
541
|
-
const minimalCount = analyses.filter((a) => a.tier === "minimal").length;
|
|
542
|
-
const standardCount = analyses.filter((a) => a.tier === "standard").length;
|
|
543
|
-
const fullCount = analyses.filter((a) => a.tier === "full").length;
|
|
544
|
-
return `/**
|
|
545
|
-
* Generated optimized endpoints
|
|
546
|
-
*
|
|
547
|
-
* Build-time optimization tiers:
|
|
548
|
-
* - minimal: ${minimalCount} endpoints (near-raw-Hono)
|
|
549
|
-
* - standard: ${standardCount} endpoints (auth/services)
|
|
550
|
-
* - full: ${fullCount} endpoints (audits/rls/rate-limit)
|
|
551
|
-
*/
|
|
552
|
-
import type { EnvironmentParser } from '@geekmidas/envkit';
|
|
553
|
-
import type { Logger } from '@geekmidas/logger';
|
|
554
|
-
import type { Hono } from 'hono';
|
|
555
|
-
import { ServiceDiscovery } from '@geekmidas/services';
|
|
556
|
-
import { setupMinimalEndpoints } from './minimal/index.js';
|
|
557
|
-
import { setupStandardEndpoints } from './standard/index.js';
|
|
558
|
-
import { setupFullEndpoints } from './full/index.js';
|
|
559
|
-
|
|
560
|
-
export async function setupEndpoints(
|
|
561
|
-
app: Hono,
|
|
562
|
-
envParser: EnvironmentParser<any>,
|
|
563
|
-
logger: Logger,
|
|
564
|
-
enableOpenApi: boolean = false,
|
|
565
|
-
): Promise<void> {
|
|
566
|
-
const serviceDiscovery = ServiceDiscovery.getInstance(envParser);
|
|
567
|
-
|
|
568
|
-
// Minimal handlers (${minimalCount} endpoints) - near-raw-Hono performance
|
|
569
|
-
setupMinimalEndpoints(app, logger);
|
|
570
|
-
|
|
571
|
-
// Standard handlers (${standardCount} endpoints) - auth/services
|
|
572
|
-
setupStandardEndpoints(app, serviceDiscovery, logger);
|
|
573
|
-
|
|
574
|
-
// Full handlers (${fullCount} endpoints) - audits/rls/rate-limit
|
|
575
|
-
setupFullEndpoints(app, serviceDiscovery, enableOpenApi);
|
|
576
|
-
|
|
577
|
-
// Add Swagger UI if OpenAPI is enabled
|
|
578
|
-
if (enableOpenApi) {
|
|
579
|
-
try {
|
|
580
|
-
const { swaggerUI } = await import('@hono/swagger-ui');
|
|
581
|
-
app.get('/__docs/ui', swaggerUI({ url: '/__docs' }));
|
|
582
|
-
} catch {
|
|
583
|
-
// @hono/swagger-ui not installed, skip Swagger UI
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
`;
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
//#endregion
|
|
591
|
-
//#region src/generators/EndpointGenerator.ts
|
|
592
|
-
/**
|
|
593
|
-
* How a generated entry gets its logger and environment parser.
|
|
594
|
-
*
|
|
595
|
-
* From the surface it serves, when there is one: the surface holds both as
|
|
596
|
-
* *objects*, and discovery already recorded which module declares it, so the
|
|
597
|
-
* entry imports that module and reads them off it.
|
|
598
|
-
*
|
|
599
|
-
* The alternative — the shape this replaces — was naming both in config as
|
|
600
|
-
* module paths, `envParser: './config/env#envParser'`, and printing an import
|
|
601
|
-
* for each. Two strings per app, checked by nothing, describing files the
|
|
602
|
-
* surface was already holding the contents of.
|
|
603
|
-
*
|
|
604
|
-
* Falls back to the configured paths for an app that declares no surface.
|
|
605
|
-
*/
|
|
606
|
-
function runtimeFor(context, fromDir, owner) {
|
|
607
|
-
const module = owner ? context.owners?.[owner] ?? context.surface?.module : context.surface?.module;
|
|
608
|
-
if (module) {
|
|
609
|
-
const specifier = importSpecifier(fromDir, module.specifier);
|
|
610
|
-
return {
|
|
611
|
-
imports: `import { ${module.exportName} as __surface } from '${specifier}';`,
|
|
612
|
-
bindings: [
|
|
613
|
-
"// What this construct was declared with. Objects, not module paths:",
|
|
614
|
-
"// the entry imports the owner, so there is nothing to print.",
|
|
615
|
-
"const envParser = __surface.envParser;",
|
|
616
|
-
"const logger = __surface.logger;"
|
|
617
|
-
].join("\n")
|
|
618
|
-
};
|
|
619
|
-
}
|
|
620
|
-
throw new Error(`No owner to take a logger and an environment parser from${owner ? ` for "${owner}"` : ""}. Build it from a \`RestApi\` or a \`Worker\`.`);
|
|
621
|
-
}
|
|
622
|
-
/** A relative specifier ESM will accept: always prefixed, never bare. */
|
|
623
|
-
function importSpecifier(fromDir, target) {
|
|
624
|
-
const rel = relative(fromDir, target).replace(/\.ts$/, ".js");
|
|
625
|
-
return rel.startsWith(".") ? rel : `./${rel}`;
|
|
626
|
-
}
|
|
627
|
-
/**
|
|
628
|
-
* CORS for a surface, derived rather than hand-written.
|
|
629
|
-
*
|
|
630
|
-
* Who may call a surface is already in the graph — every construct that
|
|
631
|
-
* declared an edge to it — and reaches the process as one comma-separated
|
|
632
|
-
* value. An application that maintained this by hand maintained a list that was
|
|
633
|
-
* wrong in two directions at once: it trusted origins nothing was serving, and
|
|
634
|
-
* it could not name hosts a deploy had not chosen yet.
|
|
635
|
-
*
|
|
636
|
-
* Only the parts a graph cannot answer are configurable, and every one has a
|
|
637
|
-
* default: a surface that says nothing about CORS still gets it.
|
|
638
|
-
*/
|
|
639
|
-
function corsFor(surface) {
|
|
640
|
-
if (!surface) return {
|
|
641
|
-
imports: "",
|
|
642
|
-
setup: ""
|
|
643
|
-
};
|
|
644
|
-
const cors = surface.cors ?? {};
|
|
645
|
-
const allowHeaders = [
|
|
646
|
-
"content-type",
|
|
647
|
-
"authorization",
|
|
648
|
-
...cors.allowHeaders ?? []
|
|
649
|
-
];
|
|
650
|
-
return {
|
|
651
|
-
imports: `import { cors } from 'hono/cors';`,
|
|
652
|
-
setup: `
|
|
653
|
-
// Who may call ${surface.id}, read off the graph rather than listed here.
|
|
654
|
-
{
|
|
655
|
-
const { origins } = envParser
|
|
656
|
-
.create((get) => ({
|
|
657
|
-
origins: get('${surface.trustedOriginsKey}')
|
|
658
|
-
.string()
|
|
659
|
-
.default('')
|
|
660
|
-
.transform((value: string) =>
|
|
661
|
-
value.split(',').map((origin) => origin.trim()).filter(Boolean),
|
|
662
|
-
),
|
|
663
|
-
}))
|
|
664
|
-
.parse();
|
|
665
|
-
|
|
666
|
-
honoApp.use('*', cors({
|
|
667
|
-
origin: origins,
|
|
668
|
-
allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
|
|
669
|
-
allowHeaders: ${JSON.stringify(allowHeaders)},${cors.exposeHeaders ? `\n exposeHeaders: ${JSON.stringify(cors.exposeHeaders)},` : ""}
|
|
670
|
-
credentials: ${cors.credentials ?? true},
|
|
671
|
-
maxAge: ${cors.maxAge ?? 86400},
|
|
672
|
-
}));
|
|
673
|
-
}
|
|
674
|
-
`
|
|
675
|
-
};
|
|
676
|
-
}
|
|
677
|
-
var EndpointGenerator = class extends ConstructGenerator {
|
|
678
|
-
isConstruct(value) {
|
|
679
|
-
return Endpoint.isEndpoint(value);
|
|
680
|
-
}
|
|
681
|
-
async build(context, constructs, outputDir, options) {
|
|
682
|
-
const provider = options?.provider || "aws-apigatewayv2";
|
|
683
|
-
const enableOpenApi = options?.enableOpenApi || false;
|
|
684
|
-
const logger = console;
|
|
685
|
-
const routes = [];
|
|
686
|
-
if (constructs.length === 0) return routes;
|
|
687
|
-
if (provider === "server") {
|
|
688
|
-
await this.generateEndpointsFile(outputDir, constructs, context);
|
|
689
|
-
const appFile = await this.generateAppFile(outputDir, context);
|
|
690
|
-
routes.push({
|
|
691
|
-
path: "*",
|
|
692
|
-
method: "ALL",
|
|
693
|
-
handler: relative(process.cwd(), appFile),
|
|
694
|
-
authorizer: "none"
|
|
695
|
-
});
|
|
696
|
-
logger.log(`Generated server with ${constructs.length} endpoints${enableOpenApi ? " (OpenAPI enabled)" : ""}`);
|
|
697
|
-
} else if (provider === "aws-lambda") {
|
|
698
|
-
const routesDir = join(outputDir, "routes");
|
|
699
|
-
await mkdir(routesDir, { recursive: true });
|
|
700
|
-
for (const { key, construct, path } of constructs) {
|
|
701
|
-
const handlerFile = await this.generateHandlerFile(routesDir, path.relative, key, "aws-apigatewayv2", construct, context);
|
|
702
|
-
const routeInfo = {
|
|
703
|
-
path: construct._path,
|
|
704
|
-
method: construct.method,
|
|
705
|
-
handler: relative(process.cwd(), handlerFile).replace(/\.ts$/, ".handler"),
|
|
706
|
-
timeout: construct.timeout,
|
|
707
|
-
memorySize: construct.memorySize,
|
|
708
|
-
environment: await construct.getEnvironment({ markOptional: context.markOptional }),
|
|
709
|
-
dependencies: construct.constructs,
|
|
710
|
-
authorizer: construct.authorizer?.name ?? "none"
|
|
711
|
-
};
|
|
712
|
-
routes.push(routeInfo);
|
|
713
|
-
logger.log(`Generated handler for ${routeInfo.method} ${routeInfo.path}`);
|
|
714
|
-
}
|
|
715
|
-
} else for (const { key, construct, path } of constructs) {
|
|
716
|
-
const handlerFile = await this.generateHandlerFile(outputDir, path.relative, key, provider, construct, context);
|
|
717
|
-
const routeInfo = {
|
|
718
|
-
path: construct._path,
|
|
719
|
-
method: construct.method,
|
|
720
|
-
handler: relative(process.cwd(), handlerFile).replace(/\.ts$/, ".handler"),
|
|
721
|
-
timeout: construct.timeout,
|
|
722
|
-
memorySize: construct.memorySize,
|
|
723
|
-
environment: await construct.getEnvironment({ markOptional: context.markOptional }),
|
|
724
|
-
dependencies: construct.constructs,
|
|
725
|
-
authorizer: construct.authorizer?.name ?? "none"
|
|
726
|
-
};
|
|
727
|
-
routes.push(routeInfo);
|
|
728
|
-
logger.log(`Generated handler for ${routeInfo.method} ${routeInfo.path}`);
|
|
729
|
-
}
|
|
730
|
-
return routes;
|
|
731
|
-
}
|
|
732
|
-
async generateHandlerFile(outputDir, sourceFile, exportName, provider, _endpoint, context) {
|
|
733
|
-
const handlerFileName = `${exportName}.ts`;
|
|
734
|
-
const handlerPath = join(outputDir, handlerFileName);
|
|
735
|
-
const relativePath = relative(dirname(handlerPath), sourceFile);
|
|
736
|
-
const importPath = relativePath.replace(/\.ts$/, ".js");
|
|
737
|
-
let content;
|
|
738
|
-
switch (provider) {
|
|
739
|
-
case "aws-apigatewayv1":
|
|
740
|
-
content = this.generateAWSApiGatewayV1Handler(importPath, exportName, context.storageDrivers);
|
|
741
|
-
break;
|
|
742
|
-
case "aws-apigatewayv2":
|
|
743
|
-
content = this.generateAWSApiGatewayV2Handler(importPath, exportName, context.storageDrivers);
|
|
744
|
-
break;
|
|
745
|
-
case "server":
|
|
746
|
-
content = this.generateServerHandler(importPath, exportName);
|
|
747
|
-
break;
|
|
748
|
-
default: throw new Error(`Unsupported provider: ${provider}`);
|
|
749
|
-
}
|
|
750
|
-
await writeFile(handlerPath, content);
|
|
751
|
-
return handlerPath;
|
|
752
|
-
}
|
|
753
|
-
async generateEndpointsFile(outputDir, endpoints, context) {
|
|
754
|
-
const endpointsFileName = "endpoints.ts";
|
|
755
|
-
const endpointsPath = join(outputDir, endpointsFileName);
|
|
756
|
-
const importsByFile = /* @__PURE__ */ new Map();
|
|
757
|
-
for (const { path, key } of endpoints) {
|
|
758
|
-
const relativePath = relative(dirname(endpointsPath), path.relative);
|
|
759
|
-
const importPath = relativePath.replace(/\.ts$/, ".js");
|
|
760
|
-
if (!importsByFile.has(importPath)) importsByFile.set(importPath, []);
|
|
761
|
-
importsByFile.get(importPath)?.push(key);
|
|
762
|
-
}
|
|
763
|
-
const endpointImports = Array.from(importsByFile.entries()).map(([importPath, exports]) => `import { ${exports.join(", ")} } from '${importPath}';`).join("\n");
|
|
764
|
-
const allExportNames = endpoints.map(({ key }) => key);
|
|
765
|
-
if (context.production?.enabled && context.production.optimizedHandlers) return this.generateOptimizedEndpointsFile(endpointsPath, endpoints, endpointImports, allExportNames);
|
|
766
|
-
const content = `import type { EnvironmentParser } from '@geekmidas/envkit';
|
|
767
|
-
import type { Logger } from '@geekmidas/logger';
|
|
768
|
-
import { HonoEndpoint } from '@geekmidas/constructs/hono';
|
|
769
|
-
import { Endpoint } from '@geekmidas/constructs/endpoints';
|
|
770
|
-
import { ServiceDiscovery } from '@geekmidas/services';
|
|
771
|
-
import type { Hono } from 'hono';
|
|
772
|
-
${endpointImports}
|
|
773
|
-
|
|
774
|
-
export const endpoints: Endpoint<any, any, any, any, any, any, any, any, any, any, any, any, any, any>[] = [
|
|
775
|
-
${allExportNames.join(",\n ")}
|
|
776
|
-
];
|
|
777
|
-
|
|
778
|
-
export async function setupEndpoints(
|
|
779
|
-
app: Hono,
|
|
780
|
-
envParser: EnvironmentParser<any>,
|
|
781
|
-
logger: Logger,
|
|
782
|
-
enableOpenApi: boolean = true,
|
|
783
|
-
): Promise<void> {
|
|
784
|
-
const serviceDiscovery = ServiceDiscovery.getInstance(envParser);
|
|
785
|
-
|
|
786
|
-
// Configure OpenAPI options based on enableOpenApi flag
|
|
787
|
-
const openApiOptions: any = enableOpenApi ? {
|
|
788
|
-
docsPath: '/__docs',
|
|
789
|
-
openApiOptions: {
|
|
790
|
-
title: 'API Documentation',
|
|
791
|
-
version: '1.0.0',
|
|
792
|
-
description: 'Generated API documentation'
|
|
793
|
-
}
|
|
794
|
-
} : { docsPath: false };
|
|
795
|
-
|
|
796
|
-
HonoEndpoint.addRoutes(endpoints, serviceDiscovery, app, openApiOptions);
|
|
797
|
-
|
|
798
|
-
// Add Swagger UI if OpenAPI is enabled
|
|
799
|
-
if (enableOpenApi) {
|
|
800
|
-
try {
|
|
801
|
-
const { swaggerUI } = await import('@hono/swagger-ui');
|
|
802
|
-
app.get('/__docs/ui', swaggerUI({ url: '/__docs' }));
|
|
803
|
-
} catch {
|
|
804
|
-
// @hono/swagger-ui not installed, skip Swagger UI
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
`;
|
|
809
|
-
await writeFile(endpointsPath, content);
|
|
810
|
-
return endpointsPath;
|
|
811
|
-
}
|
|
812
|
-
/**
|
|
813
|
-
* Generate optimized endpoints files with nested folder structure (per-endpoint files)
|
|
814
|
-
*/
|
|
815
|
-
async generateOptimizedEndpointsFile(endpointsPath, endpoints, _endpointImports, _allExportNames) {
|
|
816
|
-
const logger = console;
|
|
817
|
-
const outputDir = dirname(endpointsPath);
|
|
818
|
-
const endpointsDir = join(outputDir, "endpoints");
|
|
819
|
-
await mkdir(join(endpointsDir, "minimal"), { recursive: true });
|
|
820
|
-
await mkdir(join(endpointsDir, "standard"), { recursive: true });
|
|
821
|
-
await mkdir(join(endpointsDir, "full"), { recursive: true });
|
|
822
|
-
const analyses = endpoints.map(({ key, construct }) => analyzeEndpoint(construct, key));
|
|
823
|
-
const endpointImports = endpoints.map(({ key, path }) => {
|
|
824
|
-
const tierDir = join(endpointsDir, "standard");
|
|
825
|
-
const relativePath = relative(tierDir, path.relative);
|
|
826
|
-
const importPath = relativePath.replace(/\.ts$/, ".js");
|
|
827
|
-
return {
|
|
828
|
-
exportName: key,
|
|
829
|
-
importPath
|
|
830
|
-
};
|
|
831
|
-
});
|
|
832
|
-
const summary = summarizeAnalysis(analyses);
|
|
833
|
-
logger.log(`\n📊 Endpoint Analysis:`);
|
|
834
|
-
logger.log(` Total: ${summary.total} endpoints`);
|
|
835
|
-
logger.log(` - Minimal (near-raw-Hono): ${summary.byTier.minimal} endpoints`);
|
|
836
|
-
logger.log(` - Standard (auth/services): ${summary.byTier.standard} endpoints`);
|
|
837
|
-
logger.log(` - Full (audits/rls/rate-limit): ${summary.byTier.full} endpoints`);
|
|
838
|
-
const files = generateEndpointFilesNested(analyses, endpointImports);
|
|
839
|
-
for (const [filename, content] of Object.entries(files)) {
|
|
840
|
-
const filePath = join(endpointsDir, filename);
|
|
841
|
-
await mkdir(dirname(filePath), { recursive: true });
|
|
842
|
-
await writeFile(filePath, content);
|
|
843
|
-
}
|
|
844
|
-
const endpointFiles = Object.keys(files).filter((f) => !f.endsWith("index.ts") && !f.endsWith("validators.ts")).length;
|
|
845
|
-
const indexFiles = Object.keys(files).filter((f) => f.endsWith("index.ts")).length;
|
|
846
|
-
logger.log(` Generated ${endpointFiles} endpoint files + ${indexFiles} index files + validators.ts`);
|
|
847
|
-
return join(endpointsDir, "index.ts");
|
|
848
|
-
}
|
|
849
|
-
async generateAppFile(outputDir, context) {
|
|
850
|
-
if (context.production?.enabled) return this.generateProductionAppFile(outputDir, context);
|
|
851
|
-
const appFileName = "app.ts";
|
|
852
|
-
const appPath = join(outputDir, appFileName);
|
|
853
|
-
const runtime = runtimeFor(context, dirname(appPath));
|
|
854
|
-
const telescopeEnabled = context.telescope?.enabled;
|
|
855
|
-
const telescopeWebSocketEnabled = context.telescope?.websocket;
|
|
856
|
-
const usesExternalTelescope = !!context.telescope?.telescopePath;
|
|
857
|
-
const studioEnabled = context.studio?.enabled;
|
|
858
|
-
const usesExternalStudio = !!context.studio?.studioPath;
|
|
859
|
-
const studioFromDatabase = !usesExternalStudio && !!context.studio?.database;
|
|
860
|
-
const telescopeFromSurface = !!context.surface?.module && !usesExternalTelescope;
|
|
861
|
-
let telescopeImports = "";
|
|
862
|
-
if (telescopeEnabled) if (telescopeFromSurface) telescopeImports = "import { createMiddleware, createUI } from '@geekmidas/telescope/hono';";
|
|
863
|
-
else if (usesExternalTelescope) {
|
|
864
|
-
const relativeTelescopePath = relative(dirname(appPath), context.telescope?.telescopePath);
|
|
865
|
-
telescopeImports = `import ${context.telescope?.telescopeImportPattern} from '${relativeTelescopePath}';
|
|
866
|
-
import { createMiddleware, createUI } from '@geekmidas/telescope/hono';`;
|
|
867
|
-
} else telescopeImports = `import { Telescope, InMemoryStorage } from '@geekmidas/telescope';
|
|
868
|
-
import { createMiddleware, createUI } from '@geekmidas/telescope/hono';`;
|
|
869
|
-
let studioImports = "";
|
|
870
|
-
if (studioEnabled) if (usesExternalStudio) {
|
|
871
|
-
const relativeStudioPath = relative(dirname(appPath), context.studio?.studioPath);
|
|
872
|
-
studioImports = `import ${context.studio?.studioImportPattern} from '${relativeStudioPath}';
|
|
873
|
-
import { createStudioApp } from '@geekmidas/studio/server/hono';`;
|
|
874
|
-
} else if (studioFromDatabase) {
|
|
875
|
-
const dbSpecifier = importSpecifier(dirname(appPath), context.studio.database.specifier);
|
|
876
|
-
studioImports = `import { snifferContext } from '@geekmidas/constructs';
|
|
877
|
-
import { Direction, InMemoryMonitoringStorage, Studio } from '@geekmidas/studio';
|
|
878
|
-
import { ${context.studio.database.exportName} as __studioDb } from '${dbSpecifier}';
|
|
879
|
-
import { createStudioApp } from '@geekmidas/studio/server/hono';`;
|
|
880
|
-
} else studioImports = "";
|
|
881
|
-
const cors = corsFor(context.surface);
|
|
882
|
-
let hooksImports = "";
|
|
883
|
-
let beforeSetupCall = "";
|
|
884
|
-
let afterSetupCall = "";
|
|
885
|
-
if (context.hooks?.serverHooksPath) {
|
|
886
|
-
const relativeHooksPath = relative(dirname(appPath), context.hooks.serverHooksPath);
|
|
887
|
-
hooksImports = `import * as serverHooks from '${relativeHooksPath}';`;
|
|
888
|
-
beforeSetupCall = `
|
|
889
|
-
// Call beforeSetup hook if defined
|
|
890
|
-
if (typeof serverHooks.beforeSetup === 'function') {
|
|
891
|
-
await serverHooks.beforeSetup(honoApp, { envParser, logger });
|
|
892
|
-
}
|
|
893
|
-
`;
|
|
894
|
-
afterSetupCall = `
|
|
895
|
-
// Call afterSetup hook if defined
|
|
896
|
-
if (typeof serverHooks.afterSetup === 'function') {
|
|
897
|
-
await serverHooks.afterSetup(honoApp, { envParser, logger });
|
|
898
|
-
}
|
|
899
|
-
`;
|
|
900
|
-
}
|
|
901
|
-
const telescopeWebSocketSetupCode = telescopeWebSocketEnabled ? `
|
|
902
|
-
// Setup WebSocket for real-time telescope updates
|
|
903
|
-
try {
|
|
904
|
-
const { createNodeWebSocket } = await import('@hono/node-ws');
|
|
905
|
-
const { injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app: honoApp });
|
|
906
|
-
// Add WebSocket route directly to main app (sub-app routes don't support WS upgrade)
|
|
907
|
-
honoApp.get('${context.telescope?.path}/ws', upgradeWebSocket(() => ({
|
|
908
|
-
onOpen: (_event: Event, ws: any) => {
|
|
909
|
-
telescope.addWsClient(ws);
|
|
910
|
-
},
|
|
911
|
-
onClose: (_event: Event, ws: any) => {
|
|
912
|
-
telescope.removeWsClient(ws);
|
|
913
|
-
},
|
|
914
|
-
onMessage: (event: MessageEvent, ws: any) => {
|
|
915
|
-
try {
|
|
916
|
-
const data = JSON.parse(event.data);
|
|
917
|
-
if (data.type === 'ping') {
|
|
918
|
-
ws.send(JSON.stringify({ type: 'pong' }));
|
|
919
|
-
}
|
|
920
|
-
} catch {
|
|
921
|
-
// Ignore invalid messages
|
|
922
|
-
}
|
|
923
|
-
},
|
|
924
|
-
})));
|
|
925
|
-
// Store injectWebSocket for server entry to call after serve()
|
|
926
|
-
(honoApp as any).__injectWebSocket = injectWebSocket;
|
|
927
|
-
logger.info('Telescope WebSocket enabled');
|
|
928
|
-
} catch (e) {
|
|
929
|
-
logger.warn({ error: e }, 'WebSocket support not available - install @hono/node-ws for real-time updates');
|
|
930
|
-
}
|
|
931
|
-
` : "";
|
|
932
|
-
let telescopeSetup = "";
|
|
933
|
-
if (telescopeEnabled) if (telescopeFromSurface || usesExternalTelescope) telescopeSetup = `
|
|
934
|
-
${telescopeWebSocketSetupCode}
|
|
935
|
-
// Add telescope middleware (before endpoints to capture all requests)
|
|
936
|
-
honoApp.use('*', createMiddleware(telescope));
|
|
937
|
-
|
|
938
|
-
// Mount telescope UI
|
|
939
|
-
const telescopeUI = createUI(telescope);
|
|
940
|
-
honoApp.route('${context.telescope?.path}', telescopeUI);
|
|
941
|
-
`;
|
|
942
|
-
else telescopeSetup = `
|
|
943
|
-
// Setup Telescope for debugging/monitoring
|
|
944
|
-
const telescopeStorage = new InMemoryStorage({ maxEntries: ${context.telescope?.maxEntries} });
|
|
945
|
-
const telescope = new Telescope({
|
|
946
|
-
enabled: true,
|
|
947
|
-
path: '${context.telescope?.path}',
|
|
948
|
-
ignorePatterns: ${JSON.stringify(context.telescope?.ignore)},
|
|
949
|
-
recordBody: ${context.telescope?.recordBody},
|
|
950
|
-
storage: telescopeStorage,
|
|
951
|
-
});
|
|
952
|
-
${telescopeWebSocketSetupCode}
|
|
953
|
-
// Add telescope middleware (before endpoints to capture all requests)
|
|
954
|
-
honoApp.use('*', createMiddleware(telescope));
|
|
955
|
-
|
|
956
|
-
// Mount telescope UI
|
|
957
|
-
const telescopeUI = createUI(telescope);
|
|
958
|
-
honoApp.route('${context.telescope?.path}', telescopeUI);
|
|
959
|
-
`;
|
|
960
|
-
let studioSetup = "";
|
|
961
|
-
if (studioEnabled && studioFromDatabase) studioSetup = `
|
|
962
|
-
// Studio, built from the declared database. The client comes from the
|
|
963
|
-
// construct, so what you inspect is by definition what the handlers write to.
|
|
964
|
-
const studio = new Studio({
|
|
965
|
-
monitoring: { storage: new InMemoryMonitoringStorage({ maxEntries: 100 }) },
|
|
966
|
-
data: {
|
|
967
|
-
db: await __studioDb.service.register({ envParser, context: snifferContext }),
|
|
968
|
-
cursor: { field: 'id', direction: Direction.Desc },
|
|
969
|
-
},
|
|
970
|
-
enabled: true,
|
|
971
|
-
});
|
|
972
|
-
|
|
973
|
-
const studioApp = createStudioApp(studio);
|
|
974
|
-
honoApp.route('${context.studio?.path}', studioApp);
|
|
975
|
-
`;
|
|
976
|
-
else if (studioEnabled && usesExternalStudio) studioSetup = `
|
|
977
|
-
// Mount Studio data browser UI
|
|
978
|
-
const studioApp = createStudioApp(studio);
|
|
979
|
-
honoApp.route('${context.studio?.path}', studioApp);
|
|
980
|
-
`;
|
|
981
|
-
const content = `/**
|
|
982
|
-
* Generated server application
|
|
983
|
-
*
|
|
984
|
-
* ⚠️ WARNING: This is for LOCAL DEVELOPMENT ONLY
|
|
985
|
-
* The subscriber polling mechanism is not production-ready.
|
|
986
|
-
* For production, use AWS Lambda with SQS/SNS event sources.
|
|
987
|
-
*/
|
|
988
|
-
import { Hono } from 'hono';
|
|
989
|
-
import type { Hono as HonoType } from 'hono';
|
|
990
|
-
import { setupEndpoints } from './endpoints.js';
|
|
991
|
-
import { setupSubscribers } from './subscribers.js';
|
|
992
|
-
import { setupQueues } from './queues.js';
|
|
993
|
-
import { setupCrons } from './crons.js';
|
|
994
|
-
${runtime.imports}
|
|
995
|
-
${telescopeImports}
|
|
996
|
-
${studioImports}
|
|
997
|
-
${hooksImports}
|
|
998
|
-
${cors.imports}
|
|
999
|
-
${context.storageDrivers?.imports ?? ""}
|
|
1000
|
-
|
|
1001
|
-
${runtime.bindings}
|
|
1002
|
-
${telescopeFromSurface ? "const telescope = __surface.telescope;" : ""}
|
|
1003
|
-
|
|
1004
|
-
${context.storageDrivers?.setup ? `// Which storage drivers exist is the entry point's decision: the scheme in
|
|
1005
|
-
// each injected URL picks one, so no construct and no application module names
|
|
1006
|
-
// a provider.
|
|
1007
|
-
${context.storageDrivers.setup}
|
|
1008
|
-
` : ""}
|
|
1009
|
-
export interface ServerApp {
|
|
1010
|
-
app: HonoType;
|
|
1011
|
-
start: (options?: {
|
|
1012
|
-
port?: number;
|
|
1013
|
-
serve: (app: HonoType, port: number) => void | Promise<void>;
|
|
1014
|
-
}) => Promise<void>;
|
|
1015
|
-
}
|
|
1016
|
-
|
|
1017
|
-
/**
|
|
1018
|
-
* Create and configure the Hono application
|
|
1019
|
-
*
|
|
1020
|
-
* @param app - Optional Hono app instance to configure (creates new one if not provided)
|
|
1021
|
-
* @param enableOpenApi - Enable OpenAPI documentation (default: true)
|
|
1022
|
-
* @returns Server app with configured Hono app and start function
|
|
1023
|
-
*
|
|
1024
|
-
* @example
|
|
1025
|
-
* // With Bun
|
|
1026
|
-
* import { createApp } from './.gkm/server/app.js';
|
|
1027
|
-
*
|
|
1028
|
-
* const { app, start } = await createApp();
|
|
1029
|
-
*
|
|
1030
|
-
* await start({
|
|
1031
|
-
* port: 3000,
|
|
1032
|
-
* serve: (app, port) => {
|
|
1033
|
-
* Bun.serve({ port, fetch: app.fetch });
|
|
1034
|
-
* }
|
|
1035
|
-
* });
|
|
1036
|
-
*
|
|
1037
|
-
* @example
|
|
1038
|
-
* // With Node.js (using @hono/node-server)
|
|
1039
|
-
* import { serve } from '@hono/node-server';
|
|
1040
|
-
* import { createApp } from './.gkm/server/app.js';
|
|
1041
|
-
*
|
|
1042
|
-
* const { app, start } = await createApp();
|
|
1043
|
-
*
|
|
1044
|
-
* await start({
|
|
1045
|
-
* port: 3000,
|
|
1046
|
-
* serve: (app, port) => {
|
|
1047
|
-
* serve({ fetch: app.fetch, port });
|
|
1048
|
-
* }
|
|
1049
|
-
* });
|
|
1050
|
-
*/
|
|
1051
|
-
export async function createApp(app?: HonoType, enableOpenApi: boolean = true): Promise<ServerApp> {
|
|
1052
|
-
const honoApp = app || new Hono();
|
|
1053
|
-
${telescopeSetup}${cors.setup}${beforeSetupCall}${studioSetup}
|
|
1054
|
-
// Setup HTTP endpoints
|
|
1055
|
-
await setupEndpoints(honoApp, envParser, logger, enableOpenApi);
|
|
1056
|
-
${afterSetupCall}
|
|
1057
|
-
|
|
1058
|
-
return {
|
|
1059
|
-
app: honoApp,
|
|
1060
|
-
async start(options) {
|
|
1061
|
-
if (!options?.serve) {
|
|
1062
|
-
throw new Error(
|
|
1063
|
-
'serve function is required. Pass a serve function for your runtime:\\n' +
|
|
1064
|
-
' - Bun: (app, port) => Bun.serve({ port, fetch: app.fetch })\\n' +
|
|
1065
|
-
' - Node: (app, port) => serve({ fetch: app.fetch, port })'
|
|
1066
|
-
);
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
const port = options.port ?? 3000;
|
|
1070
|
-
|
|
1071
|
-
// Start subscribers in background (non-blocking, local development only)
|
|
1072
|
-
await setupSubscribers(envParser, logger).catch((error) => {
|
|
1073
|
-
logger.error({ error }, 'Failed to start subscribers');
|
|
1074
|
-
});
|
|
1075
|
-
|
|
1076
|
-
// Start queue workers in background (non-blocking, local development only)
|
|
1077
|
-
await setupQueues(envParser, logger).catch((error) => {
|
|
1078
|
-
logger.error({ error }, 'Failed to start queue workers');
|
|
1079
|
-
});
|
|
1080
|
-
|
|
1081
|
-
// Schedule this app's crons. Caught like the others: a scheduler that
|
|
1082
|
-
// cannot start is not a reason for the HTTP server not to.
|
|
1083
|
-
await setupCrons(envParser, logger).catch((error) => {
|
|
1084
|
-
logger.error({ error }, 'Failed to schedule crons');
|
|
1085
|
-
});
|
|
1086
|
-
|
|
1087
|
-
logger.info({ port }, 'Starting server');
|
|
1088
|
-
|
|
1089
|
-
// Start HTTP server using provided serve function
|
|
1090
|
-
await options.serve(honoApp, port);
|
|
1091
|
-
|
|
1092
|
-
logger.info({ port }, 'Server started');
|
|
1093
|
-
}
|
|
1094
|
-
};
|
|
1095
|
-
}
|
|
1096
|
-
|
|
1097
|
-
// Default export for convenience
|
|
1098
|
-
export default createApp;
|
|
1099
|
-
`;
|
|
1100
|
-
await writeFile(appPath, content);
|
|
1101
|
-
return appPath;
|
|
1102
|
-
}
|
|
1103
|
-
/**
|
|
1104
|
-
* One Lambda, one endpoint — and no environment import.
|
|
1105
|
-
*
|
|
1106
|
-
* The endpoint was built from its surface, so it carries the parser the
|
|
1107
|
-
* adaptor needs. This used to read
|
|
1108
|
-
* `import { envParser } from '../../config/env'`, a line the build wrote
|
|
1109
|
-
* because a generator can print a module specifier and cannot print an
|
|
1110
|
-
* object. Nothing has to be printed now, which is also why nothing has to be
|
|
1111
|
-
* named in config.
|
|
1112
|
-
*/
|
|
1113
|
-
generateAWSApiGatewayV1Handler(importPath, exportName, drivers) {
|
|
1114
|
-
return `import { AmazonApiGatewayV1Endpoint } from '@geekmidas/constructs/aws';
|
|
1115
|
-
import { ${exportName} } from '${importPath}';
|
|
1116
|
-
${drivers?.imports ?? ""}
|
|
1117
|
-
${drivers?.setup ? `\n// The handler registers the drivers its target needs.\n${drivers.setup}\n` : ""}
|
|
1118
|
-
const adapter = new AmazonApiGatewayV1Endpoint(${exportName});
|
|
1119
|
-
|
|
1120
|
-
export const handler = adapter.handler;
|
|
1121
|
-
`;
|
|
1122
|
-
}
|
|
1123
|
-
/**
|
|
1124
|
-
* One Lambda, one endpoint — and no environment import.
|
|
1125
|
-
*
|
|
1126
|
-
* The endpoint was built from its surface, so it carries the parser the
|
|
1127
|
-
* adaptor needs. This used to read
|
|
1128
|
-
* `import { envParser } from '../../config/env'`, a line the build wrote
|
|
1129
|
-
* because a generator can print a module specifier and cannot print an
|
|
1130
|
-
* object. Nothing has to be printed now, which is also why nothing has to be
|
|
1131
|
-
* named in config.
|
|
1132
|
-
*/
|
|
1133
|
-
generateAWSApiGatewayV2Handler(importPath, exportName, drivers) {
|
|
1134
|
-
return `import { AmazonApiGatewayV2Endpoint } from '@geekmidas/constructs/aws';
|
|
1135
|
-
import { ${exportName} } from '${importPath}';
|
|
1136
|
-
${drivers?.imports ?? ""}
|
|
1137
|
-
${drivers?.setup ? `\n// The handler registers the drivers its target needs.\n${drivers.setup}\n` : ""}
|
|
1138
|
-
const adapter = new AmazonApiGatewayV2Endpoint(${exportName});
|
|
1139
|
-
|
|
1140
|
-
export const handler = adapter.handler;
|
|
1141
|
-
`;
|
|
1142
|
-
}
|
|
1143
|
-
generateServerHandler(importPath, exportName) {
|
|
1144
|
-
return `import { ${exportName} } from '${importPath}';
|
|
1145
|
-
|
|
1146
|
-
// Server handler - implement based on your server framework
|
|
1147
|
-
export const handler = ${exportName};
|
|
1148
|
-
`;
|
|
1149
|
-
}
|
|
1150
|
-
/**
|
|
1151
|
-
* Generate a production-optimized app.ts file
|
|
1152
|
-
* No dev tools (Telescope, Studio, WebSocket), includes health checks and graceful shutdown
|
|
1153
|
-
*/
|
|
1154
|
-
async generateProductionAppFile(outputDir, context) {
|
|
1155
|
-
const appFileName = "app.ts";
|
|
1156
|
-
const appPath = join(outputDir, appFileName);
|
|
1157
|
-
const runtime = runtimeFor(context, dirname(appPath));
|
|
1158
|
-
const production = context.production;
|
|
1159
|
-
const healthCheckPath = production.healthCheck;
|
|
1160
|
-
const enableGracefulShutdown = production.gracefulShutdown;
|
|
1161
|
-
const enableOpenApi = production.openapi;
|
|
1162
|
-
const includeSubscribers = production.subscribers === "include";
|
|
1163
|
-
const cors = corsFor(context.surface);
|
|
1164
|
-
let hooksImports = "";
|
|
1165
|
-
let beforeSetupCall = "";
|
|
1166
|
-
let afterSetupCall = "";
|
|
1167
|
-
if (context.hooks?.serverHooksPath) {
|
|
1168
|
-
const relativeHooksPath = relative(dirname(appPath), context.hooks.serverHooksPath);
|
|
1169
|
-
hooksImports = `import * as serverHooks from '${relativeHooksPath}';`;
|
|
1170
|
-
beforeSetupCall = `
|
|
1171
|
-
// Call beforeSetup hook if defined
|
|
1172
|
-
if (typeof serverHooks.beforeSetup === 'function') {
|
|
1173
|
-
await serverHooks.beforeSetup(honoApp, { envParser, logger });
|
|
1174
|
-
}
|
|
1175
|
-
`;
|
|
1176
|
-
afterSetupCall = `
|
|
1177
|
-
// Call afterSetup hook if defined
|
|
1178
|
-
if (typeof serverHooks.afterSetup === 'function') {
|
|
1179
|
-
await serverHooks.afterSetup(honoApp, { envParser, logger });
|
|
1180
|
-
}
|
|
1181
|
-
`;
|
|
1182
|
-
}
|
|
1183
|
-
const subscriberSetup = includeSubscribers ? `
|
|
1184
|
-
// Start subscribers in background
|
|
1185
|
-
await setupSubscribers(envParser, logger).catch((error) => {
|
|
1186
|
-
logger.error({ error }, 'Failed to start subscribers');
|
|
1187
|
-
});
|
|
1188
|
-
|
|
1189
|
-
// Start queue workers in background
|
|
1190
|
-
await setupQueues(envParser, logger).catch((error) => {
|
|
1191
|
-
logger.error({ error }, 'Failed to start queue workers');
|
|
1192
|
-
});
|
|
1193
|
-
|
|
1194
|
-
// Schedule this app's crons. Caught like the others: a scheduler that
|
|
1195
|
-
// cannot start is not a reason for the HTTP server not to.
|
|
1196
|
-
await setupCrons(envParser, logger).catch((error) => {
|
|
1197
|
-
logger.error({ error }, 'Failed to schedule crons');
|
|
1198
|
-
});
|
|
1199
|
-
` : "";
|
|
1200
|
-
const subscriberImport = includeSubscribers ? `import { setupSubscribers } from './subscribers.js';
|
|
1201
|
-
import { setupQueues } from './queues.js';
|
|
1202
|
-
import { setupCrons } from './crons.js';` : "";
|
|
1203
|
-
const gracefulShutdownCode = enableGracefulShutdown ? `
|
|
1204
|
-
// Graceful shutdown handling
|
|
1205
|
-
let isShuttingDown = false;
|
|
1206
|
-
|
|
1207
|
-
const shutdown = async () => {
|
|
1208
|
-
if (isShuttingDown) return;
|
|
1209
|
-
isShuttingDown = true;
|
|
1210
|
-
logger.info('Graceful shutdown initiated');
|
|
1211
|
-
// Allow in-flight requests to complete (30s timeout)
|
|
1212
|
-
setTimeout(() => process.exit(0), 30000);
|
|
1213
|
-
};
|
|
1214
|
-
|
|
1215
|
-
process.on('SIGTERM', shutdown);
|
|
1216
|
-
process.on('SIGINT', shutdown);
|
|
1217
|
-
` : "";
|
|
1218
|
-
const endpointsImportPath = production.optimizedHandlers ? "./endpoints/index.js" : "./endpoints.js";
|
|
1219
|
-
const content = `/**
|
|
1220
|
-
* Generated production server application
|
|
1221
|
-
*
|
|
1222
|
-
* This is a production-optimized build without dev tools.
|
|
1223
|
-
* - No Telescope debugging dashboard
|
|
1224
|
-
* - No Studio database browser
|
|
1225
|
-
* - No WebSocket updates
|
|
1226
|
-
* - Includes health checks and graceful shutdown
|
|
1227
|
-
*/
|
|
1228
|
-
import { Hono } from 'hono';
|
|
1229
|
-
import type { Hono as HonoType } from 'hono';
|
|
1230
|
-
import { setupEndpoints } from '${endpointsImportPath}';
|
|
1231
|
-
${subscriberImport}
|
|
1232
|
-
${runtime.imports}
|
|
1233
|
-
${runtime.bindings}
|
|
1234
|
-
${hooksImports}
|
|
1235
|
-
${cors.imports}
|
|
1236
|
-
${context.storageDrivers?.imports ?? ""}
|
|
1237
|
-
|
|
1238
|
-
${context.storageDrivers?.setup ? `// The entry point registers the drivers its target needs.
|
|
1239
|
-
${context.storageDrivers.setup}
|
|
1240
|
-
` : ""}
|
|
1241
|
-
export interface ServerApp {
|
|
1242
|
-
app: HonoType;
|
|
1243
|
-
start: (options?: {
|
|
1244
|
-
port?: number;
|
|
1245
|
-
serve: (app: HonoType, port: number) => void | Promise<void>;
|
|
1246
|
-
}) => Promise<void>;
|
|
1247
|
-
}
|
|
1248
|
-
|
|
1249
|
-
/**
|
|
1250
|
-
* Create and configure the production Hono application
|
|
1251
|
-
*/
|
|
1252
|
-
export async function createApp(app?: HonoType): Promise<ServerApp> {
|
|
1253
|
-
const honoApp = app || new Hono();
|
|
1254
|
-
|
|
1255
|
-
// Health check endpoint (always first)
|
|
1256
|
-
honoApp.get('${healthCheckPath}', (c) => c.json({ status: 'ok', timestamp: Date.now() }));
|
|
1257
|
-
honoApp.get('/ready', (c) => c.json({ ready: true }));
|
|
1258
|
-
${cors.setup}${beforeSetupCall}
|
|
1259
|
-
// Setup HTTP endpoints (OpenAPI: ${enableOpenApi})
|
|
1260
|
-
await setupEndpoints(honoApp, envParser, logger, ${enableOpenApi});
|
|
1261
|
-
${afterSetupCall}
|
|
1262
|
-
return {
|
|
1263
|
-
app: honoApp,
|
|
1264
|
-
async start(options) {
|
|
1265
|
-
if (!options?.serve) {
|
|
1266
|
-
throw new Error(
|
|
1267
|
-
'serve function is required. Pass a serve function for your runtime:\\n' +
|
|
1268
|
-
' - Bun: (app, port) => Bun.serve({ port, fetch: app.fetch })\\n' +
|
|
1269
|
-
' - Node: (app, port) => serve({ fetch: app.fetch, port })'
|
|
1270
|
-
);
|
|
1271
|
-
}
|
|
1272
|
-
|
|
1273
|
-
const port = options.port ?? Number(process.env.PORT) ?? 3000;
|
|
1274
|
-
${gracefulShutdownCode}${subscriberSetup}
|
|
1275
|
-
logger.info({ port }, 'Starting production server');
|
|
1276
|
-
|
|
1277
|
-
// Start HTTP server using provided serve function
|
|
1278
|
-
await options.serve(honoApp, port);
|
|
1279
|
-
|
|
1280
|
-
logger.info({ port }, 'Production server started');
|
|
1281
|
-
}
|
|
1282
|
-
};
|
|
1283
|
-
}
|
|
1284
|
-
|
|
1285
|
-
// Default export for convenience
|
|
1286
|
-
export default createApp;
|
|
1287
|
-
`;
|
|
1288
|
-
await writeFile(appPath, content);
|
|
1289
|
-
await this.generateProductionServerEntry(outputDir);
|
|
1290
|
-
return appPath;
|
|
1291
|
-
}
|
|
1292
|
-
/**
|
|
1293
|
-
* Generate production server.ts entry point
|
|
1294
|
-
*/
|
|
1295
|
-
async generateProductionServerEntry(outputDir) {
|
|
1296
|
-
const serverPath = join(outputDir, "server.ts");
|
|
1297
|
-
const content = `#!/usr/bin/env node
|
|
1298
|
-
/**
|
|
1299
|
-
* Production server entry point
|
|
1300
|
-
* Generated by 'gkm build --provider server --production'
|
|
1301
|
-
*/
|
|
1302
|
-
import { serve } from '@hono/node-server';
|
|
1303
|
-
import { createApp } from './app.js';
|
|
1304
|
-
|
|
1305
|
-
const port = Number(process.env.PORT) || 3000;
|
|
1306
|
-
|
|
1307
|
-
const { start } = await createApp();
|
|
1308
|
-
|
|
1309
|
-
await start({
|
|
1310
|
-
port,
|
|
1311
|
-
serve: (app, port) => { serve({ fetch: app.fetch, port }); },
|
|
1312
|
-
});
|
|
1313
|
-
`;
|
|
1314
|
-
await writeFile(serverPath, content);
|
|
1315
|
-
}
|
|
1316
|
-
};
|
|
1317
|
-
|
|
1318
|
-
//#endregion
|
|
1319
|
-
//#region src/generators/OpenApiTsGenerator.ts
|
|
1320
|
-
/**
|
|
1321
|
-
* Generates TypeScript OpenAPI module from endpoints.
|
|
1322
|
-
* Outputs:
|
|
1323
|
-
* - securitySchemes: typed security scheme definitions
|
|
1324
|
-
* - endpointAuth: runtime map of endpoints to auth requirements
|
|
1325
|
-
* - paths: TypeScript interface for type-safe fetcher
|
|
1326
|
-
* - schema interfaces: reusable TypeScript types from Zod/Valibot schemas
|
|
1327
|
-
*/
|
|
1328
|
-
var OpenApiTsGenerator = class {
|
|
1329
|
-
async generate(endpoints, options = {}) {
|
|
1330
|
-
const { title = "API", version = "1.0.0", description } = options;
|
|
1331
|
-
const endpointInfos = await this.extractEndpointInfos(endpoints);
|
|
1332
|
-
const securitySchemes = this.collectSecuritySchemes(endpointInfos);
|
|
1333
|
-
const endpointAuth = this.buildEndpointAuthMap(endpointInfos);
|
|
1334
|
-
const schemaInterfaces = await this.generateSchemaInterfaces(endpointInfos);
|
|
1335
|
-
const pathsInterface = await this.generatePathsInterface(endpointInfos);
|
|
1336
|
-
return this.buildModule({
|
|
1337
|
-
title,
|
|
1338
|
-
version,
|
|
1339
|
-
description,
|
|
1340
|
-
securitySchemes,
|
|
1341
|
-
endpointAuth,
|
|
1342
|
-
schemaInterfaces,
|
|
1343
|
-
pathsInterface
|
|
1344
|
-
});
|
|
1345
|
-
}
|
|
1346
|
-
async extractEndpointInfos(endpoints) {
|
|
1347
|
-
return endpoints.map((ep) => {
|
|
1348
|
-
const route = ep.route.replace(/:(\w+)/g, "{$1}");
|
|
1349
|
-
const method = ep.method.toUpperCase();
|
|
1350
|
-
const securityScheme = ep.authorizer?.securityScheme;
|
|
1351
|
-
return {
|
|
1352
|
-
endpoint: `${method} ${route}`,
|
|
1353
|
-
route,
|
|
1354
|
-
method,
|
|
1355
|
-
authorizerName: ep.authorizer?.name ?? null,
|
|
1356
|
-
authorizerType: ep.authorizer?.type ?? null,
|
|
1357
|
-
securityScheme: securityScheme ?? null,
|
|
1358
|
-
input: ep.input,
|
|
1359
|
-
output: ep.outputSchema,
|
|
1360
|
-
description: ep.description,
|
|
1361
|
-
tags: ep.tags,
|
|
1362
|
-
operationId: ep.operationId,
|
|
1363
|
-
responseType: ep.responseType ?? "application/json"
|
|
1364
|
-
};
|
|
1365
|
-
});
|
|
1366
|
-
}
|
|
1367
|
-
collectSecuritySchemes(endpointInfos) {
|
|
1368
|
-
const schemes = /* @__PURE__ */ new Map();
|
|
1369
|
-
for (const info of endpointInfos) if (info.authorizerName && !schemes.has(info.authorizerName)) {
|
|
1370
|
-
const scheme = info.securityScheme ?? (info.authorizerType ? this.mapAuthorizerToSecurityScheme(info.authorizerType, info.authorizerName) : null);
|
|
1371
|
-
if (scheme) schemes.set(info.authorizerName, {
|
|
1372
|
-
name: info.authorizerName,
|
|
1373
|
-
type: scheme.type,
|
|
1374
|
-
scheme
|
|
1375
|
-
});
|
|
1376
|
-
}
|
|
1377
|
-
return Array.from(schemes.values());
|
|
1378
|
-
}
|
|
1379
|
-
mapAuthorizerToSecurityScheme(type, _name) {
|
|
1380
|
-
switch (type.toLowerCase()) {
|
|
1381
|
-
case "jwt":
|
|
1382
|
-
case "bearer": return {
|
|
1383
|
-
type: "http",
|
|
1384
|
-
scheme: "bearer",
|
|
1385
|
-
bearerFormat: "JWT"
|
|
1386
|
-
};
|
|
1387
|
-
case "iam":
|
|
1388
|
-
case "aws-sigv4":
|
|
1389
|
-
case "sigv4": return {
|
|
1390
|
-
type: "apiKey",
|
|
1391
|
-
in: "header",
|
|
1392
|
-
name: "Authorization",
|
|
1393
|
-
"x-amazon-apigateway-authtype": "awsSigv4"
|
|
1394
|
-
};
|
|
1395
|
-
case "apikey":
|
|
1396
|
-
case "api-key": return {
|
|
1397
|
-
type: "apiKey",
|
|
1398
|
-
in: "header",
|
|
1399
|
-
name: "X-API-Key"
|
|
1400
|
-
};
|
|
1401
|
-
case "oauth2": return {
|
|
1402
|
-
type: "oauth2",
|
|
1403
|
-
flows: {}
|
|
1404
|
-
};
|
|
1405
|
-
case "oidc":
|
|
1406
|
-
case "openidconnect": return {
|
|
1407
|
-
type: "openIdConnect",
|
|
1408
|
-
openIdConnectUrl: ""
|
|
1409
|
-
};
|
|
1410
|
-
default: return {
|
|
1411
|
-
type: "http",
|
|
1412
|
-
scheme: "bearer"
|
|
1413
|
-
};
|
|
1414
|
-
}
|
|
1415
|
-
}
|
|
1416
|
-
buildEndpointAuthMap(endpointInfos) {
|
|
1417
|
-
const authMap = {};
|
|
1418
|
-
for (const info of endpointInfos) authMap[info.endpoint] = info.authorizerName;
|
|
1419
|
-
return authMap;
|
|
1420
|
-
}
|
|
1421
|
-
async generateSchemaInterfaces(endpointInfos) {
|
|
1422
|
-
const interfaces = [];
|
|
1423
|
-
const generatedNames = /* @__PURE__ */ new Set();
|
|
1424
|
-
const collectedDefs = /* @__PURE__ */ new Map();
|
|
1425
|
-
for (const info of endpointInfos) {
|
|
1426
|
-
const baseName = this.getSchemaBaseName(info);
|
|
1427
|
-
if (info.input?.body) {
|
|
1428
|
-
const name = await this.getSchemaName(info.input.body, `${baseName}Input`);
|
|
1429
|
-
if (!generatedNames.has(name)) {
|
|
1430
|
-
const schema = await this.schemaToInterfaceWithDefs(info.input.body, name, collectedDefs);
|
|
1431
|
-
if (schema) {
|
|
1432
|
-
interfaces.push(schema);
|
|
1433
|
-
generatedNames.add(name);
|
|
1434
|
-
}
|
|
1435
|
-
}
|
|
1436
|
-
}
|
|
1437
|
-
if (info.input?.params) {
|
|
1438
|
-
const name = await this.getSchemaName(info.input.params, `${baseName}Params`);
|
|
1439
|
-
if (!generatedNames.has(name)) {
|
|
1440
|
-
const schema = await this.schemaToInterfaceWithDefs(info.input.params, name, collectedDefs);
|
|
1441
|
-
if (schema) {
|
|
1442
|
-
interfaces.push(schema);
|
|
1443
|
-
generatedNames.add(name);
|
|
1444
|
-
}
|
|
1445
|
-
}
|
|
1446
|
-
}
|
|
1447
|
-
if (info.input?.query) {
|
|
1448
|
-
const name = await this.getSchemaName(info.input.query, `${baseName}Query`);
|
|
1449
|
-
if (!generatedNames.has(name)) {
|
|
1450
|
-
const schema = await this.schemaToInterfaceWithDefs(info.input.query, name, collectedDefs);
|
|
1451
|
-
if (schema) {
|
|
1452
|
-
interfaces.push(schema);
|
|
1453
|
-
generatedNames.add(name);
|
|
1454
|
-
}
|
|
1455
|
-
}
|
|
1456
|
-
}
|
|
1457
|
-
if (info.output) {
|
|
1458
|
-
const name = await this.getSchemaName(info.output, `${baseName}Output`);
|
|
1459
|
-
if (!generatedNames.has(name)) {
|
|
1460
|
-
const schema = await this.schemaToInterfaceWithDefs(info.output, name, collectedDefs);
|
|
1461
|
-
if (schema) {
|
|
1462
|
-
interfaces.push(schema);
|
|
1463
|
-
generatedNames.add(name);
|
|
1464
|
-
}
|
|
1465
|
-
}
|
|
1466
|
-
}
|
|
1467
|
-
}
|
|
1468
|
-
const registrySchemas = await getRegisteredZodJsonSchemas();
|
|
1469
|
-
for (const [rawName, rawSchema] of Object.entries(registrySchemas)) {
|
|
1470
|
-
const defName = this.pascalCase(rawName);
|
|
1471
|
-
if (collectedDefs.has(defName) || generatedNames.has(defName)) continue;
|
|
1472
|
-
const { id: _id, $defs: _defs,...rest } = rawSchema;
|
|
1473
|
-
collectedDefs.set(defName, rest);
|
|
1474
|
-
}
|
|
1475
|
-
for (const [defName, defSchema] of collectedDefs) if (!generatedNames.has(defName)) {
|
|
1476
|
-
const interfaceStr = this.jsonSchemaToInterface(defSchema, defName);
|
|
1477
|
-
interfaces.push(interfaceStr);
|
|
1478
|
-
generatedNames.add(defName);
|
|
1479
|
-
}
|
|
1480
|
-
return interfaces.join("\n\n");
|
|
1481
|
-
}
|
|
1482
|
-
/**
|
|
1483
|
-
* Get the name for a schema, using metadata `id` if available,
|
|
1484
|
-
* otherwise falling back to the provided default name.
|
|
1485
|
-
*/
|
|
1486
|
-
async getSchemaName(schema, defaultName) {
|
|
1487
|
-
try {
|
|
1488
|
-
const metadata = await getSchemaMetadata(schema);
|
|
1489
|
-
if (metadata?.id) return this.pascalCase(metadata.id);
|
|
1490
|
-
} catch {}
|
|
1491
|
-
return defaultName;
|
|
1492
|
-
}
|
|
1493
|
-
getSchemaBaseName(info) {
|
|
1494
|
-
if (info.operationId) return this.pascalCase(info.operationId);
|
|
1495
|
-
const routeParts = info.route.replace(/[{}]/g, "").split("/").filter(Boolean).map((part) => this.pascalCase(part));
|
|
1496
|
-
return `${this.pascalCase(info.method.toLowerCase())}${routeParts.join("")}`;
|
|
1497
|
-
}
|
|
1498
|
-
pascalCase(str) {
|
|
1499
|
-
return str.replace(/[-_](.)/g, (_, c) => c.toUpperCase()).replace(/^./, (c) => c.toUpperCase());
|
|
1500
|
-
}
|
|
1501
|
-
/**
|
|
1502
|
-
* Convert schema to interface while collecting $defs for nested schemas
|
|
1503
|
-
* with .meta({ id: 'X' }).
|
|
1504
|
-
*/
|
|
1505
|
-
async schemaToInterfaceWithDefs(schema, name, collectedDefs) {
|
|
1506
|
-
try {
|
|
1507
|
-
const vendor = schema["~standard"]?.vendor;
|
|
1508
|
-
if (!vendor || !(vendor in StandardSchemaJsonSchema)) return null;
|
|
1509
|
-
const toJsonSchema = StandardSchemaJsonSchema[vendor];
|
|
1510
|
-
const jsonSchema = await toJsonSchema(schema);
|
|
1511
|
-
if (!jsonSchema) return null;
|
|
1512
|
-
if (jsonSchema.$defs && typeof jsonSchema.$defs === "object") {
|
|
1513
|
-
for (const [defName, defSchema] of Object.entries(jsonSchema.$defs)) if (!collectedDefs.has(defName)) {
|
|
1514
|
-
const { id,...schemaWithoutId } = defSchema;
|
|
1515
|
-
collectedDefs.set(defName, schemaWithoutId);
|
|
1516
|
-
}
|
|
1517
|
-
}
|
|
1518
|
-
const { $defs,...schemaWithoutDefs } = jsonSchema;
|
|
1519
|
-
const ref = schemaWithoutDefs.$ref;
|
|
1520
|
-
if (ref && Object.keys(schemaWithoutDefs).every((k) => k === "$ref" || k === "$schema")) {
|
|
1521
|
-
const target = ref.split("/").pop();
|
|
1522
|
-
return target === name ? null : `export type ${name} = ${target};`;
|
|
1523
|
-
}
|
|
1524
|
-
return this.jsonSchemaToInterface(schemaWithoutDefs, name);
|
|
1525
|
-
} catch {
|
|
1526
|
-
return null;
|
|
1527
|
-
}
|
|
1528
|
-
}
|
|
1529
|
-
jsonSchemaToInterface(schema, name) {
|
|
1530
|
-
if (schema.type !== "object" || !schema.properties) {
|
|
1531
|
-
const typeStr = this.jsonSchemaTypeToTs(schema);
|
|
1532
|
-
return `export type ${name} = ${typeStr};`;
|
|
1533
|
-
}
|
|
1534
|
-
const props = [];
|
|
1535
|
-
const required = new Set(schema.required || []);
|
|
1536
|
-
for (const [propName, propSchema] of Object.entries(schema.properties)) {
|
|
1537
|
-
const isRequired = required.has(propName);
|
|
1538
|
-
const typeStr = this.jsonSchemaTypeToTs(propSchema);
|
|
1539
|
-
const optionalMark = isRequired ? "" : "?";
|
|
1540
|
-
props.push(` ${propName}${optionalMark}: ${typeStr};`);
|
|
1541
|
-
}
|
|
1542
|
-
return `export interface ${name} {\n${props.join("\n")}\n}`;
|
|
1543
|
-
}
|
|
1544
|
-
jsonSchemaTypeToTs(schema) {
|
|
1545
|
-
if (!schema) return "unknown";
|
|
1546
|
-
if (schema.$ref) {
|
|
1547
|
-
const refName = schema.$ref.split("/").pop() || "unknown";
|
|
1548
|
-
return refName;
|
|
1549
|
-
}
|
|
1550
|
-
if (schema.anyOf) return schema.anyOf.map((s) => this.jsonSchemaTypeToTs(s)).join(" | ");
|
|
1551
|
-
if (schema.oneOf) return schema.oneOf.map((s) => this.jsonSchemaTypeToTs(s)).join(" | ");
|
|
1552
|
-
if (schema.allOf) return schema.allOf.map((s) => this.jsonSchemaTypeToTs(s)).join(" & ");
|
|
1553
|
-
switch (schema.type) {
|
|
1554
|
-
case "string":
|
|
1555
|
-
if (schema.enum) return schema.enum.map((e) => `'${e}'`).join(" | ");
|
|
1556
|
-
return "string";
|
|
1557
|
-
case "number":
|
|
1558
|
-
case "integer": return "number";
|
|
1559
|
-
case "boolean": return "boolean";
|
|
1560
|
-
case "null": return "null";
|
|
1561
|
-
case "array":
|
|
1562
|
-
if (schema.items) return `Array<${this.jsonSchemaTypeToTs(schema.items)}>`;
|
|
1563
|
-
return "Array<unknown>";
|
|
1564
|
-
case "object":
|
|
1565
|
-
if (schema.properties) {
|
|
1566
|
-
const props = [];
|
|
1567
|
-
const required = new Set(schema.required || []);
|
|
1568
|
-
for (const [propName, propSchema] of Object.entries(schema.properties)) {
|
|
1569
|
-
const isRequired = required.has(propName);
|
|
1570
|
-
const typeStr = this.jsonSchemaTypeToTs(propSchema);
|
|
1571
|
-
const optionalMark = isRequired ? "" : "?";
|
|
1572
|
-
props.push(`${propName}${optionalMark}: ${typeStr}`);
|
|
1573
|
-
}
|
|
1574
|
-
return `{ ${props.join("; ")} }`;
|
|
1575
|
-
}
|
|
1576
|
-
if (schema.additionalProperties) {
|
|
1577
|
-
const valueType = this.jsonSchemaTypeToTs(schema.additionalProperties);
|
|
1578
|
-
return `Record<string, ${valueType}>`;
|
|
1579
|
-
}
|
|
1580
|
-
return "Record<string, unknown>";
|
|
1581
|
-
default: return "unknown";
|
|
1582
|
-
}
|
|
1583
|
-
}
|
|
1584
|
-
async generatePathsInterface(endpointInfos) {
|
|
1585
|
-
const pathGroups = /* @__PURE__ */ new Map();
|
|
1586
|
-
for (const info of endpointInfos) {
|
|
1587
|
-
const existing = pathGroups.get(info.route) || [];
|
|
1588
|
-
existing.push(info);
|
|
1589
|
-
pathGroups.set(info.route, existing);
|
|
1590
|
-
}
|
|
1591
|
-
const pathEntries = [];
|
|
1592
|
-
for (const [route, infos] of pathGroups) {
|
|
1593
|
-
const methodEntries = [];
|
|
1594
|
-
for (const info of infos) {
|
|
1595
|
-
const methodDef = await this.generateMethodDefinition(info);
|
|
1596
|
-
methodEntries.push(` ${info.method.toLowerCase()}: ${methodDef};`);
|
|
1597
|
-
}
|
|
1598
|
-
const firstWithParams = infos.find((i) => i.input?.params);
|
|
1599
|
-
let paramsEntry = "";
|
|
1600
|
-
if (firstWithParams?.input?.params) {
|
|
1601
|
-
const paramsName = await this.getSchemaName(firstWithParams.input.params, `${this.getSchemaBaseName(firstWithParams)}Params`);
|
|
1602
|
-
paramsEntry = `\n parameters: {\n path: ${paramsName};\n };`;
|
|
1603
|
-
}
|
|
1604
|
-
pathEntries.push(` '${route}': {${paramsEntry}\n${methodEntries.join("\n")}\n };`);
|
|
1605
|
-
}
|
|
1606
|
-
return `export interface paths {\n${pathEntries.join("\n")}\n}`;
|
|
1607
|
-
}
|
|
1608
|
-
async generateMethodDefinition(info) {
|
|
1609
|
-
const parts = [];
|
|
1610
|
-
const baseName = this.getSchemaBaseName(info);
|
|
1611
|
-
if (info.input?.body) {
|
|
1612
|
-
const bodyName = await this.getSchemaName(info.input.body, `${baseName}Input`);
|
|
1613
|
-
parts.push(`requestBody: {
|
|
1614
|
-
content: {
|
|
1615
|
-
'application/json': ${bodyName};
|
|
1616
|
-
};
|
|
1617
|
-
}`);
|
|
1618
|
-
}
|
|
1619
|
-
if (info.input?.query) {
|
|
1620
|
-
const queryName = await this.getSchemaName(info.input.query, `${baseName}Query`);
|
|
1621
|
-
parts.push(`parameters: {
|
|
1622
|
-
query: ${queryName};
|
|
1623
|
-
}`);
|
|
1624
|
-
}
|
|
1625
|
-
const outputName = info.output ? await this.getSchemaName(info.output, `${baseName}Output`) : "unknown";
|
|
1626
|
-
parts.push(`responses: {
|
|
1627
|
-
200: {
|
|
1628
|
-
content: {
|
|
1629
|
-
'${info.responseType}': ${outputName};
|
|
1630
|
-
};
|
|
1631
|
-
};
|
|
1632
|
-
}`);
|
|
1633
|
-
return `{\n ${parts.join(";\n ")};\n }`;
|
|
1634
|
-
}
|
|
1635
|
-
buildModule(params) {
|
|
1636
|
-
const { title, version, description, securitySchemes, endpointAuth, schemaInterfaces, pathsInterface } = params;
|
|
1637
|
-
const securitySchemesObj = securitySchemes.reduce((acc, s) => {
|
|
1638
|
-
acc[s.name] = s.scheme;
|
|
1639
|
-
return acc;
|
|
1640
|
-
}, {});
|
|
1641
|
-
const schemeNames = securitySchemes.map((s) => `'${s.name}'`).join(" | ");
|
|
1642
|
-
const hasSecuritySchemes = schemeNames.length > 0;
|
|
1643
|
-
const createApiSection = hasSecuritySchemes ? `
|
|
1644
|
-
// ============================================================
|
|
1645
|
-
// API Client Factory
|
|
1646
|
-
// ============================================================
|
|
1647
|
-
|
|
1648
|
-
import {
|
|
1649
|
-
createAuthAwareFetcher,
|
|
1650
|
-
type AuthStrategy,
|
|
1651
|
-
} from '@geekmidas/client/auth-fetcher';
|
|
1652
|
-
import { createEndpointHooks } from '@geekmidas/client/endpoint-hooks';
|
|
1653
|
-
import type { QueryClient } from '@tanstack/react-query';
|
|
1654
|
-
|
|
1655
|
-
/**
|
|
1656
|
-
* Options for creating the API client.
|
|
1657
|
-
*/
|
|
1658
|
-
export interface CreateApiOptions {
|
|
1659
|
-
/** Base URL for all API requests (required) */
|
|
1660
|
-
baseURL: string;
|
|
1661
|
-
/** Auth strategies for each security scheme used in this API */
|
|
1662
|
-
authStrategies: Record<SecuritySchemeId, AuthStrategy>;
|
|
1663
|
-
/** Optional React Query client instance */
|
|
1664
|
-
queryClient?: QueryClient;
|
|
1665
|
-
/** Optional request interceptor */
|
|
1666
|
-
onRequest?: (config: RequestInit) => RequestInit | Promise<RequestInit>;
|
|
1667
|
-
}
|
|
1668
|
-
|
|
1669
|
-
/**
|
|
1670
|
-
* Create a type-safe API client with authentication and React Query hooks.
|
|
1671
|
-
*
|
|
1672
|
-
* @example
|
|
1673
|
-
* \`\`\`typescript
|
|
1674
|
-
* const api = createApi({
|
|
1675
|
-
* baseURL: 'https://api.example.com',
|
|
1676
|
-
* authStrategies: {
|
|
1677
|
-
* jwt: { type: 'bearer', tokenProvider },
|
|
1678
|
-
* },
|
|
1679
|
-
* });
|
|
1680
|
-
*
|
|
1681
|
-
* // Imperative fetch
|
|
1682
|
-
* const user = await api('GET /users/{id}', { params: { id: '123' } });
|
|
1683
|
-
*
|
|
1684
|
-
* // React Query hooks
|
|
1685
|
-
* const { data } = api.useQuery('GET /users/{id}', { params: { id: '123' } });
|
|
1686
|
-
* const mutation = api.useMutation('POST /users');
|
|
1687
|
-
* \`\`\`
|
|
1688
|
-
*/
|
|
1689
|
-
export function createApi(options: CreateApiOptions) {
|
|
1690
|
-
const fetcher = createAuthAwareFetcher<paths, typeof endpointAuth, typeof securitySchemes>({
|
|
1691
|
-
baseURL: options.baseURL,
|
|
1692
|
-
endpointAuth,
|
|
1693
|
-
securitySchemes,
|
|
1694
|
-
authStrategies: options.authStrategies,
|
|
1695
|
-
onRequest: options.onRequest,
|
|
1696
|
-
});
|
|
1697
|
-
|
|
1698
|
-
const hooks = createEndpointHooks<paths>(fetcher, { queryClient: options.queryClient });
|
|
1699
|
-
|
|
1700
|
-
return Object.assign(fetcher, hooks);
|
|
1701
|
-
}
|
|
1702
|
-
` : `
|
|
1703
|
-
// ============================================================
|
|
1704
|
-
// API Client Factory
|
|
1705
|
-
// ============================================================
|
|
1706
|
-
|
|
1707
|
-
import { createTypedFetcher, type FetcherOptions } from '@geekmidas/client/fetcher';
|
|
1708
|
-
import { createEndpointHooks } from '@geekmidas/client/endpoint-hooks';
|
|
1709
|
-
import type { QueryClient } from '@tanstack/react-query';
|
|
1710
|
-
|
|
1711
|
-
/**
|
|
1712
|
-
* Options for creating the API client.
|
|
1713
|
-
*/
|
|
1714
|
-
export interface CreateApiOptions extends Omit<FetcherOptions, 'baseURL'> {
|
|
1715
|
-
/** Base URL for all API requests (required) */
|
|
1716
|
-
baseURL: string;
|
|
1717
|
-
/** Optional React Query client instance */
|
|
1718
|
-
queryClient?: QueryClient;
|
|
1719
|
-
}
|
|
1720
|
-
|
|
1721
|
-
/**
|
|
1722
|
-
* Create a type-safe API client with React Query hooks.
|
|
1723
|
-
*
|
|
1724
|
-
* @example
|
|
1725
|
-
* \`\`\`typescript
|
|
1726
|
-
* const api = createApi({
|
|
1727
|
-
* baseURL: 'https://api.example.com',
|
|
1728
|
-
* });
|
|
1729
|
-
*
|
|
1730
|
-
* // Imperative fetch
|
|
1731
|
-
* const data = await api('GET /health');
|
|
1732
|
-
*
|
|
1733
|
-
* // React Query hooks
|
|
1734
|
-
* const { data } = api.useQuery('GET /health');
|
|
1735
|
-
* \`\`\`
|
|
1736
|
-
*/
|
|
1737
|
-
export function createApi(options: CreateApiOptions) {
|
|
1738
|
-
const { queryClient, ...fetcherOptions } = options;
|
|
1739
|
-
const request = createTypedFetcher<paths>(fetcherOptions);
|
|
1740
|
-
|
|
1741
|
-
const hooks = createEndpointHooks<paths>(request, { queryClient });
|
|
1742
|
-
|
|
1743
|
-
return Object.assign(request, hooks);
|
|
1744
|
-
}
|
|
1745
|
-
`;
|
|
1746
|
-
return `// Auto-generated by @geekmidas/cli - DO NOT EDIT
|
|
1747
|
-
|
|
1748
|
-
// ============================================================
|
|
1749
|
-
// Security Scheme Type
|
|
1750
|
-
// ============================================================
|
|
1751
|
-
|
|
1752
|
-
interface SecuritySchemeObject {
|
|
1753
|
-
type: 'apiKey' | 'http' | 'mutualTLS' | 'oauth2' | 'openIdConnect';
|
|
1754
|
-
description?: string;
|
|
1755
|
-
name?: string;
|
|
1756
|
-
in?: 'query' | 'header' | 'cookie';
|
|
1757
|
-
scheme?: string;
|
|
1758
|
-
bearerFormat?: string;
|
|
1759
|
-
flows?: Record<string, unknown>;
|
|
1760
|
-
openIdConnectUrl?: string;
|
|
1761
|
-
[key: string]: unknown;
|
|
1762
|
-
}
|
|
1763
|
-
|
|
1764
|
-
// ============================================================
|
|
1765
|
-
// API Info
|
|
1766
|
-
// ============================================================
|
|
1767
|
-
|
|
1768
|
-
export const apiInfo = {
|
|
1769
|
-
title: '${title}',
|
|
1770
|
-
version: '${version}',${description ? `\n description: '${description.replace(/'/g, "\\'")}',` : ""}
|
|
1771
|
-
} as const;
|
|
1772
|
-
|
|
1773
|
-
// ============================================================
|
|
1774
|
-
// Security Schemes
|
|
1775
|
-
// ============================================================
|
|
1776
|
-
|
|
1777
|
-
/**
|
|
1778
|
-
* Available security schemes for this API.
|
|
1779
|
-
* Maps authorizer names to OpenAPI security scheme definitions.
|
|
1780
|
-
*/
|
|
1781
|
-
export const securitySchemes = ${JSON.stringify(securitySchemesObj, null, 2).replace(/"([a-zA-Z_$][a-zA-Z0-9_$]*)":/g, "$1:")} as const satisfies Record<string, SecuritySchemeObject>;
|
|
1782
|
-
|
|
1783
|
-
export type SecuritySchemeId = ${schemeNames || "never"};
|
|
1784
|
-
|
|
1785
|
-
// ============================================================
|
|
1786
|
-
// Endpoint Authentication Map
|
|
1787
|
-
// ============================================================
|
|
1788
|
-
|
|
1789
|
-
/**
|
|
1790
|
-
* Runtime map of endpoints to their required authentication scheme.
|
|
1791
|
-
* \`null\` indicates a public endpoint (no auth required).
|
|
1792
|
-
*/
|
|
1793
|
-
export const endpointAuth = ${JSON.stringify(endpointAuth, null, 2).replace(/"([^"]+)":/g, "'$1':")} as const satisfies Record<string, SecuritySchemeId | null>;
|
|
1794
|
-
|
|
1795
|
-
export type EndpointString = keyof typeof endpointAuth;
|
|
1796
|
-
|
|
1797
|
-
export type AuthenticatedEndpoint = {
|
|
1798
|
-
[K in EndpointString]: typeof endpointAuth[K] extends null ? never : K;
|
|
1799
|
-
}[EndpointString];
|
|
1800
|
-
|
|
1801
|
-
export type PublicEndpoint = {
|
|
1802
|
-
[K in EndpointString]: typeof endpointAuth[K] extends null ? K : never;
|
|
1803
|
-
}[EndpointString];
|
|
1804
|
-
|
|
1805
|
-
// ============================================================
|
|
1806
|
-
// Schema Definitions
|
|
1807
|
-
// ============================================================
|
|
1808
|
-
|
|
1809
|
-
${schemaInterfaces}
|
|
1810
|
-
|
|
1811
|
-
// ============================================================
|
|
1812
|
-
// OpenAPI Paths
|
|
1813
|
-
// ============================================================
|
|
1814
|
-
|
|
1815
|
-
${pathsInterface}
|
|
1816
|
-
${createApiSection}
|
|
1817
|
-
`;
|
|
1818
|
-
}
|
|
1819
|
-
};
|
|
1820
|
-
|
|
1821
|
-
//#endregion
|
|
1822
|
-
//#region src/workspace/client-generator.ts
|
|
1823
|
-
/**
|
|
1824
|
-
* Normalize routes to an array of patterns.
|
|
1825
|
-
* Handles string, string[], and PartitionedRoutes (extracts paths).
|
|
1826
|
-
* @internal Exported for use in dev command
|
|
1827
|
-
*/
|
|
1828
|
-
function normalizeRoutes(routes) {
|
|
1829
|
-
if (!routes) return [];
|
|
1830
|
-
if (isPartitionedRoutes(routes)) return Array.isArray(routes.paths) ? routes.paths : [routes.paths];
|
|
1831
|
-
return Array.isArray(routes) ? routes : [routes];
|
|
1832
|
-
}
|
|
1833
|
-
|
|
1834
|
-
//#endregion
|
|
1835
|
-
//#region src/openapi.ts
|
|
1836
|
-
/**
|
|
1837
|
-
* Default output path for generated OpenAPI client (used for single-app configs)
|
|
1838
|
-
*/
|
|
1839
|
-
const OPENAPI_OUTPUT_PATH = "./.gkm/openapi.ts";
|
|
1840
|
-
/**
|
|
1841
|
-
* Where a surface's generated client lands.
|
|
1842
|
-
*
|
|
1843
|
-
* Named from the surface, so `new RestApi('Webhooks')` writes
|
|
1844
|
-
* `.gkm/openapi/webhooks.ts` — the same kebab form that gives it its container
|
|
1845
|
-
* name and its `WEBHOOKS_URL`.
|
|
1846
|
-
*/
|
|
1847
|
-
function openApiPathFor(surfaceId) {
|
|
1848
|
-
return `./.gkm/openapi/${kebabCase(surfaceId)}.ts`;
|
|
1849
|
-
}
|
|
1850
|
-
/**
|
|
1851
|
-
* Resolve OpenAPI config from GkmConfig
|
|
1852
|
-
*/
|
|
1853
|
-
function resolveOpenApiConfig(config) {
|
|
1854
|
-
if (config.openapi === false) return { enabled: false };
|
|
1855
|
-
if (config.openapi === true || config.openapi === void 0) return {
|
|
1856
|
-
enabled: true,
|
|
1857
|
-
title: "API Documentation",
|
|
1858
|
-
version: "1.0.0",
|
|
1859
|
-
description: "Auto-generated API documentation from endpoints"
|
|
1860
|
-
};
|
|
1861
|
-
return {
|
|
1862
|
-
enabled: config.openapi.enabled !== false,
|
|
1863
|
-
title: config.openapi.title || "API Documentation",
|
|
1864
|
-
version: config.openapi.version || "1.0.0",
|
|
1865
|
-
description: config.openapi.description || "Auto-generated API documentation from endpoints"
|
|
1866
|
-
};
|
|
1867
|
-
}
|
|
1868
|
-
/**
|
|
1869
|
-
* Load endpoints from a glob, then generate.
|
|
1870
|
-
*
|
|
1871
|
-
* For the callers that have no endpoints in hand — `gkm openapi`, and `gkm dev`
|
|
1872
|
-
* regenerating on reload. The glob is the app's derived one — the conventional
|
|
1873
|
-
* directories under it — not a `routes` field anybody wrote.
|
|
1874
|
-
*/
|
|
1875
|
-
async function generateOpenApiFrom(routes, options = {}) {
|
|
1876
|
-
const loaded = await new EndpointGenerator().load(routes, void 0, options.bustCache);
|
|
1877
|
-
return generateOpenApi(loaded.map(({ construct }) => construct), options);
|
|
1878
|
-
}
|
|
1879
|
-
/**
|
|
1880
|
-
* Generate an OpenAPI spec for each surface these endpoints name.
|
|
1881
|
-
*
|
|
1882
|
-
* Takes the endpoints, not a glob to find them with. The build has already
|
|
1883
|
-
* loaded every one of them to generate handlers, so globbing again was a second
|
|
1884
|
-
* discovery pass over the same files — and the reason this function needed a
|
|
1885
|
-
* `routes` to be told where to look, which is how a retired config field stayed
|
|
1886
|
-
* load-bearing.
|
|
1887
|
-
*
|
|
1888
|
-
* @returns What was written, or null if disabled or there were none
|
|
1889
|
-
*/
|
|
1890
|
-
async function generateOpenApi(endpoints, options = {}) {
|
|
1891
|
-
const logger = options.silent ? { log: () => {} } : console;
|
|
1892
|
-
const openApiConfig = resolveOpenApiConfig({ openapi: options.openapi });
|
|
1893
|
-
if (!openApiConfig.enabled) return null;
|
|
1894
|
-
if (endpoints.length === 0) {
|
|
1895
|
-
logger.log("No valid endpoints found for OpenAPI generation");
|
|
1896
|
-
return null;
|
|
1897
|
-
}
|
|
1898
|
-
const bySurface = /* @__PURE__ */ new Map();
|
|
1899
|
-
for (const endpoint of endpoints) {
|
|
1900
|
-
const owner = endpoint.owner ?? endpoint.surface?.id;
|
|
1901
|
-
if (!owner) continue;
|
|
1902
|
-
const existing = bySurface.get(owner);
|
|
1903
|
-
if (existing) existing.push(endpoint);
|
|
1904
|
-
else bySurface.set(owner, [endpoint]);
|
|
1905
|
-
}
|
|
1906
|
-
if (bySurface.size === 0) {
|
|
1907
|
-
logger.log("No endpoints named a surface, so no OpenAPI was generated");
|
|
1908
|
-
return null;
|
|
1909
|
-
}
|
|
1910
|
-
const tsGenerator = new OpenApiTsGenerator();
|
|
1911
|
-
const written = [];
|
|
1912
|
-
for (const [surfaceId, surfaceEndpoints] of bySurface) {
|
|
1913
|
-
const relative$1 = openApiPathFor(surfaceId);
|
|
1914
|
-
const outputPath = join(process.cwd(), relative$1);
|
|
1915
|
-
await mkdir(dirname(outputPath), { recursive: true });
|
|
1916
|
-
const tsContent = await tsGenerator.generate(surfaceEndpoints, {
|
|
1917
|
-
title: openApiConfig.title ?? surfaceId,
|
|
1918
|
-
version: openApiConfig.version,
|
|
1919
|
-
description: openApiConfig.description
|
|
1920
|
-
});
|
|
1921
|
-
await writeFile(outputPath, tsContent);
|
|
1922
|
-
written.push(relative$1);
|
|
1923
|
-
logger.log(`📄 OpenAPI client generated: ${relative$1} (${surfaceEndpoints.length} endpoints)`);
|
|
1924
|
-
}
|
|
1925
|
-
return {
|
|
1926
|
-
outputPath: join(process.cwd(), written[0]),
|
|
1927
|
-
outputPaths: written.map((r) => join(process.cwd(), r)),
|
|
1928
|
-
endpointCount: endpoints.length
|
|
1929
|
-
};
|
|
1930
|
-
}
|
|
1931
|
-
async function openapiCommand(options = {}) {
|
|
1932
|
-
const logger = console;
|
|
1933
|
-
try {
|
|
1934
|
-
const loadedConfig = await loadWorkspaceConfig(options.cwd);
|
|
1935
|
-
if (loadedConfig.type === "single") {
|
|
1936
|
-
const config = loadedConfig.raw;
|
|
1937
|
-
if (!config.openapi) config.openapi = { enabled: true };
|
|
1938
|
-
const result = await generateOpenApiFrom(config.constructs, { openapi: config.openapi });
|
|
1939
|
-
if (result) logger.log(`Found ${result.endpointCount} endpoints`);
|
|
1940
|
-
} else {
|
|
1941
|
-
const { workspace } = loadedConfig;
|
|
1942
|
-
const workspaceRoot = workspace.root;
|
|
1943
|
-
const backendApps = Object.entries(workspace.apps).filter(([_, app]) => app.type === "backend" && app.openapi !== false && (typeof app.openapi !== "object" || app.openapi.enabled !== false));
|
|
1944
|
-
if (backendApps.length === 0) {
|
|
1945
|
-
logger.log("No backend apps with OpenAPI enabled found");
|
|
1946
|
-
return;
|
|
1947
|
-
}
|
|
1948
|
-
if (options.app) {
|
|
1949
|
-
const entry = backendApps.find(([name]) => name === options.app);
|
|
1950
|
-
if (!entry) throw new Error(`App "${options.app}" not found or has OpenAPI disabled`);
|
|
1951
|
-
const [appName, app] = entry;
|
|
1952
|
-
const result = await generateOpenApiForApp(workspaceRoot, appName, app);
|
|
1953
|
-
if (result) logger.log(`📄 [${appName}] Generated OpenAPI (${result.endpointCount} endpoints)`);
|
|
1954
|
-
return;
|
|
1955
|
-
}
|
|
1956
|
-
for (const [appName, app] of backendApps) {
|
|
1957
|
-
const appPath = join(workspaceRoot, app.path);
|
|
1958
|
-
if (resolve(appPath) === resolve(workspaceRoot)) {
|
|
1959
|
-
const result = await generateOpenApiForApp(workspaceRoot, appName, app, false);
|
|
1960
|
-
if (result) logger.log(`Found ${result.endpointCount} endpoints`);
|
|
1961
|
-
continue;
|
|
1962
|
-
}
|
|
1963
|
-
await runOpenApiInSubprocess(appPath, appName);
|
|
1964
|
-
}
|
|
1965
|
-
}
|
|
1966
|
-
} catch (error) {
|
|
1967
|
-
throw new Error(`OpenAPI generation failed: ${error.message}`);
|
|
1968
|
-
}
|
|
1969
|
-
}
|
|
1970
|
-
/**
|
|
1971
|
-
* Generate OpenAPI for a single named app within a workspace.
|
|
1972
|
-
* Runs in-process. The caller is responsible for ensuring `process.cwd()`
|
|
1973
|
-
* is the app's directory so tsx loads the app's tsconfig path aliases.
|
|
1974
|
-
*/
|
|
1975
|
-
async function generateOpenApiForApp(workspaceRoot, _appName, app, silent = true) {
|
|
1976
|
-
if (app.type !== "backend") return null;
|
|
1977
|
-
const appPath = join(workspaceRoot, app.path);
|
|
1978
|
-
const globs = [join(appPath, "**/*.ts")];
|
|
1979
|
-
return generateOpenApiFrom(globs, {
|
|
1980
|
-
openapi: app.openapi ?? { enabled: true },
|
|
1981
|
-
silent
|
|
1982
|
-
});
|
|
1983
|
-
}
|
|
1984
|
-
/**
|
|
1985
|
-
* Resolve the gkm bin path. Tests can override via `GKM_BIN_PATH` env var
|
|
1986
|
-
* to avoid depending on the built dist.
|
|
1987
|
-
*/
|
|
1988
|
-
function resolveGkmBinPath() {
|
|
1989
|
-
if (process.env.GKM_BIN_PATH) return process.env.GKM_BIN_PATH;
|
|
1990
|
-
return fileURLToPath(new URL("../bin/gkm.mjs", import.meta.url));
|
|
1991
|
-
}
|
|
1992
|
-
/**
|
|
1993
|
-
* Spawn a subprocess that runs `gkm openapi --app <name>` with `cwd` set to
|
|
1994
|
-
* the app's directory. Inherits `NODE_OPTIONS` (which already contains
|
|
1995
|
-
* `--import tsx`), so the child gets a fresh tsx instance whose tsconfig
|
|
1996
|
-
* discovery picks up the app's tsconfig path aliases.
|
|
1997
|
-
*/
|
|
1998
|
-
async function runOpenApiInSubprocess(appCwd, appName) {
|
|
1999
|
-
const binPath = resolveGkmBinPath();
|
|
2000
|
-
await new Promise((resolve$1, reject) => {
|
|
2001
|
-
const child = spawn(process.execPath, [
|
|
2002
|
-
binPath,
|
|
2003
|
-
"openapi",
|
|
2004
|
-
"--app",
|
|
2005
|
-
appName
|
|
2006
|
-
], {
|
|
2007
|
-
cwd: appCwd,
|
|
2008
|
-
stdio: "inherit",
|
|
2009
|
-
env: process.env
|
|
2010
|
-
});
|
|
2011
|
-
child.on("error", reject);
|
|
2012
|
-
child.on("close", (code) => {
|
|
2013
|
-
if (code === 0) resolve$1();
|
|
2014
|
-
else reject(new Error(`OpenAPI generation for app "${appName}" exited with code ${code}`));
|
|
2015
|
-
});
|
|
2016
|
-
});
|
|
2017
|
-
}
|
|
2018
|
-
|
|
2019
|
-
//#endregion
|
|
2020
|
-
export { EndpointGenerator, OPENAPI_OUTPUT_PATH, generateOpenApi, generateOpenApiFrom, normalizeRoutes, openApiPathFor, openapiCommand, resolveOpenApiConfig, runtimeFor };
|
|
2021
|
-
//# sourceMappingURL=openapi-Xx0A7Wg0.mjs.map
|