@geekmidas/cli 0.5.1 → 0.6.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 (247) hide show
  1. package/README.md +7 -7
  2. package/dist/{CronGenerator-BPTqNYOR.d.cts → CronGenerator-DWS3CCZt.d.cts} +4 -4
  3. package/dist/{CronGenerator-YAj59JUd.d.mts → CronGenerator-DZjdkEjI.d.mts} +4 -4
  4. package/dist/{EndpointGenerator-ChAD1INz.d.cts → EndpointGenerator-Dh7kMtuL.d.mts} +4 -4
  5. package/dist/{EndpointGenerator-Cj3O1U8-.d.mts → EndpointGenerator-zBsie_7s.d.cts} +4 -4
  6. package/dist/{FunctionGenerator-429-9NER.d.cts → FunctionGenerator-BmDHo27U.d.mts} +4 -4
  7. package/dist/{FunctionGenerator-BQ4ehoID.d.mts → FunctionGenerator-DXjXBxUd.d.cts} +4 -4
  8. package/dist/{Generator-BjHK_qce.d.mts → Generator-BGY-2dgI.d.cts} +3 -3
  9. package/dist/{Generator-DxQMCQp7.d.cts → Generator-yi9DH5TN.d.mts} +3 -3
  10. package/dist/{OpenApiTsGenerator-C4mHHaku.mjs → OpenApiTsGenerator-BVS4pOH7.mjs} +2 -2
  11. package/dist/{OpenApiTsGenerator-C4mHHaku.mjs.map → OpenApiTsGenerator-BVS4pOH7.mjs.map} +1 -1
  12. package/dist/{OpenApiTsGenerator-Be-sKGTT.cjs → OpenApiTsGenerator-gPIIyppX.cjs} +2 -2
  13. package/dist/{OpenApiTsGenerator-Be-sKGTT.cjs.map → OpenApiTsGenerator-gPIIyppX.cjs.map} +1 -1
  14. package/dist/{SubscriberGenerator-7uX42xyG.d.mts → SubscriberGenerator-Bb-z3Kvx.d.cts} +4 -4
  15. package/dist/{SubscriberGenerator-Dtb3HS4i.d.cts → SubscriberGenerator-CwsXqCpS.d.mts} +4 -4
  16. package/dist/{api-BKIN0s0S.mjs → api-Bp5TIl1R.mjs} +29 -46
  17. package/dist/api-Bp5TIl1R.mjs.map +1 -0
  18. package/dist/{api-B3SCEHPf.cjs → api-D4W9-tdZ.cjs} +29 -46
  19. package/dist/api-D4W9-tdZ.cjs.map +1 -0
  20. package/dist/build/index.cjs +5 -3
  21. package/dist/build/index.d.cts +1 -1
  22. package/dist/build/index.d.mts +1 -1
  23. package/dist/build/index.mjs +5 -3
  24. package/dist/build/manifests.cjs +1 -1
  25. package/dist/build/manifests.d.cts +1 -1
  26. package/dist/build/manifests.d.mts +1 -1
  27. package/dist/build/manifests.mjs +1 -1
  28. package/dist/build/providerResolver.d.cts +1 -1
  29. package/dist/build/providerResolver.d.mts +1 -1
  30. package/dist/build/types.d.cts +2 -2
  31. package/dist/build/types.d.mts +2 -2
  32. package/dist/{build-B8C_qHir.mjs → build-Cu6Mi0Lf.mjs} +3 -3
  33. package/dist/{build-B8C_qHir.mjs.map → build-Cu6Mi0Lf.mjs.map} +1 -1
  34. package/dist/{build-D0Wr49bf.cjs → build-wmt8ZcmA.cjs} +3 -3
  35. package/dist/{build-D0Wr49bf.cjs.map → build-wmt8ZcmA.cjs.map} +1 -1
  36. package/dist/{config-CLEDqKO3.cjs → config-BP1IZynR.cjs} +17 -6
  37. package/dist/config-BP1IZynR.cjs.map +1 -0
  38. package/dist/{config-Ba-Gbpbc.d.cts → config-CIzRhm_D.d.mts} +2 -2
  39. package/dist/{config-DBsmMDhf.d.mts → config-CvehIYsb.d.cts} +2 -2
  40. package/dist/{config-Dp8RonV_.mjs → config-UCK12Lrr.mjs} +17 -6
  41. package/dist/config-UCK12Lrr.mjs.map +1 -0
  42. package/dist/config.d.cts +1 -1
  43. package/dist/config.d.mts +1 -1
  44. package/dist/dev/index.cjs +3 -1
  45. package/dist/dev/index.d.cts +2 -2
  46. package/dist/dev/index.d.mts +2 -2
  47. package/dist/dev/index.mjs +3 -1
  48. package/dist/{dev-B734w3L1.mjs → dev-BBPWSllq.mjs} +6 -1
  49. package/dist/dev-BBPWSllq.mjs.map +1 -0
  50. package/dist/{dev-DHqYn8k4.cjs → dev-C2lCgE53.cjs} +6 -1
  51. package/dist/dev-C2lCgE53.cjs.map +1 -0
  52. package/dist/{docker-5d8Yh5_X.cjs → docker-2-ipZDOJ.cjs} +1 -1
  53. package/dist/{docker-5d8Yh5_X.cjs.map → docker-2-ipZDOJ.cjs.map} +1 -1
  54. package/dist/{docker-DlUqdFle.mjs → docker-31GNwU3F.mjs} +1 -1
  55. package/dist/{docker-DlUqdFle.mjs.map → docker-31GNwU3F.mjs.map} +1 -1
  56. package/dist/{env-HfuJRlg5.d.cts → env-CQ3hXAAW.d.mts} +2 -2
  57. package/dist/{env-B-OKjgI4.cjs → env-CS0jvg7k.cjs} +1 -1
  58. package/dist/{env-B-OKjgI4.cjs.map → env-CS0jvg7k.cjs.map} +1 -1
  59. package/dist/{env-nd-iQPYM.d.mts → env-D4YFgMqo.d.cts} +2 -2
  60. package/dist/{env-tv1HlZlw.mjs → env-DEeVOvVu.mjs} +1 -1
  61. package/dist/{env-tv1HlZlw.mjs.map → env-DEeVOvVu.mjs.map} +1 -1
  62. package/dist/generators/CronGenerator.d.cts +4 -4
  63. package/dist/generators/CronGenerator.d.mts +4 -4
  64. package/dist/generators/EndpointGenerator.d.cts +4 -4
  65. package/dist/generators/EndpointGenerator.d.mts +4 -4
  66. package/dist/generators/FunctionGenerator.d.cts +4 -4
  67. package/dist/generators/FunctionGenerator.d.mts +4 -4
  68. package/dist/generators/Generator.d.cts +3 -3
  69. package/dist/generators/Generator.d.mts +3 -3
  70. package/dist/generators/OpenApiTsGenerator.cjs +1 -1
  71. package/dist/generators/OpenApiTsGenerator.mjs +1 -1
  72. package/dist/generators/SubscriberGenerator.d.cts +4 -4
  73. package/dist/generators/SubscriberGenerator.d.mts +4 -4
  74. package/dist/generators/index.d.cts +7 -7
  75. package/dist/generators/index.d.mts +7 -7
  76. package/dist/{index-C523No_B.d.mts → index-DG6xNQMH.d.cts} +25 -8
  77. package/dist/{index-DrzN4xkQ.d.cts → index-DZgrOOOW.d.mts} +25 -8
  78. package/dist/index.cjs +21 -21
  79. package/dist/index.cjs.map +1 -1
  80. package/dist/index.mjs +21 -21
  81. package/dist/index.mjs.map +1 -1
  82. package/dist/init/generators/config.cjs +1 -1
  83. package/dist/init/generators/config.d.cts +2 -2
  84. package/dist/init/generators/config.d.mts +2 -2
  85. package/dist/init/generators/config.mjs +1 -1
  86. package/dist/init/generators/docker.cjs +1 -1
  87. package/dist/init/generators/docker.d.cts +1 -1
  88. package/dist/init/generators/docker.d.mts +1 -1
  89. package/dist/init/generators/docker.mjs +1 -1
  90. package/dist/init/generators/env.cjs +1 -1
  91. package/dist/init/generators/env.d.cts +2 -2
  92. package/dist/init/generators/env.d.mts +2 -2
  93. package/dist/init/generators/env.mjs +1 -1
  94. package/dist/init/generators/index.cjs +9 -4
  95. package/dist/init/generators/index.d.cts +5 -5
  96. package/dist/init/generators/index.d.mts +5 -5
  97. package/dist/init/generators/index.mjs +9 -4
  98. package/dist/init/generators/models.cjs +1 -1
  99. package/dist/init/generators/models.d.cts +1 -1
  100. package/dist/init/generators/models.d.mts +1 -1
  101. package/dist/init/generators/models.mjs +1 -1
  102. package/dist/init/generators/monorepo.cjs +1 -1
  103. package/dist/init/generators/monorepo.d.cts +1 -1
  104. package/dist/init/generators/monorepo.d.mts +1 -1
  105. package/dist/init/generators/monorepo.mjs +1 -1
  106. package/dist/init/generators/package.cjs +6 -1
  107. package/dist/init/generators/package.d.cts +2 -2
  108. package/dist/init/generators/package.d.mts +2 -2
  109. package/dist/init/generators/package.mjs +6 -1
  110. package/dist/init/generators/source.cjs +1 -1
  111. package/dist/init/generators/source.d.cts +2 -2
  112. package/dist/init/generators/source.d.mts +2 -2
  113. package/dist/init/generators/source.mjs +1 -1
  114. package/dist/init/index.cjs +14 -14
  115. package/dist/init/index.d.cts +1 -1
  116. package/dist/init/index.d.mts +1 -1
  117. package/dist/init/index.mjs +14 -14
  118. package/dist/init/templates/api.cjs +1 -1
  119. package/dist/init/templates/api.d.cts +1 -1
  120. package/dist/init/templates/api.d.mts +1 -1
  121. package/dist/init/templates/api.mjs +1 -1
  122. package/dist/init/templates/index.cjs +8 -6
  123. package/dist/init/templates/index.d.cts +2 -2
  124. package/dist/init/templates/index.d.mts +2 -2
  125. package/dist/init/templates/index.mjs +6 -6
  126. package/dist/init/templates/minimal.cjs +1 -1
  127. package/dist/init/templates/minimal.d.cts +1 -1
  128. package/dist/init/templates/minimal.d.mts +1 -1
  129. package/dist/init/templates/minimal.mjs +1 -1
  130. package/dist/init/templates/serverless.cjs +1 -1
  131. package/dist/init/templates/serverless.d.cts +1 -1
  132. package/dist/init/templates/serverless.d.mts +1 -1
  133. package/dist/init/templates/serverless.mjs +1 -1
  134. package/dist/init/templates/worker.cjs +1 -1
  135. package/dist/init/templates/worker.d.cts +1 -1
  136. package/dist/init/templates/worker.d.mts +1 -1
  137. package/dist/init/templates/worker.mjs +1 -1
  138. package/dist/init/utils.cjs +1 -1
  139. package/dist/init/utils.mjs +1 -1
  140. package/dist/{init-CtOnZn3G.mjs → init-BMA7xi8r.mjs} +37 -21
  141. package/dist/init-BMA7xi8r.mjs.map +1 -0
  142. package/dist/{init-qLFsWR-R.cjs → init-D-7WEk-b.cjs} +37 -21
  143. package/dist/init-D-7WEk-b.cjs.map +1 -0
  144. package/dist/{manifests-DIA_2QYd.mjs → manifests-BNKG6AXf.mjs} +1 -1
  145. package/dist/{manifests-DIA_2QYd.mjs.map → manifests-BNKG6AXf.mjs.map} +1 -1
  146. package/dist/{manifests-VJ9-2JpW.cjs → manifests-D13Ej8AE.cjs} +1 -1
  147. package/dist/{manifests-VJ9-2JpW.cjs.map → manifests-D13Ej8AE.cjs.map} +1 -1
  148. package/dist/{minimal-C4GsE45s.mjs → minimal-BkyASH_C.mjs} +15 -9
  149. package/dist/minimal-BkyASH_C.mjs.map +1 -0
  150. package/dist/{minimal-Bdhhpp7v.cjs → minimal-CSFggzdH.cjs} +15 -9
  151. package/dist/minimal-CSFggzdH.cjs.map +1 -0
  152. package/dist/{models-cvNg6Oea.mjs → models-BWlDfviw.mjs} +1 -1
  153. package/dist/{models-cvNg6Oea.mjs.map → models-BWlDfviw.mjs.map} +1 -1
  154. package/dist/{models-DyNwdOcz.cjs → models-BapGSoHC.cjs} +1 -1
  155. package/dist/{models-DyNwdOcz.cjs.map → models-BapGSoHC.cjs.map} +1 -1
  156. package/dist/{monorepo-Cknwzj5C.mjs → monorepo-BBOWhkcd.mjs} +1 -1
  157. package/dist/{monorepo-Cknwzj5C.mjs.map → monorepo-BBOWhkcd.mjs.map} +1 -1
  158. package/dist/{monorepo-sEK8gW59.cjs → monorepo-CFtxHeDh.cjs} +1 -1
  159. package/dist/{monorepo-sEK8gW59.cjs.map → monorepo-CFtxHeDh.cjs.map} +1 -1
  160. package/dist/openapi-DA9RkPJl.mjs +74 -0
  161. package/dist/openapi-DA9RkPJl.mjs.map +1 -0
  162. package/dist/openapi-DZH6RQHk.cjs +98 -0
  163. package/dist/openapi-DZH6RQHk.cjs.map +1 -0
  164. package/dist/{openapi-react-query-DxHjXQvg.cjs → openapi-react-query-Cp-w8_05.cjs} +1 -1
  165. package/dist/{openapi-react-query-DxHjXQvg.cjs.map → openapi-react-query-Cp-w8_05.cjs.map} +1 -1
  166. package/dist/{openapi-react-query-o7Mp1Jd5.mjs → openapi-react-query-_-B3s8v_.mjs} +1 -1
  167. package/dist/{openapi-react-query-o7Mp1Jd5.mjs.map → openapi-react-query-_-B3s8v_.mjs.map} +1 -1
  168. package/dist/openapi-react-query.cjs +1 -1
  169. package/dist/openapi-react-query.mjs +1 -1
  170. package/dist/openapi.cjs +6 -3
  171. package/dist/openapi.d.cts +23 -3
  172. package/dist/openapi.d.mts +23 -3
  173. package/dist/openapi.mjs +3 -3
  174. package/dist/{package-C7WhWU8m.d.mts → package-6h-7QfJZ.d.cts} +2 -2
  175. package/dist/{package-DvWEMz6z.d.cts → package-BCe_KvGv.d.mts} +2 -2
  176. package/dist/{package-CIfmeuSW.mjs → package-C3If80n1.mjs} +7 -1
  177. package/dist/package-C3If80n1.mjs.map +1 -0
  178. package/dist/{package-PP-o1nvq.cjs → package-Dk8IMBOB.cjs} +6 -1
  179. package/dist/package-Dk8IMBOB.cjs.map +1 -0
  180. package/dist/{serverless-Yav3GRVz.cjs → serverless-AGOS-l3G.cjs} +15 -10
  181. package/dist/serverless-AGOS-l3G.cjs.map +1 -0
  182. package/dist/{serverless-DkHBF2vC.mjs → serverless-D5HjJByU.mjs} +15 -10
  183. package/dist/serverless-D5HjJByU.mjs.map +1 -0
  184. package/dist/{source-DT5Xhiob.cjs → source-C1cyfHcF.cjs} +1 -1
  185. package/dist/{source-DT5Xhiob.cjs.map → source-C1cyfHcF.cjs.map} +1 -1
  186. package/dist/{source-D6v2BnKT.d.mts → source-C3LiNUV9.d.mts} +2 -2
  187. package/dist/{source-DnaH_MLA.mjs → source-CkQHBpwu.mjs} +1 -1
  188. package/dist/{source-DnaH_MLA.mjs.map → source-CkQHBpwu.mjs.map} +1 -1
  189. package/dist/{source-D8fK9qRo.d.cts → source-Dtcjbokc.d.cts} +2 -2
  190. package/dist/templates-C0EMmhwb.mjs +88 -0
  191. package/dist/templates-C0EMmhwb.mjs.map +1 -0
  192. package/dist/templates-CbgQ9dw0.cjs +123 -0
  193. package/dist/templates-CbgQ9dw0.cjs.map +1 -0
  194. package/dist/{types-Cxl8-uwV.d.mts → types-Bi7VzDUZ.d.mts} +31 -2
  195. package/dist/{types-C4KITv-y.d.mts → types-D2xYkOal.d.mts} +2 -2
  196. package/dist/{types-DLFN49M3.d.cts → types-DA-r8HWZ.d.cts} +2 -2
  197. package/dist/{types-DB99_qIy.d.cts → types-KmjzMgu8.d.cts} +31 -2
  198. package/dist/types.d.cts +2 -2
  199. package/dist/types.d.mts +2 -2
  200. package/dist/{utils-C31-SWHP.mjs → utils-CKEzCxc1.mjs} +1 -1
  201. package/dist/{utils-C31-SWHP.mjs.map → utils-CKEzCxc1.mjs.map} +1 -1
  202. package/dist/{utils-BX3F4fT8.cjs → utils-DSdN2MTt.cjs} +1 -1
  203. package/dist/{utils-BX3F4fT8.cjs.map → utils-DSdN2MTt.cjs.map} +1 -1
  204. package/dist/{worker--8O5a3Hv.cjs → worker-CGhlqNH-.cjs} +15 -9
  205. package/dist/worker-CGhlqNH-.cjs.map +1 -0
  206. package/dist/{worker-Jme7uOOJ.mjs → worker-CiP420As.mjs} +15 -9
  207. package/dist/worker-CiP420As.mjs.map +1 -0
  208. package/examples/gkm.config.ts +3 -5
  209. package/package.json +4 -4
  210. package/src/__tests__/openapi.spec.ts +395 -302
  211. package/src/dev/__tests__/index.spec.ts +6 -3
  212. package/src/dev/index.ts +18 -0
  213. package/src/generators/OpenApiTsGenerator.ts +1 -1
  214. package/src/init/generators/config.ts +23 -5
  215. package/src/init/generators/package.ts +11 -4
  216. package/src/init/index.ts +26 -7
  217. package/src/init/templates/api.ts +38 -56
  218. package/src/init/templates/index.ts +46 -11
  219. package/src/init/templates/minimal.ts +23 -10
  220. package/src/init/templates/serverless.ts +23 -11
  221. package/src/init/templates/worker.ts +23 -10
  222. package/src/openapi.ts +83 -45
  223. package/src/types.ts +30 -0
  224. package/dist/api-B3SCEHPf.cjs.map +0 -1
  225. package/dist/api-BKIN0s0S.mjs.map +0 -1
  226. package/dist/config-CLEDqKO3.cjs.map +0 -1
  227. package/dist/config-Dp8RonV_.mjs.map +0 -1
  228. package/dist/dev-B734w3L1.mjs.map +0 -1
  229. package/dist/dev-DHqYn8k4.cjs.map +0 -1
  230. package/dist/init-CtOnZn3G.mjs.map +0 -1
  231. package/dist/init-qLFsWR-R.cjs.map +0 -1
  232. package/dist/minimal-Bdhhpp7v.cjs.map +0 -1
  233. package/dist/minimal-C4GsE45s.mjs.map +0 -1
  234. package/dist/openapi-BQWPWyNB.cjs +0 -56
  235. package/dist/openapi-BQWPWyNB.cjs.map +0 -1
  236. package/dist/openapi-DBX8cJJ8.mjs +0 -50
  237. package/dist/openapi-DBX8cJJ8.mjs.map +0 -1
  238. package/dist/package-CIfmeuSW.mjs.map +0 -1
  239. package/dist/package-PP-o1nvq.cjs.map +0 -1
  240. package/dist/serverless-DkHBF2vC.mjs.map +0 -1
  241. package/dist/serverless-Yav3GRVz.cjs.map +0 -1
  242. package/dist/templates-CBFUwpBy.mjs +0 -64
  243. package/dist/templates-CBFUwpBy.mjs.map +0 -1
  244. package/dist/templates-DM_rtYYW.cjs +0 -87
  245. package/dist/templates-DM_rtYYW.cjs.map +0 -1
  246. package/dist/worker--8O5a3Hv.cjs.map +0 -1
  247. package/dist/worker-Jme7uOOJ.mjs.map +0 -1
@@ -1,8 +1,14 @@
1
- import { existsSync } from 'node:fs';
2
- import { readFile } from 'node:fs/promises';
1
+ import { existsSync, realpathSync } from 'node:fs';
2
+ import { readFile, rm } from 'node:fs/promises';
3
3
  import { join } from 'node:path';
4
4
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
5
- import { openapiCommand } from '../openapi';
5
+ import {
6
+ OPENAPI_OUTPUT_PATH,
7
+ generateOpenApi,
8
+ openapiCommand,
9
+ resolveOpenApiConfig,
10
+ } from '../openapi';
11
+ import type { GkmConfig } from '../types';
6
12
  import {
7
13
  cleanupDir,
8
14
  createMockEndpointFile,
@@ -10,328 +16,419 @@ import {
10
16
  createTestFile,
11
17
  } from './test-helpers';
12
18
 
13
- describe('OpenAPI Generation', () => {
19
+ describe('resolveOpenApiConfig', () => {
20
+ const baseConfig: GkmConfig = {
21
+ routes: './src/endpoints/**/*.ts',
22
+ envParser: './src/config/env#envParser',
23
+ logger: './src/config/logger#logger',
24
+ };
25
+
26
+ it('should return disabled when openapi is false', () => {
27
+ const result = resolveOpenApiConfig({ ...baseConfig, openapi: false });
28
+ expect(result).toEqual({ enabled: false });
29
+ });
30
+
31
+ it('should return enabled with defaults when openapi is true', () => {
32
+ const result = resolveOpenApiConfig({ ...baseConfig, openapi: true });
33
+ expect(result).toEqual({
34
+ enabled: true,
35
+ title: 'API Documentation',
36
+ version: '1.0.0',
37
+ description: 'Auto-generated API documentation from endpoints',
38
+ });
39
+ });
40
+
41
+ it('should return disabled when openapi is undefined', () => {
42
+ const result = resolveOpenApiConfig({ ...baseConfig });
43
+ expect(result.enabled).toBe(false);
44
+ });
45
+
46
+ it('should use custom config values when provided', () => {
47
+ const result = resolveOpenApiConfig({
48
+ ...baseConfig,
49
+ openapi: {
50
+ enabled: true,
51
+ title: 'My API',
52
+ version: '2.0.0',
53
+ description: 'Custom description',
54
+ },
55
+ });
56
+ expect(result).toEqual({
57
+ enabled: true,
58
+ title: 'My API',
59
+ version: '2.0.0',
60
+ description: 'Custom description',
61
+ });
62
+ });
63
+
64
+ it('should use defaults for missing optional config values', () => {
65
+ const result = resolveOpenApiConfig({
66
+ ...baseConfig,
67
+ openapi: { enabled: true },
68
+ });
69
+ expect(result).toEqual({
70
+ enabled: true,
71
+ title: 'API Documentation',
72
+ version: '1.0.0',
73
+ description: 'Auto-generated API documentation from endpoints',
74
+ });
75
+ });
76
+
77
+ it('should be enabled by default when object provided without enabled field', () => {
78
+ const result = resolveOpenApiConfig({
79
+ ...baseConfig,
80
+ openapi: { title: 'Custom Title' },
81
+ });
82
+ expect(result.enabled).toBe(true);
83
+ });
84
+ });
85
+
86
+ describe('generateOpenApi', () => {
14
87
  let tempDir: string;
88
+ const originalCwd = process.cwd();
15
89
 
16
90
  beforeEach(async () => {
17
- tempDir = await createTempDir('openapi-test-');
91
+ tempDir = realpathSync(await createTempDir('openapi-gen-'));
92
+ // Change to temp dir so output goes there
93
+ process.chdir(tempDir);
18
94
  });
19
95
 
20
96
  afterEach(async () => {
97
+ process.chdir(originalCwd);
21
98
  await cleanupDir(tempDir);
22
99
  vi.restoreAllMocks();
23
100
  });
24
101
 
25
- describe('openapiCommand - TypeScript output (default)', () => {
26
- it('should generate TypeScript module by default', async () => {
27
- await createMockEndpointFile(
28
- tempDir,
29
- 'getUser.ts',
30
- 'getUser',
31
- '/users/:id',
32
- 'GET',
33
- );
102
+ it('should return null when openapi is disabled', async () => {
103
+ const config: GkmConfig = {
104
+ routes: './src/endpoints/**/*.ts',
105
+ envParser: './src/config/env#envParser',
106
+ logger: './src/config/logger#logger',
107
+ openapi: false,
108
+ };
34
109
 
35
- await createTestFile(
36
- tempDir,
37
- 'gkm.config.json',
38
- JSON.stringify({
39
- routes: [`${tempDir}/**/*.ts`],
40
- }),
41
- );
110
+ const result = await generateOpenApi(config);
111
+ expect(result).toBeNull();
112
+ });
42
113
 
43
- const outputPath = join(tempDir, 'openapi.ts');
114
+ it('should return null when openapi is undefined', async () => {
115
+ const config: GkmConfig = {
116
+ routes: './src/endpoints/**/*.ts',
117
+ envParser: './src/config/env#envParser',
118
+ logger: './src/config/logger#logger',
119
+ };
44
120
 
45
- await openapiCommand({ output: outputPath, cwd: tempDir });
121
+ const result = await generateOpenApi(config);
122
+ expect(result).toBeNull();
123
+ });
46
124
 
47
- expect(existsSync(outputPath)).toBe(true);
125
+ it('should generate to fixed .gkm/openapi.ts path', async () => {
126
+ await createMockEndpointFile(tempDir, 'test.ts', 'test', '/test', 'GET');
48
127
 
49
- const content = await readFile(outputPath, 'utf-8');
128
+ const config: GkmConfig = {
129
+ routes: `${tempDir}/**/*.ts`,
130
+ envParser: './src/config/env#envParser',
131
+ logger: './src/config/logger#logger',
132
+ openapi: { enabled: true },
133
+ };
50
134
 
51
- expect(content).toContain('// Auto-generated by @geekmidas/cli');
52
- expect(content).toContain('export const securitySchemes');
53
- expect(content).toContain('export const endpointAuth');
54
- expect(content).toContain('export interface paths');
55
- });
135
+ const result = await generateOpenApi(config, { silent: true });
56
136
 
57
- it('should include endpoint auth map', async () => {
58
- await createMockEndpointFile(
59
- tempDir,
60
- 'getUser.ts',
61
- 'getUser',
62
- '/users/:id',
63
- 'GET',
64
- );
137
+ expect(result).not.toBeNull();
138
+ expect(result?.endpointCount).toBe(1);
139
+ expect(result?.outputPath).toBe(join(tempDir, OPENAPI_OUTPUT_PATH));
140
+ expect(existsSync(join(tempDir, OPENAPI_OUTPUT_PATH))).toBe(true);
141
+ });
65
142
 
66
- await createTestFile(
67
- tempDir,
68
- 'gkm.config.json',
69
- JSON.stringify({
70
- routes: [`${tempDir}/**/*.ts`],
71
- }),
72
- );
143
+ it('should generate TypeScript content', async () => {
144
+ await createMockEndpointFile(tempDir, 'test.ts', 'test', '/test', 'GET');
73
145
 
74
- const outputPath = join(tempDir, 'openapi.ts');
146
+ const config: GkmConfig = {
147
+ routes: `${tempDir}/**/*.ts`,
148
+ envParser: './src/config/env#envParser',
149
+ logger: './src/config/logger#logger',
150
+ openapi: { enabled: true },
151
+ };
75
152
 
76
- await openapiCommand({ output: outputPath, cwd: tempDir });
153
+ await generateOpenApi(config, { silent: true });
77
154
 
78
- const content = await readFile(outputPath, 'utf-8');
155
+ const content = await readFile(join(tempDir, OPENAPI_OUTPUT_PATH), 'utf-8');
156
+ expect(content).toContain('// Auto-generated by @geekmidas/cli');
157
+ expect(content).toContain('export const securitySchemes');
158
+ expect(content).toContain('export interface paths');
159
+ });
79
160
 
80
- expect(content).toContain('endpointAuth');
81
- expect(content).toContain("'GET /users/{id}'");
82
- });
161
+ it('should log no endpoints message when none found', async () => {
162
+ const config: GkmConfig = {
163
+ routes: `${tempDir}/nonexistent/**/*.ts`,
164
+ envParser: './src/config/env#envParser',
165
+ logger: './src/config/logger#logger',
166
+ openapi: { enabled: true },
167
+ };
168
+
169
+ const consoleSpy = vi.spyOn(console, 'log');
170
+ const result = await generateOpenApi(config);
171
+
172
+ expect(result).toBeNull();
173
+ expect(consoleSpy).toHaveBeenCalledWith(
174
+ 'No valid endpoints found for OpenAPI generation',
175
+ );
83
176
  });
177
+ });
84
178
 
85
- describe('openapiCommand - JSON output (legacy)', () => {
86
- it('should generate JSON OpenAPI spec with --json flag', async () => {
87
- await createMockEndpointFile(
88
- tempDir,
89
- 'getUser.ts',
90
- 'getUser',
91
- '/users/:id',
92
- 'GET',
93
- );
94
-
95
- await createTestFile(
96
- tempDir,
97
- 'gkm.config.json',
98
- JSON.stringify({
99
- routes: [`${tempDir}/**/*.ts`],
100
- }),
101
- );
102
-
103
- const outputPath = join(tempDir, 'openapi.json');
104
-
105
- await openapiCommand({ output: outputPath, json: true, cwd: tempDir });
106
-
107
- expect(existsSync(outputPath)).toBe(true);
108
-
109
- const content = await readFile(outputPath, 'utf-8');
110
- const spec = JSON.parse(content);
111
-
112
- expect(spec).toHaveProperty('openapi');
113
- expect(spec).toHaveProperty('info');
114
- expect(spec.info.title).toBe('API Documentation');
115
- expect(spec).toHaveProperty('paths');
116
- expect(Object.keys(spec.paths).length).toBeGreaterThan(0);
117
- });
179
+ describe('openapiCommand', () => {
180
+ let tempDir: string;
181
+ const originalCwd = process.cwd();
118
182
 
119
- it('should handle no endpoints found', async () => {
120
- await createTestFile(
121
- tempDir,
122
- 'gkm.config.json',
123
- JSON.stringify({
124
- routes: [`${tempDir}/nonexistent/**/*.ts`],
125
- }),
126
- );
183
+ beforeEach(async () => {
184
+ tempDir = realpathSync(await createTempDir('openapi-cmd-'));
185
+ });
127
186
 
128
- const consoleSpy = vi.spyOn(console, 'log');
187
+ afterEach(async () => {
188
+ process.chdir(originalCwd);
189
+ // Clean up any generated .gkm folder in current directory
190
+ await rm(join(originalCwd, '.gkm'), { recursive: true, force: true });
191
+ await cleanupDir(tempDir);
192
+ vi.restoreAllMocks();
193
+ });
129
194
 
130
- await openapiCommand({
131
- output: join(tempDir, 'openapi.json'),
132
- json: true,
133
- cwd: tempDir,
134
- });
195
+ it('should generate OpenAPI client to .gkm/openapi.ts', async () => {
196
+ await createMockEndpointFile(
197
+ tempDir,
198
+ 'test.ts',
199
+ 'testEndpoint',
200
+ '/test',
201
+ 'GET',
202
+ );
203
+
204
+ await createTestFile(
205
+ tempDir,
206
+ 'gkm.config.json',
207
+ JSON.stringify({
208
+ routes: [`${tempDir}/**/*.ts`],
209
+ openapi: { enabled: true },
210
+ }),
211
+ );
212
+
213
+ // Change to temp dir so output goes there
214
+ process.chdir(tempDir);
215
+
216
+ await openapiCommand({ cwd: tempDir });
217
+
218
+ const outputPath = join(tempDir, OPENAPI_OUTPUT_PATH);
219
+ expect(existsSync(outputPath)).toBe(true);
220
+
221
+ const content = await readFile(outputPath, 'utf-8');
222
+ expect(content).toContain('// Auto-generated by @geekmidas/cli');
223
+ });
135
224
 
136
- expect(consoleSpy).toHaveBeenCalledWith('No valid endpoints found');
137
- });
225
+ it('should enable openapi with defaults when not configured', async () => {
226
+ await createMockEndpointFile(
227
+ tempDir,
228
+ 'test.ts',
229
+ 'testEndpoint',
230
+ '/test',
231
+ 'GET',
232
+ );
233
+
234
+ await createTestFile(
235
+ tempDir,
236
+ 'gkm.config.json',
237
+ JSON.stringify({
238
+ routes: [`${tempDir}/**/*.ts`],
239
+ }),
240
+ );
241
+
242
+ process.chdir(tempDir);
243
+ const consoleSpy = vi.spyOn(console, 'log');
244
+
245
+ await openapiCommand({ cwd: tempDir });
246
+
247
+ expect(consoleSpy).toHaveBeenCalledWith(
248
+ expect.stringContaining('Found 1 endpoints'),
249
+ );
250
+ expect(existsSync(join(tempDir, OPENAPI_OUTPUT_PATH))).toBe(true);
251
+ });
138
252
 
139
- it('should generate spec with multiple endpoints', async () => {
140
- await createMockEndpointFile(
141
- tempDir,
142
- 'getUsers.ts',
143
- 'getUsers',
144
- '/users',
145
- 'GET',
146
- );
147
- await createMockEndpointFile(
148
- tempDir,
149
- 'createUser.ts',
150
- 'createUser',
151
- '/users',
152
- 'POST',
153
- );
154
- await createMockEndpointFile(
155
- tempDir,
156
- 'deleteUser.ts',
157
- 'deleteUser',
158
- '/users/:id',
159
- 'DELETE',
160
- );
161
-
162
- await createTestFile(
163
- tempDir,
164
- 'gkm.config.json',
165
- JSON.stringify({
166
- routes: [`${tempDir}/**/*.ts`],
167
- }),
168
- );
169
-
170
- const outputPath = join(tempDir, 'openapi.json');
171
-
172
- await openapiCommand({ output: outputPath, json: true, cwd: tempDir });
173
-
174
- const content = await readFile(outputPath, 'utf-8');
175
- const spec = JSON.parse(content);
176
-
177
- expect(Object.keys(spec.paths).length).toBeGreaterThanOrEqual(1);
178
- });
253
+ it('should include endpoint auth map', async () => {
254
+ await createMockEndpointFile(
255
+ tempDir,
256
+ 'getUser.ts',
257
+ 'getUser',
258
+ '/users/:id',
259
+ 'GET',
260
+ );
261
+
262
+ await createTestFile(
263
+ tempDir,
264
+ 'gkm.config.json',
265
+ JSON.stringify({
266
+ routes: [`${tempDir}/**/*.ts`],
267
+ openapi: { enabled: true },
268
+ }),
269
+ );
270
+
271
+ process.chdir(tempDir);
272
+
273
+ await openapiCommand({ cwd: tempDir });
274
+
275
+ const content = await readFile(join(tempDir, OPENAPI_OUTPUT_PATH), 'utf-8');
276
+ expect(content).toContain('endpointAuth');
277
+ expect(content).toContain("'GET /users/{id}'");
278
+ });
179
279
 
180
- it('should create output directory if it does not exist', async () => {
181
- await createMockEndpointFile(
182
- tempDir,
183
- 'endpoint.ts',
184
- 'testEndpoint',
185
- '/test',
186
- 'GET',
187
- );
280
+ it('should handle no endpoints found', async () => {
281
+ await createTestFile(
282
+ tempDir,
283
+ 'gkm.config.json',
284
+ JSON.stringify({
285
+ routes: [`${tempDir}/nonexistent/**/*.ts`],
286
+ openapi: { enabled: true },
287
+ }),
288
+ );
188
289
 
189
- await createTestFile(
190
- tempDir,
191
- 'gkm.config.json',
192
- JSON.stringify({
193
- routes: [`${tempDir}/**/*.ts`],
194
- }),
195
- );
290
+ process.chdir(tempDir);
291
+ const consoleSpy = vi.spyOn(console, 'log');
196
292
 
197
- const outputPath = join(tempDir, 'nested', 'dir', 'openapi.json');
293
+ await openapiCommand({ cwd: tempDir });
198
294
 
199
- await openapiCommand({ output: outputPath, json: true, cwd: tempDir });
295
+ expect(consoleSpy).toHaveBeenCalledWith(
296
+ 'No valid endpoints found for OpenAPI generation',
297
+ );
298
+ });
200
299
 
201
- expect(existsSync(outputPath)).toBe(true);
202
- });
300
+ it('should generate with multiple endpoints', async () => {
301
+ await createMockEndpointFile(
302
+ tempDir,
303
+ 'getUsers.ts',
304
+ 'getUsers',
305
+ '/users',
306
+ 'GET',
307
+ );
308
+ await createMockEndpointFile(
309
+ tempDir,
310
+ 'createUser.ts',
311
+ 'createUser',
312
+ '/users',
313
+ 'POST',
314
+ );
315
+ await createMockEndpointFile(
316
+ tempDir,
317
+ 'deleteUser.ts',
318
+ 'deleteUser',
319
+ '/users/:id',
320
+ 'DELETE',
321
+ );
322
+
323
+ await createTestFile(
324
+ tempDir,
325
+ 'gkm.config.json',
326
+ JSON.stringify({
327
+ routes: [`${tempDir}/**/*.ts`],
328
+ openapi: { enabled: true },
329
+ }),
330
+ );
331
+
332
+ process.chdir(tempDir);
333
+ const consoleSpy = vi.spyOn(console, 'log');
334
+
335
+ await openapiCommand({ cwd: tempDir });
336
+
337
+ expect(consoleSpy).toHaveBeenCalledWith(
338
+ expect.stringContaining('Found 3 endpoints'),
339
+ );
340
+ });
203
341
 
204
- it('should include API metadata in spec', async () => {
205
- await createMockEndpointFile(
206
- tempDir,
207
- 'endpoint.ts',
208
- 'testEndpoint',
209
- '/test',
210
- 'GET',
211
- );
212
-
213
- await createTestFile(
214
- tempDir,
215
- 'gkm.config.json',
216
- JSON.stringify({
217
- routes: [`${tempDir}/**/*.ts`],
218
- }),
219
- );
220
-
221
- const outputPath = join(tempDir, 'openapi.json');
222
-
223
- await openapiCommand({ output: outputPath, json: true, cwd: tempDir });
224
-
225
- const content = await readFile(outputPath, 'utf-8');
226
- const spec = JSON.parse(content);
227
-
228
- expect(spec.info).toEqual({
229
- title: 'API Documentation',
230
- version: '1.0.0',
231
- description: 'Auto-generated API documentation from endpoints',
232
- });
233
- });
342
+ it('should create .gkm directory if it does not exist', async () => {
343
+ await createMockEndpointFile(
344
+ tempDir,
345
+ 'endpoint.ts',
346
+ 'testEndpoint',
347
+ '/test',
348
+ 'GET',
349
+ );
350
+
351
+ await createTestFile(
352
+ tempDir,
353
+ 'gkm.config.json',
354
+ JSON.stringify({
355
+ routes: [`${tempDir}/**/*.ts`],
356
+ openapi: { enabled: true },
357
+ }),
358
+ );
359
+
360
+ process.chdir(tempDir);
361
+
362
+ await openapiCommand({ cwd: tempDir });
363
+
364
+ expect(existsSync(join(tempDir, '.gkm'))).toBe(true);
365
+ expect(existsSync(join(tempDir, OPENAPI_OUTPUT_PATH))).toBe(true);
366
+ });
234
367
 
235
- it('should log generation success for JSON', async () => {
236
- // Create endpoint
237
- await createMockEndpointFile(
238
- tempDir,
239
- 'endpoint.ts',
240
- 'testEndpoint',
241
- '/test',
242
- 'GET',
243
- );
244
-
245
- // Create config
246
- await createTestFile(
247
- tempDir,
248
- 'gkm.config.json',
249
- JSON.stringify({
250
- routes: [`${tempDir}/**/*.ts`],
251
- }),
252
- );
253
-
254
- const outputPath = join(tempDir, 'openapi.json');
255
- const consoleSpy = vi.spyOn(console, 'log');
256
-
257
- await openapiCommand({ output: outputPath, json: true, cwd: tempDir });
258
-
259
- expect(consoleSpy).toHaveBeenCalledWith(
260
- expect.stringContaining('OpenAPI JSON spec generated'),
261
- );
262
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Found'));
263
- expect(consoleSpy).toHaveBeenCalledWith(
264
- expect.stringContaining('endpoints'),
265
- );
266
- });
368
+ it('should throw error when config loading fails', async () => {
369
+ process.chdir(tempDir);
267
370
 
268
- it('should throw error when config loading fails', async () => {
269
- // No config file created
270
- await expect(
271
- openapiCommand({ json: true, cwd: tempDir }),
272
- ).rejects.toThrow(/OpenAPI generation failed/);
273
- });
371
+ await expect(openapiCommand({ cwd: tempDir })).rejects.toThrow(
372
+ /OpenAPI generation failed/,
373
+ );
374
+ });
274
375
 
275
- it('should throw error for invalid TypeScript files', async () => {
276
- // Create invalid TS file
277
- await createTestFile(
278
- tempDir,
279
- 'invalid.ts',
280
- 'this is not valid typescript {[}]',
281
- );
282
-
283
- // Create config
284
- await createTestFile(
285
- tempDir,
286
- 'gkm.config.json',
287
- JSON.stringify({
288
- routes: [`${tempDir}/**/*.ts`],
289
- }),
290
- );
291
-
292
- // Should throw error for syntax errors
293
- await expect(
294
- openapiCommand({
295
- output: join(tempDir, 'openapi.json'),
296
- json: true,
297
- cwd: tempDir,
298
- }),
299
- ).rejects.toThrow(/OpenAPI generation failed/);
300
- });
376
+ it('should throw error for invalid TypeScript files', async () => {
377
+ await createTestFile(
378
+ tempDir,
379
+ 'invalid.ts',
380
+ 'this is not valid typescript {[}]',
381
+ );
382
+
383
+ await createTestFile(
384
+ tempDir,
385
+ 'gkm.config.json',
386
+ JSON.stringify({
387
+ routes: [`${tempDir}/**/*.ts`],
388
+ openapi: { enabled: true },
389
+ }),
390
+ );
391
+
392
+ process.chdir(tempDir);
393
+
394
+ await expect(openapiCommand({ cwd: tempDir })).rejects.toThrow(
395
+ /OpenAPI generation failed/,
396
+ );
397
+ });
301
398
 
302
- it('should generate valid JSON format', async () => {
303
- // Create endpoint
304
- await createMockEndpointFile(
305
- tempDir,
306
- 'endpoint.ts',
307
- 'testEndpoint',
308
- '/test',
309
- 'GET',
310
- );
311
-
312
- // Create config
313
- await createTestFile(
314
- tempDir,
315
- 'gkm.config.json',
316
- JSON.stringify({
317
- routes: [`${tempDir}/**/*.ts`],
318
- }),
319
- );
320
-
321
- const outputPath = join(tempDir, 'openapi.json');
322
-
323
- await openapiCommand({ output: outputPath, json: true, cwd: tempDir });
324
-
325
- const content = await readFile(outputPath, 'utf-8');
326
-
327
- // Should be valid JSON and properly formatted
328
- expect(() => JSON.parse(content)).not.toThrow();
329
- expect(content).toContain('\n'); // Formatted with indentation
330
- });
399
+ it('should log generation success', async () => {
400
+ await createMockEndpointFile(
401
+ tempDir,
402
+ 'endpoint.ts',
403
+ 'testEndpoint',
404
+ '/test',
405
+ 'GET',
406
+ );
407
+
408
+ await createTestFile(
409
+ tempDir,
410
+ 'gkm.config.json',
411
+ JSON.stringify({
412
+ routes: [`${tempDir}/**/*.ts`],
413
+ openapi: { enabled: true },
414
+ }),
415
+ );
416
+
417
+ process.chdir(tempDir);
418
+ const consoleSpy = vi.spyOn(console, 'log');
419
+
420
+ await openapiCommand({ cwd: tempDir });
421
+
422
+ expect(consoleSpy).toHaveBeenCalledWith(
423
+ expect.stringContaining('OpenAPI client generated'),
424
+ );
425
+ expect(consoleSpy).toHaveBeenCalledWith(
426
+ expect.stringContaining('Found 1 endpoints'),
427
+ );
428
+ });
331
429
 
332
- it('should handle endpoints with complex schemas', async () => {
333
- // Create endpoint with complex schema
334
- const complexEndpointContent = `
430
+ it('should handle endpoints with complex schemas', async () => {
431
+ const complexEndpointContent = `
335
432
  import { e } from '@geekmidas/constructs/endpoints';
336
433
  import { z } from 'zod';
337
434
 
@@ -352,26 +449,22 @@ export const complexEndpoint = e
352
449
  .handle(async () => ({ id: '123', status: 'active' as const }));
353
450
  `;
354
451
 
355
- await createTestFile(tempDir, 'complex.ts', complexEndpointContent);
356
-
357
- // Create config
358
- await createTestFile(
359
- tempDir,
360
- 'gkm.config.json',
361
- JSON.stringify({
362
- routes: [`${tempDir}/**/*.ts`],
363
- }),
364
- );
452
+ await createTestFile(tempDir, 'complex.ts', complexEndpointContent);
365
453
 
366
- const outputPath = join(tempDir, 'openapi.json');
454
+ await createTestFile(
455
+ tempDir,
456
+ 'gkm.config.json',
457
+ JSON.stringify({
458
+ routes: [`${tempDir}/**/*.ts`],
459
+ openapi: { enabled: true },
460
+ }),
461
+ );
367
462
 
368
- await openapiCommand({ output: outputPath, json: true, cwd: tempDir });
463
+ process.chdir(tempDir);
369
464
 
370
- const content = await readFile(outputPath, 'utf-8');
371
- const spec = JSON.parse(content);
465
+ await openapiCommand({ cwd: tempDir });
372
466
 
373
- // Should have generated schema for complex types
374
- expect(spec.paths).toBeDefined();
375
- });
467
+ const content = await readFile(join(tempDir, OPENAPI_OUTPUT_PATH), 'utf-8');
468
+ expect(content).toContain('export interface paths');
376
469
  });
377
470
  });