@geekmidas/cli 0.6.2 → 0.8.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 (274) hide show
  1. package/dist/config.d.cts +1 -1
  2. package/dist/config.d.mts +1 -1
  3. package/dist/index.cjs +2583 -34
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.mjs +2578 -29
  6. package/dist/index.mjs.map +1 -1
  7. package/dist/openapi--vOy9mo4.mjs +978 -0
  8. package/dist/openapi--vOy9mo4.mjs.map +1 -0
  9. package/dist/openapi-CHhTPief.cjs +1014 -0
  10. package/dist/openapi-CHhTPief.cjs.map +1 -0
  11. package/dist/{openapi-react-query-_-B3s8v_.mjs → openapi-react-query-CcciaVu5.mjs} +1 -1
  12. package/dist/{openapi-react-query-_-B3s8v_.mjs.map → openapi-react-query-CcciaVu5.mjs.map} +1 -1
  13. package/dist/{openapi-react-query-Cp-w8_05.cjs → openapi-react-query-o5iMi8tz.cjs} +1 -1
  14. package/dist/{openapi-react-query-Cp-w8_05.cjs.map → openapi-react-query-o5iMi8tz.cjs.map} +1 -1
  15. package/dist/openapi-react-query.cjs +1 -1
  16. package/dist/openapi-react-query.mjs +1 -1
  17. package/dist/openapi.cjs +1 -4
  18. package/dist/openapi.d.cts +1 -1
  19. package/dist/openapi.d.mts +1 -1
  20. package/dist/openapi.mjs +1 -4
  21. package/dist/{types-KmjzMgu8.d.cts → types-DXgiA1sF.d.mts} +58 -53
  22. package/dist/{types-Bi7VzDUZ.d.mts → types-b-vwGpqc.d.cts} +58 -53
  23. package/package.json +6 -6
  24. package/src/__tests__/EndpointGenerator.hooks.spec.ts +204 -0
  25. package/src/__tests__/normalizeHooksConfig.spec.ts +63 -0
  26. package/src/build/index.ts +8 -1
  27. package/src/build/types.ts +19 -0
  28. package/src/dev/index.ts +153 -37
  29. package/src/generators/EndpointGenerator.ts +72 -5
  30. package/src/generators/__tests__/EndpointGenerator.spec.ts +1 -1
  31. package/src/init/generators/config.ts +6 -1
  32. package/src/init/generators/package.ts +5 -1
  33. package/src/init/index.ts +9 -1
  34. package/src/init/templates/api.ts +31 -0
  35. package/src/init/templates/index.ts +1 -0
  36. package/src/init/templates/minimal.ts +83 -0
  37. package/src/types.ts +57 -0
  38. package/tsdown.config.ts +6 -0
  39. package/dist/CronGenerator-CCRYptuT.mjs +0 -55
  40. package/dist/CronGenerator-CCRYptuT.mjs.map +0 -1
  41. package/dist/CronGenerator-D4TWXQbh.cjs +0 -61
  42. package/dist/CronGenerator-D4TWXQbh.cjs.map +0 -1
  43. package/dist/CronGenerator-DWS3CCZt.d.cts +0 -14
  44. package/dist/CronGenerator-DZjdkEjI.d.mts +0 -14
  45. package/dist/EndpointGenerator-DGivkPLT.mjs +0 -335
  46. package/dist/EndpointGenerator-DGivkPLT.mjs.map +0 -1
  47. package/dist/EndpointGenerator-Dh7kMtuL.d.mts +0 -19
  48. package/dist/EndpointGenerator-npWEDoK2.cjs +0 -341
  49. package/dist/EndpointGenerator-npWEDoK2.cjs.map +0 -1
  50. package/dist/EndpointGenerator-zBsie_7s.d.cts +0 -19
  51. package/dist/FunctionGenerator-BmDHo27U.d.mts +0 -14
  52. package/dist/FunctionGenerator-CVk0h8tO.mjs +0 -54
  53. package/dist/FunctionGenerator-CVk0h8tO.mjs.map +0 -1
  54. package/dist/FunctionGenerator-DXjXBxUd.d.cts +0 -14
  55. package/dist/FunctionGenerator-DYTnyr4c.cjs +0 -60
  56. package/dist/FunctionGenerator-DYTnyr4c.cjs.map +0 -1
  57. package/dist/Generator-BGY-2dgI.d.cts +0 -27
  58. package/dist/Generator-CDt4pB3W.mjs +0 -41
  59. package/dist/Generator-CDt4pB3W.mjs.map +0 -1
  60. package/dist/Generator-CLVplqm2.cjs +0 -47
  61. package/dist/Generator-CLVplqm2.cjs.map +0 -1
  62. package/dist/Generator-yi9DH5TN.d.mts +0 -27
  63. package/dist/OpenApiTsGenerator-BVS4pOH7.mjs +0 -495
  64. package/dist/OpenApiTsGenerator-BVS4pOH7.mjs.map +0 -1
  65. package/dist/OpenApiTsGenerator-gPIIyppX.cjs +0 -501
  66. package/dist/OpenApiTsGenerator-gPIIyppX.cjs.map +0 -1
  67. package/dist/SubscriberGenerator-Bb-z3Kvx.d.cts +0 -15
  68. package/dist/SubscriberGenerator-CwsXqCpS.d.mts +0 -15
  69. package/dist/SubscriberGenerator-DABaJXML.mjs +0 -200
  70. package/dist/SubscriberGenerator-DABaJXML.mjs.map +0 -1
  71. package/dist/SubscriberGenerator-D_zpNGFr.cjs +0 -206
  72. package/dist/SubscriberGenerator-D_zpNGFr.cjs.map +0 -1
  73. package/dist/api-Bp5TIl1R.mjs +0 -167
  74. package/dist/api-Bp5TIl1R.mjs.map +0 -1
  75. package/dist/api-D4W9-tdZ.cjs +0 -173
  76. package/dist/api-D4W9-tdZ.cjs.map +0 -1
  77. package/dist/build/index.cjs +0 -15
  78. package/dist/build/index.d.cts +0 -7
  79. package/dist/build/index.d.mts +0 -7
  80. package/dist/build/index.mjs +0 -15
  81. package/dist/build/manifests.cjs +0 -4
  82. package/dist/build/manifests.d.cts +0 -13
  83. package/dist/build/manifests.d.mts +0 -13
  84. package/dist/build/manifests.mjs +0 -3
  85. package/dist/build/providerResolver.cjs +0 -5
  86. package/dist/build/providerResolver.d.cts +0 -23
  87. package/dist/build/providerResolver.d.mts +0 -23
  88. package/dist/build/providerResolver.mjs +0 -3
  89. package/dist/build/types.cjs +0 -0
  90. package/dist/build/types.d.cts +0 -3
  91. package/dist/build/types.d.mts +0 -3
  92. package/dist/build/types.mjs +0 -0
  93. package/dist/build-Cu6Mi0Lf.mjs +0 -87
  94. package/dist/build-Cu6Mi0Lf.mjs.map +0 -1
  95. package/dist/build-wmt8ZcmA.cjs +0 -93
  96. package/dist/build-wmt8ZcmA.cjs.map +0 -1
  97. package/dist/config-BP1IZynR.cjs +0 -168
  98. package/dist/config-BP1IZynR.cjs.map +0 -1
  99. package/dist/config-CIzRhm_D.d.mts +0 -11
  100. package/dist/config-CvehIYsb.d.cts +0 -11
  101. package/dist/config-UCK12Lrr.mjs +0 -162
  102. package/dist/config-UCK12Lrr.mjs.map +0 -1
  103. package/dist/dev/index.cjs +0 -17
  104. package/dist/dev/index.d.cts +0 -36
  105. package/dist/dev/index.d.mts +0 -36
  106. package/dist/dev/index.mjs +0 -13
  107. package/dist/dev-BBPWSllq.mjs +0 -348
  108. package/dist/dev-BBPWSllq.mjs.map +0 -1
  109. package/dist/dev-C2lCgE53.cjs +0 -378
  110. package/dist/dev-C2lCgE53.cjs.map +0 -1
  111. package/dist/docker-2-ipZDOJ.cjs +0 -119
  112. package/dist/docker-2-ipZDOJ.cjs.map +0 -1
  113. package/dist/docker-31GNwU3F.mjs +0 -113
  114. package/dist/docker-31GNwU3F.mjs.map +0 -1
  115. package/dist/env-CQ3hXAAW.d.mts +0 -11
  116. package/dist/env-CS0jvg7k.cjs +0 -144
  117. package/dist/env-CS0jvg7k.cjs.map +0 -1
  118. package/dist/env-D4YFgMqo.d.cts +0 -11
  119. package/dist/env-DEeVOvVu.mjs +0 -138
  120. package/dist/env-DEeVOvVu.mjs.map +0 -1
  121. package/dist/generators/CronGenerator.cjs +0 -4
  122. package/dist/generators/CronGenerator.d.cts +0 -5
  123. package/dist/generators/CronGenerator.d.mts +0 -5
  124. package/dist/generators/CronGenerator.mjs +0 -4
  125. package/dist/generators/EndpointGenerator.cjs +0 -4
  126. package/dist/generators/EndpointGenerator.d.cts +0 -5
  127. package/dist/generators/EndpointGenerator.d.mts +0 -5
  128. package/dist/generators/EndpointGenerator.mjs +0 -4
  129. package/dist/generators/FunctionGenerator.cjs +0 -4
  130. package/dist/generators/FunctionGenerator.d.cts +0 -5
  131. package/dist/generators/FunctionGenerator.d.mts +0 -5
  132. package/dist/generators/FunctionGenerator.mjs +0 -4
  133. package/dist/generators/Generator.cjs +0 -3
  134. package/dist/generators/Generator.d.cts +0 -4
  135. package/dist/generators/Generator.d.mts +0 -4
  136. package/dist/generators/Generator.mjs +0 -3
  137. package/dist/generators/OpenApiTsGenerator.cjs +0 -3
  138. package/dist/generators/OpenApiTsGenerator.d.cts +0 -44
  139. package/dist/generators/OpenApiTsGenerator.d.mts +0 -44
  140. package/dist/generators/OpenApiTsGenerator.mjs +0 -3
  141. package/dist/generators/SubscriberGenerator.cjs +0 -4
  142. package/dist/generators/SubscriberGenerator.d.cts +0 -5
  143. package/dist/generators/SubscriberGenerator.d.mts +0 -5
  144. package/dist/generators/SubscriberGenerator.mjs +0 -4
  145. package/dist/generators/index.cjs +0 -12
  146. package/dist/generators/index.d.cts +0 -8
  147. package/dist/generators/index.d.mts +0 -8
  148. package/dist/generators/index.mjs +0 -8
  149. package/dist/generators-3IemvCLk.cjs +0 -0
  150. package/dist/generators-FNpdfN6J.mjs +0 -0
  151. package/dist/index-DG6xNQMH.d.cts +0 -81
  152. package/dist/index-DZgrOOOW.d.mts +0 -81
  153. package/dist/init/generators/config.cjs +0 -3
  154. package/dist/init/generators/config.d.cts +0 -3
  155. package/dist/init/generators/config.d.mts +0 -3
  156. package/dist/init/generators/config.mjs +0 -3
  157. package/dist/init/generators/docker.cjs +0 -3
  158. package/dist/init/generators/docker.d.cts +0 -11
  159. package/dist/init/generators/docker.d.mts +0 -11
  160. package/dist/init/generators/docker.mjs +0 -3
  161. package/dist/init/generators/env.cjs +0 -3
  162. package/dist/init/generators/env.d.cts +0 -3
  163. package/dist/init/generators/env.d.mts +0 -3
  164. package/dist/init/generators/env.mjs +0 -3
  165. package/dist/init/generators/index.cjs +0 -14
  166. package/dist/init/generators/index.d.cts +0 -6
  167. package/dist/init/generators/index.d.mts +0 -6
  168. package/dist/init/generators/index.mjs +0 -11
  169. package/dist/init/generators/models.cjs +0 -3
  170. package/dist/init/generators/models.d.cts +0 -11
  171. package/dist/init/generators/models.d.mts +0 -11
  172. package/dist/init/generators/models.mjs +0 -3
  173. package/dist/init/generators/monorepo.cjs +0 -3
  174. package/dist/init/generators/monorepo.d.cts +0 -11
  175. package/dist/init/generators/monorepo.d.mts +0 -11
  176. package/dist/init/generators/monorepo.mjs +0 -3
  177. package/dist/init/generators/package.cjs +0 -8
  178. package/dist/init/generators/package.d.cts +0 -3
  179. package/dist/init/generators/package.d.mts +0 -3
  180. package/dist/init/generators/package.mjs +0 -8
  181. package/dist/init/generators/source.cjs +0 -3
  182. package/dist/init/generators/source.d.cts +0 -3
  183. package/dist/init/generators/source.d.mts +0 -3
  184. package/dist/init/generators/source.mjs +0 -3
  185. package/dist/init/index.cjs +0 -16
  186. package/dist/init/index.d.cts +0 -17
  187. package/dist/init/index.d.mts +0 -17
  188. package/dist/init/index.mjs +0 -16
  189. package/dist/init/templates/api.cjs +0 -3
  190. package/dist/init/templates/api.d.cts +0 -7
  191. package/dist/init/templates/api.d.mts +0 -7
  192. package/dist/init/templates/api.mjs +0 -3
  193. package/dist/init/templates/index.cjs +0 -12
  194. package/dist/init/templates/index.d.cts +0 -2
  195. package/dist/init/templates/index.d.mts +0 -2
  196. package/dist/init/templates/index.mjs +0 -7
  197. package/dist/init/templates/minimal.cjs +0 -3
  198. package/dist/init/templates/minimal.d.cts +0 -7
  199. package/dist/init/templates/minimal.d.mts +0 -7
  200. package/dist/init/templates/minimal.mjs +0 -3
  201. package/dist/init/templates/serverless.cjs +0 -3
  202. package/dist/init/templates/serverless.d.cts +0 -7
  203. package/dist/init/templates/serverless.d.mts +0 -7
  204. package/dist/init/templates/serverless.mjs +0 -3
  205. package/dist/init/templates/worker.cjs +0 -3
  206. package/dist/init/templates/worker.d.cts +0 -7
  207. package/dist/init/templates/worker.d.mts +0 -7
  208. package/dist/init/templates/worker.mjs +0 -3
  209. package/dist/init/utils.cjs +0 -7
  210. package/dist/init/utils.d.cts +0 -25
  211. package/dist/init/utils.d.mts +0 -25
  212. package/dist/init/utils.mjs +0 -3
  213. package/dist/init-BMA7xi8r.mjs +0 -161
  214. package/dist/init-BMA7xi8r.mjs.map +0 -1
  215. package/dist/init-D-7WEk-b.cjs +0 -167
  216. package/dist/init-D-7WEk-b.cjs.map +0 -1
  217. package/dist/manifests-BNKG6AXf.mjs +0 -68
  218. package/dist/manifests-BNKG6AXf.mjs.map +0 -1
  219. package/dist/manifests-D13Ej8AE.cjs +0 -80
  220. package/dist/manifests-D13Ej8AE.cjs.map +0 -1
  221. package/dist/minimal-BkyASH_C.mjs +0 -93
  222. package/dist/minimal-BkyASH_C.mjs.map +0 -1
  223. package/dist/minimal-CSFggzdH.cjs +0 -99
  224. package/dist/minimal-CSFggzdH.cjs.map +0 -1
  225. package/dist/models-BWlDfviw.mjs +0 -115
  226. package/dist/models-BWlDfviw.mjs.map +0 -1
  227. package/dist/models-BapGSoHC.cjs +0 -121
  228. package/dist/models-BapGSoHC.cjs.map +0 -1
  229. package/dist/monorepo-BBOWhkcd.mjs +0 -184
  230. package/dist/monorepo-BBOWhkcd.mjs.map +0 -1
  231. package/dist/monorepo-CFtxHeDh.cjs +0 -190
  232. package/dist/monorepo-CFtxHeDh.cjs.map +0 -1
  233. package/dist/openapi-DA9RkPJl.mjs +0 -74
  234. package/dist/openapi-DA9RkPJl.mjs.map +0 -1
  235. package/dist/openapi-DZH6RQHk.cjs +0 -98
  236. package/dist/openapi-DZH6RQHk.cjs.map +0 -1
  237. package/dist/package-6h-7QfJZ.d.cts +0 -11
  238. package/dist/package-BCe_KvGv.d.mts +0 -11
  239. package/dist/package-C3If80n1.mjs +0 -57
  240. package/dist/package-C3If80n1.mjs.map +0 -1
  241. package/dist/package-Dk8IMBOB.cjs +0 -62
  242. package/dist/package-Dk8IMBOB.cjs.map +0 -1
  243. package/dist/providerResolver-DEVKngbC.mjs +0 -96
  244. package/dist/providerResolver-DEVKngbC.mjs.map +0 -1
  245. package/dist/providerResolver-DOTbN9jo.cjs +0 -114
  246. package/dist/providerResolver-DOTbN9jo.cjs.map +0 -1
  247. package/dist/serverless-AGOS-l3G.cjs +0 -119
  248. package/dist/serverless-AGOS-l3G.cjs.map +0 -1
  249. package/dist/serverless-D5HjJByU.mjs +0 -113
  250. package/dist/serverless-D5HjJByU.mjs.map +0 -1
  251. package/dist/source-C1cyfHcF.cjs +0 -17
  252. package/dist/source-C1cyfHcF.cjs.map +0 -1
  253. package/dist/source-C3LiNUV9.d.mts +0 -11
  254. package/dist/source-CkQHBpwu.mjs +0 -11
  255. package/dist/source-CkQHBpwu.mjs.map +0 -1
  256. package/dist/source-Dtcjbokc.d.cts +0 -11
  257. package/dist/templates-C0EMmhwb.mjs +0 -88
  258. package/dist/templates-C0EMmhwb.mjs.map +0 -1
  259. package/dist/templates-CbgQ9dw0.cjs +0 -123
  260. package/dist/templates-CbgQ9dw0.cjs.map +0 -1
  261. package/dist/types-D2xYkOal.d.mts +0 -51
  262. package/dist/types-DA-r8HWZ.d.cts +0 -51
  263. package/dist/types.cjs +0 -0
  264. package/dist/types.d.cts +0 -2
  265. package/dist/types.d.mts +0 -2
  266. package/dist/types.mjs +0 -0
  267. package/dist/utils-CKEzCxc1.mjs +0 -69
  268. package/dist/utils-CKEzCxc1.mjs.map +0 -1
  269. package/dist/utils-DSdN2MTt.cjs +0 -99
  270. package/dist/utils-DSdN2MTt.cjs.map +0 -1
  271. package/dist/worker-CGhlqNH-.cjs +0 -156
  272. package/dist/worker-CGhlqNH-.cjs.map +0 -1
  273. package/dist/worker-CiP420As.mjs +0 -150
  274. package/dist/worker-CiP420As.mjs.map +0 -1
package/dist/index.cjs CHANGED
@@ -1,38 +1,26 @@
1
1
  #!/usr/bin/env -S npx tsx
2
2
  const require_chunk = require('./chunk-CUT6urMc.cjs');
3
- require('./config-CFls09Ey.cjs');
4
- require('./providerResolver-DOTbN9jo.cjs');
5
- require('./Generator-CLVplqm2.cjs');
6
- require('./CronGenerator-D4TWXQbh.cjs');
7
- require('./EndpointGenerator-npWEDoK2.cjs');
8
- require('./FunctionGenerator-DYTnyr4c.cjs');
9
- require('./SubscriberGenerator-D_zpNGFr.cjs');
10
- require('./generators-3IemvCLk.cjs');
11
- require('./OpenApiTsGenerator-gPIIyppX.cjs');
12
- const require_openapi = require('./openapi-DZH6RQHk.cjs');
13
- const require_dev = require('./dev-C2lCgE53.cjs');
14
- require('./manifests-D13Ej8AE.cjs');
15
- const require_build = require('./build-wmt8ZcmA.cjs');
16
- require('./config-BP1IZynR.cjs');
17
- require('./docker-2-ipZDOJ.cjs');
18
- require('./env-CS0jvg7k.cjs');
19
- require('./models-BapGSoHC.cjs');
20
- require('./monorepo-CFtxHeDh.cjs');
21
- require('./api-D4W9-tdZ.cjs');
22
- require('./minimal-CSFggzdH.cjs');
23
- require('./serverless-AGOS-l3G.cjs');
24
- require('./worker-CGhlqNH-.cjs');
25
- require('./templates-CbgQ9dw0.cjs');
26
- require('./package-Dk8IMBOB.cjs');
27
- require('./source-C1cyfHcF.cjs');
28
- require('./utils-DSdN2MTt.cjs');
29
- const require_init = require('./init-D-7WEk-b.cjs');
30
- const require_openapi_react_query = require('./openapi-react-query-Cp-w8_05.cjs');
3
+ const require_config = require('./config-CFls09Ey.cjs');
4
+ const require_openapi = require('./openapi-CHhTPief.cjs');
5
+ const require_openapi_react_query = require('./openapi-react-query-o5iMi8tz.cjs');
6
+ const path = require_chunk.__toESM(require("path"));
31
7
  const commander = require_chunk.__toESM(require("commander"));
8
+ const node_fs_promises = require_chunk.__toESM(require("node:fs/promises"));
9
+ const node_path = require_chunk.__toESM(require("node:path"));
10
+ const node_child_process = require_chunk.__toESM(require("node:child_process"));
11
+ const node_fs = require_chunk.__toESM(require("node:fs"));
12
+ const node_net = require_chunk.__toESM(require("node:net"));
13
+ const chokidar = require_chunk.__toESM(require("chokidar"));
14
+ const dotenv = require_chunk.__toESM(require("dotenv"));
15
+ const fast_glob = require_chunk.__toESM(require("fast-glob"));
16
+ const __geekmidas_constructs_crons = require_chunk.__toESM(require("@geekmidas/constructs/crons"));
17
+ const __geekmidas_constructs_functions = require_chunk.__toESM(require("@geekmidas/constructs/functions"));
18
+ const __geekmidas_constructs_subscribers = require_chunk.__toESM(require("@geekmidas/constructs/subscribers"));
19
+ const prompts = require_chunk.__toESM(require("prompts"));
32
20
 
33
21
  //#region package.json
34
22
  var name = "@geekmidas/cli";
35
- var version = "0.6.2";
23
+ var version = "0.8.0";
36
24
  var description = "CLI tools for building Lambda handlers, server applications, and generating OpenAPI specs";
37
25
  var private$1 = false;
38
26
  var type = "module";
@@ -112,6 +100,2567 @@ var package_default = {
112
100
  peerDependenciesMeta
113
101
  };
114
102
 
103
+ //#endregion
104
+ //#region src/build/providerResolver.ts
105
+ /**
106
+ * Resolves provider configuration from the new simplified system
107
+ * to the internal legacy format for backward compatibility
108
+ */
109
+ function resolveProviders(config, options) {
110
+ const providers = [];
111
+ let enableOpenApi = options.enableOpenApi || false;
112
+ if (options.providers) return {
113
+ providers: options.providers,
114
+ enableOpenApi
115
+ };
116
+ if (options.provider) {
117
+ const resolvedProviders = resolveMainProvider(options.provider, config.providers);
118
+ providers.push(...resolvedProviders.providers);
119
+ enableOpenApi = resolvedProviders.enableOpenApi || enableOpenApi;
120
+ } else if (config.providers) {
121
+ const resolvedProviders = resolveAllConfiguredProviders(config.providers);
122
+ providers.push(...resolvedProviders.providers);
123
+ enableOpenApi = resolvedProviders.enableOpenApi || enableOpenApi;
124
+ } else providers.push("aws-apigatewayv2", "aws-lambda");
125
+ return {
126
+ providers: [...new Set(providers)],
127
+ enableOpenApi
128
+ };
129
+ }
130
+ function resolveMainProvider(mainProvider, providersConfig) {
131
+ const providers = [];
132
+ let enableOpenApi = false;
133
+ if (mainProvider === "aws") {
134
+ const awsConfig = providersConfig?.aws;
135
+ if (awsConfig?.apiGateway) {
136
+ if (isEnabled(awsConfig.apiGateway.v1)) providers.push("aws-apigatewayv1");
137
+ if (isEnabled(awsConfig.apiGateway.v2)) providers.push("aws-apigatewayv2");
138
+ } else providers.push("aws-apigatewayv2");
139
+ if (awsConfig?.lambda) {
140
+ if (isEnabled(awsConfig.lambda.functions) || isEnabled(awsConfig.lambda.crons)) providers.push("aws-lambda");
141
+ } else providers.push("aws-lambda");
142
+ } else if (mainProvider === "server") {
143
+ providers.push("server");
144
+ const serverConfig = providersConfig?.server;
145
+ if (typeof serverConfig === "object" && serverConfig?.enableOpenApi) enableOpenApi = true;
146
+ }
147
+ return {
148
+ providers,
149
+ enableOpenApi
150
+ };
151
+ }
152
+ function resolveAllConfiguredProviders(providersConfig) {
153
+ const providers = [];
154
+ let enableOpenApi = false;
155
+ if (providersConfig.aws) {
156
+ const awsProviders = resolveMainProvider("aws", providersConfig);
157
+ providers.push(...awsProviders.providers);
158
+ }
159
+ if (providersConfig.server && isEnabled(providersConfig.server)) {
160
+ providers.push("server");
161
+ if (typeof providersConfig.server === "object" && providersConfig.server.enableOpenApi) enableOpenApi = true;
162
+ }
163
+ return {
164
+ providers,
165
+ enableOpenApi
166
+ };
167
+ }
168
+ function isEnabled(config) {
169
+ if (config === void 0) return false;
170
+ if (typeof config === "boolean") return config;
171
+ return config.enabled !== false;
172
+ }
173
+
174
+ //#endregion
175
+ //#region src/generators/CronGenerator.ts
176
+ var CronGenerator = class extends require_openapi.ConstructGenerator {
177
+ async build(context, constructs, outputDir, options) {
178
+ const provider = options?.provider || "aws-lambda";
179
+ const logger$3 = console;
180
+ const cronInfos = [];
181
+ if (constructs.length === 0 || provider !== "aws-lambda") return cronInfos;
182
+ const cronsDir = (0, node_path.join)(outputDir, "crons");
183
+ await (0, node_fs_promises.mkdir)(cronsDir, { recursive: true });
184
+ for (const { key, construct, path: path$1 } of constructs) {
185
+ const handlerFile = await this.generateCronHandler(cronsDir, path$1.relative, key, context);
186
+ cronInfos.push({
187
+ name: key,
188
+ handler: (0, node_path.relative)(process.cwd(), handlerFile).replace(/\.ts$/, ".handler"),
189
+ schedule: construct.schedule || "rate(1 hour)",
190
+ timeout: construct.timeout,
191
+ memorySize: construct.memorySize,
192
+ environment: await construct.getEnvironment()
193
+ });
194
+ logger$3.log(`Generated cron handler: ${key}`);
195
+ }
196
+ return cronInfos;
197
+ }
198
+ isConstruct(value) {
199
+ return __geekmidas_constructs_crons.Cron.isCron(value);
200
+ }
201
+ async generateCronHandler(outputDir, sourceFile, exportName, context) {
202
+ const handlerFileName = `${exportName}.ts`;
203
+ const handlerPath = (0, node_path.join)(outputDir, handlerFileName);
204
+ const relativePath = (0, node_path.relative)((0, node_path.dirname)(handlerPath), sourceFile);
205
+ const importPath = relativePath.replace(/\.ts$/, ".js");
206
+ const relativeEnvParserPath = (0, node_path.relative)((0, node_path.dirname)(handlerPath), context.envParserPath);
207
+ const relativeLoggerPath = (0, node_path.relative)((0, node_path.dirname)(handlerPath), context.loggerPath);
208
+ const content = `import { AWSScheduledFunction } from '@geekmidas/constructs/crons';
209
+ import { ${exportName} } from '${importPath}';
210
+ import ${context.envParserImportPattern} from '${relativeEnvParserPath}';
211
+ import ${context.loggerImportPattern} from '${relativeLoggerPath}';
212
+
213
+ const adapter = new AWSScheduledFunction(envParser, ${exportName});
214
+
215
+ export const handler = adapter.handler;
216
+ `;
217
+ await (0, node_fs_promises.writeFile)(handlerPath, content);
218
+ return handlerPath;
219
+ }
220
+ };
221
+
222
+ //#endregion
223
+ //#region src/generators/FunctionGenerator.ts
224
+ var FunctionGenerator = class extends require_openapi.ConstructGenerator {
225
+ isConstruct(value) {
226
+ return __geekmidas_constructs_functions.Function.isFunction(value);
227
+ }
228
+ async build(context, constructs, outputDir, options) {
229
+ const provider = options?.provider || "aws-lambda";
230
+ const logger$3 = console;
231
+ const functionInfos = [];
232
+ if (constructs.length === 0 || provider !== "aws-lambda") return functionInfos;
233
+ const functionsDir = (0, node_path.join)(outputDir, "functions");
234
+ await (0, node_fs_promises.mkdir)(functionsDir, { recursive: true });
235
+ for (const { key, construct, path: path$1 } of constructs) {
236
+ const handlerFile = await this.generateFunctionHandler(functionsDir, path$1.relative, key, context);
237
+ functionInfos.push({
238
+ name: key,
239
+ handler: (0, node_path.relative)(process.cwd(), handlerFile).replace(/\.ts$/, ".handler"),
240
+ timeout: construct.timeout,
241
+ memorySize: construct.memorySize,
242
+ environment: await construct.getEnvironment()
243
+ });
244
+ logger$3.log(`Generated function handler: ${key}`);
245
+ }
246
+ return functionInfos;
247
+ }
248
+ async generateFunctionHandler(outputDir, sourceFile, exportName, context) {
249
+ const handlerFileName = `${exportName}.ts`;
250
+ const handlerPath = (0, node_path.join)(outputDir, handlerFileName);
251
+ const relativePath = (0, node_path.relative)((0, node_path.dirname)(handlerPath), sourceFile);
252
+ const importPath = relativePath.replace(/\.ts$/, ".js");
253
+ const relativeEnvParserPath = (0, node_path.relative)((0, node_path.dirname)(handlerPath), context.envParserPath);
254
+ const relativeLoggerPath = (0, node_path.relative)((0, node_path.dirname)(handlerPath), context.loggerPath);
255
+ const content = `import { AWSLambdaFunction } from '@geekmidas/constructs/functions';
256
+ import { ${exportName} } from '${importPath}';
257
+ import ${context.envParserImportPattern} from '${relativeEnvParserPath}';
258
+ import ${context.loggerImportPattern} from '${relativeLoggerPath}';
259
+
260
+ const adapter = new AWSLambdaFunction(envParser, ${exportName});
261
+
262
+ export const handler = adapter.handler;
263
+ `;
264
+ await (0, node_fs_promises.writeFile)(handlerPath, content);
265
+ return handlerPath;
266
+ }
267
+ };
268
+
269
+ //#endregion
270
+ //#region src/generators/SubscriberGenerator.ts
271
+ var SubscriberGenerator = class extends require_openapi.ConstructGenerator {
272
+ isConstruct(value) {
273
+ return __geekmidas_constructs_subscribers.Subscriber.isSubscriber(value);
274
+ }
275
+ async build(context, constructs, outputDir, options) {
276
+ const provider = options?.provider || "aws-lambda";
277
+ const logger$3 = console;
278
+ const subscriberInfos = [];
279
+ if (provider === "server") {
280
+ await this.generateServerSubscribersFile(outputDir, constructs);
281
+ logger$3.log(`Generated server subscribers file with ${constructs.length} subscribers (polling mode)`);
282
+ return subscriberInfos;
283
+ }
284
+ if (constructs.length === 0) return subscriberInfos;
285
+ if (provider !== "aws-lambda") return subscriberInfos;
286
+ const subscribersDir = (0, node_path.join)(outputDir, "subscribers");
287
+ await (0, node_fs_promises.mkdir)(subscribersDir, { recursive: true });
288
+ for (const { key, construct, path: path$1 } of constructs) {
289
+ const handlerFile = await this.generateSubscriberHandler(subscribersDir, path$1.relative, key, construct, context);
290
+ subscriberInfos.push({
291
+ name: key,
292
+ handler: (0, node_path.relative)(process.cwd(), handlerFile).replace(/\.ts$/, ".handler"),
293
+ subscribedEvents: construct.subscribedEvents || [],
294
+ timeout: construct.timeout,
295
+ memorySize: construct.memorySize,
296
+ environment: await construct.getEnvironment()
297
+ });
298
+ logger$3.log(`Generated subscriber handler: ${key}`);
299
+ }
300
+ return subscriberInfos;
301
+ }
302
+ async generateSubscriberHandler(outputDir, sourceFile, exportName, _subscriber, context) {
303
+ const handlerFileName = `${exportName}.ts`;
304
+ const handlerPath = (0, node_path.join)(outputDir, handlerFileName);
305
+ const relativePath = (0, node_path.relative)((0, node_path.dirname)(handlerPath), sourceFile);
306
+ const importPath = relativePath.replace(/\.ts$/, ".js");
307
+ const relativeEnvParserPath = (0, node_path.relative)((0, node_path.dirname)(handlerPath), context.envParserPath);
308
+ const content = `import { AWSLambdaSubscriber } from '@geekmidas/constructs/aws';
309
+ import { ${exportName} } from '${importPath}';
310
+ import ${context.envParserImportPattern} from '${relativeEnvParserPath}';
311
+
312
+ const adapter = new AWSLambdaSubscriber(envParser, ${exportName});
313
+
314
+ export const handler = adapter.handler;
315
+ `;
316
+ await (0, node_fs_promises.writeFile)(handlerPath, content);
317
+ return handlerPath;
318
+ }
319
+ async generateServerSubscribersFile(outputDir, subscribers) {
320
+ await (0, node_fs_promises.mkdir)(outputDir, { recursive: true });
321
+ const subscribersFileName = "subscribers.ts";
322
+ const subscribersPath = (0, node_path.join)(outputDir, subscribersFileName);
323
+ const importsByFile = /* @__PURE__ */ new Map();
324
+ for (const { path: path$1, key } of subscribers) {
325
+ const relativePath = (0, node_path.relative)((0, node_path.dirname)(subscribersPath), path$1.relative);
326
+ const importPath = relativePath.replace(/\.ts$/, ".js");
327
+ if (!importsByFile.has(importPath)) importsByFile.set(importPath, []);
328
+ importsByFile.get(importPath).push(key);
329
+ }
330
+ const imports = Array.from(importsByFile.entries()).map(([importPath, exports$2]) => `import { ${exports$2.join(", ")} } from '${importPath}';`).join("\n");
331
+ const allExportNames = subscribers.map(({ key }) => key);
332
+ const content = `/**
333
+ * Generated subscribers setup
334
+ *
335
+ * ⚠️ WARNING: This is for LOCAL DEVELOPMENT ONLY
336
+ * This uses event polling which is not suitable for production.
337
+ *
338
+ * For production, use AWS Lambda with SQS/SNS event source mappings.
339
+ * Lambda automatically:
340
+ * - Scales based on queue depth
341
+ * - Handles batch processing and retries
342
+ * - Manages dead letter queues
343
+ * - Provides better cost optimization
344
+ *
345
+ * This polling implementation is useful for:
346
+ * - Local development and testing
347
+ * - Understanding event flow without Lambda deployment
348
+ *
349
+ * Supported connection strings:
350
+ * - sqs://region/account-id/queue-name (SQS queue)
351
+ * - sns://region/account-id/topic-name (SNS topic)
352
+ * - rabbitmq://host:port/queue-name (RabbitMQ)
353
+ * - basic://in-memory (In-memory for testing)
354
+ */
355
+ import type { EnvironmentParser } from '@geekmidas/envkit';
356
+ import type { Logger } from '@geekmidas/logger';
357
+ import { EventConnectionFactory, Subscriber } from '@geekmidas/events';
358
+ import type { EventConnection, EventSubscriber } from '@geekmidas/events';
359
+ import { ServiceDiscovery } from '@geekmidas/services';
360
+ ${imports}
361
+
362
+ const subscribers = [
363
+ ${allExportNames.join(",\n ")}
364
+ ];
365
+
366
+ const activeSubscribers: EventSubscriber<any>[] = [];
367
+
368
+ export async function setupSubscribers(
369
+ envParser: EnvironmentParser<any>,
370
+ logger: Logger,
371
+ ): Promise<void> {
372
+ logger.info('Setting up subscribers in polling mode (local development)');
373
+
374
+ const config = envParser.create((get) => ({
375
+ connectionString: get('EVENT_SUBSCRIBER_CONNECTION_STRING').string().optional(),
376
+ })).parse();
377
+
378
+ if (!config.connectionString) {
379
+ logger.warn('EVENT_SUBSCRIBER_CONNECTION_STRING not configured, skipping subscriber setup');
380
+ return;
381
+ }
382
+
383
+ const serviceDiscovery = ServiceDiscovery.getInstance(logger, envParser);
384
+
385
+ // Create connection once, outside the loop (more efficient)
386
+ // EventConnectionFactory automatically determines the right connection type
387
+ let connection: EventConnection;
388
+ try {
389
+ connection = await EventConnectionFactory.fromConnectionString(config.connectionString);
390
+
391
+ const connectionType = new URL(config.connectionString).protocol.replace(':', '');
392
+ logger.info({ connectionType }, 'Created shared event connection');
393
+ } catch (error) {
394
+ logger.error({ error }, 'Failed to create event connection');
395
+ return;
396
+ }
397
+
398
+ for (const subscriber of subscribers) {
399
+ try {
400
+ // Create subscriber from shared connection
401
+ const eventSubscriber = await Subscriber.fromConnection(connection);
402
+
403
+ // Register services
404
+ const services = subscriber.services.length > 0
405
+ ? await serviceDiscovery.register(subscriber.services)
406
+ : {};
407
+
408
+ // Subscribe to events
409
+ const subscribedEvents = subscriber.subscribedEvents || [];
410
+
411
+ if (subscribedEvents.length === 0) {
412
+ logger.warn({ subscriber: subscriber.constructor.name }, 'Subscriber has no subscribed events, skipping');
413
+ continue;
414
+ }
415
+
416
+ await eventSubscriber.subscribe(subscribedEvents, async (event) => {
417
+ try {
418
+ // Process single event (batch of 1)
419
+ await subscriber.handler({
420
+ events: [event],
421
+ services: services as any,
422
+ logger: subscriber.logger,
423
+ });
424
+
425
+ logger.debug({ eventType: event.type }, 'Successfully processed event');
426
+ } catch (error) {
427
+ logger.error({ error, event }, 'Failed to process event');
428
+ // Event will become visible again for retry
429
+ }
430
+ });
431
+
432
+ activeSubscribers.push(eventSubscriber);
433
+
434
+ logger.info(
435
+ {
436
+ events: subscribedEvents,
437
+ },
438
+ 'Subscriber started polling'
439
+ );
440
+ } catch (error) {
441
+ logger.error({ error, subscriber: subscriber.constructor.name }, 'Failed to setup subscriber');
442
+ }
443
+ }
444
+
445
+ // Setup graceful shutdown
446
+ const shutdown = () => {
447
+ logger.info('Stopping all subscribers');
448
+ for (const eventSubscriber of activeSubscribers) {
449
+ connection.stop();
450
+ }
451
+ };
452
+
453
+ process.on('SIGTERM', shutdown);
454
+ process.on('SIGINT', shutdown);
455
+ }
456
+ `;
457
+ await (0, node_fs_promises.writeFile)(subscribersPath, content);
458
+ return subscribersPath;
459
+ }
460
+ };
461
+
462
+ //#endregion
463
+ //#region src/dev/index.ts
464
+ const logger$2 = console;
465
+ /**
466
+ * Load environment files
467
+ * @internal Exported for testing
468
+ */
469
+ function loadEnvFiles(envConfig, cwd = process.cwd()) {
470
+ const loaded = [];
471
+ const missing = [];
472
+ const envFiles = envConfig ? Array.isArray(envConfig) ? envConfig : [envConfig] : [".env"];
473
+ for (const envFile of envFiles) {
474
+ const envPath = (0, node_path.resolve)(cwd, envFile);
475
+ if ((0, node_fs.existsSync)(envPath)) {
476
+ (0, dotenv.config)({
477
+ path: envPath,
478
+ override: true,
479
+ quiet: true
480
+ });
481
+ loaded.push(envFile);
482
+ } else if (envConfig) missing.push(envFile);
483
+ }
484
+ return {
485
+ loaded,
486
+ missing
487
+ };
488
+ }
489
+ /**
490
+ * Check if a port is available
491
+ * @internal Exported for testing
492
+ */
493
+ async function isPortAvailable(port) {
494
+ return new Promise((resolve$1) => {
495
+ const server = (0, node_net.createServer)();
496
+ server.once("error", (err) => {
497
+ if (err.code === "EADDRINUSE") resolve$1(false);
498
+ else resolve$1(false);
499
+ });
500
+ server.once("listening", () => {
501
+ server.close();
502
+ resolve$1(true);
503
+ });
504
+ server.listen(port);
505
+ });
506
+ }
507
+ /**
508
+ * Find an available port starting from the preferred port
509
+ * @internal Exported for testing
510
+ */
511
+ async function findAvailablePort(preferredPort, maxAttempts = 10) {
512
+ for (let i = 0; i < maxAttempts; i++) {
513
+ const port = preferredPort + i;
514
+ if (await isPortAvailable(port)) return port;
515
+ logger$2.log(`⚠️ Port ${port} is in use, trying ${port + 1}...`);
516
+ }
517
+ throw new Error(`Could not find an available port after trying ${maxAttempts} ports starting from ${preferredPort}`);
518
+ }
519
+ /**
520
+ * Normalize telescope configuration
521
+ * @internal Exported for testing
522
+ */
523
+ function normalizeTelescopeConfig(config) {
524
+ if (config === false) return void 0;
525
+ if (typeof config === "string") {
526
+ const { path: telescopePath, importPattern: telescopeImportPattern } = require_config.parseModuleConfig(config, "telescope");
527
+ return {
528
+ enabled: true,
529
+ telescopePath,
530
+ telescopeImportPattern,
531
+ path: "/__telescope",
532
+ ignore: [],
533
+ recordBody: true,
534
+ maxEntries: 1e3,
535
+ websocket: true
536
+ };
537
+ }
538
+ const isEnabled$1 = config === true || config === void 0 || config.enabled !== false;
539
+ if (!isEnabled$1) return void 0;
540
+ const telescopeConfig = typeof config === "object" ? config : {};
541
+ return {
542
+ enabled: true,
543
+ path: telescopeConfig.path ?? "/__telescope",
544
+ ignore: telescopeConfig.ignore ?? [],
545
+ recordBody: telescopeConfig.recordBody ?? true,
546
+ maxEntries: telescopeConfig.maxEntries ?? 1e3,
547
+ websocket: telescopeConfig.websocket ?? true
548
+ };
549
+ }
550
+ /**
551
+ * Normalize studio configuration
552
+ * @internal Exported for testing
553
+ */
554
+ function normalizeStudioConfig(config) {
555
+ if (config === false) return void 0;
556
+ if (typeof config === "string") {
557
+ const { path: studioPath, importPattern: studioImportPattern } = require_config.parseModuleConfig(config, "studio");
558
+ return {
559
+ enabled: true,
560
+ studioPath,
561
+ studioImportPattern,
562
+ path: "/__studio",
563
+ schema: "public"
564
+ };
565
+ }
566
+ const isEnabled$1 = config === true || config === void 0 || config.enabled !== false;
567
+ if (!isEnabled$1) return void 0;
568
+ const studioConfig = typeof config === "object" ? config : {};
569
+ return {
570
+ enabled: true,
571
+ path: studioConfig.path ?? "/__studio",
572
+ schema: studioConfig.schema ?? "public"
573
+ };
574
+ }
575
+ /**
576
+ * Normalize hooks configuration
577
+ * @internal Exported for testing
578
+ */
579
+ function normalizeHooksConfig(config) {
580
+ if (!config?.server) return void 0;
581
+ const serverPath = config.server.endsWith(".ts") ? config.server : `${config.server}.ts`;
582
+ const resolvedPath = (0, node_path.resolve)(process.cwd(), serverPath);
583
+ return { serverHooksPath: resolvedPath };
584
+ }
585
+ async function devCommand(options) {
586
+ const defaultEnv = loadEnvFiles(".env");
587
+ if (defaultEnv.loaded.length > 0) logger$2.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
588
+ const config = await require_config.loadConfig();
589
+ if (config.env) {
590
+ const { loaded, missing } = loadEnvFiles(config.env);
591
+ if (loaded.length > 0) logger$2.log(`📦 Loaded env: ${loaded.join(", ")}`);
592
+ if (missing.length > 0) logger$2.warn(`⚠️ Missing env files: ${missing.join(", ")}`);
593
+ }
594
+ const resolved = resolveProviders(config, { provider: "server" });
595
+ logger$2.log("🚀 Starting development server...");
596
+ logger$2.log(`Loading routes from: ${config.routes}`);
597
+ if (config.functions) logger$2.log(`Loading functions from: ${config.functions}`);
598
+ if (config.crons) logger$2.log(`Loading crons from: ${config.crons}`);
599
+ if (config.subscribers) logger$2.log(`Loading subscribers from: ${config.subscribers}`);
600
+ logger$2.log(`Using envParser: ${config.envParser}`);
601
+ const { path: envParserPath, importPattern: envParserImportPattern } = require_config.parseModuleConfig(config.envParser, "envParser");
602
+ const { path: loggerPath, importPattern: loggerImportPattern } = require_config.parseModuleConfig(config.logger, "logger");
603
+ const telescope = normalizeTelescopeConfig(config.telescope);
604
+ if (telescope) logger$2.log(`🔭 Telescope enabled at ${telescope.path}`);
605
+ const studio = normalizeStudioConfig(config.studio);
606
+ if (studio) logger$2.log(`🗄️ Studio enabled at ${studio.path}`);
607
+ const hooks = normalizeHooksConfig(config.hooks);
608
+ if (hooks) logger$2.log(`🪝 Server hooks enabled from ${config.hooks?.server}`);
609
+ const openApiConfig = require_openapi.resolveOpenApiConfig(config);
610
+ const enableOpenApi = openApiConfig.enabled || resolved.enableOpenApi;
611
+ if (enableOpenApi) logger$2.log(`📄 OpenAPI output: ${require_openapi.OPENAPI_OUTPUT_PATH}`);
612
+ const buildContext = {
613
+ envParserPath,
614
+ envParserImportPattern,
615
+ loggerPath,
616
+ loggerImportPattern,
617
+ telescope,
618
+ studio,
619
+ hooks
620
+ };
621
+ await buildServer(config, buildContext, resolved.providers[0], enableOpenApi);
622
+ if (enableOpenApi) await require_openapi.generateOpenApi(config);
623
+ const runtime = config.runtime ?? "node";
624
+ const devServer = new DevServer(resolved.providers[0], options.port || 3e3, enableOpenApi, telescope, studio, runtime);
625
+ await devServer.start();
626
+ const envParserFile = config.envParser.split("#")[0];
627
+ const loggerFile = config.logger.split("#")[0];
628
+ const hooksFile = config.hooks?.server?.split("#")[0];
629
+ const watchPatterns = [
630
+ config.routes,
631
+ ...config.functions ? [config.functions] : [],
632
+ ...config.crons ? [config.crons] : [],
633
+ ...config.subscribers ? [config.subscribers] : [],
634
+ envParserFile.endsWith(".ts") ? envParserFile : `${envParserFile}.ts`,
635
+ loggerFile.endsWith(".ts") ? loggerFile : `${loggerFile}.ts`,
636
+ ...hooksFile ? [hooksFile.endsWith(".ts") ? hooksFile : `${hooksFile}.ts`] : []
637
+ ].flat();
638
+ const normalizedPatterns = watchPatterns.map((p) => p.startsWith("./") ? p.slice(2) : p);
639
+ logger$2.log(`👀 Watching for changes in: ${normalizedPatterns.join(", ")}`);
640
+ const resolvedFiles = await (0, fast_glob.default)(normalizedPatterns, {
641
+ cwd: process.cwd(),
642
+ absolute: false,
643
+ onlyFiles: true
644
+ });
645
+ const dirsToWatch = [...new Set(resolvedFiles.map((f) => f.split("/").slice(0, -1).join("/")))];
646
+ logger$2.log(`📁 Found ${resolvedFiles.length} files in ${dirsToWatch.length} directories`);
647
+ const watcher = chokidar.default.watch([...resolvedFiles, ...dirsToWatch], {
648
+ ignored: /(^|[\/\\])\../,
649
+ persistent: true,
650
+ ignoreInitial: true,
651
+ cwd: process.cwd()
652
+ });
653
+ watcher.on("ready", () => {
654
+ logger$2.log("🔍 File watcher ready");
655
+ });
656
+ watcher.on("error", (error) => {
657
+ logger$2.error("❌ Watcher error:", error);
658
+ });
659
+ let rebuildTimeout = null;
660
+ watcher.on("change", async (path$1) => {
661
+ logger$2.log(`📝 File changed: ${path$1}`);
662
+ if (rebuildTimeout) clearTimeout(rebuildTimeout);
663
+ rebuildTimeout = setTimeout(async () => {
664
+ try {
665
+ logger$2.log("🔄 Rebuilding...");
666
+ await buildServer(config, buildContext, resolved.providers[0], enableOpenApi);
667
+ if (enableOpenApi) await require_openapi.generateOpenApi(config, { silent: true });
668
+ logger$2.log("✅ Rebuild complete, restarting server...");
669
+ await devServer.restart();
670
+ } catch (error) {
671
+ logger$2.error("❌ Rebuild failed:", error.message);
672
+ }
673
+ }, 300);
674
+ });
675
+ let isShuttingDown = false;
676
+ const shutdown = () => {
677
+ if (isShuttingDown) return;
678
+ isShuttingDown = true;
679
+ logger$2.log("\n🛑 Shutting down...");
680
+ Promise.all([watcher.close(), devServer.stop()]).catch((err) => {
681
+ logger$2.error("Error during shutdown:", err);
682
+ }).finally(() => {
683
+ process.exit(0);
684
+ });
685
+ };
686
+ process.on("SIGINT", shutdown);
687
+ process.on("SIGTERM", shutdown);
688
+ }
689
+ async function buildServer(config, context, provider, enableOpenApi) {
690
+ const endpointGenerator = new require_openapi.EndpointGenerator();
691
+ const functionGenerator = new FunctionGenerator();
692
+ const cronGenerator = new CronGenerator();
693
+ const subscriberGenerator = new SubscriberGenerator();
694
+ const [allEndpoints, allFunctions, allCrons, allSubscribers] = await Promise.all([
695
+ endpointGenerator.load(config.routes),
696
+ config.functions ? functionGenerator.load(config.functions) : [],
697
+ config.crons ? cronGenerator.load(config.crons) : [],
698
+ config.subscribers ? subscriberGenerator.load(config.subscribers) : []
699
+ ]);
700
+ const outputDir = (0, node_path.join)(process.cwd(), ".gkm", provider);
701
+ await (0, node_fs_promises.mkdir)(outputDir, { recursive: true });
702
+ await Promise.all([
703
+ endpointGenerator.build(context, allEndpoints, outputDir, {
704
+ provider,
705
+ enableOpenApi
706
+ }),
707
+ functionGenerator.build(context, allFunctions, outputDir, { provider }),
708
+ cronGenerator.build(context, allCrons, outputDir, { provider }),
709
+ subscriberGenerator.build(context, allSubscribers, outputDir, { provider })
710
+ ]);
711
+ }
712
+ var DevServer = class {
713
+ serverProcess = null;
714
+ isRunning = false;
715
+ actualPort;
716
+ constructor(provider, requestedPort, enableOpenApi, telescope, studio, runtime = "node") {
717
+ this.provider = provider;
718
+ this.requestedPort = requestedPort;
719
+ this.enableOpenApi = enableOpenApi;
720
+ this.telescope = telescope;
721
+ this.studio = studio;
722
+ this.runtime = runtime;
723
+ this.actualPort = requestedPort;
724
+ }
725
+ async start() {
726
+ if (this.isRunning) await this.stop();
727
+ this.actualPort = await findAvailablePort(this.requestedPort);
728
+ if (this.actualPort !== this.requestedPort) logger$2.log(`ℹ️ Port ${this.requestedPort} was in use, using port ${this.actualPort} instead`);
729
+ const serverEntryPath = (0, node_path.join)(process.cwd(), ".gkm", this.provider, "server.ts");
730
+ await this.createServerEntry();
731
+ logger$2.log(`\n✨ Starting server on port ${this.actualPort}...`);
732
+ this.serverProcess = (0, node_child_process.spawn)("npx", [
733
+ "tsx",
734
+ serverEntryPath,
735
+ "--port",
736
+ this.actualPort.toString()
737
+ ], {
738
+ stdio: "inherit",
739
+ env: {
740
+ ...process.env,
741
+ NODE_ENV: "development"
742
+ },
743
+ detached: true
744
+ });
745
+ this.isRunning = true;
746
+ this.serverProcess.on("error", (error) => {
747
+ logger$2.error("❌ Server error:", error);
748
+ });
749
+ this.serverProcess.on("exit", (code, signal) => {
750
+ if (code !== null && code !== 0 && signal !== "SIGTERM") logger$2.error(`❌ Server exited with code ${code}`);
751
+ this.isRunning = false;
752
+ });
753
+ await new Promise((resolve$1) => setTimeout(resolve$1, 1e3));
754
+ if (this.isRunning) {
755
+ logger$2.log(`\n🎉 Server running at http://localhost:${this.actualPort}`);
756
+ if (this.enableOpenApi) logger$2.log(`📚 API Docs available at http://localhost:${this.actualPort}/__docs`);
757
+ if (this.telescope) logger$2.log(`🔭 Telescope available at http://localhost:${this.actualPort}${this.telescope.path}`);
758
+ if (this.studio) logger$2.log(`🗄️ Studio available at http://localhost:${this.actualPort}${this.studio.path}`);
759
+ }
760
+ }
761
+ async stop() {
762
+ const port = this.actualPort;
763
+ if (this.serverProcess && this.isRunning) {
764
+ const pid = this.serverProcess.pid;
765
+ if (pid) try {
766
+ process.kill(-pid, "SIGKILL");
767
+ } catch {
768
+ try {
769
+ process.kill(pid, "SIGKILL");
770
+ } catch {}
771
+ }
772
+ this.serverProcess = null;
773
+ this.isRunning = false;
774
+ }
775
+ this.killProcessesOnPort(port);
776
+ }
777
+ killProcessesOnPort(port) {
778
+ try {
779
+ (0, node_child_process.execSync)(`lsof -ti tcp:${port} | xargs kill -9 2>/dev/null || true`, { stdio: "ignore" });
780
+ } catch {}
781
+ }
782
+ async restart() {
783
+ const portToReuse = this.actualPort;
784
+ await this.stop();
785
+ let attempts = 0;
786
+ while (attempts < 30) {
787
+ if (await isPortAvailable(portToReuse)) break;
788
+ await new Promise((resolve$1) => setTimeout(resolve$1, 100));
789
+ attempts++;
790
+ }
791
+ this.requestedPort = portToReuse;
792
+ await this.start();
793
+ }
794
+ async createServerEntry() {
795
+ const { writeFile: writeFile$5 } = await import("node:fs/promises");
796
+ const { relative: relative$5, dirname: dirname$4 } = await import("node:path");
797
+ const serverPath = (0, node_path.join)(process.cwd(), ".gkm", this.provider, "server.ts");
798
+ const relativeAppPath = relative$5(dirname$4(serverPath), (0, node_path.join)(dirname$4(serverPath), "app.js"));
799
+ const serveCode = this.runtime === "bun" ? `Bun.serve({
800
+ port,
801
+ fetch: app.fetch,
802
+ });` : `const { serve } = await import('@hono/node-server');
803
+ const server = serve({
804
+ fetch: app.fetch,
805
+ port,
806
+ });
807
+ // Inject WebSocket support if available
808
+ const injectWs = (app as any).__injectWebSocket;
809
+ if (injectWs) {
810
+ injectWs(server);
811
+ console.log('🔌 Telescope real-time updates enabled');
812
+ }`;
813
+ const content = `#!/usr/bin/env node
814
+ /**
815
+ * Development server entry point
816
+ * This file is auto-generated by 'gkm dev'
817
+ */
818
+ import { createApp } from './${relativeAppPath.startsWith(".") ? relativeAppPath : "./" + relativeAppPath}';
819
+
820
+ const port = process.argv.includes('--port')
821
+ ? Number.parseInt(process.argv[process.argv.indexOf('--port') + 1])
822
+ : 3000;
823
+
824
+ // createApp is async to support optional WebSocket setup
825
+ const { app, start } = await createApp(undefined, ${this.enableOpenApi});
826
+
827
+ // Start the server
828
+ start({
829
+ port,
830
+ serve: async (app, port) => {
831
+ ${serveCode}
832
+ },
833
+ }).catch((error) => {
834
+ console.error('Failed to start server:', error);
835
+ process.exit(1);
836
+ });
837
+ `;
838
+ await writeFile$5(serverPath, content);
839
+ }
840
+ };
841
+
842
+ //#endregion
843
+ //#region src/build/manifests.ts
844
+ const logger$1 = console;
845
+ async function generateAwsManifest(outputDir, routes, functions, crons, subscribers) {
846
+ const manifestDir = (0, path.join)(outputDir, "manifest");
847
+ await (0, node_fs_promises.mkdir)(manifestDir, { recursive: true });
848
+ const awsRoutes = routes.filter((r) => r.method !== "ALL");
849
+ const content = `export const manifest = {
850
+ routes: ${JSON.stringify(awsRoutes, null, 2)},
851
+ functions: ${JSON.stringify(functions, null, 2)},
852
+ crons: ${JSON.stringify(crons, null, 2)},
853
+ subscribers: ${JSON.stringify(subscribers, null, 2)},
854
+ } as const;
855
+
856
+ // Derived types
857
+ export type Route = (typeof manifest.routes)[number];
858
+ export type Function = (typeof manifest.functions)[number];
859
+ export type Cron = (typeof manifest.crons)[number];
860
+ export type Subscriber = (typeof manifest.subscribers)[number];
861
+
862
+ // Useful union types
863
+ export type Authorizer = Route['authorizer'];
864
+ export type HttpMethod = Route['method'];
865
+ export type RoutePath = Route['path'];
866
+ `;
867
+ const manifestPath = (0, path.join)(manifestDir, "aws.ts");
868
+ await (0, node_fs_promises.writeFile)(manifestPath, content);
869
+ logger$1.log(`Generated AWS manifest with ${awsRoutes.length} routes, ${functions.length} functions, ${crons.length} crons, ${subscribers.length} subscribers`);
870
+ logger$1.log(`Manifest: ${(0, path.relative)(process.cwd(), manifestPath)}`);
871
+ }
872
+ async function generateServerManifest(outputDir, appInfo, routes, subscribers) {
873
+ const manifestDir = (0, path.join)(outputDir, "manifest");
874
+ await (0, node_fs_promises.mkdir)(manifestDir, { recursive: true });
875
+ const serverRoutes = routes.filter((r) => r.method !== "ALL").map((r) => ({
876
+ path: r.path,
877
+ method: r.method,
878
+ authorizer: r.authorizer
879
+ }));
880
+ const serverSubscribers = subscribers.map((s) => ({
881
+ name: s.name,
882
+ subscribedEvents: s.subscribedEvents
883
+ }));
884
+ const content = `export const manifest = {
885
+ app: ${JSON.stringify(appInfo, null, 2)},
886
+ routes: ${JSON.stringify(serverRoutes, null, 2)},
887
+ subscribers: ${JSON.stringify(serverSubscribers, null, 2)},
888
+ } as const;
889
+
890
+ // Derived types
891
+ export type Route = (typeof manifest.routes)[number];
892
+ export type Subscriber = (typeof manifest.subscribers)[number];
893
+
894
+ // Useful union types
895
+ export type Authorizer = Route['authorizer'];
896
+ export type HttpMethod = Route['method'];
897
+ export type RoutePath = Route['path'];
898
+ `;
899
+ const manifestPath = (0, path.join)(manifestDir, "server.ts");
900
+ await (0, node_fs_promises.writeFile)(manifestPath, content);
901
+ logger$1.log(`Generated server manifest with ${serverRoutes.length} routes, ${serverSubscribers.length} subscribers`);
902
+ logger$1.log(`Manifest: ${(0, path.relative)(process.cwd(), manifestPath)}`);
903
+ }
904
+
905
+ //#endregion
906
+ //#region src/build/index.ts
907
+ const logger = console;
908
+ async function buildCommand(options) {
909
+ const config = await require_config.loadConfig();
910
+ const resolved = resolveProviders(config, options);
911
+ logger.log(`Building with providers: ${resolved.providers.join(", ")}`);
912
+ logger.log(`Loading routes from: ${config.routes}`);
913
+ if (config.functions) logger.log(`Loading functions from: ${config.functions}`);
914
+ if (config.crons) logger.log(`Loading crons from: ${config.crons}`);
915
+ if (config.subscribers) logger.log(`Loading subscribers from: ${config.subscribers}`);
916
+ logger.log(`Using envParser: ${config.envParser}`);
917
+ const { path: envParserPath, importPattern: envParserImportPattern } = require_config.parseModuleConfig(config.envParser, "envParser");
918
+ const { path: loggerPath, importPattern: loggerImportPattern } = require_config.parseModuleConfig(config.logger, "logger");
919
+ const telescope = normalizeTelescopeConfig(config.telescope);
920
+ if (telescope) logger.log(`🔭 Telescope enabled at ${telescope.path}`);
921
+ const hooks = normalizeHooksConfig(config.hooks);
922
+ if (hooks) logger.log(`🪝 Server hooks enabled`);
923
+ const buildContext = {
924
+ envParserPath,
925
+ envParserImportPattern,
926
+ loggerPath,
927
+ loggerImportPattern,
928
+ telescope,
929
+ hooks
930
+ };
931
+ const endpointGenerator = new require_openapi.EndpointGenerator();
932
+ const functionGenerator = new FunctionGenerator();
933
+ const cronGenerator = new CronGenerator();
934
+ const subscriberGenerator = new SubscriberGenerator();
935
+ const [allEndpoints, allFunctions, allCrons, allSubscribers] = await Promise.all([
936
+ endpointGenerator.load(config.routes),
937
+ config.functions ? functionGenerator.load(config.functions) : [],
938
+ config.crons ? cronGenerator.load(config.crons) : [],
939
+ config.subscribers ? subscriberGenerator.load(config.subscribers) : []
940
+ ]);
941
+ logger.log(`Found ${allEndpoints.length} endpoints`);
942
+ logger.log(`Found ${allFunctions.length} functions`);
943
+ logger.log(`Found ${allCrons.length} crons`);
944
+ logger.log(`Found ${allSubscribers.length} subscribers`);
945
+ if (allEndpoints.length === 0 && allFunctions.length === 0 && allCrons.length === 0 && allSubscribers.length === 0) {
946
+ logger.log("No endpoints, functions, crons, or subscribers found to process");
947
+ return;
948
+ }
949
+ const rootOutputDir = (0, node_path.join)(process.cwd(), ".gkm");
950
+ await (0, node_fs_promises.mkdir)(rootOutputDir, { recursive: true });
951
+ for (const provider of resolved.providers) await buildForProvider(provider, buildContext, rootOutputDir, endpointGenerator, functionGenerator, cronGenerator, subscriberGenerator, allEndpoints, allFunctions, allCrons, allSubscribers, resolved.enableOpenApi);
952
+ }
953
+ async function buildForProvider(provider, context, rootOutputDir, endpointGenerator, functionGenerator, cronGenerator, subscriberGenerator, endpoints, functions, crons, subscribers, enableOpenApi) {
954
+ const outputDir = (0, node_path.join)(process.cwd(), ".gkm", provider);
955
+ await (0, node_fs_promises.mkdir)(outputDir, { recursive: true });
956
+ logger.log(`\nGenerating handlers for provider: ${provider}`);
957
+ const [routes, functionInfos, cronInfos, subscriberInfos] = await Promise.all([
958
+ endpointGenerator.build(context, endpoints, outputDir, {
959
+ provider,
960
+ enableOpenApi
961
+ }),
962
+ functionGenerator.build(context, functions, outputDir, { provider }),
963
+ cronGenerator.build(context, crons, outputDir, { provider }),
964
+ subscriberGenerator.build(context, subscribers, outputDir, { provider })
965
+ ]);
966
+ logger.log(`Generated ${routes.length} routes, ${functionInfos.length} functions, ${cronInfos.length} crons, ${subscriberInfos.length} subscribers for ${provider}`);
967
+ if (provider === "server") {
968
+ const routeMetadata = await Promise.all(endpoints.map(async ({ construct }) => ({
969
+ path: construct._path,
970
+ method: construct.method,
971
+ handler: "",
972
+ authorizer: construct.authorizer?.name ?? "none"
973
+ })));
974
+ const appInfo = {
975
+ handler: (0, node_path.relative)(process.cwd(), (0, node_path.join)(outputDir, "app.ts")),
976
+ endpoints: (0, node_path.relative)(process.cwd(), (0, node_path.join)(outputDir, "endpoints.ts"))
977
+ };
978
+ await generateServerManifest(rootOutputDir, appInfo, routeMetadata, subscriberInfos);
979
+ } else await generateAwsManifest(rootOutputDir, routes, functionInfos, cronInfos, subscriberInfos);
980
+ }
981
+
982
+ //#endregion
983
+ //#region src/init/generators/config.ts
984
+ /**
985
+ * Generate configuration files (gkm.config.ts, tsconfig.json, biome.json, turbo.json)
986
+ */
987
+ function generateConfigFiles(options, template) {
988
+ const { telescope, studio, routesStructure } = options;
989
+ const isServerless = template.name === "serverless";
990
+ const hasWorker = template.name === "worker";
991
+ const getRoutesGlob = () => {
992
+ switch (routesStructure) {
993
+ case "centralized-endpoints": return "./src/endpoints/**/*.ts";
994
+ case "centralized-routes": return "./src/routes/**/*.ts";
995
+ case "domain-based": return "./src/**/routes/*.ts";
996
+ }
997
+ };
998
+ let gkmConfig = `import { defineConfig } from '@geekmidas/cli/config';
999
+
1000
+ export default defineConfig({
1001
+ routes: '${getRoutesGlob()}',
1002
+ envParser: './src/config/env#envParser',
1003
+ logger: './src/config/logger#logger',`;
1004
+ if (isServerless || hasWorker) gkmConfig += `
1005
+ functions: './src/functions/**/*.ts',`;
1006
+ if (hasWorker) gkmConfig += `
1007
+ crons: './src/crons/**/*.ts',
1008
+ subscribers: './src/subscribers/**/*.ts',`;
1009
+ if (telescope) gkmConfig += `
1010
+ telescope: {
1011
+ enabled: true,
1012
+ path: '/__telescope',
1013
+ },`;
1014
+ if (studio) gkmConfig += `
1015
+ studio: './src/config/studio#studio',`;
1016
+ gkmConfig += `
1017
+ openapi: {
1018
+ enabled: true,
1019
+ },`;
1020
+ gkmConfig += `
1021
+ });
1022
+ `;
1023
+ const tsConfig = options.monorepo ? {
1024
+ extends: "../../tsconfig.json",
1025
+ compilerOptions: {
1026
+ outDir: "./dist",
1027
+ rootDir: "./src",
1028
+ baseUrl: ".",
1029
+ paths: { [`@${options.name}/*`]: ["../../packages/*/src"] }
1030
+ },
1031
+ include: ["src/**/*.ts"],
1032
+ exclude: ["node_modules", "dist"]
1033
+ } : {
1034
+ compilerOptions: {
1035
+ target: "ES2022",
1036
+ module: "NodeNext",
1037
+ moduleResolution: "NodeNext",
1038
+ lib: ["ES2022"],
1039
+ strict: true,
1040
+ esModuleInterop: true,
1041
+ skipLibCheck: true,
1042
+ forceConsistentCasingInFileNames: true,
1043
+ resolveJsonModule: true,
1044
+ declaration: true,
1045
+ declarationMap: true,
1046
+ outDir: "./dist",
1047
+ rootDir: "./src"
1048
+ },
1049
+ include: ["src/**/*.ts"],
1050
+ exclude: ["node_modules", "dist"]
1051
+ };
1052
+ if (options.monorepo) return [{
1053
+ path: "gkm.config.ts",
1054
+ content: gkmConfig
1055
+ }, {
1056
+ path: "tsconfig.json",
1057
+ content: JSON.stringify(tsConfig, null, 2) + "\n"
1058
+ }];
1059
+ const biomeConfig = {
1060
+ $schema: "https://biomejs.dev/schemas/1.9.4/schema.json",
1061
+ vcs: {
1062
+ enabled: true,
1063
+ clientKind: "git",
1064
+ useIgnoreFile: true
1065
+ },
1066
+ organizeImports: { enabled: true },
1067
+ formatter: {
1068
+ enabled: true,
1069
+ indentStyle: "space",
1070
+ indentWidth: 2,
1071
+ lineWidth: 80
1072
+ },
1073
+ javascript: { formatter: {
1074
+ quoteStyle: "single",
1075
+ trailingCommas: "all",
1076
+ semicolons: "always",
1077
+ arrowParentheses: "always"
1078
+ } },
1079
+ linter: {
1080
+ enabled: true,
1081
+ rules: {
1082
+ recommended: true,
1083
+ correctness: {
1084
+ noUnusedImports: "error",
1085
+ noUnusedVariables: "error"
1086
+ },
1087
+ style: { noNonNullAssertion: "off" }
1088
+ }
1089
+ },
1090
+ files: { ignore: [
1091
+ "node_modules",
1092
+ "dist",
1093
+ ".gkm",
1094
+ "coverage"
1095
+ ] }
1096
+ };
1097
+ const turboConfig = {
1098
+ $schema: "https://turbo.build/schema.json",
1099
+ tasks: {
1100
+ build: {
1101
+ dependsOn: ["^build"],
1102
+ outputs: ["dist/**"]
1103
+ },
1104
+ dev: {
1105
+ cache: false,
1106
+ persistent: true
1107
+ },
1108
+ test: {
1109
+ dependsOn: ["^build"],
1110
+ cache: false
1111
+ },
1112
+ "test:once": {
1113
+ dependsOn: ["^build"],
1114
+ outputs: ["coverage/**"]
1115
+ },
1116
+ typecheck: {
1117
+ dependsOn: ["^build"],
1118
+ outputs: []
1119
+ },
1120
+ lint: { outputs: [] },
1121
+ fmt: { outputs: [] }
1122
+ }
1123
+ };
1124
+ return [
1125
+ {
1126
+ path: "gkm.config.ts",
1127
+ content: gkmConfig
1128
+ },
1129
+ {
1130
+ path: "tsconfig.json",
1131
+ content: JSON.stringify(tsConfig, null, 2) + "\n"
1132
+ },
1133
+ {
1134
+ path: "biome.json",
1135
+ content: JSON.stringify(biomeConfig, null, 2) + "\n"
1136
+ },
1137
+ {
1138
+ path: "turbo.json",
1139
+ content: JSON.stringify(turboConfig, null, 2) + "\n"
1140
+ }
1141
+ ];
1142
+ }
1143
+
1144
+ //#endregion
1145
+ //#region src/init/generators/docker.ts
1146
+ /**
1147
+ * Generate docker-compose.yml based on template and options
1148
+ */
1149
+ function generateDockerFiles(options, template) {
1150
+ const { database } = options;
1151
+ const isServerless = template.name === "serverless";
1152
+ const hasWorker = template.name === "worker";
1153
+ const services = [];
1154
+ const volumes = [];
1155
+ if (database) {
1156
+ services.push(` postgres:
1157
+ image: postgres:16-alpine
1158
+ container_name: ${options.name}-postgres
1159
+ restart: unless-stopped
1160
+ environment:
1161
+ POSTGRES_USER: postgres
1162
+ POSTGRES_PASSWORD: postgres
1163
+ POSTGRES_DB: ${options.name.replace(/-/g, "_")}_dev
1164
+ ports:
1165
+ - '5432:5432'
1166
+ volumes:
1167
+ - postgres_data:/var/lib/postgresql/data
1168
+ healthcheck:
1169
+ test: ['CMD-SHELL', 'pg_isready -U postgres']
1170
+ interval: 5s
1171
+ timeout: 5s
1172
+ retries: 5`);
1173
+ volumes.push(" postgres_data:");
1174
+ }
1175
+ if (isServerless) {
1176
+ services.push(` redis:
1177
+ image: redis:7-alpine
1178
+ container_name: ${options.name}-redis
1179
+ restart: unless-stopped
1180
+ ports:
1181
+ - '6379:6379'
1182
+ volumes:
1183
+ - redis_data:/data
1184
+ healthcheck:
1185
+ test: ['CMD', 'redis-cli', 'ping']
1186
+ interval: 5s
1187
+ timeout: 5s
1188
+ retries: 5
1189
+
1190
+ serverless-redis:
1191
+ image: hiett/serverless-redis-http:latest
1192
+ container_name: ${options.name}-serverless-redis
1193
+ restart: unless-stopped
1194
+ ports:
1195
+ - '8079:80'
1196
+ environment:
1197
+ SRH_MODE: env
1198
+ SRH_TOKEN: local_dev_token
1199
+ SRH_CONNECTION_STRING: redis://redis:6379
1200
+ depends_on:
1201
+ redis:
1202
+ condition: service_healthy`);
1203
+ volumes.push(" redis_data:");
1204
+ } else {
1205
+ services.push(` redis:
1206
+ image: redis:7-alpine
1207
+ container_name: ${options.name}-redis
1208
+ restart: unless-stopped
1209
+ ports:
1210
+ - '6379:6379'
1211
+ volumes:
1212
+ - redis_data:/data
1213
+ healthcheck:
1214
+ test: ['CMD', 'redis-cli', 'ping']
1215
+ interval: 5s
1216
+ timeout: 5s
1217
+ retries: 5`);
1218
+ volumes.push(" redis_data:");
1219
+ }
1220
+ if (hasWorker) {
1221
+ services.push(` rabbitmq:
1222
+ image: rabbitmq:3-management-alpine
1223
+ container_name: ${options.name}-rabbitmq
1224
+ restart: unless-stopped
1225
+ ports:
1226
+ - '5672:5672'
1227
+ - '15672:15672'
1228
+ environment:
1229
+ RABBITMQ_DEFAULT_USER: guest
1230
+ RABBITMQ_DEFAULT_PASS: guest
1231
+ volumes:
1232
+ - rabbitmq_data:/var/lib/rabbitmq
1233
+ healthcheck:
1234
+ test: ['CMD', 'rabbitmq-diagnostics', 'check_running']
1235
+ interval: 10s
1236
+ timeout: 5s
1237
+ retries: 5`);
1238
+ volumes.push(" rabbitmq_data:");
1239
+ }
1240
+ let dockerCompose = `version: '3.8'
1241
+
1242
+ services:
1243
+ ${services.join("\n\n")}
1244
+ `;
1245
+ if (volumes.length > 0) dockerCompose += `
1246
+ volumes:
1247
+ ${volumes.join("\n")}
1248
+ `;
1249
+ return [{
1250
+ path: "docker-compose.yml",
1251
+ content: dockerCompose
1252
+ }];
1253
+ }
1254
+
1255
+ //#endregion
1256
+ //#region src/init/generators/env.ts
1257
+ /**
1258
+ * Generate environment files (.env, .env.example, .env.development, .env.test, .gitignore)
1259
+ */
1260
+ function generateEnvFiles(options, template) {
1261
+ const { database } = options;
1262
+ const isServerless = template.name === "serverless";
1263
+ const hasWorker = template.name === "worker";
1264
+ let baseEnv = `# Application
1265
+ NODE_ENV=development
1266
+ PORT=3000
1267
+ LOG_LEVEL=info
1268
+ `;
1269
+ if (isServerless) baseEnv = `# AWS
1270
+ STAGE=dev
1271
+ AWS_REGION=us-east-1
1272
+ LOG_LEVEL=info
1273
+ `;
1274
+ if (database) baseEnv += `
1275
+ # Database
1276
+ DATABASE_URL=postgresql://user:password@localhost:5432/mydb
1277
+ `;
1278
+ if (hasWorker) baseEnv += `
1279
+ # Message Queue
1280
+ RABBITMQ_URL=amqp://localhost:5672
1281
+ `;
1282
+ baseEnv += `
1283
+ # Authentication
1284
+ JWT_SECRET=your-secret-key-change-in-production
1285
+ `;
1286
+ let devEnv = `# Development Environment
1287
+ NODE_ENV=development
1288
+ PORT=3000
1289
+ LOG_LEVEL=debug
1290
+ `;
1291
+ if (isServerless) devEnv = `# Development Environment
1292
+ STAGE=dev
1293
+ AWS_REGION=us-east-1
1294
+ LOG_LEVEL=debug
1295
+ `;
1296
+ if (database) devEnv += `
1297
+ # Database
1298
+ DATABASE_URL=postgresql://postgres:postgres@localhost:5432/mydb_dev
1299
+ `;
1300
+ if (hasWorker) devEnv += `
1301
+ # Message Queue
1302
+ RABBITMQ_URL=amqp://localhost:5672
1303
+ `;
1304
+ devEnv += `
1305
+ # Authentication
1306
+ JWT_SECRET=dev-secret-not-for-production
1307
+ `;
1308
+ let testEnv = `# Test Environment
1309
+ NODE_ENV=test
1310
+ PORT=3001
1311
+ LOG_LEVEL=error
1312
+ `;
1313
+ if (isServerless) testEnv = `# Test Environment
1314
+ STAGE=test
1315
+ AWS_REGION=us-east-1
1316
+ LOG_LEVEL=error
1317
+ `;
1318
+ if (database) testEnv += `
1319
+ # Database
1320
+ DATABASE_URL=postgresql://postgres:postgres@localhost:5432/mydb_test
1321
+ `;
1322
+ if (hasWorker) testEnv += `
1323
+ # Message Queue
1324
+ RABBITMQ_URL=amqp://localhost:5672
1325
+ `;
1326
+ testEnv += `
1327
+ # Authentication
1328
+ JWT_SECRET=test-secret-not-for-production
1329
+ `;
1330
+ const files = [
1331
+ {
1332
+ path: ".env.example",
1333
+ content: baseEnv
1334
+ },
1335
+ {
1336
+ path: ".env",
1337
+ content: baseEnv
1338
+ },
1339
+ {
1340
+ path: ".env.development",
1341
+ content: devEnv
1342
+ },
1343
+ {
1344
+ path: ".env.test",
1345
+ content: testEnv
1346
+ }
1347
+ ];
1348
+ if (!options.monorepo) {
1349
+ const gitignore = `# Dependencies
1350
+ node_modules/
1351
+
1352
+ # Build output
1353
+ dist/
1354
+ .gkm/
1355
+
1356
+ # Environment
1357
+ .env
1358
+ .env.local
1359
+ .env.*.local
1360
+
1361
+ # IDE
1362
+ .idea/
1363
+ .vscode/
1364
+ *.swp
1365
+ *.swo
1366
+
1367
+ # OS
1368
+ .DS_Store
1369
+ Thumbs.db
1370
+
1371
+ # Logs
1372
+ *.log
1373
+ npm-debug.log*
1374
+ yarn-debug.log*
1375
+ pnpm-debug.log*
1376
+
1377
+ # Test coverage
1378
+ coverage/
1379
+
1380
+ # TypeScript cache
1381
+ *.tsbuildinfo
1382
+ `;
1383
+ files.push({
1384
+ path: ".gitignore",
1385
+ content: gitignore
1386
+ });
1387
+ }
1388
+ return files;
1389
+ }
1390
+
1391
+ //#endregion
1392
+ //#region src/init/generators/models.ts
1393
+ /**
1394
+ * Generate packages/models for shared Zod schemas (monorepo only)
1395
+ */
1396
+ function generateModelsPackage(options) {
1397
+ if (!options.monorepo) return [];
1398
+ const packageName = `@${options.name}/models`;
1399
+ const packageJson = {
1400
+ name: packageName,
1401
+ version: "0.0.1",
1402
+ private: true,
1403
+ type: "module",
1404
+ exports: {
1405
+ ".": {
1406
+ types: "./dist/index.d.ts",
1407
+ import: "./dist/index.js"
1408
+ },
1409
+ "./*": {
1410
+ types: "./dist/*.d.ts",
1411
+ import: "./dist/*.js"
1412
+ }
1413
+ },
1414
+ scripts: {
1415
+ build: "tsc",
1416
+ "build:watch": "tsc --watch",
1417
+ typecheck: "tsc --noEmit"
1418
+ },
1419
+ dependencies: { zod: "~4.1.0" },
1420
+ devDependencies: { typescript: "~5.8.2" }
1421
+ };
1422
+ const tsConfig = {
1423
+ extends: "../../tsconfig.json",
1424
+ compilerOptions: {
1425
+ outDir: "./dist",
1426
+ rootDir: "./src"
1427
+ },
1428
+ include: ["src/**/*.ts"],
1429
+ exclude: ["node_modules", "dist"]
1430
+ };
1431
+ const indexTs = `import { z } from 'zod';
1432
+
1433
+ // ============================================
1434
+ // Common Schemas
1435
+ // ============================================
1436
+
1437
+ export const idSchema = z.string().uuid();
1438
+
1439
+ export const timestampsSchema = z.object({
1440
+ createdAt: z.coerce.date(),
1441
+ updatedAt: z.coerce.date(),
1442
+ });
1443
+
1444
+ export const paginationSchema = z.object({
1445
+ page: z.coerce.number().int().positive().default(1),
1446
+ limit: z.coerce.number().int().positive().max(100).default(20),
1447
+ });
1448
+
1449
+ export const paginatedResponseSchema = <T extends z.ZodTypeAny>(itemSchema: T) =>
1450
+ z.object({
1451
+ items: z.array(itemSchema),
1452
+ total: z.number(),
1453
+ page: z.number(),
1454
+ limit: z.number(),
1455
+ totalPages: z.number(),
1456
+ });
1457
+
1458
+ // ============================================
1459
+ // User Schemas
1460
+ // ============================================
1461
+
1462
+ export const userSchema = z.object({
1463
+ id: idSchema,
1464
+ email: z.string().email(),
1465
+ name: z.string().min(1).max(100),
1466
+ ...timestampsSchema.shape,
1467
+ });
1468
+
1469
+ export const createUserSchema = userSchema.omit({
1470
+ id: true,
1471
+ createdAt: true,
1472
+ updatedAt: true,
1473
+ });
1474
+
1475
+ export const updateUserSchema = createUserSchema.partial();
1476
+
1477
+ // ============================================
1478
+ // Type Exports
1479
+ // ============================================
1480
+
1481
+ export type Id = z.infer<typeof idSchema>;
1482
+ export type Timestamps = z.infer<typeof timestampsSchema>;
1483
+ export type Pagination = z.infer<typeof paginationSchema>;
1484
+ export type User = z.infer<typeof userSchema>;
1485
+ export type CreateUser = z.infer<typeof createUserSchema>;
1486
+ export type UpdateUser = z.infer<typeof updateUserSchema>;
1487
+ `;
1488
+ return [
1489
+ {
1490
+ path: "packages/models/package.json",
1491
+ content: JSON.stringify(packageJson, null, 2) + "\n"
1492
+ },
1493
+ {
1494
+ path: "packages/models/tsconfig.json",
1495
+ content: JSON.stringify(tsConfig, null, 2) + "\n"
1496
+ },
1497
+ {
1498
+ path: "packages/models/src/index.ts",
1499
+ content: indexTs
1500
+ }
1501
+ ];
1502
+ }
1503
+
1504
+ //#endregion
1505
+ //#region src/init/generators/monorepo.ts
1506
+ /**
1507
+ * Generate monorepo root files (pnpm-workspace.yaml, root package.json, etc.)
1508
+ */
1509
+ function generateMonorepoFiles(options, _template) {
1510
+ if (!options.monorepo) return [];
1511
+ const rootPackageJson = {
1512
+ name: options.name,
1513
+ version: "0.0.1",
1514
+ private: true,
1515
+ type: "module",
1516
+ scripts: {
1517
+ dev: "turbo dev",
1518
+ build: "turbo build",
1519
+ test: "turbo test",
1520
+ "test:once": "turbo test:once",
1521
+ typecheck: "turbo typecheck",
1522
+ lint: "biome lint .",
1523
+ fmt: "biome format . --write",
1524
+ "fmt:check": "biome format ."
1525
+ },
1526
+ devDependencies: {
1527
+ "@biomejs/biome": "~1.9.4",
1528
+ turbo: "~2.3.0",
1529
+ typescript: "~5.8.2",
1530
+ vitest: "~4.0.0"
1531
+ }
1532
+ };
1533
+ const apiPathParts = options.apiPath.split("/");
1534
+ const appsFolder = apiPathParts[0] || "apps";
1535
+ const pnpmWorkspace = `packages:
1536
+ - '${appsFolder}/*'
1537
+ - 'packages/*'
1538
+ `;
1539
+ const biomeConfig = {
1540
+ $schema: "https://biomejs.dev/schemas/1.9.4/schema.json",
1541
+ vcs: {
1542
+ enabled: true,
1543
+ clientKind: "git",
1544
+ useIgnoreFile: true
1545
+ },
1546
+ organizeImports: { enabled: true },
1547
+ formatter: {
1548
+ enabled: true,
1549
+ indentStyle: "space",
1550
+ indentWidth: 2,
1551
+ lineWidth: 80
1552
+ },
1553
+ javascript: { formatter: {
1554
+ quoteStyle: "single",
1555
+ trailingCommas: "all",
1556
+ semicolons: "always",
1557
+ arrowParentheses: "always"
1558
+ } },
1559
+ linter: {
1560
+ enabled: true,
1561
+ rules: {
1562
+ recommended: true,
1563
+ correctness: {
1564
+ noUnusedImports: "error",
1565
+ noUnusedVariables: "error"
1566
+ },
1567
+ style: { noNonNullAssertion: "off" }
1568
+ }
1569
+ },
1570
+ files: { ignore: [
1571
+ "node_modules",
1572
+ "dist",
1573
+ ".gkm",
1574
+ "coverage"
1575
+ ] }
1576
+ };
1577
+ const turboConfig = {
1578
+ $schema: "https://turbo.build/schema.json",
1579
+ tasks: {
1580
+ build: {
1581
+ dependsOn: ["^build"],
1582
+ outputs: ["dist/**"]
1583
+ },
1584
+ dev: {
1585
+ cache: false,
1586
+ persistent: true
1587
+ },
1588
+ test: {
1589
+ dependsOn: ["^build"],
1590
+ cache: false
1591
+ },
1592
+ "test:once": {
1593
+ dependsOn: ["^build"],
1594
+ outputs: ["coverage/**"]
1595
+ },
1596
+ typecheck: {
1597
+ dependsOn: ["^build"],
1598
+ outputs: []
1599
+ },
1600
+ lint: { outputs: [] },
1601
+ fmt: { outputs: [] }
1602
+ }
1603
+ };
1604
+ const gitignore = `# Dependencies
1605
+ node_modules/
1606
+
1607
+ # Build output
1608
+ dist/
1609
+ .gkm/
1610
+
1611
+ # Environment
1612
+ .env
1613
+ .env.local
1614
+ .env.*.local
1615
+
1616
+ # IDE
1617
+ .idea/
1618
+ .vscode/
1619
+ *.swp
1620
+ *.swo
1621
+
1622
+ # OS
1623
+ .DS_Store
1624
+ Thumbs.db
1625
+
1626
+ # Logs
1627
+ *.log
1628
+ npm-debug.log*
1629
+ yarn-debug.log*
1630
+ pnpm-debug.log*
1631
+
1632
+ # Test coverage
1633
+ coverage/
1634
+
1635
+ # TypeScript cache
1636
+ *.tsbuildinfo
1637
+
1638
+ # Turbo
1639
+ .turbo/
1640
+ `;
1641
+ const tsConfig = {
1642
+ compilerOptions: {
1643
+ target: "ES2022",
1644
+ module: "NodeNext",
1645
+ moduleResolution: "NodeNext",
1646
+ lib: ["ES2022"],
1647
+ strict: true,
1648
+ esModuleInterop: true,
1649
+ skipLibCheck: true,
1650
+ forceConsistentCasingInFileNames: true,
1651
+ resolveJsonModule: true,
1652
+ declaration: true,
1653
+ declarationMap: true,
1654
+ composite: true
1655
+ },
1656
+ exclude: ["node_modules", "dist"]
1657
+ };
1658
+ return [
1659
+ {
1660
+ path: "package.json",
1661
+ content: JSON.stringify(rootPackageJson, null, 2) + "\n"
1662
+ },
1663
+ {
1664
+ path: "pnpm-workspace.yaml",
1665
+ content: pnpmWorkspace
1666
+ },
1667
+ {
1668
+ path: "tsconfig.json",
1669
+ content: JSON.stringify(tsConfig, null, 2) + "\n"
1670
+ },
1671
+ {
1672
+ path: "biome.json",
1673
+ content: JSON.stringify(biomeConfig, null, 2) + "\n"
1674
+ },
1675
+ {
1676
+ path: "turbo.json",
1677
+ content: JSON.stringify(turboConfig, null, 2) + "\n"
1678
+ },
1679
+ {
1680
+ path: ".gitignore",
1681
+ content: gitignore
1682
+ }
1683
+ ];
1684
+ }
1685
+
1686
+ //#endregion
1687
+ //#region src/init/templates/api.ts
1688
+ const apiTemplate = {
1689
+ name: "api",
1690
+ description: "Full API with auth, database, services",
1691
+ dependencies: {
1692
+ "@geekmidas/constructs": "workspace:*",
1693
+ "@geekmidas/envkit": "workspace:*",
1694
+ "@geekmidas/logger": "workspace:*",
1695
+ "@geekmidas/services": "workspace:*",
1696
+ "@geekmidas/errors": "workspace:*",
1697
+ "@geekmidas/auth": "workspace:*",
1698
+ hono: "~4.8.2",
1699
+ pino: "~9.6.0"
1700
+ },
1701
+ devDependencies: {
1702
+ "@biomejs/biome": "~1.9.4",
1703
+ "@geekmidas/cli": "workspace:*",
1704
+ "@types/node": "~22.0.0",
1705
+ tsx: "~4.20.0",
1706
+ turbo: "~2.3.0",
1707
+ typescript: "~5.8.2",
1708
+ vitest: "~4.0.0"
1709
+ },
1710
+ scripts: {
1711
+ dev: "gkm dev",
1712
+ build: "gkm build",
1713
+ test: "vitest",
1714
+ "test:once": "vitest run",
1715
+ typecheck: "tsc --noEmit",
1716
+ lint: "biome lint .",
1717
+ fmt: "biome format . --write",
1718
+ "fmt:check": "biome format ."
1719
+ },
1720
+ files: (options) => {
1721
+ const { loggerType, routesStructure } = options;
1722
+ const loggerContent = `import { createLogger } from '@geekmidas/logger/${loggerType}';
1723
+
1724
+ export const logger = createLogger();
1725
+ `;
1726
+ const getRoutePath = (file) => {
1727
+ switch (routesStructure) {
1728
+ case "centralized-endpoints": return `src/endpoints/${file}`;
1729
+ case "centralized-routes": return `src/routes/${file}`;
1730
+ case "domain-based": {
1731
+ const parts = file.split("/");
1732
+ if (parts.length === 1) return `src/${file.replace(".ts", "")}/routes/index.ts`;
1733
+ return `src/${parts[0]}/routes/${parts.slice(1).join("/")}`;
1734
+ }
1735
+ }
1736
+ };
1737
+ const files = [
1738
+ {
1739
+ path: "src/config/env.ts",
1740
+ content: `import { EnvironmentParser } from '@geekmidas/envkit';
1741
+
1742
+ export const envParser = new EnvironmentParser(process.env);
1743
+
1744
+ export const config = envParser
1745
+ .create((get) => ({
1746
+ port: get('PORT').string().transform(Number).default(3000),
1747
+ nodeEnv: get('NODE_ENV').string().default('development'),
1748
+ jwtSecret: get('JWT_SECRET').string().default('change-me-in-production'),${options.database ? `
1749
+ database: {
1750
+ url: get('DATABASE_URL').string().default('postgresql://localhost:5432/mydb'),
1751
+ },` : ""}
1752
+ }))
1753
+ .parse();
1754
+ `
1755
+ },
1756
+ {
1757
+ path: "src/config/logger.ts",
1758
+ content: loggerContent
1759
+ },
1760
+ {
1761
+ path: getRoutePath("health.ts"),
1762
+ content: `import { e } from '@geekmidas/constructs/endpoints';
1763
+
1764
+ export default e
1765
+ .get('/health')
1766
+ .handle(async () => ({
1767
+ status: 'ok',
1768
+ timestamp: new Date().toISOString(),
1769
+ }));
1770
+ `
1771
+ },
1772
+ {
1773
+ path: getRoutePath("users/list.ts"),
1774
+ content: `import { e } from '@geekmidas/constructs/endpoints';
1775
+
1776
+ export default e
1777
+ .get('/users')
1778
+ .handle(async () => ({
1779
+ users: [
1780
+ { id: '1', name: 'Alice' },
1781
+ { id: '2', name: 'Bob' },
1782
+ ],
1783
+ }));
1784
+ `
1785
+ },
1786
+ {
1787
+ path: getRoutePath("users/get.ts"),
1788
+ content: `import { e } from '@geekmidas/constructs/endpoints';
1789
+ import { z } from 'zod';
1790
+
1791
+ export default e
1792
+ .get('/users/:id')
1793
+ .params(z.object({ id: z.string() }))
1794
+ .handle(async ({ params }) => ({
1795
+ id: params.id,
1796
+ name: 'Alice',
1797
+ email: 'alice@example.com',
1798
+ }));
1799
+ `
1800
+ }
1801
+ ];
1802
+ if (options.database) files.push({
1803
+ path: "src/services/database.ts",
1804
+ content: `import type { Service } from '@geekmidas/services';
1805
+ import { Kysely, PostgresDialect } from 'kysely';
1806
+ import pg from 'pg';
1807
+
1808
+ // Define your database schema
1809
+ export interface Database {
1810
+ users: {
1811
+ id: string;
1812
+ name: string;
1813
+ email: string;
1814
+ created_at: Date;
1815
+ };
1816
+ }
1817
+
1818
+ export const databaseService = {
1819
+ serviceName: 'database' as const,
1820
+ async register(envParser) {
1821
+ const config = envParser
1822
+ .create((get) => ({
1823
+ url: get('DATABASE_URL').string(),
1824
+ }))
1825
+ .parse();
1826
+
1827
+ return new Kysely<Database>({
1828
+ dialect: new PostgresDialect({
1829
+ pool: new pg.Pool({ connectionString: config.url }),
1830
+ }),
1831
+ });
1832
+ },
1833
+ } satisfies Service<'database', Kysely<Database>>;
1834
+ `
1835
+ });
1836
+ if (options.telescope) files.push({
1837
+ path: "src/config/telescope.ts",
1838
+ content: `import { Telescope } from '@geekmidas/telescope';
1839
+ import { InMemoryStorage } from '@geekmidas/telescope/storage/memory';
1840
+
1841
+ export const telescope = new Telescope({
1842
+ storage: new InMemoryStorage({ maxEntries: 100 }),
1843
+ enabled: process.env.NODE_ENV === 'development',
1844
+ });
1845
+ `
1846
+ });
1847
+ if (options.studio && options.database) files.push({
1848
+ path: "src/config/studio.ts",
1849
+ content: `import { Direction, InMemoryMonitoringStorage, Studio } from '@geekmidas/studio';
1850
+ import { Kysely, PostgresDialect } from 'kysely';
1851
+ import pg from 'pg';
1852
+ import type { Database } from '../services/database';
1853
+ import { config } from './env';
1854
+
1855
+ // Create a Kysely instance for Studio
1856
+ const db = new Kysely<Database>({
1857
+ dialect: new PostgresDialect({
1858
+ pool: new pg.Pool({ connectionString: config.database.url }),
1859
+ }),
1860
+ });
1861
+
1862
+ export const studio = new Studio<Database>({
1863
+ monitoring: {
1864
+ storage: new InMemoryMonitoringStorage({ maxEntries: 100 }),
1865
+ },
1866
+ data: {
1867
+ db,
1868
+ cursor: { field: 'id', direction: Direction.Desc },
1869
+ },
1870
+ enabled: process.env.NODE_ENV === 'development',
1871
+ });
1872
+ `
1873
+ });
1874
+ return files;
1875
+ }
1876
+ };
1877
+
1878
+ //#endregion
1879
+ //#region src/init/templates/minimal.ts
1880
+ const minimalTemplate = {
1881
+ name: "minimal",
1882
+ description: "Basic health endpoint",
1883
+ dependencies: {
1884
+ "@geekmidas/constructs": "workspace:*",
1885
+ "@geekmidas/envkit": "workspace:*",
1886
+ "@geekmidas/logger": "workspace:*",
1887
+ hono: "~4.8.2",
1888
+ pino: "~9.6.0"
1889
+ },
1890
+ devDependencies: {
1891
+ "@biomejs/biome": "~1.9.4",
1892
+ "@geekmidas/cli": "workspace:*",
1893
+ "@types/node": "~22.0.0",
1894
+ tsx: "~4.20.0",
1895
+ turbo: "~2.3.0",
1896
+ typescript: "~5.8.2",
1897
+ vitest: "~4.0.0"
1898
+ },
1899
+ scripts: {
1900
+ dev: "gkm dev",
1901
+ build: "gkm build",
1902
+ test: "vitest",
1903
+ "test:once": "vitest run",
1904
+ typecheck: "tsc --noEmit",
1905
+ lint: "biome lint .",
1906
+ fmt: "biome format . --write",
1907
+ "fmt:check": "biome format ."
1908
+ },
1909
+ files: (options) => {
1910
+ const { loggerType, routesStructure } = options;
1911
+ const loggerContent = `import { createLogger } from '@geekmidas/logger/${loggerType}';
1912
+
1913
+ export const logger = createLogger();
1914
+ `;
1915
+ const getRoutePath = (file) => {
1916
+ switch (routesStructure) {
1917
+ case "centralized-endpoints": return `src/endpoints/${file}`;
1918
+ case "centralized-routes": return `src/routes/${file}`;
1919
+ case "domain-based": return `src/${file.replace(".ts", "")}/routes/index.ts`;
1920
+ }
1921
+ };
1922
+ const files = [
1923
+ {
1924
+ path: "src/config/env.ts",
1925
+ content: `import { EnvironmentParser } from '@geekmidas/envkit';
1926
+
1927
+ export const envParser = new EnvironmentParser(process.env);
1928
+
1929
+ export const config = envParser
1930
+ .create((get) => ({
1931
+ port: get('PORT').string().transform(Number).default(3000),
1932
+ nodeEnv: get('NODE_ENV').string().default('development'),
1933
+ }))
1934
+ .parse();
1935
+ `
1936
+ },
1937
+ {
1938
+ path: "src/config/logger.ts",
1939
+ content: loggerContent
1940
+ },
1941
+ {
1942
+ path: getRoutePath("health.ts"),
1943
+ content: `import { e } from '@geekmidas/constructs/endpoints';
1944
+
1945
+ export default e
1946
+ .get('/health')
1947
+ .handle(async () => ({
1948
+ status: 'ok',
1949
+ timestamp: new Date().toISOString(),
1950
+ }));
1951
+ `
1952
+ }
1953
+ ];
1954
+ if (options.database) {
1955
+ files[0] = {
1956
+ path: "src/config/env.ts",
1957
+ content: `import { EnvironmentParser } from '@geekmidas/envkit';
1958
+
1959
+ export const envParser = new EnvironmentParser(process.env);
1960
+
1961
+ export const config = envParser
1962
+ .create((get) => ({
1963
+ port: get('PORT').string().transform(Number).default(3000),
1964
+ nodeEnv: get('NODE_ENV').string().default('development'),
1965
+ database: {
1966
+ url: get('DATABASE_URL').string().default('postgresql://localhost:5432/mydb'),
1967
+ },
1968
+ }))
1969
+ .parse();
1970
+ `
1971
+ };
1972
+ files.push({
1973
+ path: "src/services/database.ts",
1974
+ content: `import type { Service } from '@geekmidas/services';
1975
+ import { Kysely, PostgresDialect } from 'kysely';
1976
+ import pg from 'pg';
1977
+
1978
+ // Define your database schema
1979
+ export interface Database {
1980
+ // Add your tables here
1981
+ }
1982
+
1983
+ export const databaseService = {
1984
+ serviceName: 'database' as const,
1985
+ async register(envParser) {
1986
+ const config = envParser
1987
+ .create((get) => ({
1988
+ url: get('DATABASE_URL').string(),
1989
+ }))
1990
+ .parse();
1991
+
1992
+ return new Kysely<Database>({
1993
+ dialect: new PostgresDialect({
1994
+ pool: new pg.Pool({ connectionString: config.url }),
1995
+ }),
1996
+ });
1997
+ },
1998
+ } satisfies Service<'database', Kysely<Database>>;
1999
+ `
2000
+ });
2001
+ }
2002
+ if (options.telescope) files.push({
2003
+ path: "src/config/telescope.ts",
2004
+ content: `import { Telescope } from '@geekmidas/telescope';
2005
+ import { InMemoryStorage } from '@geekmidas/telescope/storage/memory';
2006
+
2007
+ export const telescope = new Telescope({
2008
+ storage: new InMemoryStorage({ maxEntries: 100 }),
2009
+ enabled: process.env.NODE_ENV === 'development',
2010
+ });
2011
+ `
2012
+ });
2013
+ if (options.studio && options.database) files.push({
2014
+ path: "src/config/studio.ts",
2015
+ content: `import { Direction, InMemoryMonitoringStorage, Studio } from '@geekmidas/studio';
2016
+ import { Kysely, PostgresDialect } from 'kysely';
2017
+ import pg from 'pg';
2018
+ import type { Database } from '../services/database';
2019
+ import { config } from './env';
2020
+
2021
+ // Create a Kysely instance for Studio
2022
+ const db = new Kysely<Database>({
2023
+ dialect: new PostgresDialect({
2024
+ pool: new pg.Pool({ connectionString: config.database.url }),
2025
+ }),
2026
+ });
2027
+
2028
+ export const studio = new Studio<Database>({
2029
+ monitoring: {
2030
+ storage: new InMemoryMonitoringStorage({ maxEntries: 100 }),
2031
+ },
2032
+ data: {
2033
+ db,
2034
+ cursor: { field: 'id', direction: Direction.Desc },
2035
+ },
2036
+ enabled: process.env.NODE_ENV === 'development',
2037
+ });
2038
+ `
2039
+ });
2040
+ return files;
2041
+ }
2042
+ };
2043
+
2044
+ //#endregion
2045
+ //#region src/init/templates/serverless.ts
2046
+ const serverlessTemplate = {
2047
+ name: "serverless",
2048
+ description: "AWS Lambda handlers",
2049
+ dependencies: {
2050
+ "@geekmidas/constructs": "workspace:*",
2051
+ "@geekmidas/envkit": "workspace:*",
2052
+ "@geekmidas/logger": "workspace:*",
2053
+ "@geekmidas/cloud": "workspace:*",
2054
+ hono: "~4.8.2",
2055
+ pino: "~9.6.0"
2056
+ },
2057
+ devDependencies: {
2058
+ "@biomejs/biome": "~1.9.4",
2059
+ "@geekmidas/cli": "workspace:*",
2060
+ "@types/aws-lambda": "~8.10.92",
2061
+ "@types/node": "~22.0.0",
2062
+ tsx: "~4.20.0",
2063
+ turbo: "~2.3.0",
2064
+ typescript: "~5.8.2",
2065
+ vitest: "~4.0.0"
2066
+ },
2067
+ scripts: {
2068
+ dev: "gkm dev",
2069
+ build: "gkm build --provider aws-apigatewayv2",
2070
+ test: "vitest",
2071
+ "test:once": "vitest run",
2072
+ typecheck: "tsc --noEmit",
2073
+ lint: "biome lint .",
2074
+ fmt: "biome format . --write",
2075
+ "fmt:check": "biome format ."
2076
+ },
2077
+ files: (options) => {
2078
+ const { loggerType, routesStructure } = options;
2079
+ const loggerContent = `import { createLogger } from '@geekmidas/logger/${loggerType}';
2080
+
2081
+ export const logger = createLogger();
2082
+ `;
2083
+ const getRoutePath = (file) => {
2084
+ switch (routesStructure) {
2085
+ case "centralized-endpoints": return `src/endpoints/${file}`;
2086
+ case "centralized-routes": return `src/routes/${file}`;
2087
+ case "domain-based": return `src/${file.replace(".ts", "")}/routes/index.ts`;
2088
+ }
2089
+ };
2090
+ const files = [
2091
+ {
2092
+ path: "src/config/env.ts",
2093
+ content: `import { EnvironmentParser } from '@geekmidas/envkit';
2094
+
2095
+ export const envParser = new EnvironmentParser(process.env);
2096
+
2097
+ export const config = envParser
2098
+ .create((get) => ({
2099
+ stage: get('STAGE').string().default('dev'),
2100
+ region: get('AWS_REGION').string().default('us-east-1'),${options.database ? `
2101
+ database: {
2102
+ url: get('DATABASE_URL').string(),
2103
+ },` : ""}
2104
+ }))
2105
+ .parse();
2106
+ `
2107
+ },
2108
+ {
2109
+ path: "src/config/logger.ts",
2110
+ content: loggerContent
2111
+ },
2112
+ {
2113
+ path: getRoutePath("health.ts"),
2114
+ content: `import { e } from '@geekmidas/constructs/endpoints';
2115
+
2116
+ export default e
2117
+ .get('/health')
2118
+ .handle(async () => ({
2119
+ status: 'ok',
2120
+ timestamp: new Date().toISOString(),
2121
+ region: process.env.AWS_REGION || 'local',
2122
+ }));
2123
+ `
2124
+ },
2125
+ {
2126
+ path: "src/functions/hello.ts",
2127
+ content: `import { f } from '@geekmidas/constructs/functions';
2128
+ import { z } from 'zod';
2129
+
2130
+ export default f
2131
+ .input(z.object({ name: z.string() }))
2132
+ .output(z.object({ message: z.string() }))
2133
+ .handle(async ({ input }) => ({
2134
+ message: \`Hello, \${input.name}!\`,
2135
+ }));
2136
+ `
2137
+ }
2138
+ ];
2139
+ if (options.telescope) files.push({
2140
+ path: "src/config/telescope.ts",
2141
+ content: `import { Telescope } from '@geekmidas/telescope';
2142
+ import { InMemoryStorage } from '@geekmidas/telescope/storage/memory';
2143
+
2144
+ // Note: For production Lambda, consider using a persistent storage
2145
+ export const telescope = new Telescope({
2146
+ storage: new InMemoryStorage({ maxEntries: 50 }),
2147
+ enabled: process.env.STAGE === 'dev',
2148
+ });
2149
+ `
2150
+ });
2151
+ return files;
2152
+ }
2153
+ };
2154
+
2155
+ //#endregion
2156
+ //#region src/init/templates/worker.ts
2157
+ const workerTemplate = {
2158
+ name: "worker",
2159
+ description: "Background job processing",
2160
+ dependencies: {
2161
+ "@geekmidas/constructs": "workspace:*",
2162
+ "@geekmidas/envkit": "workspace:*",
2163
+ "@geekmidas/logger": "workspace:*",
2164
+ "@geekmidas/events": "workspace:*",
2165
+ hono: "~4.8.2",
2166
+ pino: "~9.6.0"
2167
+ },
2168
+ devDependencies: {
2169
+ "@biomejs/biome": "~1.9.4",
2170
+ "@geekmidas/cli": "workspace:*",
2171
+ "@types/node": "~22.0.0",
2172
+ tsx: "~4.20.0",
2173
+ turbo: "~2.3.0",
2174
+ typescript: "~5.8.2",
2175
+ vitest: "~4.0.0"
2176
+ },
2177
+ scripts: {
2178
+ dev: "gkm dev",
2179
+ build: "gkm build",
2180
+ test: "vitest",
2181
+ "test:once": "vitest run",
2182
+ typecheck: "tsc --noEmit",
2183
+ lint: "biome lint .",
2184
+ fmt: "biome format . --write",
2185
+ "fmt:check": "biome format ."
2186
+ },
2187
+ files: (options) => {
2188
+ const { loggerType, routesStructure } = options;
2189
+ const loggerContent = `import { createLogger } from '@geekmidas/logger/${loggerType}';
2190
+
2191
+ export const logger = createLogger();
2192
+ `;
2193
+ const getRoutePath = (file) => {
2194
+ switch (routesStructure) {
2195
+ case "centralized-endpoints": return `src/endpoints/${file}`;
2196
+ case "centralized-routes": return `src/routes/${file}`;
2197
+ case "domain-based": return `src/${file.replace(".ts", "")}/routes/index.ts`;
2198
+ }
2199
+ };
2200
+ const files = [
2201
+ {
2202
+ path: "src/config/env.ts",
2203
+ content: `import { EnvironmentParser } from '@geekmidas/envkit';
2204
+
2205
+ export const envParser = new EnvironmentParser(process.env);
2206
+
2207
+ export const config = envParser
2208
+ .create((get) => ({
2209
+ port: get('PORT').string().transform(Number).default(3000),
2210
+ nodeEnv: get('NODE_ENV').string().default('development'),
2211
+ rabbitmq: {
2212
+ url: get('RABBITMQ_URL').string().default('amqp://localhost:5672'),
2213
+ },${options.database ? `
2214
+ database: {
2215
+ url: get('DATABASE_URL').string().default('postgresql://localhost:5432/mydb'),
2216
+ },` : ""}
2217
+ }))
2218
+ .parse();
2219
+ `
2220
+ },
2221
+ {
2222
+ path: "src/config/logger.ts",
2223
+ content: loggerContent
2224
+ },
2225
+ {
2226
+ path: getRoutePath("health.ts"),
2227
+ content: `import { e } from '@geekmidas/constructs/endpoints';
2228
+
2229
+ export default e
2230
+ .get('/health')
2231
+ .handle(async () => ({
2232
+ status: 'ok',
2233
+ timestamp: new Date().toISOString(),
2234
+ }));
2235
+ `
2236
+ },
2237
+ {
2238
+ path: "src/events/types.ts",
2239
+ content: `import type { PublishableMessage } from '@geekmidas/events';
2240
+
2241
+ // Define your event types here
2242
+ export type AppEvents =
2243
+ | PublishableMessage<'user.created', { userId: string; email: string }>
2244
+ | PublishableMessage<'user.updated', { userId: string; changes: Record<string, unknown> }>
2245
+ | PublishableMessage<'order.placed', { orderId: string; userId: string; total: number }>;
2246
+ `
2247
+ },
2248
+ {
2249
+ path: "src/subscribers/user-events.ts",
2250
+ content: `import { s } from '@geekmidas/constructs/subscribers';
2251
+ import type { AppEvents } from '../events/types.js';
2252
+
2253
+ export default s<AppEvents>()
2254
+ .events(['user.created', 'user.updated'])
2255
+ .handle(async ({ event, logger }) => {
2256
+ logger.info({ type: event.type, payload: event.payload }, 'Processing user event');
2257
+
2258
+ switch (event.type) {
2259
+ case 'user.created':
2260
+ // Handle user creation
2261
+ logger.info({ userId: event.payload.userId }, 'New user created');
2262
+ break;
2263
+ case 'user.updated':
2264
+ // Handle user update
2265
+ logger.info({ userId: event.payload.userId }, 'User updated');
2266
+ break;
2267
+ }
2268
+ });
2269
+ `
2270
+ },
2271
+ {
2272
+ path: "src/crons/cleanup.ts",
2273
+ content: `import { cron } from '@geekmidas/constructs/crons';
2274
+
2275
+ // Run every day at midnight
2276
+ export default cron('0 0 * * *')
2277
+ .handle(async ({ logger }) => {
2278
+ logger.info('Running cleanup job');
2279
+
2280
+ // Add your cleanup logic here
2281
+ // e.g., delete old sessions, clean up temp files, etc.
2282
+
2283
+ logger.info('Cleanup job completed');
2284
+ });
2285
+ `
2286
+ }
2287
+ ];
2288
+ if (options.telescope) files.push({
2289
+ path: "src/config/telescope.ts",
2290
+ content: `import { Telescope } from '@geekmidas/telescope';
2291
+ import { InMemoryStorage } from '@geekmidas/telescope/storage/memory';
2292
+
2293
+ export const telescope = new Telescope({
2294
+ storage: new InMemoryStorage({ maxEntries: 100 }),
2295
+ enabled: process.env.NODE_ENV === 'development',
2296
+ });
2297
+ `
2298
+ });
2299
+ return files;
2300
+ }
2301
+ };
2302
+
2303
+ //#endregion
2304
+ //#region src/init/templates/index.ts
2305
+ /**
2306
+ * OpenAPI output path (fixed, not configurable)
2307
+ */
2308
+ const OPENAPI_OUTPUT_PATH$1 = "./.gkm/openapi.ts";
2309
+ /**
2310
+ * All available templates
2311
+ */
2312
+ const templates = {
2313
+ minimal: minimalTemplate,
2314
+ api: apiTemplate,
2315
+ serverless: serverlessTemplate,
2316
+ worker: workerTemplate
2317
+ };
2318
+ /**
2319
+ * Template choices for prompts
2320
+ */
2321
+ const templateChoices = [
2322
+ {
2323
+ title: "Minimal",
2324
+ value: "minimal",
2325
+ description: "Basic health endpoint"
2326
+ },
2327
+ {
2328
+ title: "API",
2329
+ value: "api",
2330
+ description: "Full API with auth, database, services"
2331
+ },
2332
+ {
2333
+ title: "Serverless",
2334
+ value: "serverless",
2335
+ description: "AWS Lambda handlers"
2336
+ },
2337
+ {
2338
+ title: "Worker",
2339
+ value: "worker",
2340
+ description: "Background job processing"
2341
+ }
2342
+ ];
2343
+ /**
2344
+ * Logger type choices for prompts
2345
+ */
2346
+ const loggerTypeChoices = [{
2347
+ title: "Pino",
2348
+ value: "pino",
2349
+ description: "Fast JSON logger for production (recommended)"
2350
+ }, {
2351
+ title: "Console",
2352
+ value: "console",
2353
+ description: "Simple console logger for development"
2354
+ }];
2355
+ /**
2356
+ * Routes structure choices for prompts
2357
+ */
2358
+ const routesStructureChoices = [
2359
+ {
2360
+ title: "Centralized (endpoints)",
2361
+ value: "centralized-endpoints",
2362
+ description: "src/endpoints/**/*.ts"
2363
+ },
2364
+ {
2365
+ title: "Centralized (routes)",
2366
+ value: "centralized-routes",
2367
+ description: "src/routes/**/*.ts"
2368
+ },
2369
+ {
2370
+ title: "Domain-based",
2371
+ value: "domain-based",
2372
+ description: "src/**/routes/*.ts (e.g., src/users/routes/list.ts)"
2373
+ }
2374
+ ];
2375
+ /**
2376
+ * Get a template by name
2377
+ */
2378
+ function getTemplate(name$1) {
2379
+ const template = templates[name$1];
2380
+ if (!template) throw new Error(`Unknown template: ${name$1}`);
2381
+ return template;
2382
+ }
2383
+
2384
+ //#endregion
2385
+ //#region src/init/generators/package.ts
2386
+ /**
2387
+ * Generate package.json with dependencies based on template and options
2388
+ */
2389
+ function generatePackageJson(options, template) {
2390
+ const { name: name$1, telescope, database, studio, monorepo } = options;
2391
+ const dependencies$1 = { ...template.dependencies };
2392
+ const devDependencies$1 = { ...template.devDependencies };
2393
+ const scripts$1 = { ...template.scripts };
2394
+ if (telescope) dependencies$1["@geekmidas/telescope"] = "workspace:*";
2395
+ if (studio) dependencies$1["@geekmidas/studio"] = "workspace:*";
2396
+ if (database) {
2397
+ dependencies$1["@geekmidas/db"] = "workspace:*";
2398
+ dependencies$1["kysely"] = "~0.28.2";
2399
+ dependencies$1["pg"] = "~8.16.0";
2400
+ devDependencies$1["@types/pg"] = "~8.15.0";
2401
+ }
2402
+ dependencies$1["zod"] = "~4.1.0";
2403
+ if (monorepo) {
2404
+ delete devDependencies$1["@biomejs/biome"];
2405
+ delete devDependencies$1["turbo"];
2406
+ delete scripts$1["lint"];
2407
+ delete scripts$1["fmt"];
2408
+ delete scripts$1["fmt:check"];
2409
+ dependencies$1[`@${name$1}/models`] = "workspace:*";
2410
+ delete dependencies$1["zod"];
2411
+ }
2412
+ const sortObject = (obj) => Object.fromEntries(Object.entries(obj).sort(([a], [b]) => a.localeCompare(b)));
2413
+ let packageName = name$1;
2414
+ if (monorepo && options.apiPath) {
2415
+ const pathParts = options.apiPath.split("/");
2416
+ const appName = pathParts[pathParts.length - 1] || "api";
2417
+ packageName = `@${name$1}/${appName}`;
2418
+ }
2419
+ const packageJson = {
2420
+ name: packageName,
2421
+ version: "0.0.1",
2422
+ private: true,
2423
+ type: "module",
2424
+ exports: { "./client": {
2425
+ types: OPENAPI_OUTPUT_PATH$1,
2426
+ import: OPENAPI_OUTPUT_PATH$1
2427
+ } },
2428
+ scripts: scripts$1,
2429
+ dependencies: sortObject(dependencies$1),
2430
+ devDependencies: sortObject(devDependencies$1)
2431
+ };
2432
+ return [{
2433
+ path: "package.json",
2434
+ content: JSON.stringify(packageJson, null, 2) + "\n"
2435
+ }];
2436
+ }
2437
+
2438
+ //#endregion
2439
+ //#region src/init/generators/source.ts
2440
+ /**
2441
+ * Generate source files from template
2442
+ */
2443
+ function generateSourceFiles(options, template) {
2444
+ return template.files(options);
2445
+ }
2446
+
2447
+ //#endregion
2448
+ //#region src/init/utils.ts
2449
+ /**
2450
+ * Detect the package manager being used based on lockfiles or npm_config_user_agent
2451
+ */
2452
+ function detectPackageManager(cwd = process.cwd()) {
2453
+ if ((0, node_fs.existsSync)((0, node_path.join)(cwd, "pnpm-lock.yaml"))) return "pnpm";
2454
+ if ((0, node_fs.existsSync)((0, node_path.join)(cwd, "yarn.lock"))) return "yarn";
2455
+ if ((0, node_fs.existsSync)((0, node_path.join)(cwd, "bun.lockb"))) return "bun";
2456
+ if ((0, node_fs.existsSync)((0, node_path.join)(cwd, "package-lock.json"))) return "npm";
2457
+ const userAgent = process.env.npm_config_user_agent || "";
2458
+ if (userAgent.includes("pnpm")) return "pnpm";
2459
+ if (userAgent.includes("yarn")) return "yarn";
2460
+ if (userAgent.includes("bun")) return "bun";
2461
+ return "npm";
2462
+ }
2463
+ /**
2464
+ * Validate project name for npm package naming conventions
2465
+ */
2466
+ function validateProjectName(name$1) {
2467
+ if (!name$1) return "Project name is required";
2468
+ if (!/^[a-z0-9-_@/.]+$/i.test(name$1)) return "Project name can only contain letters, numbers, hyphens, underscores, @, /, and .";
2469
+ const reserved = [
2470
+ "node_modules",
2471
+ ".git",
2472
+ "package.json",
2473
+ "src"
2474
+ ];
2475
+ if (reserved.includes(name$1.toLowerCase())) return `"${name$1}" is a reserved name`;
2476
+ return true;
2477
+ }
2478
+ /**
2479
+ * Check if a directory already exists at the target path
2480
+ */
2481
+ function checkDirectoryExists(name$1, cwd = process.cwd()) {
2482
+ const targetPath = (0, node_path.join)(cwd, name$1);
2483
+ if ((0, node_fs.existsSync)(targetPath)) return `Directory "${name$1}" already exists`;
2484
+ return true;
2485
+ }
2486
+ /**
2487
+ * Get the install command for a package manager
2488
+ */
2489
+ function getInstallCommand(pkgManager) {
2490
+ switch (pkgManager) {
2491
+ case "pnpm": return "pnpm install";
2492
+ case "yarn": return "yarn";
2493
+ case "bun": return "bun install";
2494
+ case "npm":
2495
+ default: return "npm install";
2496
+ }
2497
+ }
2498
+ /**
2499
+ * Get the dev command for a package manager
2500
+ */
2501
+ function getRunCommand(pkgManager, script) {
2502
+ switch (pkgManager) {
2503
+ case "pnpm": return `pnpm ${script}`;
2504
+ case "yarn": return `yarn ${script}`;
2505
+ case "bun": return `bun run ${script}`;
2506
+ case "npm":
2507
+ default: return `npm run ${script}`;
2508
+ }
2509
+ }
2510
+
2511
+ //#endregion
2512
+ //#region src/init/index.ts
2513
+ /**
2514
+ * Main init command - scaffolds a new project
2515
+ */
2516
+ async function initCommand(projectName, options = {}) {
2517
+ const cwd = process.cwd();
2518
+ const pkgManager = detectPackageManager(cwd);
2519
+ prompts.default.override({});
2520
+ const onCancel = () => {
2521
+ process.exit(0);
2522
+ };
2523
+ const answers = await (0, prompts.default)([
2524
+ {
2525
+ type: projectName ? null : "text",
2526
+ name: "name",
2527
+ message: "Project name:",
2528
+ initial: "my-api",
2529
+ validate: (value) => {
2530
+ const nameValid = validateProjectName(value);
2531
+ if (nameValid !== true) return nameValid;
2532
+ const dirValid = checkDirectoryExists(value, cwd);
2533
+ if (dirValid !== true) return dirValid;
2534
+ return true;
2535
+ }
2536
+ },
2537
+ {
2538
+ type: options.template || options.yes ? null : "select",
2539
+ name: "template",
2540
+ message: "Template:",
2541
+ choices: templateChoices,
2542
+ initial: 0
2543
+ },
2544
+ {
2545
+ type: options.yes ? null : "confirm",
2546
+ name: "telescope",
2547
+ message: "Include Telescope (debugging dashboard)?",
2548
+ initial: true
2549
+ },
2550
+ {
2551
+ type: options.yes ? null : "confirm",
2552
+ name: "database",
2553
+ message: "Include database support (Kysely)?",
2554
+ initial: true
2555
+ },
2556
+ {
2557
+ type: (prev) => options.yes ? null : prev ? "confirm" : null,
2558
+ name: "studio",
2559
+ message: "Include Studio (database browser)?",
2560
+ initial: true
2561
+ },
2562
+ {
2563
+ type: options.yes ? null : "select",
2564
+ name: "loggerType",
2565
+ message: "Logger:",
2566
+ choices: loggerTypeChoices,
2567
+ initial: 0
2568
+ },
2569
+ {
2570
+ type: options.yes ? null : "select",
2571
+ name: "routesStructure",
2572
+ message: "Routes structure:",
2573
+ choices: routesStructureChoices,
2574
+ initial: 0
2575
+ },
2576
+ {
2577
+ type: options.yes || options.monorepo !== void 0 ? null : "confirm",
2578
+ name: "monorepo",
2579
+ message: "Setup as monorepo?",
2580
+ initial: false
2581
+ },
2582
+ {
2583
+ type: (prev) => (prev === true || options.monorepo) && !options.apiPath ? "text" : null,
2584
+ name: "apiPath",
2585
+ message: "API app path:",
2586
+ initial: "apps/api"
2587
+ }
2588
+ ], { onCancel });
2589
+ const name$1 = projectName || answers.name;
2590
+ if (!name$1) {
2591
+ console.error(" Error: Project name is required\n");
2592
+ process.exit(1);
2593
+ }
2594
+ if (projectName) {
2595
+ const nameValid = validateProjectName(projectName);
2596
+ if (nameValid !== true) {
2597
+ console.error(` Error: ${nameValid}\n`);
2598
+ process.exit(1);
2599
+ }
2600
+ const dirValid = checkDirectoryExists(projectName, cwd);
2601
+ if (dirValid !== true) {
2602
+ console.error(` Error: ${dirValid}\n`);
2603
+ process.exit(1);
2604
+ }
2605
+ }
2606
+ const monorepo = options.monorepo ?? (options.yes ? false : answers.monorepo ?? false);
2607
+ const database = options.yes ? true : answers.database ?? true;
2608
+ const templateOptions = {
2609
+ name: name$1,
2610
+ template: options.template || answers.template || "minimal",
2611
+ telescope: options.yes ? true : answers.telescope ?? true,
2612
+ database,
2613
+ studio: database && (options.yes ? true : answers.studio ?? true),
2614
+ loggerType: options.yes ? "pino" : answers.loggerType ?? "pino",
2615
+ routesStructure: options.yes ? "centralized-endpoints" : answers.routesStructure ?? "centralized-endpoints",
2616
+ monorepo,
2617
+ apiPath: monorepo ? options.apiPath ?? answers.apiPath ?? "apps/api" : ""
2618
+ };
2619
+ const targetDir = (0, node_path.join)(cwd, name$1);
2620
+ const template = getTemplate(templateOptions.template);
2621
+ const isMonorepo = templateOptions.monorepo;
2622
+ const apiPath = templateOptions.apiPath;
2623
+ await (0, node_fs_promises.mkdir)(targetDir, { recursive: true });
2624
+ const appDir = isMonorepo ? (0, node_path.join)(targetDir, apiPath) : targetDir;
2625
+ if (isMonorepo) await (0, node_fs_promises.mkdir)(appDir, { recursive: true });
2626
+ const appFiles = [
2627
+ ...generatePackageJson(templateOptions, template),
2628
+ ...generateConfigFiles(templateOptions, template),
2629
+ ...generateEnvFiles(templateOptions, template),
2630
+ ...generateSourceFiles(templateOptions, template),
2631
+ ...generateDockerFiles(templateOptions, template)
2632
+ ];
2633
+ const rootFiles = [...generateMonorepoFiles(templateOptions, template), ...generateModelsPackage(templateOptions)];
2634
+ for (const { path: path$1, content } of rootFiles) {
2635
+ const fullPath = (0, node_path.join)(targetDir, path$1);
2636
+ await (0, node_fs_promises.mkdir)((0, node_path.dirname)(fullPath), { recursive: true });
2637
+ await (0, node_fs_promises.writeFile)(fullPath, content);
2638
+ }
2639
+ for (const { path: path$1, content } of appFiles) {
2640
+ const fullPath = (0, node_path.join)(appDir, path$1);
2641
+ const displayPath = isMonorepo ? `${apiPath}/${path$1}` : path$1;
2642
+ await (0, node_fs_promises.mkdir)((0, node_path.dirname)(fullPath), { recursive: true });
2643
+ await (0, node_fs_promises.writeFile)(fullPath, content);
2644
+ }
2645
+ if (!options.skipInstall) {
2646
+ try {
2647
+ (0, node_child_process.execSync)(getInstallCommand(pkgManager), {
2648
+ cwd: targetDir,
2649
+ stdio: "inherit"
2650
+ });
2651
+ } catch {
2652
+ console.error("\n Warning: Failed to install dependencies.");
2653
+ }
2654
+ try {
2655
+ (0, node_child_process.execSync)("npx @biomejs/biome format --write --unsafe .", {
2656
+ cwd: targetDir,
2657
+ stdio: "inherit"
2658
+ });
2659
+ } catch {}
2660
+ }
2661
+ const devCommand$1 = getRunCommand(pkgManager, "dev");
2662
+ }
2663
+
115
2664
  //#endregion
116
2665
  //#region src/index.ts
117
2666
  const program = new commander.Command();
@@ -120,7 +2669,7 @@ program.command("init").description("Scaffold a new project").argument("[name]",
120
2669
  try {
121
2670
  const globalOptions = program.opts();
122
2671
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
123
- await require_init.initCommand(name$1, options);
2672
+ await initCommand(name$1, options);
124
2673
  } catch (error) {
125
2674
  console.error("Init failed:", error.message);
126
2675
  process.exit(1);
@@ -135,18 +2684,18 @@ program.command("build").description("Build handlers from endpoints, functions,
135
2684
  console.error(`Invalid provider: ${options.provider}. Must be 'aws' or 'server'.`);
136
2685
  process.exit(1);
137
2686
  }
138
- await require_build.buildCommand({
2687
+ await buildCommand({
139
2688
  provider: options.provider,
140
2689
  enableOpenApi: options.enableOpenapi || false
141
2690
  });
142
2691
  } else if (options.providers) {
143
2692
  console.warn("⚠️ --providers flag is deprecated. Use --provider instead.");
144
2693
  const providerList = [...new Set(options.providers.split(",").map((p) => p.trim()))];
145
- await require_build.buildCommand({
2694
+ await buildCommand({
146
2695
  providers: providerList,
147
2696
  enableOpenApi: options.enableOpenapi || false
148
2697
  });
149
- } else await require_build.buildCommand({ enableOpenApi: options.enableOpenapi || false });
2698
+ } else await buildCommand({ enableOpenApi: options.enableOpenapi || false });
150
2699
  } catch (error) {
151
2700
  console.error("Build failed:", error.message);
152
2701
  process.exit(1);
@@ -156,7 +2705,7 @@ program.command("dev").description("Start development server with automatic relo
156
2705
  try {
157
2706
  const globalOptions = program.opts();
158
2707
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
159
- await require_dev.devCommand({
2708
+ await devCommand({
160
2709
  port: options.port ? Number.parseInt(options.port) : 3e3,
161
2710
  enableOpenApi: options.enableOpenapi ?? true
162
2711
  });