@geekmidas/cli 10.0.0-alpha.6 → 10.0.0-alpha.60
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 +225 -268
- package/bin/adjacent-tsconfig.mjs +97 -0
- package/bin/gkm.mjs +16 -2
- package/bin/owning-tsconfig-jsx-register.mjs +18 -0
- package/bin/owning-tsconfig-jsx.mjs +108 -0
- package/dist/{HostingerProvider-h3EbEp-Z.cjs → HostingerProvider-BTZd2u0f.cjs} +21 -22
- package/dist/HostingerProvider-BTZd2u0f.cjs.map +1 -0
- package/dist/{HostingerProvider-Cft7lEYr.mjs → HostingerProvider-DgzKa_Tj.mjs} +20 -21
- package/dist/HostingerProvider-DgzKa_Tj.mjs.map +1 -0
- package/dist/LegacyStateStore-CToa2hga.mjs +89 -0
- package/dist/LegacyStateStore-CToa2hga.mjs.map +1 -0
- package/dist/LegacyStateStore-CidlPT0N.cjs +89 -0
- package/dist/LegacyStateStore-CidlPT0N.cjs.map +1 -0
- package/dist/LocalStateStore-BFLiOocU.mjs +165 -0
- package/dist/LocalStateStore-BFLiOocU.mjs.map +1 -0
- package/dist/LocalStateStore-Dpuqka1N.cjs +176 -0
- package/dist/LocalStateStore-Dpuqka1N.cjs.map +1 -0
- package/dist/{openapi-D-dx9Akj.cjs → OpenApiTsGenerator-BTuUumsV.cjs} +330 -469
- package/dist/OpenApiTsGenerator-BTuUumsV.cjs.map +1 -0
- package/dist/{openapi-Bw8DWFWz.mjs → OpenApiTsGenerator-CvVr1Ilz.mjs} +295 -403
- package/dist/OpenApiTsGenerator-CvVr1Ilz.mjs.map +1 -0
- package/dist/{Route53Provider-RdpUDJQk.mjs → Route53Provider-Cvj40A8I.mjs} +12 -12
- package/dist/Route53Provider-Cvj40A8I.mjs.map +1 -0
- package/dist/{Route53Provider-di2fMM5N.cjs → Route53Provider-DVILBsvV.cjs} +20 -21
- package/dist/Route53Provider-DVILBsvV.cjs.map +1 -0
- package/dist/S3StateStore-aaGkjcxO.mjs +161 -0
- package/dist/S3StateStore-aaGkjcxO.mjs.map +1 -0
- package/dist/S3StateStore-cnm813Xn.cjs +160 -0
- package/dist/S3StateStore-cnm813Xn.cjs.map +1 -0
- package/dist/SSMStateStore-BllaJKGo.mjs +154 -0
- package/dist/SSMStateStore-BllaJKGo.mjs.map +1 -0
- package/dist/SSMStateStore-DwaGprVX.cjs +153 -0
- package/dist/SSMStateStore-DwaGprVX.cjs.map +1 -0
- package/dist/appPorts-C9z9Pzn9.mjs +223 -0
- package/dist/appPorts-C9z9Pzn9.mjs.map +1 -0
- package/dist/appPorts-Cu79hJF6.cjs +264 -0
- package/dist/appPorts-Cu79hJF6.cjs.map +1 -0
- package/dist/aws-DkHdTV-p.cjs +61 -0
- package/dist/aws-DkHdTV-p.cjs.map +1 -0
- package/dist/aws-DojzQnLd.mjs +61 -0
- package/dist/aws-DojzQnLd.mjs.map +1 -0
- package/dist/{backup-provisioner-BrsHwYG0.cjs → backup-provisioner-BmhIO_Yr.cjs} +21 -19
- package/dist/backup-provisioner-BmhIO_Yr.cjs.map +1 -0
- package/dist/{backup-provisioner-Bgj5Vi_L.mjs → backup-provisioner-DIVCbfee.mjs} +10 -7
- package/dist/backup-provisioner-DIVCbfee.mjs.map +1 -0
- package/dist/{bundler-BY1OmV1e.cjs → bundler-CzXZPSBJ.cjs} +20 -32
- package/dist/bundler-CzXZPSBJ.cjs.map +1 -0
- package/dist/{bundler-Be8Zg4Wd.mjs → bundler-DJVR4QlW.mjs} +18 -29
- package/dist/bundler-DJVR4QlW.mjs.map +1 -0
- package/dist/cli-D_qTl20Z.cjs +69 -0
- package/dist/cli-D_qTl20Z.cjs.map +1 -0
- package/dist/cli-DzR1hpum.mjs +69 -0
- package/dist/cli-DzR1hpum.mjs.map +1 -0
- package/dist/{config-CxP7P-qG.mjs → config-C5-EEdmr.mjs} +144 -127
- package/dist/config-C5-EEdmr.mjs.map +1 -0
- package/dist/{config-CNz5KaSR.cjs → config-DXnkCEq0.cjs} +212 -184
- package/dist/config-DXnkCEq0.cjs.map +1 -0
- package/dist/config.cjs +9 -6
- package/dist/config.d.cts +124 -33
- package/dist/config.d.cts.map +1 -1
- package/dist/config.d.mts +124 -33
- package/dist/config.d.mts.map +1 -1
- package/dist/config.mjs +3 -5
- package/dist/deploy/public.cjs +22 -0
- package/dist/deploy/public.d.cts +459 -0
- package/dist/deploy/public.d.cts.map +1 -0
- package/dist/deploy/public.d.mts +459 -0
- package/dist/deploy/public.d.mts.map +1 -0
- package/dist/deploy/public.mjs +5 -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-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 +3 -3
- 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-0fBVeWO-.cjs +3518 -0
- package/dist/deploy-0fBVeWO-.cjs.map +1 -0
- package/dist/deploy-D6Q09OkA.mjs +3429 -0
- package/dist/deploy-D6Q09OkA.mjs.map +1 -0
- package/dist/{discover-DTmFeloL.cjs → discover-Bc-4eA_p.cjs} +131 -103
- package/dist/discover-Bc-4eA_p.cjs.map +1 -0
- package/dist/{discover-CAlFgYec.mjs → discover-FrYvzkeC.mjs} +91 -58
- package/dist/discover-FrYvzkeC.mjs.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-CML4UaPi.cjs +91 -0
- package/dist/encryption-CML4UaPi.cjs.map +1 -0
- package/dist/encryption-aDA2AzVx.mjs +90 -0
- package/dist/encryption-aDA2AzVx.mjs.map +1 -0
- package/dist/fakes-C9RiT6XG.mjs +125 -0
- package/dist/fakes-C9RiT6XG.mjs.map +1 -0
- package/dist/fakes-zW-jNAbQ.cjs +148 -0
- package/dist/fakes-zW-jNAbQ.cjs.map +1 -0
- package/dist/github-Cq57poPj.cjs +213 -0
- package/dist/github-Cq57poPj.cjs.map +1 -0
- package/dist/github-DmCj2u95.mjs +213 -0
- package/dist/github-DmCj2u95.mjs.map +1 -0
- package/dist/home-DOzBaWaL.cjs +33 -0
- package/dist/home-DOzBaWaL.cjs.map +1 -0
- package/dist/home-GI7pJl1M.mjs +22 -0
- package/dist/home-GI7pJl1M.mjs.map +1 -0
- package/dist/{index-DEbaleny.d.mts → index-Da1-oa9W.d.mts} +4 -23
- package/dist/index-Da1-oa9W.d.mts.map +1 -0
- package/dist/{index-BGGdjueF.d.cts → index-UiWCaGzU.d.cts} +4 -23
- package/dist/index-UiWCaGzU.d.cts.map +1 -0
- package/dist/index.cjs +9096 -12213
- 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 +9067 -12192
- package/dist/index.mjs.map +1 -1
- package/dist/migrate-BTqIflwc.cjs +743 -0
- package/dist/migrate-BTqIflwc.cjs.map +1 -0
- package/dist/migrate-B_6KofUo.mjs +736 -0
- package/dist/migrate-B_6KofUo.mjs.map +1 -0
- package/dist/openapi.cjs +198 -10
- package/dist/openapi.cjs.map +1 -0
- package/dist/openapi.d.cts +21 -19
- package/dist/openapi.d.cts.map +1 -1
- package/dist/openapi.d.mts +21 -19
- package/dist/openapi.d.mts.map +1 -1
- package/dist/openapi.mjs +193 -5
- package/dist/openapi.mjs.map +1 -0
- package/dist/ready-CCLIAkwR.cjs +807 -0
- package/dist/ready-CCLIAkwR.cjs.map +1 -0
- package/dist/ready-CIHpnUc5.mjs +760 -0
- package/dist/ready-CIHpnUc5.mjs.map +1 -0
- package/dist/reconcile/public.cjs +3 -5
- package/dist/reconcile/public.d.cts +30 -33
- package/dist/reconcile/public.d.cts.map +1 -1
- package/dist/reconcile/public.d.mts +30 -33
- package/dist/reconcile/public.d.mts.map +1 -1
- package/dist/reconcile/public.mjs +3 -4
- package/dist/{reconcile-CFs8C_1a.mjs → reconcile--I7V4Klj.mjs} +4 -7
- package/dist/reconcile--I7V4Klj.mjs.map +1 -0
- package/dist/{reconcile-D0pimNiy.cjs → reconcile-CuCQBa5-.cjs} +5 -8
- package/dist/reconcile-CuCQBa5-.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/sniffer-BM8fpCas.mjs +6321 -0
- package/dist/sniffer-BM8fpCas.mjs.map +1 -0
- package/dist/sniffer-DIv12ncU.cjs +6707 -0
- package/dist/sniffer-DIv12ncU.cjs.map +1 -0
- package/dist/storage-BzaUxmqe.mjs +124 -0
- package/dist/storage-BzaUxmqe.mjs.map +1 -0
- package/dist/storage-CSGoC3bK.cjs +152 -0
- package/dist/storage-CSGoC3bK.cjs.map +1 -0
- package/dist/store-BS8A7NcA.mjs +415 -0
- package/dist/store-BS8A7NcA.mjs.map +1 -0
- package/dist/store-KvNQwYaB.cjs +510 -0
- package/dist/store-KvNQwYaB.cjs.map +1 -0
- package/dist/types-Bp1mVxG0.d.mts +6410 -0
- package/dist/types-Bp1mVxG0.d.mts.map +1 -0
- package/dist/types-efNLu9xu.d.cts +6411 -0
- package/dist/types-efNLu9xu.d.cts.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/workspace/index.cjs +3 -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-LKiNyaDy.cjs → workspace-B64Xk1Mb.cjs} +1070 -1118
- package/dist/workspace-B64Xk1Mb.cjs.map +1 -0
- package/dist/{workspace-DIuZx-cc.mjs → workspace-Bp1xVaOz.mjs} +901 -972
- package/dist/workspace-Bp1xVaOz.mjs.map +1 -0
- package/package.json +38 -20
- 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-CNz5KaSR.cjs.map +0 -1
- package/dist/config-CxP7P-qG.mjs.map +0 -1
- package/dist/credentials-B5zRHdAl.mjs +0 -141
- package/dist/credentials-B5zRHdAl.mjs.map +0 -1
- package/dist/credentials-BK3Z93MX.mjs +0 -2548
- package/dist/credentials-BK3Z93MX.mjs.map +0 -1
- package/dist/credentials-BapzjyV5.cjs +0 -195
- package/dist/credentials-BapzjyV5.cjs.map +0 -1
- package/dist/credentials-DzsXZzkx.cjs +0 -2680
- package/dist/credentials-DzsXZzkx.cjs.map +0 -1
- package/dist/discover-CAlFgYec.mjs.map +0 -1
- 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-BGGdjueF.d.cts.map +0 -1
- package/dist/index-DEbaleny.d.mts.map +0 -1
- package/dist/openapi-Bw8DWFWz.mjs.map +0 -1
- package/dist/openapi-D-dx9Akj.cjs.map +0 -1
- package/dist/openapi-react-query-Byltvm_7.mjs +0 -167
- package/dist/openapi-react-query-Byltvm_7.mjs.map +0 -1
- package/dist/openapi-react-query-CmJ1xDbn.cjs +0 -173
- package/dist/openapi-react-query-CmJ1xDbn.cjs.map +0 -1
- package/dist/openapi-react-query.cjs +0 -4
- package/dist/openapi-react-query.d.cts +0 -11
- package/dist/openapi-react-query.d.cts.map +0 -1
- package/dist/openapi-react-query.d.mts +0 -11
- package/dist/openapi-react-query.d.mts.map +0 -1
- package/dist/openapi-react-query.mjs +0 -4
- package/dist/reconcile-CFs8C_1a.mjs.map +0 -1
- package/dist/reconcile-D0pimNiy.cjs.map +0 -1
- package/dist/setup-8IWPsRIN.cjs +0 -11
- package/dist/setup-BWMhbW-p.mjs +0 -443
- package/dist/setup-BWMhbW-p.mjs.map +0 -1
- package/dist/setup-D7L4BgBT.cjs +0 -473
- package/dist/setup-D7L4BgBT.cjs.map +0 -1
- package/dist/setup-DGIPd2fh.mjs +0 -11
- 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-BLSNdrk0.d.cts +0 -2176
- package/dist/types-BLSNdrk0.d.cts.map +0 -1
- package/dist/types-C3POXhw3.d.mts +0 -2176
- package/dist/types-C3POXhw3.d.mts.map +0 -1
- package/dist/workspace-CGaUAw5A.cjs +0 -9
- package/dist/workspace-CnTqIlB3.mjs +0 -8
- package/dist/workspace-DIuZx-cc.mjs.map +0 -1
- package/dist/workspace-LKiNyaDy.cjs.map +0 -1
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
import { ConstructGenerator
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { kebabCase } from "@geekmidas/manifest";
|
|
1
|
+
import { i as ConstructGenerator } from "./discover-FrYvzkeC.mjs";
|
|
2
|
+
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
3
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
5
4
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
6
|
-
import { spawn } from "node:child_process";
|
|
7
5
|
import { Endpoint } from "@geekmidas/constructs/endpoints";
|
|
8
|
-
import { fileURLToPath } from "node:url";
|
|
9
6
|
import { StandardSchemaJsonSchema, getRegisteredZodJsonSchemas, getSchemaMetadata } from "@geekmidas/schema/conversion";
|
|
10
|
-
|
|
11
7
|
//#region src/build/endpoint-analyzer.ts
|
|
12
8
|
/**
|
|
13
9
|
* Analyze an endpoint to extract its features
|
|
@@ -84,7 +80,6 @@ function summarizeAnalysis(analyses) {
|
|
|
84
80
|
byFeature
|
|
85
81
|
};
|
|
86
82
|
}
|
|
87
|
-
|
|
88
83
|
//#endregion
|
|
89
84
|
//#region src/build/handler-templates.ts
|
|
90
85
|
/**
|
|
@@ -106,8 +101,7 @@ function generateMinimalHandler(analysis) {
|
|
|
106
101
|
const { exportName, features } = analysis;
|
|
107
102
|
const method = analysis.method.toLowerCase();
|
|
108
103
|
const validators = generateValidators(exportName, features);
|
|
109
|
-
|
|
110
|
-
if (!hasValidators && !features.hasOutputValidation) return `
|
|
104
|
+
if (!(validators.length > 0) && !features.hasOutputValidation) return `
|
|
111
105
|
// Minimal handler: ${analysis.route} (${analysis.method})
|
|
112
106
|
app.${method}('${analysis.route}', async (c) => {
|
|
113
107
|
const result = await ${exportName}.handler(
|
|
@@ -305,8 +299,7 @@ ${factories.join("\n")}
|
|
|
305
299
|
*/
|
|
306
300
|
function generateMinimalEndpointFile(analysis, endpointImport) {
|
|
307
301
|
const { exportName, features } = analysis;
|
|
308
|
-
const
|
|
309
|
-
const validatorImport = needsValidators ? generateValidatorImportsForEndpoint(analysis) : "";
|
|
302
|
+
const validatorImport = features.hasBodyValidation || features.hasQueryValidation || features.hasParamValidation ? generateValidatorImportsForEndpoint(analysis) : "";
|
|
310
303
|
const handler = generateMinimalHandler(analysis);
|
|
311
304
|
return `/**
|
|
312
305
|
* Minimal endpoint: ${analysis.route} (${analysis.method})
|
|
@@ -331,8 +324,7 @@ ${handler}
|
|
|
331
324
|
*/
|
|
332
325
|
function generateStandardEndpointFile(analysis, endpointImport) {
|
|
333
326
|
const { exportName, features } = analysis;
|
|
334
|
-
const
|
|
335
|
-
const validatorImport = needsValidators ? generateValidatorImportsForEndpoint(analysis) : "";
|
|
327
|
+
const validatorImport = features.hasBodyValidation || features.hasQueryValidation || features.hasParamValidation ? generateValidatorImportsForEndpoint(analysis) : "";
|
|
336
328
|
const eventsImport = features.hasEvents ? `import { publishConstructEvents } from '@geekmidas/constructs/endpoints';` : "";
|
|
337
329
|
const handler = generateStandardHandler(analysis);
|
|
338
330
|
return `/**
|
|
@@ -424,7 +416,7 @@ export function setupFullEndpoints(
|
|
|
424
416
|
}
|
|
425
417
|
const imports = tierEndpoints.map((a) => `import { setup${capitalize(a.exportName)} } from './${a.exportName}.js';`).join("\n");
|
|
426
418
|
if (tier === "minimal") {
|
|
427
|
-
const calls
|
|
419
|
+
const calls = tierEndpoints.map((a) => ` setup${capitalize(a.exportName)}(app, logger);`).join("\n");
|
|
428
420
|
return `/**
|
|
429
421
|
* Minimal-tier endpoint index (${tierEndpoints.length} endpoints)
|
|
430
422
|
* Near-raw-Hono performance
|
|
@@ -437,12 +429,12 @@ export function setupMinimalEndpoints(
|
|
|
437
429
|
app: Hono,
|
|
438
430
|
logger: Logger,
|
|
439
431
|
): void {
|
|
440
|
-
${calls
|
|
432
|
+
${calls}
|
|
441
433
|
}
|
|
442
434
|
`;
|
|
443
435
|
}
|
|
444
436
|
if (tier === "standard") {
|
|
445
|
-
const calls
|
|
437
|
+
const calls = tierEndpoints.map((a) => ` setup${capitalize(a.exportName)}(app, serviceDiscovery, logger);`).join("\n");
|
|
446
438
|
return `/**
|
|
447
439
|
* Standard-tier endpoint index (${tierEndpoints.length} endpoints)
|
|
448
440
|
* Auth and/or services enabled
|
|
@@ -457,7 +449,7 @@ export function setupStandardEndpoints(
|
|
|
457
449
|
serviceDiscovery: ServiceDiscovery<any, any>,
|
|
458
450
|
logger: Logger,
|
|
459
451
|
): void {
|
|
460
|
-
${calls
|
|
452
|
+
${calls}
|
|
461
453
|
}
|
|
462
454
|
`;
|
|
463
455
|
}
|
|
@@ -527,9 +519,7 @@ function generateEndpointFilesNested(analyses, endpointImports) {
|
|
|
527
519
|
case "standard":
|
|
528
520
|
files[fileName] = generateStandardEndpointFile(analysis, endpointImport);
|
|
529
521
|
break;
|
|
530
|
-
case "full":
|
|
531
|
-
files[fileName] = generateFullEndpointFile(analysis, endpointImport);
|
|
532
|
-
break;
|
|
522
|
+
case "full": files[fileName] = generateFullEndpointFile(analysis, endpointImport);
|
|
533
523
|
}
|
|
534
524
|
}
|
|
535
525
|
return files;
|
|
@@ -586,7 +576,142 @@ export async function setupEndpoints(
|
|
|
586
576
|
}
|
|
587
577
|
`;
|
|
588
578
|
}
|
|
579
|
+
//#endregion
|
|
580
|
+
//#region src/generators/telemetry.ts
|
|
581
|
+
/**
|
|
582
|
+
* Every package `@geekmidas/telescope/instrumentation` loads at runtime.
|
|
583
|
+
*
|
|
584
|
+
* All of them, rather than one as a proxy: the production bundle follows each
|
|
585
|
+
* import, and a single missing one fails the build rather than the telemetry.
|
|
586
|
+
*/
|
|
587
|
+
const TELEMETRY_PACKAGES = [
|
|
588
|
+
"@opentelemetry/api",
|
|
589
|
+
"@opentelemetry/auto-instrumentations-node",
|
|
590
|
+
"@opentelemetry/exporter-logs-otlp-http",
|
|
591
|
+
"@opentelemetry/exporter-trace-otlp-http",
|
|
592
|
+
"@opentelemetry/instrumentation-pino",
|
|
593
|
+
"@opentelemetry/resources",
|
|
594
|
+
"@opentelemetry/sdk-logs",
|
|
595
|
+
"@opentelemetry/sdk-node",
|
|
596
|
+
"@opentelemetry/sdk-trace-base",
|
|
597
|
+
"@opentelemetry/sdk-trace-node",
|
|
598
|
+
"@opentelemetry/semantic-conventions"
|
|
599
|
+
];
|
|
600
|
+
/**
|
|
601
|
+
* The directory of `name` as `node_modules` lookup finds it from `fromDir`,
|
|
602
|
+
* symlinks resolved — or undefined.
|
|
603
|
+
*
|
|
604
|
+
* The directory walk alone, not `require.resolve`: that also searches
|
|
605
|
+
* `NODE_PATH`, which a package manager's bin shim sets to its own store, so a
|
|
606
|
+
* `gkm build` run through one would find packages the app never installed —
|
|
607
|
+
* and the bundler, which does not look there, would then fail on them.
|
|
608
|
+
*/
|
|
609
|
+
function packageDir(fromDir, name) {
|
|
610
|
+
for (let dir = resolve(fromDir);; dir = dirname(dir)) {
|
|
611
|
+
const candidate = join(dir, "node_modules", name);
|
|
612
|
+
if (existsSync(join(candidate, "package.json"))) return realpathSync(candidate);
|
|
613
|
+
if (dirname(dir) === dir) return void 0;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Whether the telemetry packages resolve the way the bundler will resolve them:
|
|
618
|
+
* telescope from the app, and the OpenTelemetry packages from telescope —
|
|
619
|
+
* where a peer is linked under pnpm, which need not be where the app sees it.
|
|
620
|
+
*/
|
|
621
|
+
function telemetryResolvesFrom(appRoot) {
|
|
622
|
+
const telescope = packageDir(appRoot, "@geekmidas/telescope");
|
|
623
|
+
if (!telescope) return false;
|
|
624
|
+
return TELEMETRY_PACKAGES.every((pkg) => packageDir(telescope, pkg));
|
|
625
|
+
}
|
|
626
|
+
/** The telemetry half of a build context. */
|
|
627
|
+
function telemetryFor(options) {
|
|
628
|
+
return {
|
|
629
|
+
serviceName: options.surfaceId ?? basename(options.appRoot),
|
|
630
|
+
...options.workspaceName && { serviceNamespace: options.workspaceName },
|
|
631
|
+
available: telemetryResolvesFrom(options.appRoot)
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
const HEADER = `/**
|
|
635
|
+
* Generated by 'gkm build --production'.
|
|
636
|
+
*
|
|
637
|
+
* Starts OpenTelemetry when OTEL_EXPORTER_OTLP_ENDPOINT names a collector, and
|
|
638
|
+
* does nothing otherwise: without it, the telemetry packages are not loaded.
|
|
639
|
+
* The standard OTEL_* variables (OTEL_TRACES_SAMPLER, OTEL_TRACES_SAMPLER_ARG,
|
|
640
|
+
* OTEL_EXPORTER_OTLP_HEADERS, OTEL_RESOURCE_ATTRIBUTES, OTEL_SERVICE_NAME)
|
|
641
|
+
* configure the rest.
|
|
642
|
+
*/
|
|
643
|
+
|
|
644
|
+
/** Telemetry was asked for and cannot start. The server runs without it. */
|
|
645
|
+
export class TelemetryUnavailable extends Error {
|
|
646
|
+
constructor(readonly reason: string, options?: { cause?: unknown }) {
|
|
647
|
+
super(
|
|
648
|
+
\`OTEL_EXPORTER_OTLP_ENDPOINT is set, but \${reason} This server exports no traces or logs until that is fixed.\`,
|
|
649
|
+
options,
|
|
650
|
+
);
|
|
651
|
+
this.name = 'TelemetryUnavailable';
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
`;
|
|
655
|
+
/**
|
|
656
|
+
* The entry's `telemetry.ts`: a `startTelemetry()` the server awaits before it
|
|
657
|
+
* imports the app, so the libraries the app loads are instrumented.
|
|
658
|
+
*
|
|
659
|
+
* Failing to start telemetry warns (a named `TelemetryUnavailable`, through
|
|
660
|
+
* `process.emitWarning`) and never stops the server: an outage in observability
|
|
661
|
+
* is not a reason to have an outage in the service.
|
|
662
|
+
*/
|
|
663
|
+
function generateTelemetryModule(telemetry) {
|
|
664
|
+
if (!telemetry?.available) return `${HEADER}
|
|
665
|
+
export async function startTelemetry(): Promise<void> {
|
|
666
|
+
if (!process.env.OTEL_EXPORTER_OTLP_ENDPOINT) return;
|
|
667
|
+
|
|
668
|
+
process.emitWarning(
|
|
669
|
+
new TelemetryUnavailable(
|
|
670
|
+
'this server was built without @geekmidas/telescope and the @opentelemetry packages it needs. Add them to the app and rebuild.',
|
|
671
|
+
),
|
|
672
|
+
);
|
|
673
|
+
}
|
|
674
|
+
`;
|
|
675
|
+
const options = [
|
|
676
|
+
`serviceName: ${JSON.stringify(telemetry.serviceName)},`,
|
|
677
|
+
...telemetry.serviceNamespace ? [`serviceNamespace: ${JSON.stringify(telemetry.serviceNamespace)},`] : [],
|
|
678
|
+
"deploymentEnvironment: process.env.STAGE,",
|
|
679
|
+
"handleSignals: false,"
|
|
680
|
+
];
|
|
681
|
+
return `${HEADER}
|
|
682
|
+
export async function startTelemetry(): Promise<void> {
|
|
683
|
+
if (!process.env.OTEL_EXPORTER_OTLP_ENDPOINT) return;
|
|
589
684
|
|
|
685
|
+
let instrumentation;
|
|
686
|
+
try {
|
|
687
|
+
instrumentation = await import('@geekmidas/telescope/instrumentation');
|
|
688
|
+
} catch (cause) {
|
|
689
|
+
process.emitWarning(
|
|
690
|
+
new TelemetryUnavailable(
|
|
691
|
+
'@geekmidas/telescope/instrumentation or an @opentelemetry package it needs could not be loaded. Install them in the app and rebuild.',
|
|
692
|
+
{ cause },
|
|
693
|
+
),
|
|
694
|
+
);
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
instrumentation.setupTelemetry({
|
|
699
|
+
${options.join("\n ")}
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
// Flush what is buffered when the process is told to stop. When nothing
|
|
703
|
+
// else handles the signal, re-raise it once flushed, so the process still
|
|
704
|
+
// stops the way it would have without this listener.
|
|
705
|
+
for (const signal of ['SIGTERM', 'SIGINT'] as const) {
|
|
706
|
+
process.once(signal, () => {
|
|
707
|
+
void instrumentation.flushTelemetry().finally(() => {
|
|
708
|
+
if (process.listenerCount(signal) === 0) process.kill(process.pid, signal);
|
|
709
|
+
});
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
`;
|
|
714
|
+
}
|
|
590
715
|
//#endregion
|
|
591
716
|
//#region src/generators/EndpointGenerator.ts
|
|
592
717
|
/**
|
|
@@ -679,45 +804,32 @@ var EndpointGenerator = class extends ConstructGenerator {
|
|
|
679
804
|
return Endpoint.isEndpoint(value);
|
|
680
805
|
}
|
|
681
806
|
async build(context, constructs, outputDir, options) {
|
|
682
|
-
const
|
|
807
|
+
const target = options?.target ?? "aws";
|
|
808
|
+
const root = options?.root ?? process.cwd();
|
|
683
809
|
const enableOpenApi = options?.enableOpenApi || false;
|
|
684
810
|
const logger = console;
|
|
685
811
|
const routes = [];
|
|
686
812
|
if (constructs.length === 0) return routes;
|
|
687
|
-
if (
|
|
813
|
+
if (target === "server") {
|
|
688
814
|
await this.generateEndpointsFile(outputDir, constructs, context);
|
|
689
815
|
const appFile = await this.generateAppFile(outputDir, context);
|
|
690
816
|
routes.push({
|
|
691
817
|
path: "*",
|
|
692
818
|
method: "ALL",
|
|
693
|
-
handler: relative(
|
|
819
|
+
handler: relative(root, appFile),
|
|
694
820
|
authorizer: "none"
|
|
695
821
|
});
|
|
696
822
|
logger.log(`Generated server with ${constructs.length} endpoints${enableOpenApi ? " (OpenAPI enabled)" : ""}`);
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
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);
|
|
823
|
+
return routes;
|
|
824
|
+
}
|
|
825
|
+
const routesDir = join(outputDir, "routes");
|
|
826
|
+
await mkdir(routesDir, { recursive: true });
|
|
827
|
+
for (const { key, construct, path } of constructs) {
|
|
828
|
+
const handlerFile = await this.generateHandlerFile(routesDir, path.relative, key, context);
|
|
717
829
|
const routeInfo = {
|
|
718
830
|
path: construct._path,
|
|
719
831
|
method: construct.method,
|
|
720
|
-
handler: relative(
|
|
832
|
+
handler: relative(root, handlerFile).replace(/\.ts$/, ".handler"),
|
|
721
833
|
timeout: construct.timeout,
|
|
722
834
|
memorySize: construct.memorySize,
|
|
723
835
|
environment: await construct.getEnvironment({ markOptional: context.markOptional }),
|
|
@@ -729,34 +841,17 @@ var EndpointGenerator = class extends ConstructGenerator {
|
|
|
729
841
|
}
|
|
730
842
|
return routes;
|
|
731
843
|
}
|
|
732
|
-
async generateHandlerFile(outputDir, sourceFile, exportName,
|
|
733
|
-
const
|
|
734
|
-
const
|
|
735
|
-
|
|
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);
|
|
844
|
+
async generateHandlerFile(outputDir, sourceFile, exportName, context) {
|
|
845
|
+
const handlerPath = join(outputDir, `${exportName}.ts`);
|
|
846
|
+
const importPath = relative(dirname(handlerPath), sourceFile).replace(/\.ts$/, ".js");
|
|
847
|
+
await writeFile(handlerPath, this.generateAWSApiGatewayV2Handler(importPath, exportName, context.storageDrivers));
|
|
751
848
|
return handlerPath;
|
|
752
849
|
}
|
|
753
850
|
async generateEndpointsFile(outputDir, endpoints, context) {
|
|
754
|
-
const
|
|
755
|
-
const endpointsPath = join(outputDir, endpointsFileName);
|
|
851
|
+
const endpointsPath = join(outputDir, "endpoints.ts");
|
|
756
852
|
const importsByFile = /* @__PURE__ */ new Map();
|
|
757
853
|
for (const { path, key } of endpoints) {
|
|
758
|
-
const
|
|
759
|
-
const importPath = relativePath.replace(/\.ts$/, ".js");
|
|
854
|
+
const importPath = relative(dirname(endpointsPath), path.relative).replace(/\.ts$/, ".js");
|
|
760
855
|
if (!importsByFile.has(importPath)) importsByFile.set(importPath, []);
|
|
761
856
|
importsByFile.get(importPath)?.push(key);
|
|
762
857
|
}
|
|
@@ -771,7 +866,7 @@ import { ServiceDiscovery } from '@geekmidas/services';
|
|
|
771
866
|
import type { Hono } from 'hono';
|
|
772
867
|
${endpointImports}
|
|
773
868
|
|
|
774
|
-
export const endpoints: Endpoint<any, any, any, any, any, any, any, any, any, any, any, any
|
|
869
|
+
export const endpoints: Endpoint<any, any, any, any, any, any, any, any, any, any, any, any>[] = [
|
|
775
870
|
${allExportNames.join(",\n ")}
|
|
776
871
|
];
|
|
777
872
|
|
|
@@ -822,11 +917,9 @@ export async function setupEndpoints(
|
|
|
822
917
|
const analyses = endpoints.map(({ key, construct }) => analyzeEndpoint(construct, key));
|
|
823
918
|
const endpointImports = endpoints.map(({ key, path }) => {
|
|
824
919
|
const tierDir = join(endpointsDir, "standard");
|
|
825
|
-
const relativePath = relative(tierDir, path.relative);
|
|
826
|
-
const importPath = relativePath.replace(/\.ts$/, ".js");
|
|
827
920
|
return {
|
|
828
921
|
exportName: key,
|
|
829
|
-
importPath
|
|
922
|
+
importPath: relative(tierDir, path.relative).replace(/\.ts$/, ".js")
|
|
830
923
|
};
|
|
831
924
|
});
|
|
832
925
|
const summary = summarizeAnalysis(analyses);
|
|
@@ -848,43 +941,33 @@ export async function setupEndpoints(
|
|
|
848
941
|
}
|
|
849
942
|
async generateAppFile(outputDir, context) {
|
|
850
943
|
if (context.production?.enabled) return this.generateProductionAppFile(outputDir, context);
|
|
851
|
-
const
|
|
852
|
-
const appPath = join(outputDir, appFileName);
|
|
944
|
+
const appPath = join(outputDir, "app.ts");
|
|
853
945
|
const runtime = runtimeFor(context, dirname(appPath));
|
|
854
946
|
const telescopeEnabled = context.telescope?.enabled;
|
|
855
947
|
const telescopeWebSocketEnabled = context.telescope?.websocket;
|
|
856
948
|
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
949
|
const telescopeFromSurface = !!context.surface?.module && !usesExternalTelescope;
|
|
861
950
|
let telescopeImports = "";
|
|
862
|
-
if (telescopeEnabled)
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
import {
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
import {
|
|
874
|
-
|
|
875
|
-
|
|
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 = "";
|
|
951
|
+
if (telescopeEnabled) {
|
|
952
|
+
if (telescopeFromSurface) telescopeImports = `import { Telescope, InMemoryStorage } from '@geekmidas/telescope';
|
|
953
|
+
import { createApi, createMiddleware } from '@geekmidas/telescope/hono';`;
|
|
954
|
+
else if (usesExternalTelescope) {
|
|
955
|
+
const relativeTelescopePath = relative(dirname(appPath), context.telescope?.telescopePath);
|
|
956
|
+
telescopeImports = `import ${context.telescope?.telescopeImportPattern} from '${relativeTelescopePath}';
|
|
957
|
+
import { createApi, createMiddleware } from '@geekmidas/telescope/hono';`;
|
|
958
|
+
} else telescopeImports = `import { Telescope, InMemoryStorage } from '@geekmidas/telescope';
|
|
959
|
+
import { createApi, createMiddleware } from '@geekmidas/telescope/hono';`;
|
|
960
|
+
}
|
|
961
|
+
const databaseApi = context.databaseApi;
|
|
962
|
+
const databaseApiImports = databaseApi ? `import { snifferContext } from '@geekmidas/constructs';
|
|
963
|
+
import { createIntrospectionHandler } from '@geekmidas/db/introspect';
|
|
964
|
+
import { ${databaseApi.database.exportName} as __introspectedDb } from '${importSpecifier(dirname(appPath), databaseApi.database.specifier)}';` : "";
|
|
881
965
|
const cors = corsFor(context.surface);
|
|
882
966
|
let hooksImports = "";
|
|
883
967
|
let beforeSetupCall = "";
|
|
884
968
|
let afterSetupCall = "";
|
|
885
969
|
if (context.hooks?.serverHooksPath) {
|
|
886
|
-
|
|
887
|
-
hooksImports = `import * as serverHooks from '${relativeHooksPath}';`;
|
|
970
|
+
hooksImports = `import * as serverHooks from '${relative(dirname(appPath), context.hooks.serverHooksPath)}';`;
|
|
888
971
|
beforeSetupCall = `
|
|
889
972
|
// Call beforeSetup hook if defined
|
|
890
973
|
if (typeof serverHooks.beforeSetup === 'function') {
|
|
@@ -930,16 +1013,17 @@ import { createStudioApp } from '@geekmidas/studio/server/hono';`;
|
|
|
930
1013
|
}
|
|
931
1014
|
` : "";
|
|
932
1015
|
let telescopeSetup = "";
|
|
933
|
-
if (telescopeEnabled)
|
|
1016
|
+
if (telescopeEnabled) {
|
|
1017
|
+
if (telescopeFromSurface || usesExternalTelescope) telescopeSetup = `
|
|
934
1018
|
${telescopeWebSocketSetupCode}
|
|
935
1019
|
// Add telescope middleware (before endpoints to capture all requests)
|
|
936
1020
|
honoApp.use('*', createMiddleware(telescope));
|
|
937
1021
|
|
|
938
|
-
// Mount
|
|
939
|
-
const
|
|
940
|
-
honoApp.route('${context.telescope?.path}',
|
|
1022
|
+
// Mount Telescope's JSON API
|
|
1023
|
+
const telescopeApi = createApi(telescope);
|
|
1024
|
+
honoApp.route('${context.telescope?.path}', telescopeApi);
|
|
941
1025
|
`;
|
|
942
|
-
|
|
1026
|
+
else telescopeSetup = `
|
|
943
1027
|
// Setup Telescope for debugging/monitoring
|
|
944
1028
|
const telescopeStorage = new InMemoryStorage({ maxEntries: ${context.telescope?.maxEntries} });
|
|
945
1029
|
const telescope = new Telescope({
|
|
@@ -953,31 +1037,21 @@ ${telescopeWebSocketSetupCode}
|
|
|
953
1037
|
// Add telescope middleware (before endpoints to capture all requests)
|
|
954
1038
|
honoApp.use('*', createMiddleware(telescope));
|
|
955
1039
|
|
|
956
|
-
// Mount
|
|
957
|
-
const
|
|
958
|
-
honoApp.route('${context.telescope?.path}',
|
|
1040
|
+
// Mount Telescope's JSON API
|
|
1041
|
+
const telescopeApi = createApi(telescope);
|
|
1042
|
+
honoApp.route('${context.telescope?.path}', telescopeApi);
|
|
959
1043
|
`;
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
//
|
|
963
|
-
//
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
cursor: { field: 'id', direction: Direction.Desc },
|
|
969
|
-
},
|
|
970
|
-
enabled: true,
|
|
1044
|
+
}
|
|
1045
|
+
const databaseApiSetup = databaseApi ? `
|
|
1046
|
+
// The database's structure and rows as JSON, read-only, for whatever tool
|
|
1047
|
+
// the developer points at it. Dev only: it answers anyone who can reach the
|
|
1048
|
+
// port.
|
|
1049
|
+
const databaseApi = createIntrospectionHandler({
|
|
1050
|
+
db: await __introspectedDb.service.register({ envParser, context: snifferContext }),
|
|
1051
|
+
basePath: '${databaseApi.path}',
|
|
971
1052
|
});
|
|
972
|
-
|
|
973
|
-
|
|
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
|
-
`;
|
|
1053
|
+
honoApp.all('${databaseApi.path}/*', (c) => databaseApi(c.req.raw));
|
|
1054
|
+
` : "";
|
|
981
1055
|
const content = `/**
|
|
982
1056
|
* Generated server application
|
|
983
1057
|
*
|
|
@@ -993,24 +1067,37 @@ import { setupQueues } from './queues.js';
|
|
|
993
1067
|
import { setupCrons } from './crons.js';
|
|
994
1068
|
${runtime.imports}
|
|
995
1069
|
${telescopeImports}
|
|
996
|
-
${
|
|
1070
|
+
${databaseApiImports}
|
|
997
1071
|
${hooksImports}
|
|
998
1072
|
${cors.imports}
|
|
999
1073
|
${context.storageDrivers?.imports ?? ""}
|
|
1000
1074
|
|
|
1001
1075
|
${runtime.bindings}
|
|
1002
|
-
${telescopeFromSurface ?
|
|
1076
|
+
${telescopeEnabled && telescopeFromSurface ? `// The surface's own Telescope, or the one \`telescope\` config describes.
|
|
1077
|
+
const telescope =
|
|
1078
|
+
__surface.telescope ??
|
|
1079
|
+
new Telescope({
|
|
1080
|
+
enabled: true,
|
|
1081
|
+
path: '${context.telescope?.path}',
|
|
1082
|
+
ignorePatterns: ${JSON.stringify(context.telescope?.ignore)},
|
|
1083
|
+
recordBody: ${context.telescope?.recordBody},
|
|
1084
|
+
storage: new InMemoryStorage({ maxEntries: ${context.telescope?.maxEntries} }),
|
|
1085
|
+
});` : ""}
|
|
1003
1086
|
|
|
1004
1087
|
${context.storageDrivers?.setup ? `// Which storage drivers exist is the entry point's decision: the scheme in
|
|
1005
1088
|
// each injected URL picks one, so no construct and no application module names
|
|
1006
1089
|
// a provider.
|
|
1007
1090
|
${context.storageDrivers.setup}
|
|
1008
1091
|
` : ""}
|
|
1009
|
-
|
|
1092
|
+
${context.surface ? `// Who this server is to Postgres: application_name on every connection.
|
|
1093
|
+
process.env.GKM_APP_NAME ??= ${JSON.stringify(context.surface.id)};
|
|
1094
|
+
|
|
1095
|
+
` : ""}export interface ServerApp {
|
|
1010
1096
|
app: HonoType;
|
|
1011
1097
|
start: (options?: {
|
|
1012
1098
|
port?: number;
|
|
1013
|
-
|
|
1099
|
+
/** Returns the server, so a shutdown can stop it taking requests. */
|
|
1100
|
+
serve: (app: HonoType, port: number) => unknown;
|
|
1014
1101
|
}) => Promise<void>;
|
|
1015
1102
|
}
|
|
1016
1103
|
|
|
@@ -1050,7 +1137,7 @@ export interface ServerApp {
|
|
|
1050
1137
|
*/
|
|
1051
1138
|
export async function createApp(app?: HonoType, enableOpenApi: boolean = true): Promise<ServerApp> {
|
|
1052
1139
|
const honoApp = app || new Hono();
|
|
1053
|
-
${telescopeSetup}${cors.setup}${beforeSetupCall}${
|
|
1140
|
+
${telescopeSetup}${cors.setup}${beforeSetupCall}${databaseApiSetup}
|
|
1054
1141
|
// Setup HTTP endpoints
|
|
1055
1142
|
await setupEndpoints(honoApp, envParser, logger, enableOpenApi);
|
|
1056
1143
|
${afterSetupCall}
|
|
@@ -1068,9 +1155,11 @@ ${afterSetupCall}
|
|
|
1068
1155
|
|
|
1069
1156
|
const port = options.port ?? 3000;
|
|
1070
1157
|
|
|
1071
|
-
//
|
|
1072
|
-
|
|
1158
|
+
// Mount pushed subscribers' routes and start polled ones. What comes
|
|
1159
|
+
// back subscribes the routes, once the server can answer them.
|
|
1160
|
+
const subscribeForPush = await setupSubscribers(honoApp, envParser, logger).catch((error) => {
|
|
1073
1161
|
logger.error({ error }, 'Failed to start subscribers');
|
|
1162
|
+
return async (_port: number) => {};
|
|
1074
1163
|
});
|
|
1075
1164
|
|
|
1076
1165
|
// Start queue workers in background (non-blocking, local development only)
|
|
@@ -1090,6 +1179,8 @@ ${afterSetupCall}
|
|
|
1090
1179
|
await options.serve(honoApp, port);
|
|
1091
1180
|
|
|
1092
1181
|
logger.info({ port }, 'Server started');
|
|
1182
|
+
|
|
1183
|
+
await subscribeForPush(port);
|
|
1093
1184
|
}
|
|
1094
1185
|
};
|
|
1095
1186
|
}
|
|
@@ -1110,26 +1201,6 @@ export default createApp;
|
|
|
1110
1201
|
* object. Nothing has to be printed now, which is also why nothing has to be
|
|
1111
1202
|
* named in config.
|
|
1112
1203
|
*/
|
|
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
1204
|
generateAWSApiGatewayV2Handler(importPath, exportName, drivers) {
|
|
1134
1205
|
return `import { AmazonApiGatewayV2Endpoint } from '@geekmidas/constructs/aws';
|
|
1135
1206
|
import { ${exportName} } from '${importPath}';
|
|
@@ -1138,13 +1209,6 @@ ${drivers?.setup ? `\n// The handler registers the drivers its target needs.\n${
|
|
|
1138
1209
|
const adapter = new AmazonApiGatewayV2Endpoint(${exportName});
|
|
1139
1210
|
|
|
1140
1211
|
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
1212
|
`;
|
|
1149
1213
|
}
|
|
1150
1214
|
/**
|
|
@@ -1152,8 +1216,7 @@ export const handler = ${exportName};
|
|
|
1152
1216
|
* No dev tools (Telescope, Studio, WebSocket), includes health checks and graceful shutdown
|
|
1153
1217
|
*/
|
|
1154
1218
|
async generateProductionAppFile(outputDir, context) {
|
|
1155
|
-
const
|
|
1156
|
-
const appPath = join(outputDir, appFileName);
|
|
1219
|
+
const appPath = join(outputDir, "app.ts");
|
|
1157
1220
|
const runtime = runtimeFor(context, dirname(appPath));
|
|
1158
1221
|
const production = context.production;
|
|
1159
1222
|
const healthCheckPath = production.healthCheck;
|
|
@@ -1165,8 +1228,7 @@ export const handler = ${exportName};
|
|
|
1165
1228
|
let beforeSetupCall = "";
|
|
1166
1229
|
let afterSetupCall = "";
|
|
1167
1230
|
if (context.hooks?.serverHooksPath) {
|
|
1168
|
-
|
|
1169
|
-
hooksImports = `import * as serverHooks from '${relativeHooksPath}';`;
|
|
1231
|
+
hooksImports = `import * as serverHooks from '${relative(dirname(appPath), context.hooks.serverHooksPath)}';`;
|
|
1170
1232
|
beforeSetupCall = `
|
|
1171
1233
|
// Call beforeSetup hook if defined
|
|
1172
1234
|
if (typeof serverHooks.beforeSetup === 'function') {
|
|
@@ -1181,9 +1243,11 @@ export const handler = ${exportName};
|
|
|
1181
1243
|
`;
|
|
1182
1244
|
}
|
|
1183
1245
|
const subscriberSetup = includeSubscribers ? `
|
|
1184
|
-
//
|
|
1185
|
-
|
|
1246
|
+
// Mount pushed subscribers' routes and start polled ones. What comes
|
|
1247
|
+
// back subscribes the routes, once the server can answer them.
|
|
1248
|
+
const subscribeForPush = await setupSubscribers(honoApp, envParser, logger).catch((error) => {
|
|
1186
1249
|
logger.error({ error }, 'Failed to start subscribers');
|
|
1250
|
+
return async (_port: number) => {};
|
|
1187
1251
|
});
|
|
1188
1252
|
|
|
1189
1253
|
// Start queue workers in background
|
|
@@ -1201,15 +1265,29 @@ export const handler = ${exportName};
|
|
|
1201
1265
|
import { setupQueues } from './queues.js';
|
|
1202
1266
|
import { setupCrons } from './crons.js';` : "";
|
|
1203
1267
|
const gracefulShutdownCode = enableGracefulShutdown ? `
|
|
1204
|
-
// Graceful shutdown
|
|
1268
|
+
// Graceful shutdown: stop taking requests, let in-flight ones finish, then
|
|
1269
|
+
// close what constructs opened (database pools) — so a rolling deploy does
|
|
1270
|
+
// not leave the old task holding connections. Bounded by
|
|
1271
|
+
// GKM_SHUTDOWN_TIMEOUT_MS, 8s by default: under Docker's 10s stop timeout,
|
|
1272
|
+
// so the process exits on its own terms rather than being killed mid-drain.
|
|
1205
1273
|
let isShuttingDown = false;
|
|
1206
1274
|
|
|
1207
1275
|
const shutdown = async () => {
|
|
1208
1276
|
if (isShuttingDown) return;
|
|
1209
1277
|
isShuttingDown = true;
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
setTimeout(() =>
|
|
1278
|
+
const deadline = Number(process.env.GKM_SHUTDOWN_TIMEOUT_MS) || 8000;
|
|
1279
|
+
logger.info({ deadline }, 'Graceful shutdown initiated');
|
|
1280
|
+
setTimeout(() => {
|
|
1281
|
+
logger.warn({ deadline }, 'Shutdown deadline reached, exiting');
|
|
1282
|
+
process.exit(1);
|
|
1283
|
+
}, deadline).unref();
|
|
1284
|
+
await new Promise<void>((resolve) => {
|
|
1285
|
+
const close = (server as { close?: (done: () => void) => void } | undefined)?.close;
|
|
1286
|
+
if (typeof close === 'function') close.call(server, () => resolve());
|
|
1287
|
+
else resolve();
|
|
1288
|
+
});
|
|
1289
|
+
await runShutdownHooks((error) => logger.error({ error }, 'Shutdown hook failed'));
|
|
1290
|
+
process.exit(0);
|
|
1213
1291
|
};
|
|
1214
1292
|
|
|
1215
1293
|
process.on('SIGTERM', shutdown);
|
|
@@ -1227,7 +1305,7 @@ import { setupCrons } from './crons.js';` : "";
|
|
|
1227
1305
|
*/
|
|
1228
1306
|
import { Hono } from 'hono';
|
|
1229
1307
|
import type { Hono as HonoType } from 'hono';
|
|
1230
|
-
import { setupEndpoints } from '${endpointsImportPath}';
|
|
1308
|
+
${enableGracefulShutdown ? "import { runShutdownHooks } from '@geekmidas/constructs';\n" : ""}import { setupEndpoints } from '${endpointsImportPath}';
|
|
1231
1309
|
${subscriberImport}
|
|
1232
1310
|
${runtime.imports}
|
|
1233
1311
|
${runtime.bindings}
|
|
@@ -1238,7 +1316,10 @@ ${context.storageDrivers?.imports ?? ""}
|
|
|
1238
1316
|
${context.storageDrivers?.setup ? `// The entry point registers the drivers its target needs.
|
|
1239
1317
|
${context.storageDrivers.setup}
|
|
1240
1318
|
` : ""}
|
|
1241
|
-
|
|
1319
|
+
${context.surface ? `// Who this server is to Postgres: application_name on every connection.
|
|
1320
|
+
process.env.GKM_APP_NAME ??= ${JSON.stringify(context.surface.id)};
|
|
1321
|
+
|
|
1322
|
+
` : ""}export interface ServerApp {
|
|
1242
1323
|
app: HonoType;
|
|
1243
1324
|
start: (options?: {
|
|
1244
1325
|
port?: number;
|
|
@@ -1271,13 +1352,15 @@ ${afterSetupCall}
|
|
|
1271
1352
|
}
|
|
1272
1353
|
|
|
1273
1354
|
const port = options.port ?? Number(process.env.PORT) ?? 3000;
|
|
1355
|
+
let server: unknown;
|
|
1274
1356
|
${gracefulShutdownCode}${subscriberSetup}
|
|
1275
1357
|
logger.info({ port }, 'Starting production server');
|
|
1276
1358
|
|
|
1277
1359
|
// Start HTTP server using provided serve function
|
|
1278
|
-
await options.serve(honoApp, port);
|
|
1360
|
+
server = await options.serve(honoApp, port);
|
|
1279
1361
|
|
|
1280
1362
|
logger.info({ port }, 'Production server started');
|
|
1363
|
+
${includeSubscribers ? "\n await subscribeForPush(port);" : ""}
|
|
1281
1364
|
}
|
|
1282
1365
|
};
|
|
1283
1366
|
}
|
|
@@ -1286,21 +1369,27 @@ ${gracefulShutdownCode}${subscriberSetup}
|
|
|
1286
1369
|
export default createApp;
|
|
1287
1370
|
`;
|
|
1288
1371
|
await writeFile(appPath, content);
|
|
1289
|
-
await this.generateProductionServerEntry(outputDir);
|
|
1372
|
+
await this.generateProductionServerEntry(outputDir, context);
|
|
1290
1373
|
return appPath;
|
|
1291
1374
|
}
|
|
1292
1375
|
/**
|
|
1293
1376
|
* Generate production server.ts entry point
|
|
1294
1377
|
*/
|
|
1295
|
-
async generateProductionServerEntry(outputDir) {
|
|
1378
|
+
async generateProductionServerEntry(outputDir, context) {
|
|
1296
1379
|
const serverPath = join(outputDir, "server.ts");
|
|
1297
|
-
|
|
1380
|
+
await writeFile(join(outputDir, "telemetry.ts"), generateTelemetryModule(context.telemetry));
|
|
1381
|
+
await writeFile(serverPath, `#!/usr/bin/env node
|
|
1298
1382
|
/**
|
|
1299
1383
|
* Production server entry point
|
|
1300
|
-
* Generated by 'gkm build --
|
|
1384
|
+
* Generated by 'gkm build --production'
|
|
1301
1385
|
*/
|
|
1302
1386
|
import { serve } from '@hono/node-server';
|
|
1303
|
-
import {
|
|
1387
|
+
import { startTelemetry } from './telemetry.js';
|
|
1388
|
+
|
|
1389
|
+
// Before the app is imported, so the libraries it loads are instrumented.
|
|
1390
|
+
await startTelemetry();
|
|
1391
|
+
|
|
1392
|
+
const { createApp } = await import('./app.js');
|
|
1304
1393
|
|
|
1305
1394
|
const port = Number(process.env.PORT) || 3000;
|
|
1306
1395
|
|
|
@@ -1308,13 +1397,11 @@ const { start } = await createApp();
|
|
|
1308
1397
|
|
|
1309
1398
|
await start({
|
|
1310
1399
|
port,
|
|
1311
|
-
serve: (app, port) =>
|
|
1400
|
+
serve: (app, port) => serve({ fetch: app.fetch, port }),
|
|
1312
1401
|
});
|
|
1313
|
-
|
|
1314
|
-
await writeFile(serverPath, content);
|
|
1402
|
+
`);
|
|
1315
1403
|
}
|
|
1316
1404
|
};
|
|
1317
|
-
|
|
1318
1405
|
//#endregion
|
|
1319
1406
|
//#region src/generators/OpenApiTsGenerator.ts
|
|
1320
1407
|
/**
|
|
@@ -1469,7 +1556,7 @@ var OpenApiTsGenerator = class {
|
|
|
1469
1556
|
for (const [rawName, rawSchema] of Object.entries(registrySchemas)) {
|
|
1470
1557
|
const defName = this.pascalCase(rawName);
|
|
1471
1558
|
if (collectedDefs.has(defName) || generatedNames.has(defName)) continue;
|
|
1472
|
-
const { id: _id, $defs: _defs
|
|
1559
|
+
const { id: _id, $defs: _defs, ...rest } = rawSchema;
|
|
1473
1560
|
collectedDefs.set(defName, rest);
|
|
1474
1561
|
}
|
|
1475
1562
|
for (const [defName, defSchema] of collectedDefs) if (!generatedNames.has(defName)) {
|
|
@@ -1505,17 +1592,17 @@ var OpenApiTsGenerator = class {
|
|
|
1505
1592
|
async schemaToInterfaceWithDefs(schema, name, collectedDefs) {
|
|
1506
1593
|
try {
|
|
1507
1594
|
const vendor = schema["~standard"]?.vendor;
|
|
1508
|
-
if (!vendor || !(vendor in StandardSchemaJsonSchema)) return
|
|
1595
|
+
if (!vendor || !(vendor in StandardSchemaJsonSchema)) return unknownType(name);
|
|
1509
1596
|
const toJsonSchema = StandardSchemaJsonSchema[vendor];
|
|
1510
1597
|
const jsonSchema = await toJsonSchema(schema);
|
|
1511
|
-
if (!jsonSchema) return
|
|
1598
|
+
if (!jsonSchema) return unknownType(name);
|
|
1512
1599
|
if (jsonSchema.$defs && typeof jsonSchema.$defs === "object") {
|
|
1513
1600
|
for (const [defName, defSchema] of Object.entries(jsonSchema.$defs)) if (!collectedDefs.has(defName)) {
|
|
1514
|
-
const { id
|
|
1601
|
+
const { id, ...schemaWithoutId } = defSchema;
|
|
1515
1602
|
collectedDefs.set(defName, schemaWithoutId);
|
|
1516
1603
|
}
|
|
1517
1604
|
}
|
|
1518
|
-
const { $defs
|
|
1605
|
+
const { $defs, ...schemaWithoutDefs } = jsonSchema;
|
|
1519
1606
|
const ref = schemaWithoutDefs.$ref;
|
|
1520
1607
|
if (ref && Object.keys(schemaWithoutDefs).every((k) => k === "$ref" || k === "$schema")) {
|
|
1521
1608
|
const target = ref.split("/").pop();
|
|
@@ -1523,14 +1610,11 @@ var OpenApiTsGenerator = class {
|
|
|
1523
1610
|
}
|
|
1524
1611
|
return this.jsonSchemaToInterface(schemaWithoutDefs, name);
|
|
1525
1612
|
} catch {
|
|
1526
|
-
return
|
|
1613
|
+
return unknownType(name);
|
|
1527
1614
|
}
|
|
1528
1615
|
}
|
|
1529
1616
|
jsonSchemaToInterface(schema, name) {
|
|
1530
|
-
if (schema.type !== "object" || !schema.properties) {
|
|
1531
|
-
const typeStr = this.jsonSchemaTypeToTs(schema);
|
|
1532
|
-
return `export type ${name} = ${typeStr};`;
|
|
1533
|
-
}
|
|
1617
|
+
if (schema.type !== "object" || !schema.properties) return `export type ${name} = ${this.jsonSchemaTypeToTs(schema)};`;
|
|
1534
1618
|
const props = [];
|
|
1535
1619
|
const required = new Set(schema.required || []);
|
|
1536
1620
|
for (const [propName, propSchema] of Object.entries(schema.properties)) {
|
|
@@ -1543,13 +1627,15 @@ var OpenApiTsGenerator = class {
|
|
|
1543
1627
|
}
|
|
1544
1628
|
jsonSchemaTypeToTs(schema) {
|
|
1545
1629
|
if (!schema) return "unknown";
|
|
1546
|
-
if (schema.$ref)
|
|
1547
|
-
|
|
1548
|
-
return refName;
|
|
1549
|
-
}
|
|
1630
|
+
if (schema.$ref) return schema.$ref.split("/").pop() || "unknown";
|
|
1631
|
+
if (schema.const !== void 0) return typeof schema.const === "string" ? `'${schema.const}'` : String(schema.const);
|
|
1550
1632
|
if (schema.anyOf) return schema.anyOf.map((s) => this.jsonSchemaTypeToTs(s)).join(" | ");
|
|
1551
1633
|
if (schema.oneOf) return schema.oneOf.map((s) => this.jsonSchemaTypeToTs(s)).join(" | ");
|
|
1552
1634
|
if (schema.allOf) return schema.allOf.map((s) => this.jsonSchemaTypeToTs(s)).join(" & ");
|
|
1635
|
+
if (Array.isArray(schema.type)) return schema.type.map((type) => this.jsonSchemaTypeToTs({
|
|
1636
|
+
...schema,
|
|
1637
|
+
type
|
|
1638
|
+
})).join(" | ");
|
|
1553
1639
|
switch (schema.type) {
|
|
1554
1640
|
case "string":
|
|
1555
1641
|
if (schema.enum) return schema.enum.map((e) => `'${e}'`).join(" | ");
|
|
@@ -1573,10 +1659,7 @@ var OpenApiTsGenerator = class {
|
|
|
1573
1659
|
}
|
|
1574
1660
|
return `{ ${props.join("; ")} }`;
|
|
1575
1661
|
}
|
|
1576
|
-
if (schema.additionalProperties) {
|
|
1577
|
-
const valueType = this.jsonSchemaTypeToTs(schema.additionalProperties);
|
|
1578
|
-
return `Record<string, ${valueType}>`;
|
|
1579
|
-
}
|
|
1662
|
+
if (schema.additionalProperties) return `Record<string, ${this.jsonSchemaTypeToTs(schema.additionalProperties)}>`;
|
|
1580
1663
|
return "Record<string, unknown>";
|
|
1581
1664
|
default: return "unknown";
|
|
1582
1665
|
}
|
|
@@ -1597,10 +1680,7 @@ var OpenApiTsGenerator = class {
|
|
|
1597
1680
|
}
|
|
1598
1681
|
const firstWithParams = infos.find((i) => i.input?.params);
|
|
1599
1682
|
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
|
-
}
|
|
1683
|
+
if (firstWithParams?.input?.params) paramsEntry = `\n parameters: {\n path: ${await this.getSchemaName(firstWithParams.input.params, `${this.getSchemaBaseName(firstWithParams)}Params`)};\n };`;
|
|
1604
1684
|
pathEntries.push(` '${route}': {${paramsEntry}\n${methodEntries.join("\n")}\n };`);
|
|
1605
1685
|
}
|
|
1606
1686
|
return `export interface paths {\n${pathEntries.join("\n")}\n}`;
|
|
@@ -1639,8 +1719,7 @@ var OpenApiTsGenerator = class {
|
|
|
1639
1719
|
return acc;
|
|
1640
1720
|
}, {});
|
|
1641
1721
|
const schemeNames = securitySchemes.map((s) => `'${s.name}'`).join(" | ");
|
|
1642
|
-
const
|
|
1643
|
-
const createApiSection = hasSecuritySchemes ? `
|
|
1722
|
+
const createApiSection = schemeNames.length > 0 ? `
|
|
1644
1723
|
// ============================================================
|
|
1645
1724
|
// API Client Factory
|
|
1646
1725
|
// ============================================================
|
|
@@ -1664,6 +1743,8 @@ export interface CreateApiOptions {
|
|
|
1664
1743
|
queryClient?: QueryClient;
|
|
1665
1744
|
/** Optional request interceptor */
|
|
1666
1745
|
onRequest?: (config: RequestInit) => RequestInit | Promise<RequestInit>;
|
|
1746
|
+
/** The \`fetch\` requests go out on — a test browser's, for one */
|
|
1747
|
+
fetch?: typeof fetch;
|
|
1667
1748
|
}
|
|
1668
1749
|
|
|
1669
1750
|
/**
|
|
@@ -1693,6 +1774,7 @@ export function createApi(options: CreateApiOptions) {
|
|
|
1693
1774
|
securitySchemes,
|
|
1694
1775
|
authStrategies: options.authStrategies,
|
|
1695
1776
|
onRequest: options.onRequest,
|
|
1777
|
+
...(options.fetch ? { fetch: options.fetch } : {}),
|
|
1696
1778
|
});
|
|
1697
1779
|
|
|
1698
1780
|
const hooks = createEndpointHooks<paths>(fetcher, { queryClient: options.queryClient });
|
|
@@ -1817,205 +1899,15 @@ ${createApiSection}
|
|
|
1817
1899
|
`;
|
|
1818
1900
|
}
|
|
1819
1901
|
};
|
|
1820
|
-
|
|
1821
|
-
//#endregion
|
|
1822
|
-
//#region src/workspace/client-generator.ts
|
|
1823
1902
|
/**
|
|
1824
|
-
*
|
|
1825
|
-
*
|
|
1826
|
-
*
|
|
1903
|
+
* The declaration for a schema that cannot be converted — a vendor with no
|
|
1904
|
+
* converter, or one that fails. The paths interface names it either way, so
|
|
1905
|
+
* leaving it undeclared made the generated client fail to compile.
|
|
1827
1906
|
*/
|
|
1828
|
-
function
|
|
1829
|
-
|
|
1830
|
-
if (isPartitionedRoutes(routes)) return Array.isArray(routes.paths) ? routes.paths : [routes.paths];
|
|
1831
|
-
return Array.isArray(routes) ? routes : [routes];
|
|
1907
|
+
function unknownType(name) {
|
|
1908
|
+
return `export type ${name} = unknown;`;
|
|
1832
1909
|
}
|
|
1833
|
-
|
|
1834
1910
|
//#endregion
|
|
1835
|
-
|
|
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, off `AppSpec.code`,
|
|
1873
|
-
* 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
|
-
}
|
|
1911
|
+
export { telemetryFor as i, EndpointGenerator as n, runtimeFor as r, OpenApiTsGenerator as t };
|
|
2018
1912
|
|
|
2019
|
-
//#
|
|
2020
|
-
export { EndpointGenerator, OPENAPI_OUTPUT_PATH, generateOpenApi, generateOpenApiFrom, normalizeRoutes, openApiPathFor, openapiCommand, resolveOpenApiConfig, runtimeFor };
|
|
2021
|
-
//# sourceMappingURL=openapi-Bw8DWFWz.mjs.map
|
|
1913
|
+
//# sourceMappingURL=OpenApiTsGenerator-CvVr1Ilz.mjs.map
|