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