@geekmidas/cli 10.0.0-alpha.6 → 10.0.0-alpha.61

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.
Files changed (303) hide show
  1. package/README.md +225 -268
  2. package/bin/adjacent-tsconfig.mjs +97 -0
  3. package/bin/gkm.mjs +16 -2
  4. package/bin/owning-tsconfig-jsx-register.mjs +18 -0
  5. package/bin/owning-tsconfig-jsx.mjs +108 -0
  6. package/dist/{HostingerProvider-h3EbEp-Z.cjs → HostingerProvider-CgDaD8VR.cjs} +21 -22
  7. package/dist/HostingerProvider-CgDaD8VR.cjs.map +1 -0
  8. package/dist/{HostingerProvider-Cft7lEYr.mjs → HostingerProvider-DI_Pr2VE.mjs} +20 -21
  9. package/dist/HostingerProvider-DI_Pr2VE.mjs.map +1 -0
  10. package/dist/LegacyStateStore-C7vFAxbu.mjs +89 -0
  11. package/dist/LegacyStateStore-C7vFAxbu.mjs.map +1 -0
  12. package/dist/LegacyStateStore-JbBVFp_2.cjs +89 -0
  13. package/dist/LegacyStateStore-JbBVFp_2.cjs.map +1 -0
  14. package/dist/LocalStateStore-DHj_-CQB.cjs +176 -0
  15. package/dist/LocalStateStore-DHj_-CQB.cjs.map +1 -0
  16. package/dist/LocalStateStore-DfUwQsL8.mjs +165 -0
  17. package/dist/LocalStateStore-DfUwQsL8.mjs.map +1 -0
  18. package/dist/{openapi-D-dx9Akj.cjs → OpenApiTsGenerator-BSrgw7B0.cjs} +360 -470
  19. package/dist/OpenApiTsGenerator-BSrgw7B0.cjs.map +1 -0
  20. package/dist/{openapi-Bw8DWFWz.mjs → OpenApiTsGenerator-q5B0z8Xg.mjs} +325 -404
  21. package/dist/OpenApiTsGenerator-q5B0z8Xg.mjs.map +1 -0
  22. package/dist/{Route53Provider-RdpUDJQk.mjs → Route53Provider-Cvj40A8I.mjs} +12 -12
  23. package/dist/Route53Provider-Cvj40A8I.mjs.map +1 -0
  24. package/dist/{Route53Provider-di2fMM5N.cjs → Route53Provider-DVILBsvV.cjs} +20 -21
  25. package/dist/Route53Provider-DVILBsvV.cjs.map +1 -0
  26. package/dist/S3StateStore-Dq6hHtzY.mjs +161 -0
  27. package/dist/S3StateStore-Dq6hHtzY.mjs.map +1 -0
  28. package/dist/S3StateStore-xqnpUrzy.cjs +160 -0
  29. package/dist/S3StateStore-xqnpUrzy.cjs.map +1 -0
  30. package/dist/SSMStateStore-5dBRwVlJ.cjs +153 -0
  31. package/dist/SSMStateStore-5dBRwVlJ.cjs.map +1 -0
  32. package/dist/SSMStateStore-SUmvAbLi.mjs +154 -0
  33. package/dist/SSMStateStore-SUmvAbLi.mjs.map +1 -0
  34. package/dist/appPorts-C9z9Pzn9.mjs +223 -0
  35. package/dist/appPorts-C9z9Pzn9.mjs.map +1 -0
  36. package/dist/appPorts-Cu79hJF6.cjs +264 -0
  37. package/dist/appPorts-Cu79hJF6.cjs.map +1 -0
  38. package/dist/aws-DkHdTV-p.cjs +61 -0
  39. package/dist/aws-DkHdTV-p.cjs.map +1 -0
  40. package/dist/aws-DojzQnLd.mjs +61 -0
  41. package/dist/aws-DojzQnLd.mjs.map +1 -0
  42. package/dist/{backup-provisioner-BrsHwYG0.cjs → backup-provisioner-BmhIO_Yr.cjs} +21 -19
  43. package/dist/backup-provisioner-BmhIO_Yr.cjs.map +1 -0
  44. package/dist/{backup-provisioner-Bgj5Vi_L.mjs → backup-provisioner-DIVCbfee.mjs} +10 -7
  45. package/dist/backup-provisioner-DIVCbfee.mjs.map +1 -0
  46. package/dist/{bundler-Be8Zg4Wd.mjs → bundler-B5VsKW-_.mjs} +18 -29
  47. package/dist/bundler-B5VsKW-_.mjs.map +1 -0
  48. package/dist/{bundler-BY1OmV1e.cjs → bundler-DXATpO1T.cjs} +20 -32
  49. package/dist/bundler-DXATpO1T.cjs.map +1 -0
  50. package/dist/cli-DEA_J3Sa.mjs +69 -0
  51. package/dist/cli-DEA_J3Sa.mjs.map +1 -0
  52. package/dist/cli-DQdrVkj2.cjs +69 -0
  53. package/dist/cli-DQdrVkj2.cjs.map +1 -0
  54. package/dist/{config-CxP7P-qG.mjs → config-C5-EEdmr.mjs} +144 -127
  55. package/dist/config-C5-EEdmr.mjs.map +1 -0
  56. package/dist/{config-CNz5KaSR.cjs → config-DXnkCEq0.cjs} +212 -184
  57. package/dist/config-DXnkCEq0.cjs.map +1 -0
  58. package/dist/config.cjs +9 -6
  59. package/dist/config.d.cts +124 -33
  60. package/dist/config.d.cts.map +1 -1
  61. package/dist/config.d.mts +124 -33
  62. package/dist/config.d.mts.map +1 -1
  63. package/dist/config.mjs +3 -5
  64. package/dist/deploy/public.cjs +22 -0
  65. package/dist/deploy/public.d.cts +459 -0
  66. package/dist/deploy/public.d.cts.map +1 -0
  67. package/dist/deploy/public.d.mts +459 -0
  68. package/dist/deploy/public.d.mts.map +1 -0
  69. package/dist/deploy/public.mjs +5 -0
  70. package/dist/deploy/sniffer-envkit-patch.cjs +18 -10
  71. package/dist/deploy/sniffer-envkit-patch.cjs.map +1 -1
  72. package/dist/deploy/sniffer-envkit-patch.d.cts +0 -2
  73. package/dist/deploy/sniffer-envkit-patch.d.cts.map +1 -1
  74. package/dist/deploy/sniffer-envkit-patch.d.mts +0 -2
  75. package/dist/deploy/sniffer-envkit-patch.d.mts.map +1 -1
  76. package/dist/deploy/sniffer-envkit-patch.mjs +10 -2
  77. package/dist/deploy/sniffer-envkit-patch.mjs.map +1 -1
  78. package/dist/deploy/sniffer-hooks.cjs +13 -7
  79. package/dist/deploy/sniffer-hooks.cjs.map +1 -1
  80. package/dist/deploy/sniffer-hooks.d.cts +1 -2
  81. package/dist/deploy/sniffer-hooks.d.cts.map +1 -1
  82. package/dist/deploy/sniffer-hooks.d.mts +1 -2
  83. package/dist/deploy/sniffer-hooks.d.mts.map +1 -1
  84. package/dist/deploy/sniffer-hooks.mjs +10 -3
  85. package/dist/deploy/sniffer-hooks.mjs.map +1 -1
  86. package/dist/deploy/sniffer-loader.cjs +16 -7
  87. package/dist/deploy/sniffer-loader.cjs.map +1 -1
  88. package/dist/deploy/sniffer-loader.d.cts +1 -1
  89. package/dist/deploy/sniffer-loader.d.mts +1 -1
  90. package/dist/deploy/sniffer-loader.mjs +14 -3
  91. package/dist/deploy/sniffer-loader.mjs.map +1 -1
  92. package/dist/deploy/sniffer-routes-worker.cjs +11 -11
  93. package/dist/deploy/sniffer-routes-worker.cjs.map +1 -1
  94. package/dist/deploy/sniffer-routes-worker.d.cts +1 -1
  95. package/dist/deploy/sniffer-routes-worker.d.mts +1 -1
  96. package/dist/deploy/sniffer-routes-worker.mjs +3 -3
  97. package/dist/deploy/sniffer-routes-worker.mjs.map +1 -1
  98. package/dist/deploy/sniffer-worker.cjs +15 -6
  99. package/dist/deploy/sniffer-worker.cjs.map +1 -1
  100. package/dist/deploy/sniffer-worker.d.cts +1 -3
  101. package/dist/deploy/sniffer-worker.d.cts.map +1 -1
  102. package/dist/deploy/sniffer-worker.d.mts +1 -3
  103. package/dist/deploy/sniffer-worker.d.mts.map +1 -1
  104. package/dist/deploy/sniffer-worker.mjs +15 -4
  105. package/dist/deploy/sniffer-worker.mjs.map +1 -1
  106. package/dist/deploy-BJcLmVTc.mjs +3282 -0
  107. package/dist/deploy-BJcLmVTc.mjs.map +1 -0
  108. package/dist/deploy-BbJdgQ2b.cjs +3365 -0
  109. package/dist/deploy-BbJdgQ2b.cjs.map +1 -0
  110. package/dist/{discover-DTmFeloL.cjs → discover-Bc-4eA_p.cjs} +131 -103
  111. package/dist/discover-Bc-4eA_p.cjs.map +1 -0
  112. package/dist/{discover-CAlFgYec.mjs → discover-FrYvzkeC.mjs} +91 -58
  113. package/dist/discover-FrYvzkeC.mjs.map +1 -0
  114. package/dist/{emit-CiZS_5Ub.mjs → emit-C1e8qD08.mjs} +13 -26
  115. package/dist/emit-C1e8qD08.mjs.map +1 -0
  116. package/dist/{emit-CbcRolsa.cjs → emit-Dmc1nvdS.cjs} +31 -57
  117. package/dist/emit-Dmc1nvdS.cjs.map +1 -0
  118. package/dist/encryption-DaA9gRXr.mjs +90 -0
  119. package/dist/encryption-DaA9gRXr.mjs.map +1 -0
  120. package/dist/encryption-ojzUo7-z.cjs +91 -0
  121. package/dist/encryption-ojzUo7-z.cjs.map +1 -0
  122. package/dist/fakes-C9RiT6XG.mjs +125 -0
  123. package/dist/fakes-C9RiT6XG.mjs.map +1 -0
  124. package/dist/fakes-zW-jNAbQ.cjs +148 -0
  125. package/dist/fakes-zW-jNAbQ.cjs.map +1 -0
  126. package/dist/github-Cq57poPj.cjs +213 -0
  127. package/dist/github-Cq57poPj.cjs.map +1 -0
  128. package/dist/github-DmCj2u95.mjs +213 -0
  129. package/dist/github-DmCj2u95.mjs.map +1 -0
  130. package/dist/home-DOzBaWaL.cjs +33 -0
  131. package/dist/home-DOzBaWaL.cjs.map +1 -0
  132. package/dist/home-GI7pJl1M.mjs +22 -0
  133. package/dist/home-GI7pJl1M.mjs.map +1 -0
  134. package/dist/{index-DEbaleny.d.mts → index-Da1-oa9W.d.mts} +4 -23
  135. package/dist/index-Da1-oa9W.d.mts.map +1 -0
  136. package/dist/{index-BGGdjueF.d.cts → index-UiWCaGzU.d.cts} +4 -23
  137. package/dist/index-UiWCaGzU.d.cts.map +1 -0
  138. package/dist/index.cjs +10712 -12248
  139. package/dist/index.cjs.map +1 -1
  140. package/dist/index.d.cts +1 -1
  141. package/dist/index.d.mts +1 -1
  142. package/dist/index.mjs +10480 -12040
  143. package/dist/index.mjs.map +1 -1
  144. package/dist/migrate-5_J5JySj.mjs +265 -0
  145. package/dist/migrate-5_J5JySj.mjs.map +1 -0
  146. package/dist/migrate-BScRpCEx.cjs +270 -0
  147. package/dist/migrate-BScRpCEx.cjs.map +1 -0
  148. package/dist/openapi.cjs +198 -10
  149. package/dist/openapi.cjs.map +1 -0
  150. package/dist/openapi.d.cts +21 -19
  151. package/dist/openapi.d.cts.map +1 -1
  152. package/dist/openapi.d.mts +21 -19
  153. package/dist/openapi.d.mts.map +1 -1
  154. package/dist/openapi.mjs +193 -5
  155. package/dist/openapi.mjs.map +1 -0
  156. package/dist/ready-DQIy21W5.mjs +760 -0
  157. package/dist/ready-DQIy21W5.mjs.map +1 -0
  158. package/dist/ready-XlNx-Gbo.cjs +807 -0
  159. package/dist/ready-XlNx-Gbo.cjs.map +1 -0
  160. package/dist/reconcile/public.cjs +3 -5
  161. package/dist/reconcile/public.d.cts +30 -33
  162. package/dist/reconcile/public.d.cts.map +1 -1
  163. package/dist/reconcile/public.d.mts +30 -33
  164. package/dist/reconcile/public.d.mts.map +1 -1
  165. package/dist/reconcile/public.mjs +3 -4
  166. package/dist/{reconcile-CFs8C_1a.mjs → reconcile--I7V4Klj.mjs} +4 -7
  167. package/dist/reconcile--I7V4Klj.mjs.map +1 -0
  168. package/dist/{reconcile-D0pimNiy.cjs → reconcile-CuCQBa5-.cjs} +5 -8
  169. package/dist/reconcile-CuCQBa5-.cjs.map +1 -0
  170. package/dist/rolldown-runtime-BhDjJH2R.mjs +15 -0
  171. package/dist/{chunk-CUT6urMc.cjs → rolldown-runtime-C0BPl7ul.cjs} +23 -10
  172. package/dist/sniffer-CQmJnNJM.cjs +7115 -0
  173. package/dist/sniffer-CQmJnNJM.cjs.map +1 -0
  174. package/dist/sniffer-bdleks5l.mjs +6561 -0
  175. package/dist/sniffer-bdleks5l.mjs.map +1 -0
  176. package/dist/storage-BzaUxmqe.mjs +124 -0
  177. package/dist/storage-BzaUxmqe.mjs.map +1 -0
  178. package/dist/storage-CSGoC3bK.cjs +152 -0
  179. package/dist/storage-CSGoC3bK.cjs.map +1 -0
  180. package/dist/store-BS8A7NcA.mjs +415 -0
  181. package/dist/store-BS8A7NcA.mjs.map +1 -0
  182. package/dist/store-KvNQwYaB.cjs +510 -0
  183. package/dist/store-KvNQwYaB.cjs.map +1 -0
  184. package/dist/types-Bp1mVxG0.d.mts +6410 -0
  185. package/dist/types-Bp1mVxG0.d.mts.map +1 -0
  186. package/dist/types-efNLu9xu.d.cts +6411 -0
  187. package/dist/types-efNLu9xu.d.cts.map +1 -0
  188. package/dist/vitest.cjs +65 -0
  189. package/dist/vitest.cjs.map +1 -0
  190. package/dist/vitest.d.cts +29 -0
  191. package/dist/vitest.d.cts.map +1 -0
  192. package/dist/vitest.d.mts +29 -0
  193. package/dist/vitest.d.mts.map +1 -0
  194. package/dist/vitest.mjs +65 -0
  195. package/dist/vitest.mjs.map +1 -0
  196. package/dist/workspace/index.cjs +3 -4
  197. package/dist/workspace/index.d.cts +3 -3
  198. package/dist/workspace/index.d.mts +3 -3
  199. package/dist/workspace/index.mjs +2 -3
  200. package/dist/{workspace-LKiNyaDy.cjs → workspace-B64Xk1Mb.cjs} +1070 -1118
  201. package/dist/workspace-B64Xk1Mb.cjs.map +1 -0
  202. package/dist/{workspace-DIuZx-cc.mjs → workspace-Bp1xVaOz.mjs} +901 -972
  203. package/dist/workspace-Bp1xVaOz.mjs.map +1 -0
  204. package/package.json +38 -20
  205. package/dist/CachedStateProvider-BLo7zv7S.cjs +0 -3
  206. package/dist/CachedStateProvider-BxoAm-B2.mjs +0 -3
  207. package/dist/CachedStateProvider-CqPdW7CB.cjs +0 -60
  208. package/dist/CachedStateProvider-CqPdW7CB.cjs.map +0 -1
  209. package/dist/CachedStateProvider-DTniehMP.mjs +0 -54
  210. package/dist/CachedStateProvider-DTniehMP.mjs.map +0 -1
  211. package/dist/HostingerProvider-Cft7lEYr.mjs.map +0 -1
  212. package/dist/HostingerProvider-h3EbEp-Z.cjs.map +0 -1
  213. package/dist/LocalStateProvider-Bn6tS-oL.mjs +0 -42
  214. package/dist/LocalStateProvider-Bn6tS-oL.mjs.map +0 -1
  215. package/dist/LocalStateProvider-Db9SEjyH.cjs +0 -43
  216. package/dist/LocalStateProvider-Db9SEjyH.cjs.map +0 -1
  217. package/dist/Route53Provider-RdpUDJQk.mjs.map +0 -1
  218. package/dist/Route53Provider-di2fMM5N.cjs.map +0 -1
  219. package/dist/SSMStateProvider-BRt2py-A.mjs +0 -66
  220. package/dist/SSMStateProvider-BRt2py-A.mjs.map +0 -1
  221. package/dist/SSMStateProvider-DyX8s9HI.cjs +0 -66
  222. package/dist/SSMStateProvider-DyX8s9HI.cjs.map +0 -1
  223. package/dist/backup-provisioner-Bgj5Vi_L.mjs.map +0 -1
  224. package/dist/backup-provisioner-BrsHwYG0.cjs.map +0 -1
  225. package/dist/bundler-BY1OmV1e.cjs.map +0 -1
  226. package/dist/bundler-Be8Zg4Wd.mjs.map +0 -1
  227. package/dist/chunk-Duj1WY3L.mjs +0 -7
  228. package/dist/config-CNz5KaSR.cjs.map +0 -1
  229. package/dist/config-CxP7P-qG.mjs.map +0 -1
  230. package/dist/credentials-B5zRHdAl.mjs +0 -141
  231. package/dist/credentials-B5zRHdAl.mjs.map +0 -1
  232. package/dist/credentials-BK3Z93MX.mjs +0 -2548
  233. package/dist/credentials-BK3Z93MX.mjs.map +0 -1
  234. package/dist/credentials-BapzjyV5.cjs +0 -195
  235. package/dist/credentials-BapzjyV5.cjs.map +0 -1
  236. package/dist/credentials-DzsXZzkx.cjs +0 -2680
  237. package/dist/credentials-DzsXZzkx.cjs.map +0 -1
  238. package/dist/discover-CAlFgYec.mjs.map +0 -1
  239. package/dist/discover-DTmFeloL.cjs.map +0 -1
  240. package/dist/dokploy-api-BS2xnJLZ.mjs +0 -3
  241. package/dist/dokploy-api-BsVH0y_s.cjs +0 -718
  242. package/dist/dokploy-api-BsVH0y_s.cjs.map +0 -1
  243. package/dist/dokploy-api-BuxDRo3l.cjs +0 -3
  244. package/dist/dokploy-api-aTFPz7_4.mjs +0 -706
  245. package/dist/dokploy-api-aTFPz7_4.mjs.map +0 -1
  246. package/dist/emit-CbcRolsa.cjs.map +0 -1
  247. package/dist/emit-CiZS_5Ub.mjs.map +0 -1
  248. package/dist/encryption-C6jaU0hE.cjs +0 -54
  249. package/dist/encryption-C6jaU0hE.cjs.map +0 -1
  250. package/dist/encryption-CHzzeR8a.cjs +0 -4
  251. package/dist/encryption-CQUncu7v.mjs +0 -3
  252. package/dist/encryption-D-abyhZx.mjs +0 -42
  253. package/dist/encryption-D-abyhZx.mjs.map +0 -1
  254. package/dist/fullstack-secrets-C-GGohke.cjs +0 -111
  255. package/dist/fullstack-secrets-C-GGohke.cjs.map +0 -1
  256. package/dist/fullstack-secrets-D1o9-G5l.mjs +0 -87
  257. package/dist/fullstack-secrets-D1o9-G5l.mjs.map +0 -1
  258. package/dist/generator-CNzBw7g5.cjs +0 -289
  259. package/dist/generator-CNzBw7g5.cjs.map +0 -1
  260. package/dist/generator-VE2JrFlY.mjs +0 -253
  261. package/dist/generator-VE2JrFlY.mjs.map +0 -1
  262. package/dist/index-BGGdjueF.d.cts.map +0 -1
  263. package/dist/index-DEbaleny.d.mts.map +0 -1
  264. package/dist/openapi-Bw8DWFWz.mjs.map +0 -1
  265. package/dist/openapi-D-dx9Akj.cjs.map +0 -1
  266. package/dist/openapi-react-query-Byltvm_7.mjs +0 -167
  267. package/dist/openapi-react-query-Byltvm_7.mjs.map +0 -1
  268. package/dist/openapi-react-query-CmJ1xDbn.cjs +0 -173
  269. package/dist/openapi-react-query-CmJ1xDbn.cjs.map +0 -1
  270. package/dist/openapi-react-query.cjs +0 -4
  271. package/dist/openapi-react-query.d.cts +0 -11
  272. package/dist/openapi-react-query.d.cts.map +0 -1
  273. package/dist/openapi-react-query.d.mts +0 -11
  274. package/dist/openapi-react-query.d.mts.map +0 -1
  275. package/dist/openapi-react-query.mjs +0 -4
  276. package/dist/reconcile-CFs8C_1a.mjs.map +0 -1
  277. package/dist/reconcile-D0pimNiy.cjs.map +0 -1
  278. package/dist/setup-8IWPsRIN.cjs +0 -11
  279. package/dist/setup-BWMhbW-p.mjs +0 -443
  280. package/dist/setup-BWMhbW-p.mjs.map +0 -1
  281. package/dist/setup-D7L4BgBT.cjs +0 -473
  282. package/dist/setup-D7L4BgBT.cjs.map +0 -1
  283. package/dist/setup-DGIPd2fh.mjs +0 -11
  284. package/dist/storage-BJmzYxt8.cjs +0 -363
  285. package/dist/storage-BJmzYxt8.cjs.map +0 -1
  286. package/dist/storage-BM9hGpgv.cjs +0 -7
  287. package/dist/storage-Bi24WfoW.mjs +0 -297
  288. package/dist/storage-Bi24WfoW.mjs.map +0 -1
  289. package/dist/storage-CYlGwWTj.mjs +0 -3
  290. package/dist/sync-CmEuNZsB.mjs +0 -76
  291. package/dist/sync-CmEuNZsB.mjs.map +0 -1
  292. package/dist/sync-D306yOWu.cjs +0 -4
  293. package/dist/sync-SizaChmC.cjs +0 -93
  294. package/dist/sync-SizaChmC.cjs.map +0 -1
  295. package/dist/sync-hOqAWHR6.mjs +0 -3
  296. package/dist/types-BLSNdrk0.d.cts +0 -2176
  297. package/dist/types-BLSNdrk0.d.cts.map +0 -1
  298. package/dist/types-C3POXhw3.d.mts +0 -2176
  299. package/dist/types-C3POXhw3.d.mts.map +0 -1
  300. package/dist/workspace-CGaUAw5A.cjs +0 -9
  301. package/dist/workspace-CnTqIlB3.mjs +0 -8
  302. package/dist/workspace-DIuZx-cc.mjs.map +0 -1
  303. package/dist/workspace-LKiNyaDy.cjs.map +0 -1
@@ -1,20 +1,16 @@
1
- import { ConstructGenerator, isPartitionedRoutes } from "./discover-CAlFgYec.mjs";
2
- import { loadWorkspaceConfig } from "./config-CxP7P-qG.mjs";
3
- import { dirname, join, relative, resolve } from "node:path";
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
14
10
  */
15
11
  function analyzeEndpointFeatures(endpoint) {
16
12
  return {
17
- hasAuth: !!endpoint.authorizer,
13
+ hasAuth: !!endpoint.authorizer || endpoint.hasSession === true,
18
14
  hasServices: endpoint.services.length > 0,
19
15
  hasDatabase: !!endpoint.databaseService,
20
16
  hasBodyValidation: !!endpoint.input?.body,
@@ -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
- const hasValidators = validators.length > 0;
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 needsValidators = features.hasBodyValidation || features.hasQueryValidation || features.hasParamValidation;
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 needsValidators = features.hasBodyValidation || features.hasQueryValidation || features.hasParamValidation;
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$1 = tierEndpoints.map((a) => ` setup${capitalize(a.exportName)}(app, logger);`).join("\n");
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$1}
432
+ ${calls}
441
433
  }
442
434
  `;
443
435
  }
444
436
  if (tier === "standard") {
445
- const calls$1 = tierEndpoints.map((a) => ` setup${capitalize(a.exportName)}(app, serviceDiscovery, logger);`).join("\n");
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$1}
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;
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
+ }
589
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 provider = options?.provider || "aws-apigatewayv2";
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 (provider === "server") {
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(process.cwd(), appFile),
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
- } 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);
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(process.cwd(), handlerFile).replace(/\.ts$/, ".handler"),
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, 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);
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 endpointsFileName = "endpoints.ts";
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 relativePath = relative(dirname(endpointsPath), path.relative);
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, 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 appFileName = "app.ts";
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) 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 = "";
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
- const relativeHooksPath = relative(dirname(appPath), context.hooks.serverHooksPath);
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) if (telescopeFromSurface || usesExternalTelescope) telescopeSetup = `
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 telescope UI
939
- const telescopeUI = createUI(telescope);
940
- honoApp.route('${context.telescope?.path}', telescopeUI);
1022
+ // Mount Telescope's JSON API
1023
+ const telescopeApi = createApi(telescope);
1024
+ honoApp.route('${context.telescope?.path}', telescopeApi);
941
1025
  `;
942
- else telescopeSetup = `
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 telescope UI
957
- const telescopeUI = createUI(telescope);
958
- honoApp.route('${context.telescope?.path}', telescopeUI);
1040
+ // Mount Telescope's JSON API
1041
+ const telescopeApi = createApi(telescope);
1042
+ honoApp.route('${context.telescope?.path}', telescopeApi);
959
1043
  `;
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,
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
- 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
- `;
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
- ${studioImports}
1070
+ ${databaseApiImports}
997
1071
  ${hooksImports}
998
1072
  ${cors.imports}
999
1073
  ${context.storageDrivers?.imports ?? ""}
1000
1074
 
1001
1075
  ${runtime.bindings}
1002
- ${telescopeFromSurface ? "const telescope = __surface.telescope;" : ""}
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
- export interface ServerApp {
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
- serve: (app: HonoType, port: number) => void | Promise<void>;
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}${studioSetup}
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
- // Start subscribers in background (non-blocking, local development only)
1072
- await setupSubscribers(envParser, logger).catch((error) => {
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 appFileName = "app.ts";
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
- const relativeHooksPath = relative(dirname(appPath), context.hooks.serverHooksPath);
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
- // Start subscribers in background
1185
- await setupSubscribers(envParser, logger).catch((error) => {
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 handling
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
- logger.info('Graceful shutdown initiated');
1211
- // Allow in-flight requests to complete (30s timeout)
1212
- setTimeout(() => process.exit(0), 30000);
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
- export interface ServerApp {
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;
@@ -1255,6 +1336,35 @@ export async function createApp(app?: HonoType): Promise<ServerApp> {
1255
1336
  // Health check endpoint (always first)
1256
1337
  honoApp.get('${healthCheckPath}', (c) => c.json({ status: 'ok', timestamp: Date.now() }));
1257
1338
  honoApp.get('/ready', (c) => c.json({ ready: true }));
1339
+
1340
+ // An HttpError a handler or a session callback throws — a 401, a 404 —
1341
+ // answers with its own status, as it does under gkm dev. The optimized
1342
+ // handlers do not catch, so without this every one of them was a 500.
1343
+ // Anything else is a 500 that says nothing about why, and is logged.
1344
+ honoApp.onError((error, c) => {
1345
+ const http = error as {
1346
+ isHttpError?: boolean;
1347
+ statusCode?: number;
1348
+ statusMessage?: string;
1349
+ code?: string;
1350
+ details?: unknown;
1351
+ };
1352
+ if (http.isHttpError === true && typeof http.statusCode === 'number') {
1353
+ return c.json(
1354
+ {
1355
+ name: error.name,
1356
+ message: error.message,
1357
+ statusCode: http.statusCode,
1358
+ statusMessage: http.statusMessage,
1359
+ code: http.code,
1360
+ details: http.details,
1361
+ },
1362
+ http.statusCode as 500,
1363
+ );
1364
+ }
1365
+ logger.error({ error }, 'Unhandled error');
1366
+ return c.json({ message: 'Internal Server Error' }, 500);
1367
+ });
1258
1368
  ${cors.setup}${beforeSetupCall}
1259
1369
  // Setup HTTP endpoints (OpenAPI: ${enableOpenApi})
1260
1370
  await setupEndpoints(honoApp, envParser, logger, ${enableOpenApi});
@@ -1271,13 +1381,15 @@ ${afterSetupCall}
1271
1381
  }
1272
1382
 
1273
1383
  const port = options.port ?? Number(process.env.PORT) ?? 3000;
1384
+ let server: unknown;
1274
1385
  ${gracefulShutdownCode}${subscriberSetup}
1275
1386
  logger.info({ port }, 'Starting production server');
1276
1387
 
1277
1388
  // Start HTTP server using provided serve function
1278
- await options.serve(honoApp, port);
1389
+ server = await options.serve(honoApp, port);
1279
1390
 
1280
1391
  logger.info({ port }, 'Production server started');
1392
+ ${includeSubscribers ? "\n await subscribeForPush(port);" : ""}
1281
1393
  }
1282
1394
  };
1283
1395
  }
@@ -1286,21 +1398,27 @@ ${gracefulShutdownCode}${subscriberSetup}
1286
1398
  export default createApp;
1287
1399
  `;
1288
1400
  await writeFile(appPath, content);
1289
- await this.generateProductionServerEntry(outputDir);
1401
+ await this.generateProductionServerEntry(outputDir, context);
1290
1402
  return appPath;
1291
1403
  }
1292
1404
  /**
1293
1405
  * Generate production server.ts entry point
1294
1406
  */
1295
- async generateProductionServerEntry(outputDir) {
1407
+ async generateProductionServerEntry(outputDir, context) {
1296
1408
  const serverPath = join(outputDir, "server.ts");
1297
- const content = `#!/usr/bin/env node
1409
+ await writeFile(join(outputDir, "telemetry.ts"), generateTelemetryModule(context.telemetry));
1410
+ await writeFile(serverPath, `#!/usr/bin/env node
1298
1411
  /**
1299
1412
  * Production server entry point
1300
- * Generated by 'gkm build --provider server --production'
1413
+ * Generated by 'gkm build --production'
1301
1414
  */
1302
1415
  import { serve } from '@hono/node-server';
1303
- import { createApp } from './app.js';
1416
+ import { startTelemetry } from './telemetry.js';
1417
+
1418
+ // Before the app is imported, so the libraries it loads are instrumented.
1419
+ await startTelemetry();
1420
+
1421
+ const { createApp } = await import('./app.js');
1304
1422
 
1305
1423
  const port = Number(process.env.PORT) || 3000;
1306
1424
 
@@ -1308,13 +1426,11 @@ const { start } = await createApp();
1308
1426
 
1309
1427
  await start({
1310
1428
  port,
1311
- serve: (app, port) => { serve({ fetch: app.fetch, port }); },
1429
+ serve: (app, port) => serve({ fetch: app.fetch, port }),
1312
1430
  });
1313
- `;
1314
- await writeFile(serverPath, content);
1431
+ `);
1315
1432
  }
1316
1433
  };
1317
-
1318
1434
  //#endregion
1319
1435
  //#region src/generators/OpenApiTsGenerator.ts
1320
1436
  /**
@@ -1469,7 +1585,7 @@ var OpenApiTsGenerator = class {
1469
1585
  for (const [rawName, rawSchema] of Object.entries(registrySchemas)) {
1470
1586
  const defName = this.pascalCase(rawName);
1471
1587
  if (collectedDefs.has(defName) || generatedNames.has(defName)) continue;
1472
- const { id: _id, $defs: _defs,...rest } = rawSchema;
1588
+ const { id: _id, $defs: _defs, ...rest } = rawSchema;
1473
1589
  collectedDefs.set(defName, rest);
1474
1590
  }
1475
1591
  for (const [defName, defSchema] of collectedDefs) if (!generatedNames.has(defName)) {
@@ -1505,17 +1621,17 @@ var OpenApiTsGenerator = class {
1505
1621
  async schemaToInterfaceWithDefs(schema, name, collectedDefs) {
1506
1622
  try {
1507
1623
  const vendor = schema["~standard"]?.vendor;
1508
- if (!vendor || !(vendor in StandardSchemaJsonSchema)) return null;
1624
+ if (!vendor || !(vendor in StandardSchemaJsonSchema)) return unknownType(name);
1509
1625
  const toJsonSchema = StandardSchemaJsonSchema[vendor];
1510
1626
  const jsonSchema = await toJsonSchema(schema);
1511
- if (!jsonSchema) return null;
1627
+ if (!jsonSchema) return unknownType(name);
1512
1628
  if (jsonSchema.$defs && typeof jsonSchema.$defs === "object") {
1513
1629
  for (const [defName, defSchema] of Object.entries(jsonSchema.$defs)) if (!collectedDefs.has(defName)) {
1514
- const { id,...schemaWithoutId } = defSchema;
1630
+ const { id, ...schemaWithoutId } = defSchema;
1515
1631
  collectedDefs.set(defName, schemaWithoutId);
1516
1632
  }
1517
1633
  }
1518
- const { $defs,...schemaWithoutDefs } = jsonSchema;
1634
+ const { $defs, ...schemaWithoutDefs } = jsonSchema;
1519
1635
  const ref = schemaWithoutDefs.$ref;
1520
1636
  if (ref && Object.keys(schemaWithoutDefs).every((k) => k === "$ref" || k === "$schema")) {
1521
1637
  const target = ref.split("/").pop();
@@ -1523,14 +1639,11 @@ var OpenApiTsGenerator = class {
1523
1639
  }
1524
1640
  return this.jsonSchemaToInterface(schemaWithoutDefs, name);
1525
1641
  } catch {
1526
- return null;
1642
+ return unknownType(name);
1527
1643
  }
1528
1644
  }
1529
1645
  jsonSchemaToInterface(schema, name) {
1530
- if (schema.type !== "object" || !schema.properties) {
1531
- const typeStr = this.jsonSchemaTypeToTs(schema);
1532
- return `export type ${name} = ${typeStr};`;
1533
- }
1646
+ if (schema.type !== "object" || !schema.properties) return `export type ${name} = ${this.jsonSchemaTypeToTs(schema)};`;
1534
1647
  const props = [];
1535
1648
  const required = new Set(schema.required || []);
1536
1649
  for (const [propName, propSchema] of Object.entries(schema.properties)) {
@@ -1543,13 +1656,15 @@ var OpenApiTsGenerator = class {
1543
1656
  }
1544
1657
  jsonSchemaTypeToTs(schema) {
1545
1658
  if (!schema) return "unknown";
1546
- if (schema.$ref) {
1547
- const refName = schema.$ref.split("/").pop() || "unknown";
1548
- return refName;
1549
- }
1659
+ if (schema.$ref) return schema.$ref.split("/").pop() || "unknown";
1660
+ if (schema.const !== void 0) return typeof schema.const === "string" ? `'${schema.const}'` : String(schema.const);
1550
1661
  if (schema.anyOf) return schema.anyOf.map((s) => this.jsonSchemaTypeToTs(s)).join(" | ");
1551
1662
  if (schema.oneOf) return schema.oneOf.map((s) => this.jsonSchemaTypeToTs(s)).join(" | ");
1552
1663
  if (schema.allOf) return schema.allOf.map((s) => this.jsonSchemaTypeToTs(s)).join(" & ");
1664
+ if (Array.isArray(schema.type)) return schema.type.map((type) => this.jsonSchemaTypeToTs({
1665
+ ...schema,
1666
+ type
1667
+ })).join(" | ");
1553
1668
  switch (schema.type) {
1554
1669
  case "string":
1555
1670
  if (schema.enum) return schema.enum.map((e) => `'${e}'`).join(" | ");
@@ -1573,10 +1688,7 @@ var OpenApiTsGenerator = class {
1573
1688
  }
1574
1689
  return `{ ${props.join("; ")} }`;
1575
1690
  }
1576
- if (schema.additionalProperties) {
1577
- const valueType = this.jsonSchemaTypeToTs(schema.additionalProperties);
1578
- return `Record<string, ${valueType}>`;
1579
- }
1691
+ if (schema.additionalProperties) return `Record<string, ${this.jsonSchemaTypeToTs(schema.additionalProperties)}>`;
1580
1692
  return "Record<string, unknown>";
1581
1693
  default: return "unknown";
1582
1694
  }
@@ -1597,10 +1709,7 @@ var OpenApiTsGenerator = class {
1597
1709
  }
1598
1710
  const firstWithParams = infos.find((i) => i.input?.params);
1599
1711
  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
- }
1712
+ if (firstWithParams?.input?.params) paramsEntry = `\n parameters: {\n path: ${await this.getSchemaName(firstWithParams.input.params, `${this.getSchemaBaseName(firstWithParams)}Params`)};\n };`;
1604
1713
  pathEntries.push(` '${route}': {${paramsEntry}\n${methodEntries.join("\n")}\n };`);
1605
1714
  }
1606
1715
  return `export interface paths {\n${pathEntries.join("\n")}\n}`;
@@ -1639,8 +1748,7 @@ var OpenApiTsGenerator = class {
1639
1748
  return acc;
1640
1749
  }, {});
1641
1750
  const schemeNames = securitySchemes.map((s) => `'${s.name}'`).join(" | ");
1642
- const hasSecuritySchemes = schemeNames.length > 0;
1643
- const createApiSection = hasSecuritySchemes ? `
1751
+ const createApiSection = schemeNames.length > 0 ? `
1644
1752
  // ============================================================
1645
1753
  // API Client Factory
1646
1754
  // ============================================================
@@ -1664,6 +1772,8 @@ export interface CreateApiOptions {
1664
1772
  queryClient?: QueryClient;
1665
1773
  /** Optional request interceptor */
1666
1774
  onRequest?: (config: RequestInit) => RequestInit | Promise<RequestInit>;
1775
+ /** The \`fetch\` requests go out on — a test browser's, for one */
1776
+ fetch?: typeof fetch;
1667
1777
  }
1668
1778
 
1669
1779
  /**
@@ -1693,6 +1803,7 @@ export function createApi(options: CreateApiOptions) {
1693
1803
  securitySchemes,
1694
1804
  authStrategies: options.authStrategies,
1695
1805
  onRequest: options.onRequest,
1806
+ ...(options.fetch ? { fetch: options.fetch } : {}),
1696
1807
  });
1697
1808
 
1698
1809
  const hooks = createEndpointHooks<paths>(fetcher, { queryClient: options.queryClient });
@@ -1817,205 +1928,15 @@ ${createApiSection}
1817
1928
  `;
1818
1929
  }
1819
1930
  };
1820
-
1821
- //#endregion
1822
- //#region src/workspace/client-generator.ts
1823
1931
  /**
1824
- * Normalize routes to an array of patterns.
1825
- * Handles string, string[], and PartitionedRoutes (extracts paths).
1826
- * @internal Exported for use in dev command
1932
+ * The declaration for a schema that cannot be converted — a vendor with no
1933
+ * converter, or one that fails. The paths interface names it either way, so
1934
+ * leaving it undeclared made the generated client fail to compile.
1827
1935
  */
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];
1936
+ function unknownType(name) {
1937
+ return `export type ${name} = unknown;`;
1832
1938
  }
1833
-
1834
1939
  //#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, 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
- }
1940
+ export { telemetryFor as i, EndpointGenerator as n, runtimeFor as r, OpenApiTsGenerator as t };
2018
1941
 
2019
- //#endregion
2020
- export { EndpointGenerator, OPENAPI_OUTPUT_PATH, generateOpenApi, generateOpenApiFrom, normalizeRoutes, openApiPathFor, openapiCommand, resolveOpenApiConfig, runtimeFor };
2021
- //# sourceMappingURL=openapi-Bw8DWFWz.mjs.map
1942
+ //# sourceMappingURL=OpenApiTsGenerator-q5B0z8Xg.mjs.map