@geekmidas/cli 0.4.0 → 0.5.1

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 (299) hide show
  1. package/dist/CronGenerator-BPTqNYOR.d.cts +14 -0
  2. package/dist/{CronGenerator-Bh26MaNA.mjs → CronGenerator-CCRYptuT.mjs} +2 -2
  3. package/dist/{CronGenerator-Bh26MaNA.mjs.map → CronGenerator-CCRYptuT.mjs.map} +1 -1
  4. package/dist/{CronGenerator-C6MF8rlG.cjs → CronGenerator-D4TWXQbh.cjs} +2 -2
  5. package/dist/{CronGenerator-C6MF8rlG.cjs.map → CronGenerator-D4TWXQbh.cjs.map} +1 -1
  6. package/dist/CronGenerator-YAj59JUd.d.mts +14 -0
  7. package/dist/EndpointGenerator-ChAD1INz.d.cts +19 -0
  8. package/dist/EndpointGenerator-Cj3O1U8-.d.mts +19 -0
  9. package/dist/{EndpointGenerator-CzDhG7Or.mjs → EndpointGenerator-DGivkPLT.mjs} +22 -6
  10. package/dist/EndpointGenerator-DGivkPLT.mjs.map +1 -0
  11. package/dist/{EndpointGenerator-BxNCkus4.cjs → EndpointGenerator-npWEDoK2.cjs} +22 -6
  12. package/dist/EndpointGenerator-npWEDoK2.cjs.map +1 -0
  13. package/dist/FunctionGenerator-429-9NER.d.cts +14 -0
  14. package/dist/FunctionGenerator-BQ4ehoID.d.mts +14 -0
  15. package/dist/{FunctionGenerator-BNE_GC7N.mjs → FunctionGenerator-CVk0h8tO.mjs} +2 -2
  16. package/dist/{FunctionGenerator-BNE_GC7N.mjs.map → FunctionGenerator-CVk0h8tO.mjs.map} +1 -1
  17. package/dist/{FunctionGenerator-FgZUTd8L.cjs → FunctionGenerator-DYTnyr4c.cjs} +2 -2
  18. package/dist/{FunctionGenerator-FgZUTd8L.cjs.map → FunctionGenerator-DYTnyr4c.cjs.map} +1 -1
  19. package/dist/Generator-BjHK_qce.d.mts +27 -0
  20. package/dist/{Generator-UanJW0_V.mjs → Generator-CDt4pB3W.mjs} +1 -1
  21. package/dist/{Generator-UanJW0_V.mjs.map → Generator-CDt4pB3W.mjs.map} +1 -1
  22. package/dist/{Generator-CDoEXCDg.cjs → Generator-CLVplqm2.cjs} +1 -1
  23. package/dist/{Generator-CDoEXCDg.cjs.map → Generator-CLVplqm2.cjs.map} +1 -1
  24. package/dist/Generator-DxQMCQp7.d.cts +27 -0
  25. package/dist/{OpenApiTsGenerator-NBNEoaeO.cjs → OpenApiTsGenerator-Be-sKGTT.cjs} +1 -1
  26. package/dist/{OpenApiTsGenerator-NBNEoaeO.cjs.map → OpenApiTsGenerator-Be-sKGTT.cjs.map} +1 -1
  27. package/dist/{OpenApiTsGenerator-q3aWNkuM.mjs → OpenApiTsGenerator-C4mHHaku.mjs} +1 -1
  28. package/dist/{OpenApiTsGenerator-q3aWNkuM.mjs.map → OpenApiTsGenerator-C4mHHaku.mjs.map} +1 -1
  29. package/dist/SubscriberGenerator-7uX42xyG.d.mts +15 -0
  30. package/dist/{SubscriberGenerator-Dnlj_1FK.mjs → SubscriberGenerator-DABaJXML.mjs} +2 -2
  31. package/dist/{SubscriberGenerator-Dnlj_1FK.mjs.map → SubscriberGenerator-DABaJXML.mjs.map} +1 -1
  32. package/dist/{SubscriberGenerator-Bd-a7aiw.cjs → SubscriberGenerator-D_zpNGFr.cjs} +2 -2
  33. package/dist/{SubscriberGenerator-Bd-a7aiw.cjs.map → SubscriberGenerator-D_zpNGFr.cjs.map} +1 -1
  34. package/dist/SubscriberGenerator-Dtb3HS4i.d.cts +15 -0
  35. package/dist/api-B3SCEHPf.cjs +190 -0
  36. package/dist/api-B3SCEHPf.cjs.map +1 -0
  37. package/dist/api-BKIN0s0S.mjs +184 -0
  38. package/dist/api-BKIN0s0S.mjs.map +1 -0
  39. package/dist/build/index.cjs +11 -10
  40. package/dist/build/index.d.cts +7 -0
  41. package/dist/build/index.d.mts +7 -0
  42. package/dist/build/index.mjs +11 -10
  43. package/dist/build/manifests.cjs +1 -1
  44. package/dist/build/manifests.d.cts +13 -0
  45. package/dist/build/manifests.d.mts +13 -0
  46. package/dist/build/manifests.mjs +1 -1
  47. package/dist/build/providerResolver.cjs +1 -1
  48. package/dist/build/providerResolver.d.cts +23 -0
  49. package/dist/build/providerResolver.d.mts +23 -0
  50. package/dist/build/providerResolver.mjs +1 -1
  51. package/dist/build/types.d.cts +3 -0
  52. package/dist/build/types.d.mts +3 -0
  53. package/dist/{build-DyDgu_D1.mjs → build-B8C_qHir.mjs} +15 -13
  54. package/dist/build-B8C_qHir.mjs.map +1 -0
  55. package/dist/{build-CWtHnJMQ.cjs → build-D0Wr49bf.cjs} +15 -13
  56. package/dist/build-D0Wr49bf.cjs.map +1 -0
  57. package/dist/config-Ba-Gbpbc.d.cts +11 -0
  58. package/dist/config-Bq72aj8e.mjs +75 -0
  59. package/dist/config-Bq72aj8e.mjs.map +1 -0
  60. package/dist/config-CFls09Ey.cjs +93 -0
  61. package/dist/config-CFls09Ey.cjs.map +1 -0
  62. package/dist/config-CLEDqKO3.cjs +157 -0
  63. package/dist/config-CLEDqKO3.cjs.map +1 -0
  64. package/dist/config-DBsmMDhf.d.mts +11 -0
  65. package/dist/config-Dp8RonV_.mjs +151 -0
  66. package/dist/config-Dp8RonV_.mjs.map +1 -0
  67. package/dist/config.cjs +4 -2
  68. package/dist/config.d.cts +48 -0
  69. package/dist/config.d.mts +48 -0
  70. package/dist/config.mjs +2 -2
  71. package/dist/dev/index.cjs +10 -9
  72. package/dist/dev/index.d.cts +36 -0
  73. package/dist/dev/index.d.mts +36 -0
  74. package/dist/dev/index.mjs +10 -10
  75. package/dist/{dev-CpA8AQPX.mjs → dev-B734w3L1.mjs} +93 -49
  76. package/dist/dev-B734w3L1.mjs.map +1 -0
  77. package/dist/{dev-CgDYC4o8.cjs → dev-DHqYn8k4.cjs} +71 -21
  78. package/dist/dev-DHqYn8k4.cjs.map +1 -0
  79. package/dist/docker-5d8Yh5_X.cjs +119 -0
  80. package/dist/docker-5d8Yh5_X.cjs.map +1 -0
  81. package/dist/docker-DlUqdFle.mjs +113 -0
  82. package/dist/docker-DlUqdFle.mjs.map +1 -0
  83. package/dist/env-B-OKjgI4.cjs +144 -0
  84. package/dist/env-B-OKjgI4.cjs.map +1 -0
  85. package/dist/env-HfuJRlg5.d.cts +11 -0
  86. package/dist/env-nd-iQPYM.d.mts +11 -0
  87. package/dist/env-tv1HlZlw.mjs +138 -0
  88. package/dist/env-tv1HlZlw.mjs.map +1 -0
  89. package/dist/generators/CronGenerator.cjs +2 -2
  90. package/dist/generators/CronGenerator.d.cts +5 -0
  91. package/dist/generators/CronGenerator.d.mts +5 -0
  92. package/dist/generators/CronGenerator.mjs +2 -2
  93. package/dist/generators/EndpointGenerator.cjs +2 -2
  94. package/dist/generators/EndpointGenerator.d.cts +5 -0
  95. package/dist/generators/EndpointGenerator.d.mts +5 -0
  96. package/dist/generators/EndpointGenerator.mjs +2 -2
  97. package/dist/generators/FunctionGenerator.cjs +2 -2
  98. package/dist/generators/FunctionGenerator.d.cts +5 -0
  99. package/dist/generators/FunctionGenerator.d.mts +5 -0
  100. package/dist/generators/FunctionGenerator.mjs +2 -2
  101. package/dist/generators/Generator.cjs +1 -1
  102. package/dist/generators/Generator.d.cts +4 -0
  103. package/dist/generators/Generator.d.mts +4 -0
  104. package/dist/generators/Generator.mjs +1 -1
  105. package/dist/generators/OpenApiTsGenerator.cjs +1 -1
  106. package/dist/generators/OpenApiTsGenerator.d.cts +44 -0
  107. package/dist/generators/OpenApiTsGenerator.d.mts +44 -0
  108. package/dist/generators/OpenApiTsGenerator.mjs +1 -1
  109. package/dist/generators/SubscriberGenerator.cjs +2 -2
  110. package/dist/generators/SubscriberGenerator.d.cts +5 -0
  111. package/dist/generators/SubscriberGenerator.d.mts +5 -0
  112. package/dist/generators/SubscriberGenerator.mjs +2 -2
  113. package/dist/generators/index.cjs +6 -6
  114. package/dist/generators/index.d.cts +8 -0
  115. package/dist/generators/index.d.mts +8 -0
  116. package/dist/generators/index.mjs +6 -6
  117. package/dist/index-C523No_B.d.mts +64 -0
  118. package/dist/index-DrzN4xkQ.d.cts +64 -0
  119. package/dist/index.cjs +48 -16
  120. package/dist/index.cjs.map +1 -1
  121. package/dist/index.d.cts +1 -0
  122. package/dist/index.d.mts +1 -0
  123. package/dist/index.mjs +48 -16
  124. package/dist/index.mjs.map +1 -1
  125. package/dist/init/generators/config.cjs +3 -0
  126. package/dist/init/generators/config.d.cts +3 -0
  127. package/dist/init/generators/config.d.mts +3 -0
  128. package/dist/init/generators/config.mjs +3 -0
  129. package/dist/init/generators/docker.cjs +3 -0
  130. package/dist/init/generators/docker.d.cts +11 -0
  131. package/dist/init/generators/docker.d.mts +11 -0
  132. package/dist/init/generators/docker.mjs +3 -0
  133. package/dist/init/generators/env.cjs +3 -0
  134. package/dist/init/generators/env.d.cts +3 -0
  135. package/dist/init/generators/env.d.mts +3 -0
  136. package/dist/init/generators/env.mjs +3 -0
  137. package/dist/init/generators/index.cjs +9 -0
  138. package/dist/init/generators/index.d.cts +6 -0
  139. package/dist/init/generators/index.d.mts +6 -0
  140. package/dist/init/generators/index.mjs +6 -0
  141. package/dist/init/generators/models.cjs +3 -0
  142. package/dist/init/generators/models.d.cts +11 -0
  143. package/dist/init/generators/models.d.mts +11 -0
  144. package/dist/init/generators/models.mjs +3 -0
  145. package/dist/init/generators/monorepo.cjs +3 -0
  146. package/dist/init/generators/monorepo.d.cts +11 -0
  147. package/dist/init/generators/monorepo.d.mts +11 -0
  148. package/dist/init/generators/monorepo.mjs +3 -0
  149. package/dist/init/generators/package.cjs +3 -0
  150. package/dist/init/generators/package.d.cts +3 -0
  151. package/dist/init/generators/package.d.mts +3 -0
  152. package/dist/init/generators/package.mjs +3 -0
  153. package/dist/init/generators/source.cjs +3 -0
  154. package/dist/init/generators/source.d.cts +3 -0
  155. package/dist/init/generators/source.d.mts +3 -0
  156. package/dist/init/generators/source.mjs +3 -0
  157. package/dist/init/index.cjs +16 -0
  158. package/dist/init/index.d.cts +17 -0
  159. package/dist/init/index.d.mts +17 -0
  160. package/dist/init/index.mjs +16 -0
  161. package/dist/init/templates/api.cjs +3 -0
  162. package/dist/init/templates/api.d.cts +7 -0
  163. package/dist/init/templates/api.d.mts +7 -0
  164. package/dist/init/templates/api.mjs +3 -0
  165. package/dist/init/templates/index.cjs +10 -0
  166. package/dist/init/templates/index.d.cts +2 -0
  167. package/dist/init/templates/index.d.mts +2 -0
  168. package/dist/init/templates/index.mjs +7 -0
  169. package/dist/init/templates/minimal.cjs +3 -0
  170. package/dist/init/templates/minimal.d.cts +7 -0
  171. package/dist/init/templates/minimal.d.mts +7 -0
  172. package/dist/init/templates/minimal.mjs +3 -0
  173. package/dist/init/templates/serverless.cjs +3 -0
  174. package/dist/init/templates/serverless.d.cts +7 -0
  175. package/dist/init/templates/serverless.d.mts +7 -0
  176. package/dist/init/templates/serverless.mjs +3 -0
  177. package/dist/init/templates/worker.cjs +3 -0
  178. package/dist/init/templates/worker.d.cts +7 -0
  179. package/dist/init/templates/worker.d.mts +7 -0
  180. package/dist/init/templates/worker.mjs +3 -0
  181. package/dist/init/utils.cjs +7 -0
  182. package/dist/init/utils.d.cts +25 -0
  183. package/dist/init/utils.d.mts +25 -0
  184. package/dist/init/utils.mjs +3 -0
  185. package/dist/init-CtOnZn3G.mjs +145 -0
  186. package/dist/init-CtOnZn3G.mjs.map +1 -0
  187. package/dist/init-qLFsWR-R.cjs +151 -0
  188. package/dist/init-qLFsWR-R.cjs.map +1 -0
  189. package/dist/{manifests-C2eMoMUm.mjs → manifests-DIA_2QYd.mjs} +1 -1
  190. package/dist/{manifests-C2eMoMUm.mjs.map → manifests-DIA_2QYd.mjs.map} +1 -1
  191. package/dist/{manifests-CK1VV_pM.cjs → manifests-VJ9-2JpW.cjs} +1 -1
  192. package/dist/{manifests-CK1VV_pM.cjs.map → manifests-VJ9-2JpW.cjs.map} +1 -1
  193. package/dist/minimal-Bdhhpp7v.cjs +93 -0
  194. package/dist/minimal-Bdhhpp7v.cjs.map +1 -0
  195. package/dist/minimal-C4GsE45s.mjs +87 -0
  196. package/dist/minimal-C4GsE45s.mjs.map +1 -0
  197. package/dist/models-DyNwdOcz.cjs +121 -0
  198. package/dist/models-DyNwdOcz.cjs.map +1 -0
  199. package/dist/models-cvNg6Oea.mjs +115 -0
  200. package/dist/models-cvNg6Oea.mjs.map +1 -0
  201. package/dist/monorepo-Cknwzj5C.mjs +184 -0
  202. package/dist/monorepo-Cknwzj5C.mjs.map +1 -0
  203. package/dist/monorepo-sEK8gW59.cjs +190 -0
  204. package/dist/monorepo-sEK8gW59.cjs.map +1 -0
  205. package/dist/{openapi-DhK4b0lB.cjs → openapi-BQWPWyNB.cjs} +4 -4
  206. package/dist/{openapi-DhK4b0lB.cjs.map → openapi-BQWPWyNB.cjs.map} +1 -1
  207. package/dist/{openapi-DRTRGhTt.mjs → openapi-DBX8cJJ8.mjs} +4 -4
  208. package/dist/{openapi-DRTRGhTt.mjs.map → openapi-DBX8cJJ8.mjs.map} +1 -1
  209. package/dist/{openapi-react-query-D9Z7lh0p.cjs → openapi-react-query-DxHjXQvg.cjs} +1 -1
  210. package/dist/{openapi-react-query-D9Z7lh0p.cjs.map → openapi-react-query-DxHjXQvg.cjs.map} +1 -1
  211. package/dist/{openapi-react-query-MEBlYIM1.mjs → openapi-react-query-o7Mp1Jd5.mjs} +1 -1
  212. package/dist/{openapi-react-query-MEBlYIM1.mjs.map → openapi-react-query-o7Mp1Jd5.mjs.map} +1 -1
  213. package/dist/openapi-react-query.cjs +1 -1
  214. package/dist/openapi-react-query.d.cts +11 -0
  215. package/dist/openapi-react-query.d.mts +11 -0
  216. package/dist/openapi-react-query.mjs +1 -1
  217. package/dist/openapi.cjs +5 -5
  218. package/dist/openapi.d.cts +11 -0
  219. package/dist/openapi.d.mts +11 -0
  220. package/dist/openapi.mjs +5 -5
  221. package/dist/package-C7WhWU8m.d.mts +11 -0
  222. package/dist/package-CIfmeuSW.mjs +51 -0
  223. package/dist/package-CIfmeuSW.mjs.map +1 -0
  224. package/dist/package-DvWEMz6z.d.cts +11 -0
  225. package/dist/package-PP-o1nvq.cjs +57 -0
  226. package/dist/package-PP-o1nvq.cjs.map +1 -0
  227. package/dist/{providerResolver-B_TjNF0_.mjs → providerResolver-DEVKngbC.mjs} +1 -1
  228. package/dist/{providerResolver-B_TjNF0_.mjs.map → providerResolver-DEVKngbC.mjs.map} +1 -1
  229. package/dist/{providerResolver-DgvzNfP4.cjs → providerResolver-DOTbN9jo.cjs} +1 -1
  230. package/dist/{providerResolver-DgvzNfP4.cjs.map → providerResolver-DOTbN9jo.cjs.map} +1 -1
  231. package/dist/serverless-DkHBF2vC.mjs +108 -0
  232. package/dist/serverless-DkHBF2vC.mjs.map +1 -0
  233. package/dist/serverless-Yav3GRVz.cjs +114 -0
  234. package/dist/serverless-Yav3GRVz.cjs.map +1 -0
  235. package/dist/source-D6v2BnKT.d.mts +11 -0
  236. package/dist/source-D8fK9qRo.d.cts +11 -0
  237. package/dist/source-DT5Xhiob.cjs +17 -0
  238. package/dist/source-DT5Xhiob.cjs.map +1 -0
  239. package/dist/source-DnaH_MLA.mjs +11 -0
  240. package/dist/source-DnaH_MLA.mjs.map +1 -0
  241. package/dist/templates-CBFUwpBy.mjs +64 -0
  242. package/dist/templates-CBFUwpBy.mjs.map +1 -0
  243. package/dist/templates-DM_rtYYW.cjs +87 -0
  244. package/dist/templates-DM_rtYYW.cjs.map +1 -0
  245. package/dist/types-C4KITv-y.d.mts +51 -0
  246. package/dist/types-Cxl8-uwV.d.mts +129 -0
  247. package/dist/types-DB99_qIy.d.cts +129 -0
  248. package/dist/types-DLFN49M3.d.cts +51 -0
  249. package/dist/types.d.cts +2 -0
  250. package/dist/types.d.mts +2 -0
  251. package/dist/utils-BX3F4fT8.cjs +99 -0
  252. package/dist/utils-BX3F4fT8.cjs.map +1 -0
  253. package/dist/utils-C31-SWHP.mjs +69 -0
  254. package/dist/utils-C31-SWHP.mjs.map +1 -0
  255. package/dist/worker--8O5a3Hv.cjs +150 -0
  256. package/dist/worker--8O5a3Hv.cjs.map +1 -0
  257. package/dist/worker-Jme7uOOJ.mjs +144 -0
  258. package/dist/worker-Jme7uOOJ.mjs.map +1 -0
  259. package/package.json +12 -4
  260. package/src/__tests__/loadEnvFiles.spec.ts +131 -0
  261. package/src/build/index.ts +14 -16
  262. package/src/build/types.ts +5 -0
  263. package/src/config.ts +57 -0
  264. package/src/dev/__tests__/index.spec.ts +114 -119
  265. package/src/dev/index.ts +77 -17
  266. package/src/generators/EndpointGenerator.ts +37 -8
  267. package/src/index.ts +26 -0
  268. package/src/init/__tests__/generators.spec.ts +366 -0
  269. package/src/init/__tests__/init.spec.ts +341 -0
  270. package/src/init/__tests__/utils.spec.ts +104 -0
  271. package/src/init/generators/config.ts +192 -0
  272. package/src/init/generators/docker.ts +134 -0
  273. package/src/init/generators/env.ts +182 -0
  274. package/src/init/generators/index.ts +4 -0
  275. package/src/init/generators/models.ts +129 -0
  276. package/src/init/generators/monorepo.ts +211 -0
  277. package/src/init/generators/package.ts +81 -0
  278. package/src/init/generators/source.ts +15 -0
  279. package/src/init/index.ts +206 -0
  280. package/src/init/templates/api.ts +218 -0
  281. package/src/init/templates/index.ts +108 -0
  282. package/src/init/templates/minimal.ts +102 -0
  283. package/src/init/templates/serverless.ts +129 -0
  284. package/src/init/templates/worker.ts +169 -0
  285. package/src/init/utils.ts +98 -0
  286. package/src/types.ts +22 -2
  287. package/tsdown.config.ts +1 -1
  288. package/dist/EndpointGenerator-BxNCkus4.cjs.map +0 -1
  289. package/dist/EndpointGenerator-CzDhG7Or.mjs.map +0 -1
  290. package/dist/build-CWtHnJMQ.cjs.map +0 -1
  291. package/dist/build-DyDgu_D1.mjs.map +0 -1
  292. package/dist/config-AFmFKmU0.mjs +0 -30
  293. package/dist/config-AFmFKmU0.mjs.map +0 -1
  294. package/dist/config-BVIJpAsa.cjs +0 -36
  295. package/dist/config-BVIJpAsa.cjs.map +0 -1
  296. package/dist/dev-CgDYC4o8.cjs.map +0 -1
  297. package/dist/dev-CpA8AQPX.mjs.map +0 -1
  298. /package/dist/{generators-CEKtVh81.cjs → generators-3IemvCLk.cjs} +0 -0
  299. /package/dist/{generators-CsLujGXs.mjs → generators-FNpdfN6J.mjs} +0 -0
@@ -1,15 +1,37 @@
1
+ import type { AddressInfo } from 'node:net';
1
2
  import { createServer } from 'node:net';
2
- import { describe, expect, it } from 'vitest';
3
+ import { afterEach, describe, expect, it } from 'vitest';
3
4
  import {
4
5
  findAvailablePort,
5
6
  isPortAvailable,
6
7
  normalizeTelescopeConfig,
7
8
  } from '../index';
8
9
 
10
+ // Track servers to clean up after each test
11
+ const activeServers: ReturnType<typeof createServer>[] = [];
12
+
13
+ afterEach(async () => {
14
+ // Close all servers and wait for them to fully close
15
+ await Promise.all(
16
+ activeServers.map(
17
+ (server) =>
18
+ new Promise<void>((resolve) => {
19
+ server.close(() => resolve());
20
+ }),
21
+ ),
22
+ );
23
+ activeServers.length = 0;
24
+ // Give OS time to release ports
25
+ await new Promise((resolve) => setTimeout(resolve, 50));
26
+ });
27
+
9
28
  /**
10
- * Helper to occupy a port for testing
29
+ * Helper to occupy a port for testing.
30
+ * Pass port 0 to get a random available port.
11
31
  */
12
- function occupyPort(port: number): Promise<ReturnType<typeof createServer>> {
32
+ function occupyPort(
33
+ port: number,
34
+ ): Promise<{ server: ReturnType<typeof createServer>; port: number }> {
13
35
  return new Promise((resolve, reject) => {
14
36
  const server = createServer();
15
37
 
@@ -18,7 +40,9 @@ function occupyPort(port: number): Promise<ReturnType<typeof createServer>> {
18
40
  });
19
41
 
20
42
  server.once('listening', () => {
21
- resolve(server);
43
+ activeServers.push(server);
44
+ const actualPort = (server.address() as AddressInfo).port;
45
+ resolve({ server, port: actualPort });
22
46
  });
23
47
 
24
48
  server.listen(port);
@@ -28,127 +52,99 @@ function occupyPort(port: number): Promise<ReturnType<typeof createServer>> {
28
52
  describe('Port Availability Functions', () => {
29
53
  describe('isPortAvailable', () => {
30
54
  it('should return true for an available port', async () => {
31
- // Use a high port number to avoid conflicts
32
- const port = 45000;
55
+ // Get a random port, close it, then check availability
56
+ const { server, port } = await occupyPort(0);
57
+ server.close();
58
+ await new Promise((resolve) => setTimeout(resolve, 50));
59
+
33
60
  const available = await isPortAvailable(port);
34
61
  expect(available).toBe(true);
35
62
  });
36
63
 
37
64
  it('should return false for a port in use', async () => {
38
- const port = 45001;
39
- const server = await occupyPort(port);
40
-
41
- try {
42
- const available = await isPortAvailable(port);
43
- expect(available).toBe(false);
44
- } finally {
45
- server.close();
46
- }
65
+ const { port } = await occupyPort(0);
66
+
67
+ const available = await isPortAvailable(port);
68
+ expect(available).toBe(false);
69
+ // Server cleanup handled by afterEach
47
70
  });
48
71
 
49
72
  it('should handle multiple sequential checks correctly', async () => {
50
- const port = 45002;
73
+ // Get a port to test with
74
+ const { server: tempServer, port } = await occupyPort(0);
75
+ tempServer.close();
76
+ await new Promise((resolve) => setTimeout(resolve, 50));
51
77
 
52
78
  // First check - port should be available
53
79
  const firstCheck = await isPortAvailable(port);
54
80
  expect(firstCheck).toBe(true);
55
81
 
56
82
  // Occupy the port
57
- const server = await occupyPort(port);
58
-
59
- try {
60
- // Second check - port should be unavailable
61
- const secondCheck = await isPortAvailable(port);
62
- expect(secondCheck).toBe(false);
63
- } finally {
64
- server.close();
65
- }
66
-
67
- // Give a moment for the port to be released
68
- await new Promise((resolve) => setTimeout(resolve, 100));
69
-
70
- // Third check - port should be available again
71
- const thirdCheck = await isPortAvailable(port);
72
- expect(thirdCheck).toBe(true);
83
+ await occupyPort(port);
84
+
85
+ // Second check - port should be unavailable
86
+ const secondCheck = await isPortAvailable(port);
87
+ expect(secondCheck).toBe(false);
88
+ // Server cleanup and third check handled by afterEach
73
89
  });
74
90
  });
75
91
 
76
92
  describe('findAvailablePort', () => {
77
93
  it('should return the preferred port if available', async () => {
78
- const preferredPort = 45100;
94
+ // Get a random port, close it, then use as preferred
95
+ const { server, port: preferredPort } = await occupyPort(0);
96
+ server.close();
97
+ await new Promise((resolve) => setTimeout(resolve, 50));
98
+
79
99
  const foundPort = await findAvailablePort(preferredPort);
80
100
  expect(foundPort).toBe(preferredPort);
81
101
  });
82
102
 
83
103
  it('should return the next available port if preferred is in use', async () => {
84
- const preferredPort = 45101;
85
- const server = await occupyPort(preferredPort);
86
-
87
- try {
88
- const foundPort = await findAvailablePort(preferredPort);
89
- expect(foundPort).toBe(preferredPort + 1);
90
- } finally {
91
- server.close();
92
- }
104
+ const { port: preferredPort } = await occupyPort(0);
105
+
106
+ const foundPort = await findAvailablePort(preferredPort);
107
+ expect(foundPort).toBe(preferredPort + 1);
93
108
  });
94
109
 
95
110
  it('should skip multiple occupied ports', async () => {
96
- const preferredPort = 45102;
97
- const server1 = await occupyPort(preferredPort);
98
- const server2 = await occupyPort(preferredPort + 1);
99
- const server3 = await occupyPort(preferredPort + 2);
100
-
101
- try {
102
- const foundPort = await findAvailablePort(preferredPort);
103
- expect(foundPort).toBe(preferredPort + 3);
104
- } finally {
105
- server1.close();
106
- server2.close();
107
- server3.close();
108
- }
111
+ // Get a base port
112
+ const { port: basePort } = await occupyPort(0);
113
+ // Occupy consecutive ports
114
+ await occupyPort(basePort + 1);
115
+ await occupyPort(basePort + 2);
116
+
117
+ const foundPort = await findAvailablePort(basePort);
118
+ expect(foundPort).toBe(basePort + 3);
109
119
  });
110
120
 
111
121
  it('should throw error if no available port found within max attempts', async () => {
112
- const preferredPort = 45103;
122
+ const { port: preferredPort } = await occupyPort(0);
113
123
  const maxAttempts = 3;
114
124
 
115
- // Occupy ports 45103, 45104, 45105
116
- const servers = await Promise.all([
117
- occupyPort(preferredPort),
118
- occupyPort(preferredPort + 1),
119
- occupyPort(preferredPort + 2),
120
- ]);
121
-
122
- try {
123
- await expect(
124
- findAvailablePort(preferredPort, maxAttempts),
125
- ).rejects.toThrow(
126
- `Could not find an available port after trying ${maxAttempts} ports starting from ${preferredPort}`,
127
- );
128
- } finally {
129
- servers.forEach((server) => server.close());
130
- }
125
+ // Occupy consecutive ports
126
+ await occupyPort(preferredPort + 1);
127
+ await occupyPort(preferredPort + 2);
128
+
129
+ await expect(
130
+ findAvailablePort(preferredPort, maxAttempts),
131
+ ).rejects.toThrow(
132
+ `Could not find an available port after trying ${maxAttempts} ports starting from ${preferredPort}`,
133
+ );
131
134
  });
132
135
 
133
136
  it('should respect custom maxAttempts parameter', async () => {
134
- const preferredPort = 45104;
137
+ const { port: preferredPort } = await occupyPort(0);
135
138
  const maxAttempts = 5;
136
139
 
137
- // Occupy first 4 ports
138
- const servers = await Promise.all([
139
- occupyPort(preferredPort),
140
- occupyPort(preferredPort + 1),
141
- occupyPort(preferredPort + 2),
142
- occupyPort(preferredPort + 3),
143
- ]);
144
-
145
- try {
146
- const foundPort = await findAvailablePort(preferredPort, maxAttempts);
147
- // Should find port at preferredPort + 4 (within 5 attempts)
148
- expect(foundPort).toBe(preferredPort + 4);
149
- } finally {
150
- servers.forEach((server) => server.close());
151
- }
140
+ // Occupy consecutive ports (4 total including base)
141
+ await occupyPort(preferredPort + 1);
142
+ await occupyPort(preferredPort + 2);
143
+ await occupyPort(preferredPort + 3);
144
+
145
+ const foundPort = await findAvailablePort(preferredPort, maxAttempts);
146
+ // Should find port at preferredPort + 4 (within 5 attempts)
147
+ expect(foundPort).toBe(preferredPort + 4);
152
148
  });
153
149
  });
154
150
  });
@@ -156,58 +152,57 @@ describe('Port Availability Functions', () => {
156
152
  describe('DevServer', () => {
157
153
  describe('port selection', () => {
158
154
  it('should use requested port when available', async () => {
159
- // This is more of an integration test that would need the actual DevServer
160
- // For now, we test the underlying logic
161
- const requestedPort = 45200;
155
+ // Get a random port, close it, then use as requested
156
+ const { server, port: requestedPort } = await occupyPort(0);
157
+ server.close();
158
+ await new Promise((resolve) => setTimeout(resolve, 50));
159
+
162
160
  const actualPort = await findAvailablePort(requestedPort);
163
161
  expect(actualPort).toBe(requestedPort);
164
162
  });
165
163
 
166
164
  it('should select alternative port when requested is in use', async () => {
167
- const requestedPort = 45201;
168
- const server = await occupyPort(requestedPort);
169
-
170
- try {
171
- const actualPort = await findAvailablePort(requestedPort);
172
- expect(actualPort).not.toBe(requestedPort);
173
- expect(actualPort).toBeGreaterThan(requestedPort);
174
- expect(actualPort).toBeLessThanOrEqual(requestedPort + 10);
175
- } finally {
176
- server.close();
177
- }
165
+ const { port: requestedPort } = await occupyPort(0);
166
+
167
+ const actualPort = await findAvailablePort(requestedPort);
168
+ expect(actualPort).not.toBe(requestedPort);
169
+ expect(actualPort).toBeGreaterThan(requestedPort);
170
+ expect(actualPort).toBeLessThanOrEqual(requestedPort + 10);
178
171
  });
179
172
  });
180
173
  });
181
174
 
182
175
  describe('devCommand edge cases', () => {
183
176
  it('should handle port conflicts gracefully', async () => {
184
- const port = 45300;
185
- const server = await occupyPort(port);
186
-
187
- try {
188
- // The dev command should find an alternative port
189
- const alternativePort = await findAvailablePort(port);
190
- expect(alternativePort).toBeGreaterThan(port);
191
- } finally {
192
- server.close();
193
- }
177
+ const { port } = await occupyPort(0);
178
+
179
+ // The dev command should find an alternative port
180
+ const alternativePort = await findAvailablePort(port);
181
+ expect(alternativePort).toBeGreaterThan(port);
194
182
  });
195
183
 
196
184
  it('should handle concurrent port checks', async () => {
197
- const basePort = 45400;
185
+ // Get three random available ports as base
186
+ const { server: s1, port: p1 } = await occupyPort(0);
187
+ const { server: s2, port: p2 } = await occupyPort(0);
188
+ const { server: s3, port: p3 } = await occupyPort(0);
189
+ s1.close();
190
+ s2.close();
191
+ s3.close();
192
+ await new Promise((resolve) => setTimeout(resolve, 50));
198
193
 
199
194
  // Run multiple port checks concurrently
200
195
  const results = await Promise.all([
201
- findAvailablePort(basePort),
202
- findAvailablePort(basePort + 5),
203
- findAvailablePort(basePort + 10),
196
+ findAvailablePort(p1),
197
+ findAvailablePort(p2),
198
+ findAvailablePort(p3),
204
199
  ]);
205
200
 
206
201
  // All should succeed and return valid ports
207
202
  expect(results).toHaveLength(3);
208
- results.forEach((port) => {
209
- expect(port).toBeGreaterThanOrEqual(basePort);
210
- });
203
+ expect(results[0]).toBe(p1);
204
+ expect(results[1]).toBe(p2);
205
+ expect(results[2]).toBe(p3);
211
206
  });
212
207
  });
213
208
 
package/src/dev/index.ts CHANGED
@@ -1,12 +1,14 @@
1
1
  import { type ChildProcess, spawn } from 'node:child_process';
2
+ import { existsSync } from 'node:fs';
2
3
  import { mkdir } from 'node:fs/promises';
3
4
  import { createServer } from 'node:net';
4
- import { join } from 'node:path';
5
+ import { join, resolve } from 'node:path';
5
6
  import chokidar from 'chokidar';
7
+ import { config as dotenvConfig } from 'dotenv';
6
8
  import fg from 'fast-glob';
7
9
  import { resolveProviders } from '../build/providerResolver';
8
10
  import type { BuildContext, NormalizedTelescopeConfig } from '../build/types';
9
- import { loadConfig } from '../config';
11
+ import { loadConfig, parseModuleConfig } from '../config';
10
12
  import {
11
13
  CronGenerator,
12
14
  EndpointGenerator,
@@ -22,6 +24,39 @@ import type {
22
24
 
23
25
  const logger = console;
24
26
 
27
+ /**
28
+ * Load environment files
29
+ * @internal Exported for testing
30
+ */
31
+ export function loadEnvFiles(
32
+ envConfig: string | string[] | undefined,
33
+ cwd: string = process.cwd(),
34
+ ): { loaded: string[]; missing: string[] } {
35
+ const loaded: string[] = [];
36
+ const missing: string[] = [];
37
+
38
+ // Normalize to array
39
+ const envFiles = envConfig
40
+ ? Array.isArray(envConfig)
41
+ ? envConfig
42
+ : [envConfig]
43
+ : ['.env'];
44
+
45
+ // Load each env file in order (later files override earlier)
46
+ for (const envFile of envFiles) {
47
+ const envPath = resolve(cwd, envFile);
48
+ if (existsSync(envPath)) {
49
+ dotenvConfig({ path: envPath, override: true, quiet: true });
50
+ loaded.push(envFile);
51
+ } else if (envConfig) {
52
+ // Only report as missing if explicitly configured
53
+ missing.push(envFile);
54
+ }
55
+ }
56
+
57
+ return { loaded, missing };
58
+ }
59
+
25
60
  /**
26
61
  * Check if a port is available
27
62
  * @internal Exported for testing
@@ -79,6 +114,23 @@ export function normalizeTelescopeConfig(
79
114
  return undefined;
80
115
  }
81
116
 
117
+ // Handle string path (e.g., './src/config/telescope')
118
+ if (typeof config === 'string') {
119
+ const { path: telescopePath, importPattern: telescopeImportPattern } =
120
+ parseModuleConfig(config, 'telescope');
121
+
122
+ return {
123
+ enabled: true,
124
+ telescopePath,
125
+ telescopeImportPattern,
126
+ path: '/__telescope',
127
+ ignore: [],
128
+ recordBody: true,
129
+ maxEntries: 1000,
130
+ websocket: true,
131
+ };
132
+ }
133
+
82
134
  // Default to enabled in development mode
83
135
  const isEnabled =
84
136
  config === true || config === undefined || config.enabled !== false;
@@ -106,8 +158,26 @@ export interface DevOptions {
106
158
  }
107
159
 
108
160
  export async function devCommand(options: DevOptions): Promise<void> {
161
+ // Load default .env file BEFORE loading config
162
+ // This ensures env vars are available when config and its dependencies are loaded
163
+ const defaultEnv = loadEnvFiles('.env');
164
+ if (defaultEnv.loaded.length > 0) {
165
+ logger.log(`📦 Loaded env: ${defaultEnv.loaded.join(', ')}`);
166
+ }
167
+
109
168
  const config = await loadConfig();
110
169
 
170
+ // Load any additional env files specified in config
171
+ if (config.env) {
172
+ const { loaded, missing } = loadEnvFiles(config.env);
173
+ if (loaded.length > 0) {
174
+ logger.log(`📦 Loaded env: ${loaded.join(', ')}`);
175
+ }
176
+ if (missing.length > 0) {
177
+ logger.warn(`⚠️ Missing env files: ${missing.join(', ')}`);
178
+ }
179
+ }
180
+
111
181
  // Force server provider for dev mode
112
182
  const resolved = resolveProviders(config, { provider: 'server' });
113
183
 
@@ -124,21 +194,11 @@ export async function devCommand(options: DevOptions): Promise<void> {
124
194
  }
125
195
  logger.log(`Using envParser: ${config.envParser}`);
126
196
 
127
- // Parse envParser configuration
128
- const [envParserPath, envParserName] = config.envParser.split('#');
129
- const envParserImportPattern = !envParserName
130
- ? 'envParser'
131
- : envParserName === 'envParser'
132
- ? '{ envParser }'
133
- : `{ ${envParserName} as envParser }`;
134
-
135
- // Parse logger configuration
136
- const [loggerPath, loggerName] = config.logger.split('#');
137
- const loggerImportPattern = !loggerName
138
- ? 'logger'
139
- : loggerName === 'logger'
140
- ? '{ logger }'
141
- : `{ ${loggerName} as logger }`;
197
+ // Parse envParser and logger configuration
198
+ const { path: envParserPath, importPattern: envParserImportPattern } =
199
+ parseModuleConfig(config.envParser, 'envParser');
200
+ const { path: loggerPath, importPattern: loggerImportPattern } =
201
+ parseModuleConfig(config.logger, 'logger');
142
202
 
143
203
  // Normalize telescope configuration
144
204
  const telescope = normalizeTelescopeConfig(config.telescope);
@@ -336,10 +336,23 @@ export function setupEndpoints(
336
336
  // Generate telescope imports and setup if enabled
337
337
  const telescopeEnabled = context.telescope?.enabled;
338
338
  const telescopeWebSocketEnabled = context.telescope?.websocket;
339
- const telescopeImports = telescopeEnabled
340
- ? `import { Telescope, InMemoryStorage } from '@geekmidas/telescope';
341
- import { createMiddleware, createUI } from '@geekmidas/telescope/hono';`
342
- : '';
339
+ const usesExternalTelescope = !!context.telescope?.telescopePath;
340
+
341
+ // Generate imports based on whether telescope is external or inline
342
+ let telescopeImports = '';
343
+ if (telescopeEnabled) {
344
+ if (usesExternalTelescope) {
345
+ const relativeTelescopePath = relative(
346
+ dirname(appPath),
347
+ context.telescope!.telescopePath!,
348
+ );
349
+ telescopeImports = `import ${context.telescope!.telescopeImportPattern} from '${relativeTelescopePath}';
350
+ import { createMiddleware, createUI } from '@geekmidas/telescope/hono';`;
351
+ } else {
352
+ telescopeImports = `import { Telescope, InMemoryStorage } from '@geekmidas/telescope';
353
+ import { createMiddleware, createUI } from '@geekmidas/telescope/hono';`;
354
+ }
355
+ }
343
356
 
344
357
  const telescopeWebSocketSetupCode = telescopeWebSocketEnabled
345
358
  ? `
@@ -375,8 +388,23 @@ import { createMiddleware, createUI } from '@geekmidas/telescope/hono';`
375
388
  `
376
389
  : '';
377
390
 
378
- const telescopeSetup = telescopeEnabled
379
- ? `
391
+ // Generate telescope setup - either use external instance or create inline
392
+ let telescopeSetup = '';
393
+ if (telescopeEnabled) {
394
+ if (usesExternalTelescope) {
395
+ // Use external telescope instance - no need to create one
396
+ telescopeSetup = `
397
+ ${telescopeWebSocketSetupCode}
398
+ // Add telescope middleware (before endpoints to capture all requests)
399
+ honoApp.use('*', createMiddleware(telescope));
400
+
401
+ // Mount telescope UI
402
+ const telescopeUI = createUI(telescope);
403
+ honoApp.route('${context.telescope!.path}', telescopeUI);
404
+ `;
405
+ } else {
406
+ // Create inline telescope instance
407
+ telescopeSetup = `
380
408
  // Setup Telescope for debugging/monitoring
381
409
  const telescopeStorage = new InMemoryStorage({ maxEntries: ${context.telescope!.maxEntries} });
382
410
  const telescope = new Telescope({
@@ -393,8 +421,9 @@ ${telescopeWebSocketSetupCode}
393
421
  // Mount telescope UI
394
422
  const telescopeUI = createUI(telescope);
395
423
  honoApp.route('${context.telescope!.path}', telescopeUI);
396
- `
397
- : '';
424
+ `;
425
+ }
426
+ }
398
427
 
399
428
  const content = `/**
400
429
  * Generated server application
package/src/index.ts CHANGED
@@ -4,6 +4,7 @@ import { Command } from 'commander';
4
4
  import pkg from '../package.json' assert { type: 'json' };
5
5
  import { buildCommand } from './build/index.ts';
6
6
  import { devCommand } from './dev/index.ts';
7
+ import { type InitOptions, initCommand } from './init/index.ts';
7
8
  import { generateReactQueryCommand } from './openapi-react-query.ts';
8
9
  import { openapiCommand } from './openapi.ts';
9
10
  import type { LegacyProvider, MainProvider } from './types.ts';
@@ -16,6 +17,31 @@ program
16
17
  .version(pkg.version)
17
18
  .option('--cwd <path>', 'Change working directory');
18
19
 
20
+ program
21
+ .command('init')
22
+ .description('Scaffold a new project')
23
+ .argument('[name]', 'Project name')
24
+ .option(
25
+ '--template <template>',
26
+ 'Project template (minimal, api, serverless, worker)',
27
+ )
28
+ .option('--skip-install', 'Skip dependency installation', false)
29
+ .option('-y, --yes', 'Skip prompts, use defaults', false)
30
+ .option('--monorepo', 'Setup as monorepo with packages/models', false)
31
+ .option('--api-path <path>', 'API app path in monorepo (default: apps/api)')
32
+ .action(async (name: string | undefined, options: InitOptions) => {
33
+ try {
34
+ const globalOptions = program.opts();
35
+ if (globalOptions.cwd) {
36
+ process.chdir(globalOptions.cwd);
37
+ }
38
+ await initCommand(name, options);
39
+ } catch (error) {
40
+ console.error('Init failed:', (error as Error).message);
41
+ process.exit(1);
42
+ }
43
+ });
44
+
19
45
  program
20
46
  .command('build')
21
47
  .description('Build handlers from endpoints, functions, and crons')