@geekmidas/cli 9.0.2 → 10.0.0-alpha.0

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 (390) hide show
  1. package/README.md +71 -120
  2. package/dist/{CachedStateProvider-BdX7klRc.cjs → CachedStateProvider-BLo7zv7S.cjs} +1 -1
  3. package/dist/CachedStateProvider-BxoAm-B2.mjs +3 -0
  4. package/dist/{CachedStateProvider-klLWZXWF.cjs → CachedStateProvider-CqPdW7CB.cjs} +1 -1
  5. package/dist/{CachedStateProvider-klLWZXWF.cjs.map → CachedStateProvider-CqPdW7CB.cjs.map} +1 -1
  6. package/dist/{CachedStateProvider-CzyFct42.mjs → CachedStateProvider-DTniehMP.mjs} +1 -1
  7. package/dist/{CachedStateProvider-CzyFct42.mjs.map → CachedStateProvider-DTniehMP.mjs.map} +1 -1
  8. package/dist/{HostingerProvider-B2ZYz7Yc.mjs → HostingerProvider-Cft7lEYr.mjs} +4 -3
  9. package/dist/HostingerProvider-Cft7lEYr.mjs.map +1 -0
  10. package/dist/{HostingerProvider-CrHfsgz9.cjs → HostingerProvider-h3EbEp-Z.cjs} +4 -3
  11. package/dist/HostingerProvider-h3EbEp-Z.cjs.map +1 -0
  12. package/dist/{LocalStateProvider-CP49rjzt.mjs → LocalStateProvider-Bn6tS-oL.mjs} +1 -1
  13. package/dist/{LocalStateProvider-CP49rjzt.mjs.map → LocalStateProvider-Bn6tS-oL.mjs.map} +1 -1
  14. package/dist/{LocalStateProvider-CalzNfNA.cjs → LocalStateProvider-Db9SEjyH.cjs} +1 -1
  15. package/dist/{LocalStateProvider-CalzNfNA.cjs.map → LocalStateProvider-Db9SEjyH.cjs.map} +1 -1
  16. package/dist/{Route53Provider-tlgBzrRQ.mjs → Route53Provider-RdpUDJQk.mjs} +1 -1
  17. package/dist/{Route53Provider-tlgBzrRQ.mjs.map → Route53Provider-RdpUDJQk.mjs.map} +1 -1
  18. package/dist/{Route53Provider-DwgXiLxt.cjs → Route53Provider-di2fMM5N.cjs} +1 -1
  19. package/dist/{Route53Provider-DwgXiLxt.cjs.map → Route53Provider-di2fMM5N.cjs.map} +1 -1
  20. package/dist/{SSMStateProvider-BlrTiV5m.mjs → SSMStateProvider-BRt2py-A.mjs} +1 -1
  21. package/dist/{SSMStateProvider-BlrTiV5m.mjs.map → SSMStateProvider-BRt2py-A.mjs.map} +1 -1
  22. package/dist/{SSMStateProvider-Bt6r8CTL.cjs → SSMStateProvider-DyX8s9HI.cjs} +1 -1
  23. package/dist/{SSMStateProvider-Bt6r8CTL.cjs.map → SSMStateProvider-DyX8s9HI.cjs.map} +1 -1
  24. package/dist/{backup-provisioner-Xunq-p_8.mjs → backup-provisioner-Bgj5Vi_L.mjs} +2 -2
  25. package/dist/{backup-provisioner-Xunq-p_8.mjs.map → backup-provisioner-Bgj5Vi_L.mjs.map} +1 -1
  26. package/dist/{backup-provisioner-B3HBN-2k.cjs → backup-provisioner-BrsHwYG0.cjs} +2 -2
  27. package/dist/{backup-provisioner-B3HBN-2k.cjs.map → backup-provisioner-BrsHwYG0.cjs.map} +1 -1
  28. package/dist/{bundler-Dufq3uvI.cjs → bundler-BY1OmV1e.cjs} +44 -14
  29. package/dist/bundler-BY1OmV1e.cjs.map +1 -0
  30. package/dist/{bundler-DkA4qzUD.mjs → bundler-Be8Zg4Wd.mjs} +45 -15
  31. package/dist/bundler-Be8Zg4Wd.mjs.map +1 -0
  32. package/dist/config-D8o6I_7Z.mjs +469 -0
  33. package/dist/config-D8o6I_7Z.mjs.map +1 -0
  34. package/dist/config-DbPE1KXF.cjs +535 -0
  35. package/dist/config-DbPE1KXF.cjs.map +1 -0
  36. package/dist/config.cjs +3 -2
  37. package/dist/config.d.cts +2 -2
  38. package/dist/config.d.cts.map +1 -1
  39. package/dist/config.d.mts +2 -2
  40. package/dist/config.d.mts.map +1 -1
  41. package/dist/config.mjs +3 -2
  42. package/dist/{credentials-s1kLcIzK.mjs → credentials-B5zRHdAl.mjs} +21 -6
  43. package/dist/credentials-B5zRHdAl.mjs.map +1 -0
  44. package/dist/{credentials-C8DWtnMY.cjs → credentials-BapzjyV5.cjs} +26 -5
  45. package/dist/credentials-BapzjyV5.cjs.map +1 -0
  46. package/dist/credentials-Dh0Q6HzY.mjs +2548 -0
  47. package/dist/credentials-Dh0Q6HzY.mjs.map +1 -0
  48. package/dist/credentials-K961ThG3.cjs +2680 -0
  49. package/dist/credentials-K961ThG3.cjs.map +1 -0
  50. package/dist/deploy/sniffer-routes-worker.cjs +6 -2
  51. package/dist/deploy/sniffer-routes-worker.cjs.map +1 -1
  52. package/dist/deploy/sniffer-routes-worker.mjs +6 -2
  53. package/dist/deploy/sniffer-routes-worker.mjs.map +1 -1
  54. package/dist/discover-CAlFgYec.mjs +229 -0
  55. package/dist/discover-CAlFgYec.mjs.map +1 -0
  56. package/dist/discover-DTmFeloL.cjs +271 -0
  57. package/dist/discover-DTmFeloL.cjs.map +1 -0
  58. package/dist/dokploy-api-BS2xnJLZ.mjs +3 -0
  59. package/dist/{dokploy-api-CqOIKHme.cjs → dokploy-api-BsVH0y_s.cjs} +141 -32
  60. package/dist/dokploy-api-BsVH0y_s.cjs.map +1 -0
  61. package/dist/dokploy-api-BuxDRo3l.cjs +3 -0
  62. package/dist/{dokploy-api-sfZFlWuE.mjs → dokploy-api-aTFPz7_4.mjs} +141 -32
  63. package/dist/dokploy-api-aTFPz7_4.mjs.map +1 -0
  64. package/dist/emit-CbcRolsa.cjs +236 -0
  65. package/dist/emit-CbcRolsa.cjs.map +1 -0
  66. package/dist/emit-CiZS_5Ub.mjs +206 -0
  67. package/dist/emit-CiZS_5Ub.mjs.map +1 -0
  68. package/dist/{encryption-MfXAiFTm.cjs → encryption-C6jaU0hE.cjs} +1 -1
  69. package/dist/{encryption-MfXAiFTm.cjs.map → encryption-C6jaU0hE.cjs.map} +1 -1
  70. package/dist/{encryption-BVS7Vmzs.cjs → encryption-CHzzeR8a.cjs} +1 -1
  71. package/dist/encryption-CQUncu7v.mjs +3 -0
  72. package/dist/{encryption-CO5Q4Bhf.mjs → encryption-D-abyhZx.mjs} +1 -1
  73. package/dist/{encryption-CO5Q4Bhf.mjs.map → encryption-D-abyhZx.mjs.map} +1 -1
  74. package/dist/fullstack-secrets-C-GGohke.cjs +111 -0
  75. package/dist/fullstack-secrets-C-GGohke.cjs.map +1 -0
  76. package/dist/fullstack-secrets-D1o9-G5l.mjs +87 -0
  77. package/dist/fullstack-secrets-D1o9-G5l.mjs.map +1 -0
  78. package/dist/{fullstack-secrets-BYzi6Hwl.cjs → generator-CNzBw7g5.cjs} +1 -114
  79. package/dist/generator-CNzBw7g5.cjs.map +1 -0
  80. package/dist/{fullstack-secrets-l8s3II_H.mjs → generator-VE2JrFlY.mjs} +2 -85
  81. package/dist/generator-VE2JrFlY.mjs.map +1 -0
  82. package/dist/index-BThUzEIp.d.mts +175 -0
  83. package/dist/index-BThUzEIp.d.mts.map +1 -0
  84. package/dist/index-CHB4tSzX.d.cts +175 -0
  85. package/dist/index-CHB4tSzX.d.cts.map +1 -0
  86. package/dist/index.cjs +2329 -1584
  87. package/dist/index.cjs.map +1 -1
  88. package/dist/index.mjs +2306 -1561
  89. package/dist/index.mjs.map +1 -1
  90. package/dist/{openapi-Cfwi0fqF.mjs → openapi-CgpMWMmM.mjs} +174 -95
  91. package/dist/openapi-CgpMWMmM.mjs.map +1 -0
  92. package/dist/{openapi-BzNOC7_i.cjs → openapi-CljONd_M.cjs} +175 -108
  93. package/dist/openapi-CljONd_M.cjs.map +1 -0
  94. package/dist/{openapi-react-query-C4iIvpPf.mjs → openapi-react-query-Byltvm_7.mjs} +2 -2
  95. package/dist/openapi-react-query-Byltvm_7.mjs.map +1 -0
  96. package/dist/{openapi-react-query-CYWik72n.cjs → openapi-react-query-CmJ1xDbn.cjs} +2 -2
  97. package/dist/openapi-react-query-CmJ1xDbn.cjs.map +1 -0
  98. package/dist/openapi-react-query.cjs +1 -1
  99. package/dist/openapi-react-query.mjs +1 -1
  100. package/dist/openapi.cjs +4 -3
  101. package/dist/openapi.d.cts +1 -1
  102. package/dist/openapi.d.mts +1 -1
  103. package/dist/openapi.mjs +4 -3
  104. package/dist/reconcile/public.cjs +9 -0
  105. package/dist/reconcile/public.d.cts +125 -0
  106. package/dist/reconcile/public.d.cts.map +1 -0
  107. package/dist/reconcile/public.d.mts +125 -0
  108. package/dist/reconcile/public.d.mts.map +1 -0
  109. package/dist/reconcile/public.mjs +4 -0
  110. package/dist/{reconcile-B2FoJE0a.mjs → reconcile-CFs8C_1a.mjs} +5 -4
  111. package/dist/reconcile-CFs8C_1a.mjs.map +1 -0
  112. package/dist/{reconcile-BzqKJ2Rt.cjs → reconcile-D0pimNiy.cjs} +5 -4
  113. package/dist/reconcile-D0pimNiy.cjs.map +1 -0
  114. package/dist/setup-CJiQM2pX.cjs +473 -0
  115. package/dist/setup-CJiQM2pX.cjs.map +1 -0
  116. package/dist/setup-DMtU3p41.cjs +11 -0
  117. package/dist/setup-DOsYueeQ.mjs +443 -0
  118. package/dist/setup-DOsYueeQ.mjs.map +1 -0
  119. package/dist/setup-DSutDAlt.mjs +11 -0
  120. package/dist/{storage-B1wvztiJ.cjs → storage-BJmzYxt8.cjs} +1 -1
  121. package/dist/{storage-B1wvztiJ.cjs.map → storage-BJmzYxt8.cjs.map} +1 -1
  122. package/dist/{storage-DOEtT2Hr.cjs → storage-BM9hGpgv.cjs} +1 -1
  123. package/dist/{storage-dbb9RyBl.mjs → storage-Bi24WfoW.mjs} +1 -1
  124. package/dist/{storage-dbb9RyBl.mjs.map → storage-Bi24WfoW.mjs.map} +1 -1
  125. package/dist/{storage-Cs4WBsc4.mjs → storage-CYlGwWTj.mjs} +1 -1
  126. package/dist/{sync-CSooqRQ8.mjs → sync-CmEuNZsB.mjs} +2 -2
  127. package/dist/{sync-CSooqRQ8.mjs.map → sync-CmEuNZsB.mjs.map} +1 -1
  128. package/dist/sync-D306yOWu.cjs +4 -0
  129. package/dist/{sync-o_CtEbvR.cjs → sync-SizaChmC.cjs} +2 -2
  130. package/dist/{sync-o_CtEbvR.cjs.map → sync-SizaChmC.cjs.map} +1 -1
  131. package/dist/{sync-B5UHXwRM.mjs → sync-hOqAWHR6.mjs} +1 -1
  132. package/dist/{index-Bw548Bta.d.mts → types-C-arnQ1r.d.mts} +590 -143
  133. package/dist/types-C-arnQ1r.d.mts.map +1 -0
  134. package/dist/{index-DTpkeFb8.d.cts → types-abJQuk7m.d.cts} +590 -143
  135. package/dist/types-abJQuk7m.d.cts.map +1 -0
  136. package/dist/workspace/index.cjs +5 -1
  137. package/dist/workspace/index.d.cts +3 -3
  138. package/dist/workspace/index.d.mts +3 -3
  139. package/dist/workspace/index.mjs +2 -2
  140. package/dist/{workspace-D-37Ylqo.mjs → workspace-7FqprxuJ.mjs} +117 -30
  141. package/dist/workspace-7FqprxuJ.mjs.map +1 -0
  142. package/dist/workspace-8acupeaI.cjs +9 -0
  143. package/dist/{workspace-CQTT7HJQ.cjs → workspace-XvqO9jIv.cjs} +139 -28
  144. package/dist/workspace-XvqO9jIv.cjs.map +1 -0
  145. package/dist/workspace-yP5AHnrn.mjs +8 -0
  146. package/package.json +26 -9
  147. package/CHANGELOG.md +0 -650
  148. package/FUNCTION_CRON_SUPPORT.md +0 -266
  149. package/dist/CachedStateProvider-GcGziXh1.mjs +0 -3
  150. package/dist/HostingerProvider-B2ZYz7Yc.mjs.map +0 -1
  151. package/dist/HostingerProvider-CrHfsgz9.cjs.map +0 -1
  152. package/dist/bundler-DkA4qzUD.mjs.map +0 -1
  153. package/dist/bundler-Dufq3uvI.cjs.map +0 -1
  154. package/dist/config-Bbu1JKvf.mjs +0 -211
  155. package/dist/config-Bbu1JKvf.mjs.map +0 -1
  156. package/dist/config-DujV8DbR.cjs +0 -253
  157. package/dist/config-DujV8DbR.cjs.map +0 -1
  158. package/dist/credentials-C8DWtnMY.cjs.map +0 -1
  159. package/dist/credentials-s1kLcIzK.mjs.map +0 -1
  160. package/dist/dokploy-api-BPYkG-ZZ.cjs +0 -3
  161. package/dist/dokploy-api-BiSzWM4v.mjs +0 -3
  162. package/dist/dokploy-api-CqOIKHme.cjs.map +0 -1
  163. package/dist/dokploy-api-sfZFlWuE.mjs.map +0 -1
  164. package/dist/encryption-Bd_m4q3g.mjs +0 -3
  165. package/dist/fullstack-secrets-BYzi6Hwl.cjs.map +0 -1
  166. package/dist/fullstack-secrets-l8s3II_H.mjs.map +0 -1
  167. package/dist/index-Bw548Bta.d.mts.map +0 -1
  168. package/dist/index-DTpkeFb8.d.cts.map +0 -1
  169. package/dist/openapi-BzNOC7_i.cjs.map +0 -1
  170. package/dist/openapi-Cfwi0fqF.mjs.map +0 -1
  171. package/dist/openapi-react-query-C4iIvpPf.mjs.map +0 -1
  172. package/dist/openapi-react-query-CYWik72n.cjs.map +0 -1
  173. package/dist/reconcile-B2FoJE0a.mjs.map +0 -1
  174. package/dist/reconcile-BzqKJ2Rt.cjs.map +0 -1
  175. package/dist/setup-DDXUXzzl.mjs +0 -765
  176. package/dist/setup-DDXUXzzl.mjs.map +0 -1
  177. package/dist/setup-DLwhfyt-.cjs +0 -8
  178. package/dist/setup-Dh-B7YTX.mjs +0 -8
  179. package/dist/setup-WZ-xqAQO.cjs +0 -849
  180. package/dist/setup-WZ-xqAQO.cjs.map +0 -1
  181. package/dist/sync-BLA94rgC.cjs +0 -4
  182. package/dist/types-B3GQUg3k.d.mts +0 -280
  183. package/dist/types-B3GQUg3k.d.mts.map +0 -1
  184. package/dist/types-BWQDiooe.d.cts +0 -280
  185. package/dist/types-BWQDiooe.d.cts.map +0 -1
  186. package/dist/workspace-CQTT7HJQ.cjs.map +0 -1
  187. package/dist/workspace-D-37Ylqo.mjs.map +0 -1
  188. package/docs/OPENAPI_TYPESCRIPT_DESIGN.md +0 -408
  189. package/docs/dev-server-resilience-design.md +0 -189
  190. package/docs/manifest-refactor-design.md +0 -287
  191. package/examples/cron-example.ts +0 -45
  192. package/examples/env.ts +0 -33
  193. package/examples/function-example.ts +0 -40
  194. package/examples/gkm.config.json +0 -22
  195. package/examples/gkm.config.ts +0 -14
  196. package/examples/gkm.minimal.config.json +0 -7
  197. package/examples/gkm.production.config.json +0 -27
  198. package/examples/logger.ts +0 -10
  199. package/scripts/sync-versions.ts +0 -108
  200. package/src/__tests__/EndpointGenerator.hooks.spec.ts +0 -204
  201. package/src/__tests__/config.spec.ts +0 -422
  202. package/src/__tests__/loadEnvFiles.spec.ts +0 -131
  203. package/src/__tests__/normalizeHooksConfig.spec.ts +0 -63
  204. package/src/__tests__/openapi-react-query.spec.ts +0 -506
  205. package/src/__tests__/openapi.spec.ts +0 -625
  206. package/src/__tests__/test-helpers.ts +0 -181
  207. package/src/auth/__tests__/credentials.spec.ts +0 -331
  208. package/src/auth/__tests__/index.spec.ts +0 -237
  209. package/src/auth/credentials.ts +0 -286
  210. package/src/auth/index.ts +0 -233
  211. package/src/build/__tests__/bundler.spec.ts +0 -446
  212. package/src/build/__tests__/endpoint-analyzer.spec.ts +0 -623
  213. package/src/build/__tests__/handler-templates.spec.ts +0 -1287
  214. package/src/build/__tests__/index-new.spec.ts +0 -550
  215. package/src/build/__tests__/manifests.spec.ts +0 -583
  216. package/src/build/__tests__/partitions.spec.ts +0 -110
  217. package/src/build/__tests__/workspace-build.spec.ts +0 -215
  218. package/src/build/bundler.ts +0 -256
  219. package/src/build/endpoint-analyzer.ts +0 -236
  220. package/src/build/handler-templates.ts +0 -1253
  221. package/src/build/index.ts +0 -616
  222. package/src/build/manifests.ts +0 -285
  223. package/src/build/partitions.ts +0 -58
  224. package/src/build/providerResolver.ts +0 -184
  225. package/src/build/types.ts +0 -104
  226. package/src/config.ts +0 -330
  227. package/src/credentials/__tests__/fullDockerPorts.spec.ts +0 -144
  228. package/src/credentials/__tests__/helpers.ts +0 -112
  229. package/src/credentials/__tests__/prepareEntryCredentials.spec.ts +0 -125
  230. package/src/credentials/__tests__/readonlyDockerPorts.spec.ts +0 -190
  231. package/src/credentials/__tests__/workspaceCredentials.spec.ts +0 -209
  232. package/src/credentials/index.ts +0 -843
  233. package/src/debug.ts +0 -68
  234. package/src/deploy/CachedStateProvider.ts +0 -86
  235. package/src/deploy/LocalStateProvider.ts +0 -57
  236. package/src/deploy/SSMStateProvider.ts +0 -117
  237. package/src/deploy/StateProvider.ts +0 -175
  238. package/src/deploy/__tests__/CachedStateProvider.spec.ts +0 -235
  239. package/src/deploy/__tests__/HostingerProvider.spec.ts +0 -347
  240. package/src/deploy/__tests__/LocalStateProvider.spec.ts +0 -130
  241. package/src/deploy/__tests__/Route53Provider.spec.ts +0 -425
  242. package/src/deploy/__tests__/SSMStateProvider.spec.ts +0 -201
  243. package/src/deploy/__tests__/__fixtures__/entry-apps/async-entry.ts +0 -24
  244. package/src/deploy/__tests__/__fixtures__/entry-apps/nested-config-entry.ts +0 -24
  245. package/src/deploy/__tests__/__fixtures__/entry-apps/no-env-entry.ts +0 -12
  246. package/src/deploy/__tests__/__fixtures__/entry-apps/simple-entry.ts +0 -14
  247. package/src/deploy/__tests__/__fixtures__/entry-apps/throwing-entry.ts +0 -16
  248. package/src/deploy/__tests__/__fixtures__/env-parsers/non-function-export.ts +0 -10
  249. package/src/deploy/__tests__/__fixtures__/env-parsers/optional-env-parser.ts +0 -21
  250. package/src/deploy/__tests__/__fixtures__/env-parsers/parseable-env-parser.ts +0 -18
  251. package/src/deploy/__tests__/__fixtures__/env-parsers/throwing-env-parser.ts +0 -16
  252. package/src/deploy/__tests__/__fixtures__/env-parsers/valid-env-parser.ts +0 -16
  253. package/src/deploy/__tests__/__fixtures__/route-apps/endpoints/auth.ts +0 -16
  254. package/src/deploy/__tests__/__fixtures__/route-apps/endpoints/health.ts +0 -13
  255. package/src/deploy/__tests__/__fixtures__/route-apps/endpoints/users.ts +0 -15
  256. package/src/deploy/__tests__/__fixtures__/route-apps/services.ts +0 -55
  257. package/src/deploy/__tests__/backup-provisioner.spec.ts +0 -428
  258. package/src/deploy/__tests__/createDnsProvider.spec.ts +0 -195
  259. package/src/deploy/__tests__/createStateProvider.spec.ts +0 -126
  260. package/src/deploy/__tests__/dns-orchestration.spec.ts +0 -192
  261. package/src/deploy/__tests__/dns-verification.spec.ts +0 -229
  262. package/src/deploy/__tests__/docker.spec.ts +0 -149
  263. package/src/deploy/__tests__/dokploy-api.spec.ts +0 -697
  264. package/src/deploy/__tests__/dokploy.spec.ts +0 -435
  265. package/src/deploy/__tests__/domain.spec.ts +0 -235
  266. package/src/deploy/__tests__/env-resolver.spec.ts +0 -903
  267. package/src/deploy/__tests__/index.spec.ts +0 -1091
  268. package/src/deploy/__tests__/init.spec.ts +0 -793
  269. package/src/deploy/__tests__/secrets.spec.ts +0 -303
  270. package/src/deploy/__tests__/sniffer.spec.ts +0 -838
  271. package/src/deploy/__tests__/state-e2e.spec.ts +0 -387
  272. package/src/deploy/__tests__/state.spec.ts +0 -876
  273. package/src/deploy/__tests__/undeploy.spec.ts +0 -758
  274. package/src/deploy/backup-provisioner.ts +0 -316
  275. package/src/deploy/dns/DnsProvider.ts +0 -201
  276. package/src/deploy/dns/HostingerProvider.ts +0 -174
  277. package/src/deploy/dns/Route53Provider.ts +0 -340
  278. package/src/deploy/dns/hostinger-api.ts +0 -261
  279. package/src/deploy/dns/index.ts +0 -627
  280. package/src/deploy/docker.ts +0 -260
  281. package/src/deploy/dokploy-api.ts +0 -1101
  282. package/src/deploy/dokploy.ts +0 -177
  283. package/src/deploy/domain.ts +0 -134
  284. package/src/deploy/env-resolver.ts +0 -305
  285. package/src/deploy/index.ts +0 -2094
  286. package/src/deploy/init.ts +0 -427
  287. package/src/deploy/secrets.ts +0 -187
  288. package/src/deploy/sniffer-envkit-patch.ts +0 -61
  289. package/src/deploy/sniffer-hooks.ts +0 -57
  290. package/src/deploy/sniffer-loader.ts +0 -28
  291. package/src/deploy/sniffer-routes-worker.ts +0 -104
  292. package/src/deploy/sniffer-worker.ts +0 -75
  293. package/src/deploy/sniffer.ts +0 -578
  294. package/src/deploy/state-commands.ts +0 -274
  295. package/src/deploy/state.ts +0 -482
  296. package/src/deploy/types.ts +0 -102
  297. package/src/deploy/undeploy.ts +0 -407
  298. package/src/dev/__tests__/entry-integration.spec.ts +0 -347
  299. package/src/dev/__tests__/entry.spec.ts +0 -144
  300. package/src/dev/__tests__/index.spec.ts +0 -2084
  301. package/src/dev/index.ts +0 -1649
  302. package/src/docker/__tests__/compose.spec.ts +0 -1142
  303. package/src/docker/__tests__/templates.spec.ts +0 -694
  304. package/src/docker/compose.ts +0 -808
  305. package/src/docker/index.ts +0 -523
  306. package/src/docker/templates.ts +0 -1067
  307. package/src/exec/index.ts +0 -118
  308. package/src/generators/CronGenerator.ts +0 -105
  309. package/src/generators/EndpointGenerator.ts +0 -954
  310. package/src/generators/FunctionGenerator.ts +0 -117
  311. package/src/generators/Generator.ts +0 -147
  312. package/src/generators/OpenApiTsGenerator.ts +0 -821
  313. package/src/generators/QueueGenerator.ts +0 -245
  314. package/src/generators/SubscriberGenerator.ts +0 -282
  315. package/src/generators/TopicGenerator.ts +0 -45
  316. package/src/generators/__tests__/CronGenerator.spec.ts +0 -445
  317. package/src/generators/__tests__/EndpointGenerator.spec.ts +0 -544
  318. package/src/generators/__tests__/FunctionGenerator.spec.ts +0 -256
  319. package/src/generators/__tests__/Generator.bustCache.spec.ts +0 -72
  320. package/src/generators/__tests__/OpenApiTsGenerator.registry.spec.ts +0 -98
  321. package/src/generators/__tests__/OpenApiTsGenerator.responseType.spec.ts +0 -56
  322. package/src/generators/__tests__/QueueGenerator.spec.ts +0 -141
  323. package/src/generators/__tests__/SubscriberGenerator.spec.ts +0 -410
  324. package/src/generators/index.ts +0 -11
  325. package/src/index.ts +0 -972
  326. package/src/init/__tests__/generators.spec.ts +0 -1070
  327. package/src/init/__tests__/init.spec.ts +0 -577
  328. package/src/init/__tests__/utils.spec.ts +0 -104
  329. package/src/init/generators/auth.ts +0 -225
  330. package/src/init/generators/config.ts +0 -316
  331. package/src/init/generators/docker.ts +0 -388
  332. package/src/init/generators/env.ts +0 -62
  333. package/src/init/generators/index.ts +0 -6
  334. package/src/init/generators/mobile-expo.ts +0 -576
  335. package/src/init/generators/models.ts +0 -153
  336. package/src/init/generators/monorepo.ts +0 -475
  337. package/src/init/generators/package.ts +0 -95
  338. package/src/init/generators/source.ts +0 -15
  339. package/src/init/generators/test.ts +0 -178
  340. package/src/init/generators/ui.ts +0 -1473
  341. package/src/init/generators/web-tanstack.ts +0 -348
  342. package/src/init/generators/web.ts +0 -363
  343. package/src/init/index.ts +0 -542
  344. package/src/init/templates/api.ts +0 -404
  345. package/src/init/templates/index.ts +0 -342
  346. package/src/init/templates/minimal.ts +0 -207
  347. package/src/init/templates/serverless.ts +0 -148
  348. package/src/init/templates/worker.ts +0 -216
  349. package/src/init/utils.ts +0 -197
  350. package/src/init/versions.ts +0 -52
  351. package/src/openapi-react-query.ts +0 -288
  352. package/src/openapi.ts +0 -259
  353. package/src/secrets/__tests__/encryption.spec.ts +0 -226
  354. package/src/secrets/__tests__/generator.spec.ts +0 -575
  355. package/src/secrets/__tests__/index.spec.ts +0 -91
  356. package/src/secrets/__tests__/reconcile.spec.ts +0 -123
  357. package/src/secrets/__tests__/storage.spec.ts +0 -645
  358. package/src/secrets/encryption.ts +0 -91
  359. package/src/secrets/generator.ts +0 -329
  360. package/src/secrets/index.ts +0 -417
  361. package/src/secrets/keystore.ts +0 -144
  362. package/src/secrets/reconcile.ts +0 -53
  363. package/src/secrets/storage.ts +0 -337
  364. package/src/secrets/sync.ts +0 -136
  365. package/src/secrets/types.ts +0 -70
  366. package/src/setup/__tests__/auto-setup.spec.ts +0 -147
  367. package/src/setup/__tests__/reconcile-secrets.spec.ts +0 -439
  368. package/src/setup/fullstack-secrets.ts +0 -140
  369. package/src/setup/index.ts +0 -411
  370. package/src/test/__tests__/__fixtures__/workspace.ts +0 -104
  371. package/src/test/__tests__/api.spec.ts +0 -199
  372. package/src/test/__tests__/auth.spec.ts +0 -162
  373. package/src/test/__tests__/index.spec.ts +0 -502
  374. package/src/test/__tests__/web.spec.ts +0 -210
  375. package/src/test/index.ts +0 -198
  376. package/src/types.ts +0 -363
  377. package/src/upgrade/__tests__/index.spec.ts +0 -354
  378. package/src/upgrade/index.ts +0 -253
  379. package/src/workspace/__tests__/client-generator.spec.ts +0 -326
  380. package/src/workspace/__tests__/index.spec.ts +0 -766
  381. package/src/workspace/__tests__/publicEnv.spec.ts +0 -64
  382. package/src/workspace/__tests__/schema.spec.ts +0 -737
  383. package/src/workspace/__tests__/type-inference.spec.ts +0 -251
  384. package/src/workspace/client-generator.ts +0 -109
  385. package/src/workspace/index.ts +0 -433
  386. package/src/workspace/publicEnv.ts +0 -57
  387. package/src/workspace/schema.ts +0 -861
  388. package/src/workspace/types.ts +0 -943
  389. package/tsconfig.json +0 -9
  390. package/tsdown.config.ts +0 -22
package/src/dev/index.ts DELETED
@@ -1,1649 +0,0 @@
1
- import { type ChildProcess, execSync, spawn } from 'node:child_process';
2
- import { existsSync } from 'node:fs';
3
- import { mkdir, writeFile } from 'node:fs/promises';
4
- import { dirname, join, resolve } from 'node:path';
5
- import chokidar from 'chokidar';
6
- import fg from 'fast-glob';
7
- import { resolveProviders } from '../build/providerResolver';
8
- import type {
9
- BuildContext,
10
- NormalizedHooksConfig,
11
- NormalizedProductionConfig,
12
- NormalizedStudioConfig,
13
- NormalizedTelescopeConfig,
14
- } from '../build/types';
15
- import {
16
- getAppNameFromCwd,
17
- loadAppConfig,
18
- loadWorkspaceConfig,
19
- parseModuleConfig,
20
- } from '../config';
21
- import {
22
- createEntryWrapper,
23
- findAvailablePort,
24
- isPortAvailable,
25
- loadEnvFiles,
26
- loadSecretsForApp,
27
- prepareEntryCredentials,
28
- resolveServicePorts,
29
- rewriteUrlsWithPorts,
30
- startWorkspaceServices,
31
- } from '../credentials';
32
- import {
33
- CronGenerator,
34
- EndpointGenerator,
35
- FunctionGenerator,
36
- QueueGenerator,
37
- SubscriberGenerator,
38
- TopicGenerator,
39
- } from '../generators';
40
- import {
41
- generateOpenApi,
42
- OPENAPI_OUTPUT_PATH,
43
- resolveOpenApiConfig,
44
- } from '../openapi';
45
- import {
46
- readStageSecrets,
47
- secretsExist,
48
- toEmbeddableSecrets,
49
- } from '../secrets/storage.js';
50
- import type {
51
- GkmConfig,
52
- LegacyProvider,
53
- ProductionConfig,
54
- Runtime,
55
- ServerConfig,
56
- StudioConfig,
57
- TelescopeConfig,
58
- } from '../types';
59
- import {
60
- type FrontendFramework,
61
- getAppBuildOrder,
62
- getDependencyEnvVars,
63
- type NormalizedWorkspace,
64
- } from '../workspace/index.js';
65
-
66
- // Re-export shared utilities from credentials module so existing imports
67
- // from '../dev' or '../dev/index' continue to work.
68
- export {
69
- buildDockerComposeEnv,
70
- type ComposePortMapping,
71
- createCredentialsPreload,
72
- createEntryWrapper,
73
- type EntryCredentialsResult,
74
- findAvailablePort,
75
- findSecretsRoot,
76
- getContainerHostPort,
77
- isPortAvailable,
78
- loadEnvFiles,
79
- loadPortState,
80
- loadSecretsForApp,
81
- type PortState,
82
- parseComposePortMappings,
83
- parseComposeServiceNames,
84
- prepareEntryCredentials,
85
- type ResolvedServicePorts,
86
- replacePortInUrl,
87
- resolveServicePorts,
88
- rewriteUrlsWithPorts,
89
- savePortState,
90
- startComposeServices,
91
- startWorkspaceServices,
92
- } from '../credentials';
93
-
94
- // Re-export execCommand from its own module
95
- export { type ExecOptions, execCommand } from '../exec';
96
-
97
- const logger = console;
98
-
99
- /**
100
- * Normalize telescope configuration
101
- * @internal Exported for testing
102
- */
103
- export function normalizeTelescopeConfig(
104
- config: GkmConfig['telescope'],
105
- ): NormalizedTelescopeConfig | undefined {
106
- if (config === false) {
107
- return undefined;
108
- }
109
-
110
- // Handle string path (e.g., './src/config/telescope')
111
- if (typeof config === 'string') {
112
- const { path: telescopePath, importPattern: telescopeImportPattern } =
113
- parseModuleConfig(config, 'telescope');
114
-
115
- return {
116
- enabled: true,
117
- telescopePath,
118
- telescopeImportPattern,
119
- path: '/__telescope',
120
- ignore: [],
121
- recordBody: true,
122
- maxEntries: 1000,
123
- websocket: true,
124
- };
125
- }
126
-
127
- // Default to enabled in development mode
128
- const isEnabled =
129
- config === true || config === undefined || config.enabled !== false;
130
-
131
- if (!isEnabled) {
132
- return undefined;
133
- }
134
-
135
- const telescopeConfig: TelescopeConfig =
136
- typeof config === 'object' ? config : {};
137
-
138
- return {
139
- enabled: true,
140
- path: telescopeConfig.path ?? '/__telescope',
141
- ignore: telescopeConfig.ignore ?? [],
142
- recordBody: telescopeConfig.recordBody ?? true,
143
- maxEntries: telescopeConfig.maxEntries ?? 1000,
144
- websocket: telescopeConfig.websocket ?? true,
145
- };
146
- }
147
-
148
- /**
149
- * Normalize studio configuration
150
- * @internal Exported for testing
151
- */
152
- export function normalizeStudioConfig(
153
- config: GkmConfig['studio'],
154
- ): NormalizedStudioConfig | undefined {
155
- if (config === false) {
156
- return undefined;
157
- }
158
-
159
- // Handle string path (e.g., './src/config/studio')
160
- if (typeof config === 'string') {
161
- const { path: studioPath, importPattern: studioImportPattern } =
162
- parseModuleConfig(config, 'studio');
163
-
164
- return {
165
- enabled: true,
166
- studioPath,
167
- studioImportPattern,
168
- path: '/__studio',
169
- schema: 'public',
170
- };
171
- }
172
-
173
- // Default to enabled in development mode
174
- const isEnabled =
175
- config === true || config === undefined || config.enabled !== false;
176
-
177
- if (!isEnabled) {
178
- return undefined;
179
- }
180
-
181
- const studioConfig: StudioConfig = typeof config === 'object' ? config : {};
182
-
183
- return {
184
- enabled: true,
185
- path: studioConfig.path ?? '/__studio',
186
- schema: studioConfig.schema ?? 'public',
187
- };
188
- }
189
-
190
- /**
191
- * Normalize hooks configuration
192
- * @internal Exported for testing
193
- */
194
- export function normalizeHooksConfig(
195
- config: GkmConfig['hooks'],
196
- cwd: string = process.cwd(),
197
- ): NormalizedHooksConfig | undefined {
198
- if (!config?.server) {
199
- return undefined;
200
- }
201
-
202
- // Resolve the path (handle .ts extension)
203
- const serverPath = config.server.endsWith('.ts')
204
- ? config.server
205
- : `${config.server}.ts`;
206
-
207
- const resolvedPath = resolve(cwd, serverPath);
208
-
209
- return {
210
- serverHooksPath: resolvedPath,
211
- };
212
- }
213
-
214
- /**
215
- * Normalize production configuration
216
- * @internal Exported for testing
217
- */
218
- export function normalizeProductionConfig(
219
- cliProduction: boolean,
220
- configProduction?: ProductionConfig,
221
- ): NormalizedProductionConfig | undefined {
222
- // Production mode is only enabled if --production CLI flag is passed
223
- if (!cliProduction) {
224
- return undefined;
225
- }
226
-
227
- // Merge CLI flag with config options
228
- const config = configProduction ?? {};
229
-
230
- return {
231
- enabled: true,
232
- bundle: config.bundle ?? true,
233
- minify: config.minify ?? true,
234
- healthCheck: config.healthCheck ?? '/health',
235
- gracefulShutdown: config.gracefulShutdown ?? true,
236
- external: config.external ?? [],
237
- subscribers: config.subscribers ?? 'exclude',
238
- openapi: config.openapi ?? false,
239
- optimizedHandlers: config.optimizedHandlers ?? true, // Default to optimized handlers in production
240
- };
241
- }
242
-
243
- /**
244
- * Get production config from GkmConfig
245
- * @internal
246
- */
247
- export function getProductionConfigFromGkm(
248
- config: GkmConfig,
249
- ): ProductionConfig | undefined {
250
- const serverConfig = config.providers?.server;
251
- if (typeof serverConfig === 'object') {
252
- return (serverConfig as ServerConfig).production;
253
- }
254
- return undefined;
255
- }
256
-
257
- export interface DevOptions {
258
- port?: number;
259
- portExplicit?: boolean;
260
- enableOpenApi?: boolean;
261
- /** Specific app to run in workspace mode (default: all apps) */
262
- app?: string;
263
- /** Filter apps by pattern (passed to turbo --filter) */
264
- filter?: string;
265
- /** Entry file to run (bypasses gkm config) */
266
- entry?: string;
267
- /** Watch for file changes (default: true with --entry) */
268
- watch?: boolean;
269
- }
270
-
271
- export async function devCommand(options: DevOptions): Promise<void> {
272
- // Handle --entry mode: run any file with secret injection
273
- if (options.entry) {
274
- return entryDevCommand(options);
275
- }
276
-
277
- // Load default .env file BEFORE loading config
278
- // This ensures env vars are available when config and its dependencies are loaded
279
- const defaultEnv = loadEnvFiles('.env');
280
- if (defaultEnv.loaded.length > 0) {
281
- logger.log(`📦 Loaded env: ${defaultEnv.loaded.join(', ')}`);
282
- }
283
-
284
- // Check if we're in an app subdirectory
285
- const appName = getAppNameFromCwd();
286
- let config: GkmConfig;
287
- let appRoot: string = process.cwd();
288
- let secretsRoot: string = process.cwd(); // Where .gkm/secrets/ lives
289
- let workspaceAppName: string | undefined; // Set if in workspace mode
290
- let workspaceAppPort: number | undefined; // Port from workspace config
291
- let workspace: NormalizedWorkspace | undefined; // Set if in workspace mode
292
-
293
- if (appName) {
294
- // Try to load app-specific config from workspace
295
- try {
296
- const appConfig = await loadAppConfig();
297
- config = appConfig.gkmConfig;
298
- appRoot = appConfig.appRoot;
299
- secretsRoot = appConfig.workspaceRoot;
300
- workspaceAppName = appConfig.appName;
301
- workspaceAppPort = appConfig.app.port;
302
- workspace = appConfig.workspace;
303
- logger.log(
304
- `📦 Running app: ${appConfig.appName} on port ${workspaceAppPort}`,
305
- );
306
-
307
- // Check if app has an entry point (non-gkm app like better-auth)
308
- if (appConfig.app.entry) {
309
- logger.log(`📄 Using entry point: ${appConfig.app.entry}`);
310
- return entryDevCommand({
311
- ...options,
312
- entry: appConfig.app.entry,
313
- port: workspaceAppPort,
314
- portExplicit: true,
315
- });
316
- }
317
- } catch {
318
- // Not in a workspace or app not found in workspace - fall back to regular loading
319
- const loadedConfig = await loadWorkspaceConfig();
320
-
321
- // Route to workspace dev mode for multi-app workspaces
322
- if (loadedConfig.type === 'workspace') {
323
- logger.log('📦 Detected workspace configuration');
324
- return workspaceDevCommand(loadedConfig.workspace, options);
325
- }
326
-
327
- config = loadedConfig.raw as GkmConfig;
328
- }
329
- } else {
330
- // Try to load workspace config
331
- const loadedConfig = await loadWorkspaceConfig();
332
-
333
- // Route to workspace dev mode for multi-app workspaces
334
- if (loadedConfig.type === 'workspace') {
335
- logger.log('📦 Detected workspace configuration');
336
- return workspaceDevCommand(loadedConfig.workspace, options);
337
- }
338
-
339
- // Single-app mode - use existing logic
340
- config = loadedConfig.raw as GkmConfig;
341
- }
342
-
343
- // Load any additional env files specified in config
344
- if (config.env) {
345
- const { loaded, missing } = loadEnvFiles(config.env, appRoot);
346
- if (loaded.length > 0) {
347
- logger.log(`📦 Loaded env: ${loaded.join(', ')}`);
348
- }
349
- if (missing.length > 0) {
350
- logger.warn(`⚠️ Missing env files: ${missing.join(', ')}`);
351
- }
352
- }
353
-
354
- // Force server provider for dev mode
355
- const resolved = resolveProviders(config, { provider: 'server' });
356
-
357
- logger.log('🚀 Starting development server...');
358
- logger.log(`Loading routes from: ${config.routes}`);
359
- if (config.functions) {
360
- logger.log(`Loading functions from: ${config.functions}`);
361
- }
362
- if (config.crons) {
363
- logger.log(`Loading crons from: ${config.crons}`);
364
- }
365
- if (config.subscribers) {
366
- logger.log(`Loading subscribers from: ${config.subscribers}`);
367
- }
368
- logger.log(`Using envParser: ${config.envParser}`);
369
-
370
- // Parse envParser and logger configuration
371
- const { path: envParserPath, importPattern: envParserImportPattern } =
372
- parseModuleConfig(config.envParser, 'envParser');
373
- const { path: loggerPath, importPattern: loggerImportPattern } =
374
- parseModuleConfig(config.logger, 'logger');
375
-
376
- // Normalize telescope configuration
377
- const telescope = normalizeTelescopeConfig(config.telescope);
378
- if (telescope) {
379
- logger.log(`🔭 Telescope enabled at ${telescope.path}`);
380
- }
381
-
382
- // Normalize studio configuration
383
- const studio = normalizeStudioConfig(config.studio);
384
- if (studio) {
385
- logger.log(`🗄️ Studio enabled at ${studio.path}`);
386
- }
387
-
388
- // Normalize hooks configuration
389
- const hooks = normalizeHooksConfig(config.hooks, appRoot);
390
- if (hooks) {
391
- logger.log(`🪝 Server hooks enabled from ${config.hooks?.server}`);
392
- }
393
-
394
- // Resolve OpenAPI configuration
395
- const openApiConfig = resolveOpenApiConfig(config);
396
- // Enable OpenAPI docs endpoint if either root config or provider config enables it
397
- const enableOpenApi = openApiConfig.enabled || resolved.enableOpenApi;
398
- if (enableOpenApi) {
399
- logger.log(`📄 OpenAPI output: ${OPENAPI_OUTPUT_PATH}`);
400
- }
401
-
402
- const buildContext: BuildContext = {
403
- envParserPath,
404
- envParserImportPattern,
405
- loggerPath,
406
- loggerImportPattern,
407
- telescope,
408
- studio,
409
- hooks,
410
- };
411
-
412
- // Build initial version
413
- await buildServer(
414
- config,
415
- buildContext,
416
- resolved.providers[0] as LegacyProvider,
417
- enableOpenApi,
418
- appRoot,
419
- );
420
-
421
- // Generate OpenAPI spec on startup
422
- if (enableOpenApi) {
423
- await generateOpenApi(config);
424
- }
425
-
426
- // Determine runtime (default to node)
427
- const runtime: Runtime = config.runtime ?? 'node';
428
-
429
- // Load secrets for dev mode, resolve Docker ports, and write to JSON file
430
- let secretsJsonPath: string | undefined;
431
- const appSecrets = await loadSecretsForApp(secretsRoot, workspaceAppName);
432
-
433
- // Resolve Docker service ports and rewrite connection URLs
434
- const resolvedPorts = await resolveServicePorts(secretsRoot);
435
- if (Object.keys(resolvedPorts.ports).length > 0) {
436
- const rewritten = rewriteUrlsWithPorts(appSecrets, resolvedPorts);
437
- Object.assign(appSecrets, rewritten);
438
- logger.log(
439
- `🔌 Applied ${Object.keys(resolvedPorts.ports).length} port mapping(s)`,
440
- );
441
- }
442
-
443
- // Inject dependency URLs if in workspace mode
444
- if (workspace && workspaceAppName) {
445
- const depEnv = getDependencyEnvVars(workspace, workspaceAppName);
446
- Object.assign(appSecrets, depEnv);
447
- }
448
-
449
- if (Object.keys(appSecrets).length > 0) {
450
- const secretsDir = join(secretsRoot, '.gkm');
451
- await mkdir(secretsDir, { recursive: true });
452
- const secretsFileName = workspaceAppName
453
- ? `dev-secrets-${workspaceAppName}.json`
454
- : 'dev-secrets.json';
455
- secretsJsonPath = join(secretsDir, secretsFileName);
456
- await writeFile(secretsJsonPath, JSON.stringify(appSecrets, null, 2));
457
- logger.log(`🔐 Loaded ${Object.keys(appSecrets).length} secret(s)`);
458
- }
459
-
460
- // Start the dev server
461
- // Priority: explicit --port option > workspace app port > default 3000
462
- const devServer = new DevServer(
463
- resolved.providers[0] as LegacyProvider,
464
- options.port ?? workspaceAppPort ?? 3000,
465
- options.portExplicit ?? false,
466
- enableOpenApi,
467
- telescope,
468
- studio,
469
- runtime,
470
- appRoot,
471
- secretsJsonPath,
472
- );
473
-
474
- await devServer.start();
475
-
476
- // Watch for file changes
477
- const envParserFile = config.envParser.split('#')[0] ?? config.envParser;
478
- const loggerFile = config.logger.split('#')[0] ?? config.logger;
479
-
480
- // Get hooks file path for watching
481
- const hooksFileParts = config.hooks?.server?.split('#');
482
- const hooksFile = hooksFileParts?.[0];
483
-
484
- const watchPatterns = [
485
- config.routes,
486
- ...(config.functions ? [config.functions] : []),
487
- ...(config.crons ? [config.crons] : []),
488
- ...(config.subscribers ? [config.subscribers] : []),
489
- // Add .ts extension if not present for config files
490
- envParserFile.endsWith('.ts') ? envParserFile : `${envParserFile}.ts`,
491
- loggerFile.endsWith('.ts') ? loggerFile : `${loggerFile}.ts`,
492
- // Add hooks file to watch list
493
- ...(hooksFile
494
- ? [hooksFile.endsWith('.ts') ? hooksFile : `${hooksFile}.ts`]
495
- : []),
496
- ]
497
- .flat()
498
- .filter((p): p is string => typeof p === 'string');
499
-
500
- // Normalize patterns - remove leading ./ when using cwd option
501
- const normalizedPatterns = watchPatterns.map((p) =>
502
- p.startsWith('./') ? p.slice(2) : p,
503
- );
504
-
505
- logger.log(`👀 Watching for changes in: ${normalizedPatterns.join(', ')}`);
506
-
507
- // Resolve glob patterns to actual files (chokidar 4.x doesn't support globs)
508
- const resolvedFiles = await fg(normalizedPatterns, {
509
- cwd: appRoot,
510
- absolute: false,
511
- onlyFiles: true,
512
- });
513
-
514
- // Also watch the directories for new files
515
- const dirsToWatch = [
516
- ...new Set(
517
- resolvedFiles.map((f) => {
518
- const parts = f.split('/');
519
- return parts.slice(0, -1).join('/');
520
- }),
521
- ),
522
- ];
523
-
524
- logger.log(
525
- `📁 Found ${resolvedFiles.length} files in ${dirsToWatch.length} directories`,
526
- );
527
-
528
- const watcher = chokidar.watch([...resolvedFiles, ...dirsToWatch], {
529
- ignored: /(^|[/\\])\../, // ignore dotfiles
530
- persistent: true,
531
- ignoreInitial: true,
532
- cwd: appRoot,
533
- });
534
-
535
- watcher.on('ready', () => {
536
- logger.log('🔍 File watcher ready');
537
- });
538
-
539
- watcher.on('error', (error) => {
540
- logger.error('❌ Watcher error:', error);
541
- });
542
-
543
- let rebuildTimeout: NodeJS.Timeout | null = null;
544
-
545
- watcher.on('change', async (path) => {
546
- logger.log(`📝 File changed: ${path}`);
547
-
548
- // Debounce rebuilds
549
- if (rebuildTimeout) {
550
- clearTimeout(rebuildTimeout);
551
- }
552
-
553
- rebuildTimeout = setTimeout(async () => {
554
- try {
555
- logger.log('🔄 Rebuilding...');
556
- await buildServer(
557
- config,
558
- buildContext,
559
- resolved.providers[0] as LegacyProvider,
560
- enableOpenApi,
561
- appRoot,
562
- true, // bust module cache on rebuild
563
- );
564
-
565
- // Regenerate OpenAPI if enabled
566
- if (enableOpenApi) {
567
- await generateOpenApi(config, { silent: true, bustCache: true });
568
- }
569
-
570
- logger.log('✅ Rebuild complete, restarting server...');
571
- await devServer.restart();
572
- } catch (error) {
573
- const err = error as Error;
574
- logger.error(`❌ Rebuild failed: ${err.message}`);
575
- if (err.stack) {
576
- logger.error(err.stack);
577
- }
578
- }
579
- }, 300);
580
- });
581
-
582
- // Handle graceful shutdown
583
- let isShuttingDown = false;
584
- const shutdown = () => {
585
- if (isShuttingDown) return;
586
- isShuttingDown = true;
587
-
588
- logger.log('\n🛑 Shutting down...');
589
-
590
- // Use sync-style shutdown to ensure it completes before exit
591
- Promise.all([watcher.close(), devServer.stop()])
592
- .catch((err) => {
593
- logger.error('Error during shutdown:', err);
594
- })
595
- .finally(() => {
596
- process.exit(0);
597
- });
598
- };
599
-
600
- process.on('SIGINT', shutdown);
601
- process.on('SIGTERM', shutdown);
602
- }
603
-
604
- /**
605
- * Generate all dependency environment variables for all apps.
606
- * Returns a flat object with all {APP_NAME}_URL variables.
607
- * @internal Exported for testing
608
- */
609
- export function generateAllDependencyEnvVars(
610
- workspace: NormalizedWorkspace,
611
- urlPrefix = 'http://localhost',
612
- ): Record<string, string> {
613
- const env: Record<string, string> = {};
614
-
615
- for (const appName of Object.keys(workspace.apps)) {
616
- const appEnv = getDependencyEnvVars(workspace, appName, urlPrefix);
617
- Object.assign(env, appEnv);
618
- }
619
-
620
- return env;
621
- }
622
-
623
- /**
624
- * Check for port conflicts across all apps.
625
- * Returns list of conflicts if any ports are duplicated.
626
- * @internal Exported for testing
627
- */
628
- export function checkPortConflicts(
629
- workspace: NormalizedWorkspace,
630
- ): { app1: string; app2: string; port: number }[] {
631
- const conflicts: { app1: string; app2: string; port: number }[] = [];
632
- const portToApp = new Map<number, string>();
633
-
634
- for (const [appName, app] of Object.entries(workspace.apps)) {
635
- const existingApp = portToApp.get(app.port);
636
- if (existingApp) {
637
- conflicts.push({ app1: existingApp, app2: appName, port: app.port });
638
- } else {
639
- portToApp.set(app.port, appName);
640
- }
641
- }
642
-
643
- return conflicts;
644
- }
645
-
646
- /**
647
- * Per-framework validation spec for supported frontend frameworks.
648
- */
649
- interface FrontendFrameworkSpec {
650
- displayName: string;
651
- configFiles: string[];
652
- dependency: string;
653
- installCommand: string;
654
- }
655
-
656
- const FRONTEND_FRAMEWORKS: Record<FrontendFramework, FrontendFrameworkSpec> = {
657
- nextjs: {
658
- displayName: 'Next.js',
659
- configFiles: [
660
- 'next.config.js',
661
- 'next.config.ts',
662
- 'next.config.mjs',
663
- 'next.config.cjs',
664
- ],
665
- dependency: 'next',
666
- installCommand: 'pnpm add next react react-dom',
667
- },
668
- remix: {
669
- displayName: 'Remix',
670
- configFiles: [
671
- 'remix.config.js',
672
- 'remix.config.mjs',
673
- 'remix.config.ts',
674
- 'vite.config.js',
675
- 'vite.config.ts',
676
- 'vite.config.mjs',
677
- 'vite.config.cjs',
678
- ],
679
- dependency: '@remix-run/dev',
680
- installCommand: 'pnpm add -D @remix-run/dev',
681
- },
682
- vite: {
683
- displayName: 'Vite',
684
- configFiles: [
685
- 'vite.config.js',
686
- 'vite.config.ts',
687
- 'vite.config.mjs',
688
- 'vite.config.cjs',
689
- ],
690
- dependency: 'vite',
691
- installCommand: 'pnpm add -D vite',
692
- },
693
- 'tanstack-start': {
694
- displayName: 'TanStack Start',
695
- configFiles: [
696
- 'vite.config.js',
697
- 'vite.config.ts',
698
- 'vite.config.mjs',
699
- 'vite.config.cjs',
700
- ],
701
- dependency: '@tanstack/react-start',
702
- installCommand: 'pnpm add @tanstack/react-start @tanstack/react-router',
703
- },
704
- expo: {
705
- displayName: 'Expo',
706
- configFiles: ['app.config.ts', 'app.config.js', 'app.json'],
707
- dependency: 'expo',
708
- installCommand: 'pnpm add expo',
709
- },
710
- };
711
-
712
- /**
713
- * Auto-detect the frontend framework by scanning package.json deps and config
714
- * files. Dependency match wins over config file match because deps are more
715
- * distinctive (a Remix app has both vite.config.ts AND @remix-run/dev).
716
- */
717
- function detectFrontendFramework(
718
- fullPath: string,
719
- deps: Record<string, string>,
720
- ): FrontendFramework | undefined {
721
- // tanstack-start before vite/remix so its more specific dep wins; vite last
722
- // because plain vite is the fallback when neither nextjs/remix/tanstack match.
723
- // Order matters: more specific deps win over general ones.
724
- // expo first (own dep), then tanstack-start (uses vite), remix (uses vite),
725
- // nextjs (own dep), and plain vite last as the fallback.
726
- const order: FrontendFramework[] = [
727
- 'expo',
728
- 'tanstack-start',
729
- 'nextjs',
730
- 'remix',
731
- 'vite',
732
- ];
733
- for (const name of order) {
734
- if (deps[FRONTEND_FRAMEWORKS[name].dependency]) {
735
- return name;
736
- }
737
- }
738
- for (const name of order) {
739
- const hasConfig = FRONTEND_FRAMEWORKS[name].configFiles.some((file) =>
740
- existsSync(join(fullPath, file)),
741
- );
742
- if (hasConfig) {
743
- return name;
744
- }
745
- }
746
- return undefined;
747
- }
748
-
749
- /**
750
- * Validation result for a frontend app.
751
- */
752
- export interface FrontendValidationResult {
753
- appName: string;
754
- valid: boolean;
755
- errors: string[];
756
- warnings: string[];
757
- }
758
-
759
- /**
760
- * Validate a frontend app configuration.
761
- *
762
- * If `framework` is provided, validates strictly against that framework's
763
- * expected config file and dependency. Otherwise auto-detects the framework
764
- * from package.json and config files. If no recognized framework is found, the
765
- * app is allowed through with a warning provided it has a `dev` script.
766
- *
767
- * @internal Exported for testing
768
- */
769
- export async function validateFrontendApp(
770
- appName: string,
771
- appPath: string,
772
- workspaceRoot: string,
773
- framework?: FrontendFramework,
774
- ): Promise<FrontendValidationResult> {
775
- const errors: string[] = [];
776
- const warnings: string[] = [];
777
- const fullPath = join(workspaceRoot, appPath);
778
-
779
- const packageJsonPath = join(fullPath, 'package.json');
780
- if (!existsSync(packageJsonPath)) {
781
- errors.push(
782
- `package.json not found at ${appPath}. Run: pnpm init in the app directory.`,
783
- );
784
- return { appName, valid: false, errors, warnings };
785
- }
786
-
787
- let pkg: {
788
- dependencies?: Record<string, string>;
789
- devDependencies?: Record<string, string>;
790
- scripts?: Record<string, string>;
791
- };
792
- try {
793
- // eslint-disable-next-line @typescript-eslint/no-require-imports
794
- pkg = require(packageJsonPath);
795
- } catch {
796
- errors.push(`Failed to read package.json at ${packageJsonPath}`);
797
- return { appName, valid: false, errors, warnings };
798
- }
799
-
800
- const deps: Record<string, string> = {
801
- ...pkg.dependencies,
802
- ...pkg.devDependencies,
803
- };
804
-
805
- const resolvedFramework =
806
- framework ?? detectFrontendFramework(fullPath, deps);
807
-
808
- if (resolvedFramework) {
809
- const spec = FRONTEND_FRAMEWORKS[resolvedFramework];
810
- const hasConfig = spec.configFiles.some((file) =>
811
- existsSync(join(fullPath, file)),
812
- );
813
- if (!hasConfig) {
814
- errors.push(
815
- `${spec.displayName} config file not found. Expected one of: ${spec.configFiles.join(', ')}`,
816
- );
817
- }
818
- if (!deps[spec.dependency]) {
819
- errors.push(
820
- `${spec.displayName} not found in dependencies. Run: ${spec.installCommand}`,
821
- );
822
- }
823
- } else {
824
- warnings.push(
825
- 'No recognized frontend framework detected (Next.js, Vite, Remix). ' +
826
- 'The app will run via its "dev" script. Set `framework` in your app config to enable strict validation.',
827
- );
828
- }
829
-
830
- if (!pkg.scripts?.dev) {
831
- if (resolvedFramework) {
832
- warnings.push(
833
- 'No "dev" script found in package.json. Turbo expects a "dev" script to run.',
834
- );
835
- } else {
836
- errors.push(
837
- 'No "dev" script found in package.json. Without a recognized framework or a dev script, there is nothing to run.',
838
- );
839
- }
840
- }
841
-
842
- return {
843
- appName,
844
- valid: errors.length === 0,
845
- errors,
846
- warnings,
847
- };
848
- }
849
-
850
- /**
851
- * Validate all frontend apps in the workspace.
852
- * Returns validation results for each frontend app.
853
- * @internal Exported for testing
854
- */
855
- export async function validateFrontendApps(
856
- workspace: NormalizedWorkspace,
857
- ): Promise<FrontendValidationResult[]> {
858
- const results: FrontendValidationResult[] = [];
859
-
860
- for (const [appName, app] of Object.entries(workspace.apps)) {
861
- if (app.type === 'web') {
862
- const result = await validateFrontendApp(
863
- appName,
864
- app.path,
865
- workspace.root,
866
- app.framework as FrontendFramework | undefined,
867
- );
868
- results.push(result);
869
- }
870
- }
871
-
872
- return results;
873
- }
874
-
875
- /**
876
- * Load secrets for development stage.
877
- * Returns env vars to inject, or empty object if secrets not configured/found.
878
- * @internal Exported for testing
879
- */
880
- export async function loadDevSecrets(
881
- workspace: NormalizedWorkspace,
882
- ): Promise<Record<string, string>> {
883
- // Check if secrets are enabled in workspace config
884
- if (!workspace.secrets.enabled) {
885
- return {};
886
- }
887
-
888
- // Try 'dev' stage first, then 'development'
889
- const stages = ['dev', 'development'];
890
-
891
- for (const stage of stages) {
892
- if (secretsExist(stage, workspace.root)) {
893
- const secrets = await readStageSecrets(stage, workspace.root);
894
- if (secrets) {
895
- logger.log(`🔐 Loading secrets from stage: ${stage}`);
896
- return toEmbeddableSecrets(secrets);
897
- }
898
- }
899
- }
900
-
901
- logger.warn(
902
- '⚠️ Secrets enabled but no dev/development secrets found. Run "gkm setup" to initialize your development environment',
903
- );
904
- return {};
905
- }
906
-
907
- /**
908
- * Workspace dev command - orchestrates multi-app development using Turbo.
909
- *
910
- * Flow:
911
- * 1. Check for port conflicts
912
- * 2. Start docker-compose services (db, cache, mail)
913
- * 3. Generate dependency URLs ({APP_NAME}_URL)
914
- * 4. Spawn turbo run dev with injected env vars
915
- */
916
- async function workspaceDevCommand(
917
- workspace: NormalizedWorkspace,
918
- options: DevOptions,
919
- ): Promise<void> {
920
- const appCount = Object.keys(workspace.apps).length;
921
- const backendApps = Object.entries(workspace.apps).filter(
922
- ([_, app]) => app.type === 'backend',
923
- );
924
- const frontendApps = Object.entries(workspace.apps).filter(
925
- ([_, app]) => app.type === 'web',
926
- );
927
- const mobileApps = Object.entries(workspace.apps).filter(
928
- ([_, app]) => app.type === 'mobile',
929
- );
930
-
931
- logger.log(`\n🚀 Starting workspace: ${workspace.name}`);
932
- const counts = [
933
- `${backendApps.length} backend`,
934
- `${frontendApps.length} web`,
935
- ];
936
- if (mobileApps.length > 0) counts.push(`${mobileApps.length} mobile`);
937
- logger.log(` ${counts.join(', ')} app(s)`);
938
-
939
- // Check for port conflicts
940
- const conflicts = checkPortConflicts(workspace);
941
- if (conflicts.length > 0) {
942
- for (const conflict of conflicts) {
943
- logger.error(
944
- `❌ Port conflict: Apps "${conflict.app1}" and "${conflict.app2}" both use port ${conflict.port}`,
945
- );
946
- }
947
- throw new Error(
948
- 'Port conflicts detected. Please assign unique ports to each app.',
949
- );
950
- }
951
-
952
- // Validate frontend apps (Next.js setup)
953
- if (frontendApps.length > 0) {
954
- logger.log('\n🔍 Validating frontend apps...');
955
- const validationResults = await validateFrontendApps(workspace);
956
-
957
- let hasErrors = false;
958
- for (const result of validationResults) {
959
- if (!result.valid) {
960
- hasErrors = true;
961
- logger.error(
962
- `\n❌ Frontend app "${result.appName}" validation failed:`,
963
- );
964
- for (const error of result.errors) {
965
- logger.error(` • ${error}`);
966
- }
967
- }
968
- for (const warning of result.warnings) {
969
- logger.warn(` ⚠️ ${result.appName}: ${warning}`);
970
- }
971
- }
972
-
973
- if (hasErrors) {
974
- throw new Error(
975
- 'Frontend app validation failed. Fix the issues above and try again.',
976
- );
977
- }
978
- logger.log('✅ Frontend apps validated');
979
- }
980
-
981
- // Frontend apps import API clients directly from backend packages
982
- // (e.g. import { createApi } from '@myapp/api/client')
983
- // No file copying needed — pnpm workspace resolution handles it.
984
-
985
- // Resolve dynamic service ports from docker-compose.yml
986
- const resolvedPorts = await resolveServicePorts(workspace.root);
987
-
988
- // Load secrets BEFORE starting Docker so POSTGRES_USER, POSTGRES_PASSWORD,
989
- // etc. are available for docker-compose variable interpolation
990
- const rawSecrets = await loadDevSecrets(workspace);
991
-
992
- // Start docker-compose services with resolved ports AND secrets
993
- await startWorkspaceServices(workspace, resolvedPorts.dockerEnv, rawSecrets);
994
-
995
- // Rewrite URLs with resolved ports (hostnames and port numbers)
996
- const secretsEnv = rewriteUrlsWithPorts(rawSecrets, resolvedPorts);
997
- if (Object.keys(secretsEnv).length > 0) {
998
- logger.log(` Loaded ${Object.keys(secretsEnv).length} secret(s)`);
999
- }
1000
-
1001
- // Generate dependency URLs
1002
- const dependencyEnv = generateAllDependencyEnvVars(workspace);
1003
- if (Object.keys(dependencyEnv).length > 0) {
1004
- logger.log('📡 Dependency URLs:');
1005
- for (const [key, value] of Object.entries(dependencyEnv)) {
1006
- logger.log(` ${key}=${value}`);
1007
- }
1008
- }
1009
-
1010
- // Build turbo filter
1011
- let turboFilter: string[] = [];
1012
- if (options.app) {
1013
- // Run specific app
1014
- if (!workspace.apps[options.app]) {
1015
- const appNames = Object.keys(workspace.apps).join(', ');
1016
- throw new Error(
1017
- `App "${options.app}" not found. Available apps: ${appNames}`,
1018
- );
1019
- }
1020
- turboFilter = ['--filter', options.app];
1021
- logger.log(`\n🎯 Running single app: ${options.app}`);
1022
- } else if (options.filter) {
1023
- // Use custom filter
1024
- turboFilter = ['--filter', options.filter];
1025
- logger.log(`\n🔍 Using filter: ${options.filter}`);
1026
- } else {
1027
- // Run all apps
1028
- logger.log(`\n🎯 Running all ${appCount} apps`);
1029
- }
1030
-
1031
- // List apps and their ports
1032
- const buildOrder = getAppBuildOrder(workspace);
1033
- logger.log('\n📋 Apps (in dependency order):');
1034
- for (const appName of buildOrder) {
1035
- const app = workspace.apps[appName];
1036
- if (!app) continue;
1037
- const deps =
1038
- app.dependencies.length > 0
1039
- ? ` (depends on: ${app.dependencies.join(', ')})`
1040
- : '';
1041
- const icon =
1042
- app.type === 'backend' ? '🔧' : app.type === 'mobile' ? '📱' : '🌐';
1043
- logger.log(` ${icon} ${appName} → http://localhost:${app.port}${deps}`);
1044
- }
1045
-
1046
- // Find the config file path for GKM_CONFIG_PATH
1047
- const configFiles = ['gkm.config.ts', 'gkm.config.js', 'gkm.config.json'];
1048
- let configPath = '';
1049
- for (const file of configFiles) {
1050
- const fullPath = join(workspace.root, file);
1051
- if (existsSync(fullPath)) {
1052
- configPath = fullPath;
1053
- break;
1054
- }
1055
- }
1056
-
1057
- // Prepare environment variables
1058
- // Order matters: secrets first, then dependencies (dependencies can override)
1059
- const turboEnv: Record<string, string> = {
1060
- ...process.env,
1061
- ...secretsEnv,
1062
- ...dependencyEnv,
1063
- NODE_ENV: 'development',
1064
- // Inject config path so child processes can find the workspace config
1065
- ...(configPath ? { GKM_CONFIG_PATH: configPath } : {}),
1066
- };
1067
-
1068
- // Spawn turbo run dev
1069
- logger.log('\n🏃 Starting turbo run dev...\n');
1070
-
1071
- const turboProcess = spawn('pnpm', ['turbo', 'run', 'dev', ...turboFilter], {
1072
- cwd: workspace.root,
1073
- stdio: 'inherit',
1074
- env: turboEnv,
1075
- detached: true,
1076
- });
1077
-
1078
- // No file watcher needed — frontend apps import API clients directly
1079
- // from backend packages via workspace dependencies.
1080
-
1081
- // Handle graceful shutdown
1082
- let isShuttingDown = false;
1083
- const shutdown = () => {
1084
- if (isShuttingDown) return;
1085
- isShuttingDown = true;
1086
-
1087
- logger.log('\n🛑 Shutting down workspace...');
1088
-
1089
- // Kill turbo process group
1090
- const pid = turboProcess.pid;
1091
- if (pid) {
1092
- try {
1093
- process.kill(-pid, 'SIGTERM');
1094
- } catch {
1095
- try {
1096
- process.kill(pid, 'SIGTERM');
1097
- } catch {
1098
- // Process already dead
1099
- }
1100
- }
1101
- }
1102
-
1103
- // Force kill after timeout if processes are still alive
1104
- setTimeout(() => {
1105
- if (pid) {
1106
- try {
1107
- process.kill(-pid, 'SIGKILL');
1108
- } catch {
1109
- try {
1110
- process.kill(pid, 'SIGKILL');
1111
- } catch {
1112
- // Process already dead
1113
- }
1114
- }
1115
- }
1116
- process.exit(0);
1117
- }, 3000);
1118
- };
1119
-
1120
- process.on('SIGINT', shutdown);
1121
- process.on('SIGTERM', shutdown);
1122
-
1123
- // Wait for turbo to exit
1124
- return new Promise((resolve, reject) => {
1125
- turboProcess.on('error', (error) => {
1126
- logger.error('❌ Turbo error:', error);
1127
- reject(error);
1128
- });
1129
-
1130
- turboProcess.on('exit', (code) => {
1131
- if (code !== null && code !== 0) {
1132
- reject(new Error(`Turbo exited with code ${code}`));
1133
- } else {
1134
- resolve();
1135
- }
1136
- });
1137
- });
1138
- }
1139
-
1140
- async function buildServer(
1141
- config: any,
1142
- context: BuildContext,
1143
- provider: LegacyProvider,
1144
- enableOpenApi: boolean,
1145
- appRoot: string = process.cwd(),
1146
- bustCache = false,
1147
- ): Promise<void> {
1148
- // Initialize generators
1149
- const endpointGenerator = new EndpointGenerator();
1150
- const functionGenerator = new FunctionGenerator();
1151
- const cronGenerator = new CronGenerator();
1152
- const subscriberGenerator = new SubscriberGenerator();
1153
- const queueGenerator = new QueueGenerator();
1154
- const topicGenerator = new TopicGenerator();
1155
-
1156
- // Load all constructs (resolve paths relative to appRoot)
1157
- const [
1158
- allEndpoints,
1159
- allFunctions,
1160
- allCrons,
1161
- allSubscribers,
1162
- allQueues,
1163
- allTopics,
1164
- ] = await Promise.all([
1165
- endpointGenerator.load(config.routes, appRoot, bustCache),
1166
- config.functions
1167
- ? functionGenerator.load(config.functions, appRoot, bustCache)
1168
- : [],
1169
- config.crons ? cronGenerator.load(config.crons, appRoot, bustCache) : [],
1170
- config.subscribers
1171
- ? subscriberGenerator.load(config.subscribers, appRoot, bustCache)
1172
- : [],
1173
- config.queues ? queueGenerator.load(config.queues, appRoot, bustCache) : [],
1174
- config.topics ? topicGenerator.load(config.topics, appRoot, bustCache) : [],
1175
- ]);
1176
-
1177
- // Ensure .gkm directory exists in app root
1178
- const outputDir = join(appRoot, '.gkm', provider);
1179
- await mkdir(outputDir, { recursive: true });
1180
-
1181
- // Build for server provider
1182
- await Promise.all([
1183
- endpointGenerator.build(context, allEndpoints, outputDir, {
1184
- provider,
1185
- enableOpenApi,
1186
- }),
1187
- functionGenerator.build(context, allFunctions, outputDir, { provider }),
1188
- cronGenerator.build(context, allCrons, outputDir, { provider }),
1189
- subscriberGenerator.build(context, allSubscribers, outputDir, { provider }),
1190
- queueGenerator.build(context, allQueues, outputDir, { provider }),
1191
- topicGenerator.build(context, allTopics, outputDir, { provider }),
1192
- ]);
1193
- }
1194
-
1195
- /**
1196
- * Run any TypeScript file with secret injection.
1197
- * Does not require gkm.config.ts.
1198
- */
1199
- async function entryDevCommand(options: DevOptions): Promise<void> {
1200
- const { entry, watch = true } = options;
1201
-
1202
- if (!entry) {
1203
- throw new Error('--entry requires a file path');
1204
- }
1205
-
1206
- const entryPath = resolve(process.cwd(), entry);
1207
-
1208
- if (!existsSync(entryPath)) {
1209
- throw new Error(`Entry file not found: ${entryPath}`);
1210
- }
1211
-
1212
- // Load .env files
1213
- const defaultEnv = loadEnvFiles('.env');
1214
- if (defaultEnv.loaded.length > 0) {
1215
- logger.log(`📦 Loaded env: ${defaultEnv.loaded.join(', ')}`);
1216
- }
1217
-
1218
- // Prepare credentials (loads workspace config, secrets, injects PORT)
1219
- // Only pass explicitPort if --port was actually specified by the user
1220
- const { credentials, resolvedPort, secretsJsonPath, appName } =
1221
- await prepareEntryCredentials({
1222
- explicitPort: options.portExplicit ? options.port : undefined,
1223
- });
1224
-
1225
- if (appName) {
1226
- logger.log(`📦 App: ${appName} (port ${resolvedPort})`);
1227
- }
1228
-
1229
- logger.log(`🚀 Starting entry file: ${entry} on port ${resolvedPort}`);
1230
-
1231
- if (Object.keys(credentials).length > 1) {
1232
- logger.log(
1233
- `🔐 Loaded ${Object.keys(credentials).length - 1} secret(s) + PORT`,
1234
- );
1235
- }
1236
-
1237
- // Create wrapper entry that injects secrets before importing user's file
1238
- const wrapperDir = join(process.cwd(), '.gkm');
1239
- await mkdir(wrapperDir, { recursive: true });
1240
- const wrapperPath = join(wrapperDir, 'entry-wrapper.ts');
1241
- await createEntryWrapper(wrapperPath, entryPath, secretsJsonPath);
1242
-
1243
- // Start with tsx
1244
- const runner = new EntryRunner(wrapperPath, entryPath, watch, resolvedPort);
1245
- await runner.start();
1246
-
1247
- // Handle graceful shutdown
1248
- let isShuttingDown = false;
1249
- const shutdown = () => {
1250
- if (isShuttingDown) return;
1251
- isShuttingDown = true;
1252
-
1253
- logger.log('\n🛑 Shutting down...');
1254
- runner.stop();
1255
- process.exit(0);
1256
- };
1257
-
1258
- process.on('SIGINT', shutdown);
1259
- process.on('SIGTERM', shutdown);
1260
-
1261
- // Keep the process alive
1262
- await new Promise(() => {});
1263
- }
1264
-
1265
- /**
1266
- * Runs and watches a TypeScript entry file using tsx.
1267
- */
1268
- class EntryRunner {
1269
- private childProcess: ChildProcess | null = null;
1270
- private watcher: ReturnType<typeof chokidar.watch> | null = null;
1271
- private isRunning = false;
1272
-
1273
- constructor(
1274
- private wrapperPath: string,
1275
- private entryPath: string,
1276
- private watch: boolean,
1277
- private port: number,
1278
- ) {}
1279
-
1280
- async start(): Promise<void> {
1281
- await this.runProcess();
1282
-
1283
- if (this.watch) {
1284
- // Watch the entry file's directory for changes
1285
- const watchDir = dirname(this.entryPath);
1286
-
1287
- this.watcher = chokidar.watch(watchDir, {
1288
- ignored: /(^|[/\\])\../,
1289
- persistent: true,
1290
- ignoreInitial: true,
1291
- });
1292
-
1293
- let restartTimeout: NodeJS.Timeout | null = null;
1294
-
1295
- this.watcher.on('change', (path) => {
1296
- logger.log(`📝 File changed: ${path}`);
1297
-
1298
- // Debounce restarts
1299
- if (restartTimeout) {
1300
- clearTimeout(restartTimeout);
1301
- }
1302
-
1303
- restartTimeout = setTimeout(async () => {
1304
- logger.log('🔄 Restarting...');
1305
- await this.restart();
1306
- }, 300);
1307
- });
1308
-
1309
- logger.log(`👀 Watching for changes in: ${watchDir}`);
1310
- }
1311
- }
1312
-
1313
- private async runProcess(): Promise<void> {
1314
- // Pass PORT as environment variable
1315
- const env = { ...process.env, PORT: String(this.port) };
1316
-
1317
- this.childProcess = spawn('npx', ['tsx', this.wrapperPath], {
1318
- stdio: 'inherit',
1319
- env,
1320
- detached: true,
1321
- });
1322
-
1323
- this.isRunning = true;
1324
-
1325
- this.childProcess.on('error', (error) => {
1326
- logger.error('❌ Process error:', error);
1327
- });
1328
-
1329
- this.childProcess.on('exit', (code) => {
1330
- if (code !== null && code !== 0 && code !== 143) {
1331
- // 143 = SIGTERM
1332
- logger.error(`❌ Process exited with code ${code}`);
1333
- }
1334
- this.isRunning = false;
1335
- });
1336
-
1337
- // Give the process a moment to start
1338
- await new Promise((resolve) => setTimeout(resolve, 500));
1339
-
1340
- if (this.isRunning) {
1341
- logger.log('');
1342
- logger.log(
1343
- ` \x1b[32m✓ Ready\x1b[0m at \x1b[36mhttp://localhost:${this.port}\x1b[0m`,
1344
- );
1345
- logger.log('');
1346
- }
1347
- }
1348
-
1349
- async restart(): Promise<void> {
1350
- this.stopProcess();
1351
- await new Promise((resolve) => setTimeout(resolve, 500));
1352
- await this.runProcess();
1353
- }
1354
-
1355
- stop(): void {
1356
- this.watcher?.close();
1357
- this.stopProcess();
1358
- }
1359
-
1360
- private stopProcess(): void {
1361
- if (this.childProcess && this.isRunning) {
1362
- const pid = this.childProcess.pid;
1363
- if (pid) {
1364
- try {
1365
- process.kill(-pid, 'SIGTERM');
1366
- } catch {
1367
- try {
1368
- process.kill(pid, 'SIGTERM');
1369
- } catch {
1370
- // Process already dead
1371
- }
1372
- }
1373
- }
1374
- this.childProcess = null;
1375
- this.isRunning = false;
1376
- }
1377
- }
1378
- }
1379
-
1380
- /**
1381
- * Generate the content of the dev server entry file (server.ts).
1382
- * Uses dynamic import for createApp so Credentials are populated
1383
- * before any app modules evaluate.
1384
- * @internal Exported for testing
1385
- */
1386
- export function generateServerEntryContent(options: {
1387
- secretsJsonPath?: string;
1388
- runtime?: Runtime;
1389
- enableOpenApi?: boolean;
1390
- appImportPath?: string;
1391
- }): string {
1392
- const {
1393
- secretsJsonPath,
1394
- runtime = 'node',
1395
- enableOpenApi = false,
1396
- appImportPath = './app.js',
1397
- } = options;
1398
-
1399
- const credentialsInjection = secretsJsonPath
1400
- ? `import { existsSync, readFileSync } from 'node:fs';
1401
-
1402
- // Inject dev secrets via globalThis (must happen before app import)
1403
- const secretsPath = '${secretsJsonPath}';
1404
- if (existsSync(secretsPath)) {
1405
- const __secrets = JSON.parse(readFileSync(secretsPath, 'utf-8'));
1406
- globalThis.__gkm_credentials__ = __secrets;
1407
- Object.assign(process.env, __secrets);
1408
- }
1409
-
1410
- `
1411
- : '';
1412
-
1413
- const serveCode =
1414
- runtime === 'bun'
1415
- ? `Bun.serve({
1416
- port,
1417
- fetch: app.fetch,
1418
- });`
1419
- : `const { serve } = await import('@hono/node-server');
1420
- const server = serve({
1421
- fetch: app.fetch,
1422
- port,
1423
- });
1424
- // Inject WebSocket support if available
1425
- const injectWs = (app as any).__injectWebSocket;
1426
- if (injectWs) {
1427
- injectWs(server);
1428
- console.log('🔌 Telescope real-time updates enabled');
1429
- }`;
1430
-
1431
- return `#!/usr/bin/env node
1432
- /**
1433
- * Development server entry point
1434
- * This file is auto-generated by 'gkm dev'
1435
- */
1436
- ${credentialsInjection}
1437
- const port = process.argv.includes('--port')
1438
- ? Number.parseInt(process.argv[process.argv.indexOf('--port') + 1])
1439
- : 3000;
1440
-
1441
- // Dynamic import so Credentials are populated before env.ts evaluates
1442
- const { createApp } = await import('${appImportPath}');
1443
-
1444
- // createApp is async to support optional WebSocket setup
1445
- const { app, start } = await createApp(undefined, ${enableOpenApi});
1446
-
1447
- // Start the server
1448
- start({
1449
- port,
1450
- serve: async (app, port) => {
1451
- ${serveCode}
1452
- },
1453
- }).catch((error) => {
1454
- console.error('Failed to start server:', error);
1455
- process.exit(1);
1456
- });
1457
- `;
1458
- }
1459
-
1460
- class DevServer {
1461
- private serverProcess: ChildProcess | null = null;
1462
- private isRunning = false;
1463
- private actualPort: number;
1464
- private startTime = Date.now();
1465
-
1466
- constructor(
1467
- private provider: LegacyProvider,
1468
- private requestedPort: number,
1469
- private portExplicit: boolean,
1470
- private enableOpenApi: boolean,
1471
- private telescope: NormalizedTelescopeConfig | undefined,
1472
- private studio: NormalizedStudioConfig | undefined,
1473
- private runtime: Runtime = 'node',
1474
- private appRoot: string = process.cwd(),
1475
- private secretsJsonPath?: string,
1476
- ) {
1477
- this.actualPort = requestedPort;
1478
- }
1479
-
1480
- async start(): Promise<void> {
1481
- this.startTime = Date.now();
1482
- if (this.isRunning) {
1483
- await this.stop();
1484
- }
1485
-
1486
- // Check port availability
1487
- if (this.portExplicit) {
1488
- // Port was explicitly specified - throw if unavailable
1489
- const available = await isPortAvailable(this.requestedPort);
1490
- if (!available) {
1491
- throw new Error(
1492
- `Port ${this.requestedPort} is already in use. ` +
1493
- `Either stop the process using that port or omit -p/--port to auto-select an available port.`,
1494
- );
1495
- }
1496
- this.actualPort = this.requestedPort;
1497
- } else {
1498
- // Find an available port starting from the default
1499
- this.actualPort = await findAvailablePort(this.requestedPort);
1500
-
1501
- if (this.actualPort !== this.requestedPort) {
1502
- logger.log(
1503
- `ℹ️ Port ${this.requestedPort} was in use, using port ${this.actualPort} instead`,
1504
- );
1505
- }
1506
- }
1507
-
1508
- const serverEntryPath = join(
1509
- this.appRoot,
1510
- '.gkm',
1511
- this.provider,
1512
- 'server.ts',
1513
- );
1514
-
1515
- // Create server entry file
1516
- await this.createServerEntry();
1517
-
1518
- logger.log(`\n⏳ Starting server...`);
1519
-
1520
- // Start the server using tsx (TypeScript execution)
1521
- // Use detached: true so we can kill the entire process tree
1522
- this.serverProcess = spawn(
1523
- 'npx',
1524
- ['tsx', serverEntryPath, '--port', this.actualPort.toString()],
1525
- {
1526
- stdio: 'inherit',
1527
- env: { ...process.env, NODE_ENV: 'development' },
1528
- detached: true,
1529
- },
1530
- );
1531
-
1532
- this.isRunning = true;
1533
-
1534
- this.serverProcess.on('error', (error) => {
1535
- logger.error('❌ Server error:', error);
1536
- });
1537
-
1538
- this.serverProcess.on('exit', (code, signal) => {
1539
- if (code !== null && code !== 0 && signal !== 'SIGTERM') {
1540
- logger.error(`❌ Server exited with code ${code}`);
1541
- }
1542
- this.isRunning = false;
1543
- });
1544
-
1545
- // Give the server a moment to start
1546
- await new Promise((resolve) => setTimeout(resolve, 1000));
1547
-
1548
- if (this.isRunning) {
1549
- const base = `http://localhost:${this.actualPort}`;
1550
- const lines: string[] = [` Local: ${base}`];
1551
- if (this.enableOpenApi) {
1552
- lines.push(` API Docs: ${base}/__docs`);
1553
- }
1554
- if (this.telescope) {
1555
- lines.push(` Telescope: ${base}${this.telescope.path}`);
1556
- }
1557
- if (this.studio) {
1558
- lines.push(` Studio: ${base}${this.studio.path}`);
1559
- }
1560
-
1561
- const maxLen = Math.max(...lines.map((l) => l.length));
1562
- const pad = (s: string) => s.padEnd(maxLen);
1563
- const border = '─'.repeat(maxLen + 2);
1564
-
1565
- logger.log('');
1566
- logger.log(
1567
- ` \x1b[32m✓ Ready\x1b[0m in ${((Date.now() - this.startTime) / 1000).toFixed(1)}s`,
1568
- );
1569
- logger.log('');
1570
- logger.log(` ┌${border}┐`);
1571
- for (const line of lines) {
1572
- logger.log(` │ ${pad(line)} │`);
1573
- }
1574
- logger.log(` └${border}┘`);
1575
- logger.log('');
1576
- }
1577
- }
1578
-
1579
- async stop(): Promise<void> {
1580
- const port = this.actualPort;
1581
-
1582
- if (this.serverProcess && this.isRunning) {
1583
- const pid = this.serverProcess.pid;
1584
-
1585
- // Use SIGKILL directly since the server ignores SIGTERM
1586
- if (pid) {
1587
- try {
1588
- process.kill(-pid, 'SIGKILL');
1589
- } catch {
1590
- try {
1591
- process.kill(pid, 'SIGKILL');
1592
- } catch {
1593
- // Process might already be dead
1594
- }
1595
- }
1596
- }
1597
-
1598
- this.serverProcess = null;
1599
- this.isRunning = false;
1600
- }
1601
-
1602
- // Also kill any processes still holding the port
1603
- this.killProcessesOnPort(port);
1604
- }
1605
-
1606
- private killProcessesOnPort(port: number): void {
1607
- try {
1608
- // Use lsof to find PIDs on the port and kill them with -9
1609
- execSync(`lsof -ti tcp:${port} | xargs kill -9 2>/dev/null || true`, {
1610
- stdio: 'ignore',
1611
- });
1612
- } catch {
1613
- // Ignore errors - port may already be free
1614
- }
1615
- }
1616
-
1617
- async restart(): Promise<void> {
1618
- const portToReuse = this.actualPort;
1619
- await this.stop();
1620
-
1621
- // Wait for port to be released (up to 3 seconds)
1622
- let attempts = 0;
1623
- while (attempts < 30) {
1624
- if (await isPortAvailable(portToReuse)) {
1625
- break;
1626
- }
1627
- await new Promise((resolve) => setTimeout(resolve, 100));
1628
- attempts++;
1629
- }
1630
-
1631
- // Force reuse the same port
1632
- this.requestedPort = portToReuse;
1633
- await this.start();
1634
- }
1635
-
1636
- private async createServerEntry(): Promise<void> {
1637
- const { writeFile: fsWriteFile } = await import('node:fs/promises');
1638
-
1639
- const serverPath = join(this.appRoot, '.gkm', this.provider, 'server.ts');
1640
-
1641
- const content = generateServerEntryContent({
1642
- secretsJsonPath: this.secretsJsonPath,
1643
- runtime: this.runtime,
1644
- enableOpenApi: this.enableOpenApi,
1645
- });
1646
-
1647
- await fsWriteFile(serverPath, content);
1648
- }
1649
- }