@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
@@ -0,0 +1,978 @@
1
+ import { loadConfig } from "./config-Bq72aj8e.mjs";
2
+ import { relative } from "path";
3
+ import { mkdir, writeFile } from "node:fs/promises";
4
+ import { dirname, join as join$1, relative as relative$1 } from "node:path";
5
+ import fg from "fast-glob";
6
+ import kebabCase from "lodash.kebabcase";
7
+ import { Endpoint } from "@geekmidas/constructs/endpoints";
8
+ import { StandardSchemaJsonSchema, getSchemaMetadata } from "@geekmidas/schema/conversion";
9
+
10
+ //#region src/generators/Generator.ts
11
+ var ConstructGenerator = class {
12
+ static async build(context, outputDir, generator, patterns, options) {
13
+ const constructs = await generator.load(patterns);
14
+ return generator.build(context, constructs, outputDir, options);
15
+ }
16
+ async load(patterns, cwd = process.cwd()) {
17
+ const logger = console;
18
+ const globPatterns = Array.isArray(patterns) ? patterns : patterns ? [patterns] : [];
19
+ const files = fg.stream(globPatterns, {
20
+ cwd,
21
+ absolute: true
22
+ });
23
+ const constructs = [];
24
+ for await (const f of files) try {
25
+ const file = f.toString();
26
+ const module = await import(file);
27
+ for (const [key, construct] of Object.entries(module)) if (this.isConstruct(construct)) constructs.push({
28
+ key,
29
+ name: kebabCase(key),
30
+ construct,
31
+ path: {
32
+ absolute: file,
33
+ relative: relative(process.cwd(), file)
34
+ }
35
+ });
36
+ } catch (error) {
37
+ logger.warn(`Failed to load ${f}:`, error.message);
38
+ throw new Error("Failed to load constructs. Please check the logs for details.");
39
+ }
40
+ return constructs;
41
+ }
42
+ };
43
+
44
+ //#endregion
45
+ //#region src/generators/EndpointGenerator.ts
46
+ var EndpointGenerator = class extends ConstructGenerator {
47
+ isConstruct(value) {
48
+ return Endpoint.isEndpoint(value);
49
+ }
50
+ async build(context, constructs, outputDir, options) {
51
+ const provider = options?.provider || "aws-apigatewayv2";
52
+ const enableOpenApi = options?.enableOpenApi || false;
53
+ const logger = console;
54
+ const routes = [];
55
+ if (constructs.length === 0) return routes;
56
+ if (provider === "server") {
57
+ await this.generateEndpointsFile(outputDir, constructs, context);
58
+ const appFile = await this.generateAppFile(outputDir, context);
59
+ routes.push({
60
+ path: "*",
61
+ method: "ALL",
62
+ handler: relative$1(process.cwd(), appFile),
63
+ authorizer: "none"
64
+ });
65
+ logger.log(`Generated server with ${constructs.length} endpoints${enableOpenApi ? " (OpenAPI enabled)" : ""}`);
66
+ } else if (provider === "aws-lambda") {
67
+ const routesDir = join$1(outputDir, "routes");
68
+ await mkdir(routesDir, { recursive: true });
69
+ for (const { key, construct, path } of constructs) {
70
+ const handlerFile = await this.generateHandlerFile(routesDir, path.relative, key, "aws-apigatewayv2", construct, context);
71
+ const routeInfo = {
72
+ path: construct._path,
73
+ method: construct.method,
74
+ handler: relative$1(process.cwd(), handlerFile).replace(/\.ts$/, ".handler"),
75
+ timeout: construct.timeout,
76
+ memorySize: construct.memorySize,
77
+ environment: await construct.getEnvironment(),
78
+ authorizer: construct.authorizer?.name ?? "none"
79
+ };
80
+ routes.push(routeInfo);
81
+ logger.log(`Generated handler for ${routeInfo.method} ${routeInfo.path}`);
82
+ }
83
+ } else for (const { key, construct, path } of constructs) {
84
+ const handlerFile = await this.generateHandlerFile(outputDir, path.relative, key, provider, construct, context);
85
+ const routeInfo = {
86
+ path: construct._path,
87
+ method: construct.method,
88
+ handler: relative$1(process.cwd(), handlerFile).replace(/\.ts$/, ".handler"),
89
+ timeout: construct.timeout,
90
+ memorySize: construct.memorySize,
91
+ environment: await construct.getEnvironment(),
92
+ authorizer: construct.authorizer?.name ?? "none"
93
+ };
94
+ routes.push(routeInfo);
95
+ logger.log(`Generated handler for ${routeInfo.method} ${routeInfo.path}`);
96
+ }
97
+ return routes;
98
+ }
99
+ async generateHandlerFile(outputDir, sourceFile, exportName, provider, _endpoint, context) {
100
+ const handlerFileName = `${exportName}.ts`;
101
+ const handlerPath = join$1(outputDir, handlerFileName);
102
+ const relativePath = relative$1(dirname(handlerPath), sourceFile);
103
+ const importPath = relativePath.replace(/\.ts$/, ".js");
104
+ const relativeEnvParserPath = relative$1(dirname(handlerPath), context.envParserPath);
105
+ let content;
106
+ switch (provider) {
107
+ case "aws-apigatewayv1":
108
+ content = this.generateAWSApiGatewayV1Handler(importPath, exportName, relativeEnvParserPath, context.envParserImportPattern);
109
+ break;
110
+ case "aws-apigatewayv2":
111
+ content = this.generateAWSApiGatewayV2Handler(importPath, exportName, relativeEnvParserPath, context.envParserImportPattern);
112
+ break;
113
+ case "server":
114
+ content = this.generateServerHandler(importPath, exportName);
115
+ break;
116
+ default: throw new Error(`Unsupported provider: ${provider}`);
117
+ }
118
+ await writeFile(handlerPath, content);
119
+ return handlerPath;
120
+ }
121
+ async generateEndpointsFile(outputDir, endpoints, _context) {
122
+ const endpointsFileName = "endpoints.ts";
123
+ const endpointsPath = join$1(outputDir, endpointsFileName);
124
+ const importsByFile = /* @__PURE__ */ new Map();
125
+ for (const { path, key } of endpoints) {
126
+ const relativePath = relative$1(dirname(endpointsPath), path.relative);
127
+ const importPath = relativePath.replace(/\.ts$/, ".js");
128
+ if (!importsByFile.has(importPath)) importsByFile.set(importPath, []);
129
+ importsByFile.get(importPath).push(key);
130
+ }
131
+ const imports = Array.from(importsByFile.entries()).map(([importPath, exports]) => `import { ${exports.join(", ")} } from '${importPath}';`).join("\n");
132
+ const allExportNames = endpoints.map(({ key }) => key);
133
+ const content = `import type { EnvironmentParser } from '@geekmidas/envkit';
134
+ import type { Logger } from '@geekmidas/logger';
135
+ import { HonoEndpoint } from '@geekmidas/constructs/hono';
136
+ import { Endpoint } from '@geekmidas/constructs/endpoints';
137
+ import { ServiceDiscovery } from '@geekmidas/services';
138
+ import type { Hono } from 'hono';
139
+ ${imports}
140
+
141
+ const endpoints: Endpoint<any, any, any, any, any, any, any, any, any, any, any, any, any, any>[] = [
142
+ ${allExportNames.join(",\n ")}
143
+ ];
144
+
145
+ export async function setupEndpoints(
146
+ app: Hono,
147
+ envParser: EnvironmentParser<any>,
148
+ logger: Logger,
149
+ enableOpenApi: boolean = true,
150
+ ): Promise<void> {
151
+ const serviceDiscovery = ServiceDiscovery.getInstance(
152
+ logger,
153
+ envParser
154
+ );
155
+
156
+ // Configure OpenAPI options based on enableOpenApi flag
157
+ const openApiOptions: any = enableOpenApi ? {
158
+ docsPath: '/__docs',
159
+ openApiOptions: {
160
+ title: 'API Documentation',
161
+ version: '1.0.0',
162
+ description: 'Generated API documentation'
163
+ }
164
+ } : { docsPath: false };
165
+
166
+ HonoEndpoint.addRoutes(endpoints, serviceDiscovery, app, openApiOptions);
167
+
168
+ // Add Swagger UI if OpenAPI is enabled
169
+ if (enableOpenApi) {
170
+ try {
171
+ const { swaggerUI } = await import('@hono/swagger-ui');
172
+ app.get('/__docs/ui', swaggerUI({ url: '/__docs' }));
173
+ } catch {
174
+ // @hono/swagger-ui not installed, skip Swagger UI
175
+ }
176
+ }
177
+ }
178
+ `;
179
+ await writeFile(endpointsPath, content);
180
+ return endpointsPath;
181
+ }
182
+ async generateAppFile(outputDir, context) {
183
+ const appFileName = "app.ts";
184
+ const appPath = join$1(outputDir, appFileName);
185
+ const relativeLoggerPath = relative$1(dirname(appPath), context.loggerPath);
186
+ const relativeEnvParserPath = relative$1(dirname(appPath), context.envParserPath);
187
+ const telescopeEnabled = context.telescope?.enabled;
188
+ const telescopeWebSocketEnabled = context.telescope?.websocket;
189
+ const usesExternalTelescope = !!context.telescope?.telescopePath;
190
+ const studioEnabled = context.studio?.enabled;
191
+ const usesExternalStudio = !!context.studio?.studioPath;
192
+ let telescopeImports = "";
193
+ if (telescopeEnabled) if (usesExternalTelescope) {
194
+ const relativeTelescopePath = relative$1(dirname(appPath), context.telescope.telescopePath);
195
+ telescopeImports = `import ${context.telescope.telescopeImportPattern} from '${relativeTelescopePath}';
196
+ import { createMiddleware, createUI } from '@geekmidas/telescope/hono';`;
197
+ } else telescopeImports = `import { Telescope, InMemoryStorage } from '@geekmidas/telescope';
198
+ import { createMiddleware, createUI } from '@geekmidas/telescope/hono';`;
199
+ let studioImports = "";
200
+ if (studioEnabled) if (usesExternalStudio) {
201
+ const relativeStudioPath = relative$1(dirname(appPath), context.studio.studioPath);
202
+ studioImports = `import ${context.studio.studioImportPattern} from '${relativeStudioPath}';
203
+ import { createStudioApp } from '@geekmidas/studio/server/hono';`;
204
+ } else studioImports = `// Studio requires a configured instance - use studio config path
205
+ // import { createStudioApp } from '@geekmidas/studio/server/hono';`;
206
+ let hooksImports = "";
207
+ let beforeSetupCall = "";
208
+ let afterSetupCall = "";
209
+ if (context.hooks?.serverHooksPath) {
210
+ const relativeHooksPath = relative$1(dirname(appPath), context.hooks.serverHooksPath);
211
+ hooksImports = `import * as serverHooks from '${relativeHooksPath}';`;
212
+ beforeSetupCall = `
213
+ // Call beforeSetup hook if defined
214
+ if (typeof serverHooks.beforeSetup === 'function') {
215
+ await serverHooks.beforeSetup(honoApp, { envParser, logger });
216
+ }
217
+ `;
218
+ afterSetupCall = `
219
+ // Call afterSetup hook if defined
220
+ if (typeof serverHooks.afterSetup === 'function') {
221
+ await serverHooks.afterSetup(honoApp, { envParser, logger });
222
+ }
223
+ `;
224
+ }
225
+ const telescopeWebSocketSetupCode = telescopeWebSocketEnabled ? `
226
+ // Setup WebSocket for real-time telescope updates
227
+ try {
228
+ const { createNodeWebSocket } = await import('@hono/node-ws');
229
+ const { injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app: honoApp });
230
+ // Add WebSocket route directly to main app (sub-app routes don't support WS upgrade)
231
+ honoApp.get('${context.telescope.path}/ws', upgradeWebSocket(() => ({
232
+ onOpen: (_event: Event, ws: any) => {
233
+ telescope.addWsClient(ws);
234
+ },
235
+ onClose: (_event: Event, ws: any) => {
236
+ telescope.removeWsClient(ws);
237
+ },
238
+ onMessage: (event: MessageEvent, ws: any) => {
239
+ try {
240
+ const data = JSON.parse(event.data);
241
+ if (data.type === 'ping') {
242
+ ws.send(JSON.stringify({ type: 'pong' }));
243
+ }
244
+ } catch {
245
+ // Ignore invalid messages
246
+ }
247
+ },
248
+ })));
249
+ // Store injectWebSocket for server entry to call after serve()
250
+ (honoApp as any).__injectWebSocket = injectWebSocket;
251
+ logger.info('Telescope WebSocket enabled');
252
+ } catch (e) {
253
+ logger.warn({ error: e }, 'WebSocket support not available - install @hono/node-ws for real-time updates');
254
+ }
255
+ ` : "";
256
+ let telescopeSetup = "";
257
+ if (telescopeEnabled) if (usesExternalTelescope) telescopeSetup = `
258
+ ${telescopeWebSocketSetupCode}
259
+ // Add telescope middleware (before endpoints to capture all requests)
260
+ honoApp.use('*', createMiddleware(telescope));
261
+
262
+ // Mount telescope UI
263
+ const telescopeUI = createUI(telescope);
264
+ honoApp.route('${context.telescope.path}', telescopeUI);
265
+ `;
266
+ else telescopeSetup = `
267
+ // Setup Telescope for debugging/monitoring
268
+ const telescopeStorage = new InMemoryStorage({ maxEntries: ${context.telescope.maxEntries} });
269
+ const telescope = new Telescope({
270
+ enabled: true,
271
+ path: '${context.telescope.path}',
272
+ ignorePatterns: ${JSON.stringify(context.telescope.ignore)},
273
+ recordBody: ${context.telescope.recordBody},
274
+ storage: telescopeStorage,
275
+ });
276
+ ${telescopeWebSocketSetupCode}
277
+ // Add telescope middleware (before endpoints to capture all requests)
278
+ honoApp.use('*', createMiddleware(telescope));
279
+
280
+ // Mount telescope UI
281
+ const telescopeUI = createUI(telescope);
282
+ honoApp.route('${context.telescope.path}', telescopeUI);
283
+ `;
284
+ let studioSetup = "";
285
+ if (studioEnabled && usesExternalStudio) studioSetup = `
286
+ // Mount Studio data browser UI
287
+ const studioApp = createStudioApp(studio);
288
+ honoApp.route('${context.studio.path}', studioApp);
289
+ `;
290
+ const content = `/**
291
+ * Generated server application
292
+ *
293
+ * ⚠️ WARNING: This is for LOCAL DEVELOPMENT ONLY
294
+ * The subscriber polling mechanism is not production-ready.
295
+ * For production, use AWS Lambda with SQS/SNS event sources.
296
+ */
297
+ import { Hono } from 'hono';
298
+ import type { Hono as HonoType } from 'hono';
299
+ import { setupEndpoints } from './endpoints.js';
300
+ import { setupSubscribers } from './subscribers.js';
301
+ import ${context.envParserImportPattern} from '${relativeEnvParserPath}';
302
+ import ${context.loggerImportPattern} from '${relativeLoggerPath}';
303
+ ${telescopeImports}
304
+ ${studioImports}
305
+ ${hooksImports}
306
+
307
+ export interface ServerApp {
308
+ app: HonoType;
309
+ start: (options?: {
310
+ port?: number;
311
+ serve: (app: HonoType, port: number) => void | Promise<void>;
312
+ }) => Promise<void>;
313
+ }
314
+
315
+ /**
316
+ * Create and configure the Hono application
317
+ *
318
+ * @param app - Optional Hono app instance to configure (creates new one if not provided)
319
+ * @param enableOpenApi - Enable OpenAPI documentation (default: true)
320
+ * @returns Server app with configured Hono app and start function
321
+ *
322
+ * @example
323
+ * // With Bun
324
+ * import { createApp } from './.gkm/server/app.js';
325
+ *
326
+ * const { app, start } = await createApp();
327
+ *
328
+ * await start({
329
+ * port: 3000,
330
+ * serve: (app, port) => {
331
+ * Bun.serve({ port, fetch: app.fetch });
332
+ * }
333
+ * });
334
+ *
335
+ * @example
336
+ * // With Node.js (using @hono/node-server)
337
+ * import { serve } from '@hono/node-server';
338
+ * import { createApp } from './.gkm/server/app.js';
339
+ *
340
+ * const { app, start } = await createApp();
341
+ *
342
+ * await start({
343
+ * port: 3000,
344
+ * serve: (app, port) => {
345
+ * serve({ fetch: app.fetch, port });
346
+ * }
347
+ * });
348
+ */
349
+ export async function createApp(app?: HonoType, enableOpenApi: boolean = true): Promise<ServerApp> {
350
+ const honoApp = app || new Hono();
351
+ ${telescopeSetup}${beforeSetupCall}${studioSetup}
352
+ // Setup HTTP endpoints
353
+ await setupEndpoints(honoApp, envParser, logger, enableOpenApi);
354
+ ${afterSetupCall}
355
+
356
+ return {
357
+ app: honoApp,
358
+ async start(options) {
359
+ if (!options?.serve) {
360
+ throw new Error(
361
+ 'serve function is required. Pass a serve function for your runtime:\\n' +
362
+ ' - Bun: (app, port) => Bun.serve({ port, fetch: app.fetch })\\n' +
363
+ ' - Node: (app, port) => serve({ fetch: app.fetch, port })'
364
+ );
365
+ }
366
+
367
+ const port = options.port ?? 3000;
368
+
369
+ // Start subscribers in background (non-blocking, local development only)
370
+ await setupSubscribers(envParser, logger).catch((error) => {
371
+ logger.error({ error }, 'Failed to start subscribers');
372
+ });
373
+
374
+ logger.info({ port }, 'Starting server');
375
+
376
+ // Start HTTP server using provided serve function
377
+ await options.serve(honoApp, port);
378
+
379
+ logger.info({ port }, 'Server started');
380
+ }
381
+ };
382
+ }
383
+
384
+ // Default export for convenience
385
+ export default createApp;
386
+ `;
387
+ await writeFile(appPath, content);
388
+ return appPath;
389
+ }
390
+ generateAWSApiGatewayV1Handler(importPath, exportName, envParserPath, envParserImportPattern) {
391
+ return `import { AmazonApiGatewayV1Endpoint } from '@geekmidas/constructs/aws';
392
+ import { ${exportName} } from '${importPath}';
393
+ import ${envParserImportPattern} from '${envParserPath}';
394
+
395
+ const adapter = new AmazonApiGatewayV1Endpoint(envParser, ${exportName});
396
+
397
+ export const handler = adapter.handler;
398
+ `;
399
+ }
400
+ generateAWSApiGatewayV2Handler(importPath, exportName, envParserPath, envParserImportPattern) {
401
+ return `import { AmazonApiGatewayV2Endpoint } from '@geekmidas/constructs/aws';
402
+ import { ${exportName} } from '${importPath}';
403
+ import ${envParserImportPattern} from '${envParserPath}';
404
+
405
+ const adapter = new AmazonApiGatewayV2Endpoint(envParser, ${exportName});
406
+
407
+ export const handler = adapter.handler;
408
+ `;
409
+ }
410
+ generateServerHandler(importPath, exportName) {
411
+ return `import { ${exportName} } from '${importPath}';
412
+
413
+ // Server handler - implement based on your server framework
414
+ export const handler = ${exportName};
415
+ `;
416
+ }
417
+ };
418
+
419
+ //#endregion
420
+ //#region src/generators/OpenApiTsGenerator.ts
421
+ /**
422
+ * Generates TypeScript OpenAPI module from endpoints.
423
+ * Outputs:
424
+ * - securitySchemes: typed security scheme definitions
425
+ * - endpointAuth: runtime map of endpoints to auth requirements
426
+ * - paths: TypeScript interface for type-safe fetcher
427
+ * - schema interfaces: reusable TypeScript types from Zod/Valibot schemas
428
+ */
429
+ var OpenApiTsGenerator = class {
430
+ async generate(endpoints, options = {}) {
431
+ const { title = "API", version = "1.0.0", description } = options;
432
+ const endpointInfos = await this.extractEndpointInfos(endpoints);
433
+ const securitySchemes = this.collectSecuritySchemes(endpointInfos);
434
+ const endpointAuth = this.buildEndpointAuthMap(endpointInfos);
435
+ const schemaInterfaces = await this.generateSchemaInterfaces(endpointInfos);
436
+ const pathsInterface = await this.generatePathsInterface(endpointInfos);
437
+ return this.buildModule({
438
+ title,
439
+ version,
440
+ description,
441
+ securitySchemes,
442
+ endpointAuth,
443
+ schemaInterfaces,
444
+ pathsInterface
445
+ });
446
+ }
447
+ async extractEndpointInfos(endpoints) {
448
+ return endpoints.map((ep) => {
449
+ const route = ep.route.replace(/:(\w+)/g, "{$1}");
450
+ const method = ep.method.toUpperCase();
451
+ const securityScheme = ep.authorizer?.securityScheme;
452
+ return {
453
+ endpoint: `${method} ${route}`,
454
+ route,
455
+ method,
456
+ authorizerName: ep.authorizer?.name ?? null,
457
+ authorizerType: ep.authorizer?.type ?? null,
458
+ securityScheme: securityScheme ?? null,
459
+ input: ep.input,
460
+ output: ep.outputSchema,
461
+ description: ep.description,
462
+ tags: ep.tags,
463
+ operationId: ep.operationId
464
+ };
465
+ });
466
+ }
467
+ collectSecuritySchemes(endpointInfos) {
468
+ const schemes = /* @__PURE__ */ new Map();
469
+ for (const info of endpointInfos) if (info.authorizerName && !schemes.has(info.authorizerName)) {
470
+ const scheme = info.securityScheme ?? (info.authorizerType ? this.mapAuthorizerToSecurityScheme(info.authorizerType, info.authorizerName) : null);
471
+ if (scheme) schemes.set(info.authorizerName, {
472
+ name: info.authorizerName,
473
+ type: scheme.type,
474
+ scheme
475
+ });
476
+ }
477
+ return Array.from(schemes.values());
478
+ }
479
+ mapAuthorizerToSecurityScheme(type, _name) {
480
+ switch (type.toLowerCase()) {
481
+ case "jwt":
482
+ case "bearer": return {
483
+ type: "http",
484
+ scheme: "bearer",
485
+ bearerFormat: "JWT"
486
+ };
487
+ case "iam":
488
+ case "aws-sigv4":
489
+ case "sigv4": return {
490
+ type: "apiKey",
491
+ in: "header",
492
+ name: "Authorization",
493
+ "x-amazon-apigateway-authtype": "awsSigv4"
494
+ };
495
+ case "apikey":
496
+ case "api-key": return {
497
+ type: "apiKey",
498
+ in: "header",
499
+ name: "X-API-Key"
500
+ };
501
+ case "oauth2": return {
502
+ type: "oauth2",
503
+ flows: {}
504
+ };
505
+ case "oidc":
506
+ case "openidconnect": return {
507
+ type: "openIdConnect",
508
+ openIdConnectUrl: ""
509
+ };
510
+ default: return {
511
+ type: "http",
512
+ scheme: "bearer"
513
+ };
514
+ }
515
+ }
516
+ buildEndpointAuthMap(endpointInfos) {
517
+ const authMap = {};
518
+ for (const info of endpointInfos) authMap[info.endpoint] = info.authorizerName;
519
+ return authMap;
520
+ }
521
+ async generateSchemaInterfaces(endpointInfos) {
522
+ const interfaces = [];
523
+ const generatedNames = /* @__PURE__ */ new Set();
524
+ const collectedDefs = /* @__PURE__ */ new Map();
525
+ for (const info of endpointInfos) {
526
+ const baseName = this.getSchemaBaseName(info);
527
+ if (info.input?.body) {
528
+ const name = await this.getSchemaName(info.input.body, `${baseName}Input`);
529
+ if (!generatedNames.has(name)) {
530
+ const schema = await this.schemaToInterfaceWithDefs(info.input.body, name, collectedDefs);
531
+ if (schema) {
532
+ interfaces.push(schema);
533
+ generatedNames.add(name);
534
+ }
535
+ }
536
+ }
537
+ if (info.input?.params) {
538
+ const name = await this.getSchemaName(info.input.params, `${baseName}Params`);
539
+ if (!generatedNames.has(name)) {
540
+ const schema = await this.schemaToInterfaceWithDefs(info.input.params, name, collectedDefs);
541
+ if (schema) {
542
+ interfaces.push(schema);
543
+ generatedNames.add(name);
544
+ }
545
+ }
546
+ }
547
+ if (info.input?.query) {
548
+ const name = await this.getSchemaName(info.input.query, `${baseName}Query`);
549
+ if (!generatedNames.has(name)) {
550
+ const schema = await this.schemaToInterfaceWithDefs(info.input.query, name, collectedDefs);
551
+ if (schema) {
552
+ interfaces.push(schema);
553
+ generatedNames.add(name);
554
+ }
555
+ }
556
+ }
557
+ if (info.output) {
558
+ const name = await this.getSchemaName(info.output, `${baseName}Output`);
559
+ if (!generatedNames.has(name)) {
560
+ const schema = await this.schemaToInterfaceWithDefs(info.output, name, collectedDefs);
561
+ if (schema) {
562
+ interfaces.push(schema);
563
+ generatedNames.add(name);
564
+ }
565
+ }
566
+ }
567
+ }
568
+ for (const [defName, defSchema] of collectedDefs) if (!generatedNames.has(defName)) {
569
+ const interfaceStr = this.jsonSchemaToInterface(defSchema, defName);
570
+ interfaces.push(interfaceStr);
571
+ generatedNames.add(defName);
572
+ }
573
+ return interfaces.join("\n\n");
574
+ }
575
+ /**
576
+ * Get the name for a schema, using metadata `id` if available,
577
+ * otherwise falling back to the provided default name.
578
+ */
579
+ async getSchemaName(schema, defaultName) {
580
+ try {
581
+ const metadata = await getSchemaMetadata(schema);
582
+ if (metadata?.id) return this.pascalCase(metadata.id);
583
+ } catch {}
584
+ return defaultName;
585
+ }
586
+ getSchemaBaseName(info) {
587
+ if (info.operationId) return this.pascalCase(info.operationId);
588
+ const routeParts = info.route.replace(/[{}]/g, "").split("/").filter(Boolean).map((part) => this.pascalCase(part));
589
+ return `${this.pascalCase(info.method.toLowerCase())}${routeParts.join("")}`;
590
+ }
591
+ pascalCase(str) {
592
+ return str.replace(/[-_](.)/g, (_, c) => c.toUpperCase()).replace(/^./, (c) => c.toUpperCase());
593
+ }
594
+ /**
595
+ * Convert schema to interface while collecting $defs for nested schemas
596
+ * with .meta({ id: 'X' }).
597
+ */
598
+ async schemaToInterfaceWithDefs(schema, name, collectedDefs) {
599
+ try {
600
+ const vendor = schema["~standard"]?.vendor;
601
+ if (!vendor || !(vendor in StandardSchemaJsonSchema)) return null;
602
+ const toJsonSchema = StandardSchemaJsonSchema[vendor];
603
+ const jsonSchema = await toJsonSchema(schema);
604
+ if (!jsonSchema) return null;
605
+ if (jsonSchema.$defs && typeof jsonSchema.$defs === "object") {
606
+ for (const [defName, defSchema] of Object.entries(jsonSchema.$defs)) if (!collectedDefs.has(defName)) {
607
+ const { id,...schemaWithoutId } = defSchema;
608
+ collectedDefs.set(defName, schemaWithoutId);
609
+ }
610
+ }
611
+ const { $defs,...schemaWithoutDefs } = jsonSchema;
612
+ return this.jsonSchemaToInterface(schemaWithoutDefs, name);
613
+ } catch {
614
+ return null;
615
+ }
616
+ }
617
+ jsonSchemaToInterface(schema, name) {
618
+ if (schema.type !== "object" || !schema.properties) {
619
+ const typeStr = this.jsonSchemaTypeToTs(schema);
620
+ return `export type ${name} = ${typeStr};`;
621
+ }
622
+ const props = [];
623
+ const required = new Set(schema.required || []);
624
+ for (const [propName, propSchema] of Object.entries(schema.properties)) {
625
+ const isRequired = required.has(propName);
626
+ const typeStr = this.jsonSchemaTypeToTs(propSchema);
627
+ const optionalMark = isRequired ? "" : "?";
628
+ props.push(` ${propName}${optionalMark}: ${typeStr};`);
629
+ }
630
+ return `export interface ${name} {\n${props.join("\n")}\n}`;
631
+ }
632
+ jsonSchemaTypeToTs(schema) {
633
+ if (!schema) return "unknown";
634
+ if (schema.$ref) {
635
+ const refName = schema.$ref.split("/").pop() || "unknown";
636
+ return refName;
637
+ }
638
+ if (schema.anyOf) return schema.anyOf.map((s) => this.jsonSchemaTypeToTs(s)).join(" | ");
639
+ if (schema.oneOf) return schema.oneOf.map((s) => this.jsonSchemaTypeToTs(s)).join(" | ");
640
+ if (schema.allOf) return schema.allOf.map((s) => this.jsonSchemaTypeToTs(s)).join(" & ");
641
+ switch (schema.type) {
642
+ case "string":
643
+ if (schema.enum) return schema.enum.map((e) => `'${e}'`).join(" | ");
644
+ return "string";
645
+ case "number":
646
+ case "integer": return "number";
647
+ case "boolean": return "boolean";
648
+ case "null": return "null";
649
+ case "array":
650
+ if (schema.items) return `Array<${this.jsonSchemaTypeToTs(schema.items)}>`;
651
+ return "Array<unknown>";
652
+ case "object":
653
+ if (schema.properties) {
654
+ const props = [];
655
+ const required = new Set(schema.required || []);
656
+ for (const [propName, propSchema] of Object.entries(schema.properties)) {
657
+ const isRequired = required.has(propName);
658
+ const typeStr = this.jsonSchemaTypeToTs(propSchema);
659
+ const optionalMark = isRequired ? "" : "?";
660
+ props.push(`${propName}${optionalMark}: ${typeStr}`);
661
+ }
662
+ return `{ ${props.join("; ")} }`;
663
+ }
664
+ if (schema.additionalProperties) {
665
+ const valueType = this.jsonSchemaTypeToTs(schema.additionalProperties);
666
+ return `Record<string, ${valueType}>`;
667
+ }
668
+ return "Record<string, unknown>";
669
+ default: return "unknown";
670
+ }
671
+ }
672
+ async generatePathsInterface(endpointInfos) {
673
+ const pathGroups = /* @__PURE__ */ new Map();
674
+ for (const info of endpointInfos) {
675
+ const existing = pathGroups.get(info.route) || [];
676
+ existing.push(info);
677
+ pathGroups.set(info.route, existing);
678
+ }
679
+ const pathEntries = [];
680
+ for (const [route, infos] of pathGroups) {
681
+ const methodEntries = [];
682
+ for (const info of infos) {
683
+ const methodDef = await this.generateMethodDefinition(info);
684
+ methodEntries.push(` ${info.method.toLowerCase()}: ${methodDef};`);
685
+ }
686
+ const firstWithParams = infos.find((i) => i.input?.params);
687
+ let paramsEntry = "";
688
+ if (firstWithParams?.input?.params) {
689
+ const paramsName = await this.getSchemaName(firstWithParams.input.params, `${this.getSchemaBaseName(firstWithParams)}Params`);
690
+ paramsEntry = `\n parameters: {\n path: ${paramsName};\n };`;
691
+ }
692
+ pathEntries.push(` '${route}': {${paramsEntry}\n${methodEntries.join("\n")}\n };`);
693
+ }
694
+ return `export interface paths {\n${pathEntries.join("\n")}\n}`;
695
+ }
696
+ async generateMethodDefinition(info) {
697
+ const parts = [];
698
+ const baseName = this.getSchemaBaseName(info);
699
+ if (info.input?.body) {
700
+ const bodyName = await this.getSchemaName(info.input.body, `${baseName}Input`);
701
+ parts.push(`requestBody: {
702
+ content: {
703
+ 'application/json': ${bodyName};
704
+ };
705
+ }`);
706
+ }
707
+ if (info.input?.query) {
708
+ const queryName = await this.getSchemaName(info.input.query, `${baseName}Query`);
709
+ parts.push(`parameters: {
710
+ query: ${queryName};
711
+ }`);
712
+ }
713
+ const outputName = info.output ? await this.getSchemaName(info.output, `${baseName}Output`) : "unknown";
714
+ parts.push(`responses: {
715
+ 200: {
716
+ content: {
717
+ 'application/json': ${outputName};
718
+ };
719
+ };
720
+ }`);
721
+ return `{\n ${parts.join(";\n ")};\n }`;
722
+ }
723
+ buildModule(params) {
724
+ const { title, version, description, securitySchemes, endpointAuth, schemaInterfaces, pathsInterface } = params;
725
+ const securitySchemesObj = securitySchemes.reduce((acc, s) => {
726
+ acc[s.name] = s.scheme;
727
+ return acc;
728
+ }, {});
729
+ const schemeNames = securitySchemes.map((s) => `'${s.name}'`).join(" | ");
730
+ const hasSecuritySchemes = schemeNames.length > 0;
731
+ const createApiSection = hasSecuritySchemes ? `
732
+ // ============================================================
733
+ // API Client Factory
734
+ // ============================================================
735
+
736
+ import {
737
+ createAuthAwareFetcher,
738
+ type AuthStrategy,
739
+ } from '@geekmidas/client/auth-fetcher';
740
+ import { createEndpointHooks } from '@geekmidas/client/endpoint-hooks';
741
+ import type { QueryClient } from '@tanstack/react-query';
742
+
743
+ /**
744
+ * Options for creating the API client.
745
+ */
746
+ export interface CreateApiOptions {
747
+ /** Base URL for all API requests (required) */
748
+ baseURL: string;
749
+ /** Auth strategies for each security scheme used in this API */
750
+ authStrategies: Record<SecuritySchemeId, AuthStrategy>;
751
+ /** Optional React Query client instance */
752
+ queryClient?: QueryClient;
753
+ /** Optional request interceptor */
754
+ onRequest?: (config: RequestInit) => RequestInit | Promise<RequestInit>;
755
+ }
756
+
757
+ /**
758
+ * Create a type-safe API client with authentication and React Query hooks.
759
+ *
760
+ * @example
761
+ * \`\`\`typescript
762
+ * const api = createApi({
763
+ * baseURL: 'https://api.example.com',
764
+ * authStrategies: {
765
+ * jwt: { type: 'bearer', tokenProvider },
766
+ * },
767
+ * });
768
+ *
769
+ * // Imperative fetch
770
+ * const user = await api('GET /users/{id}', { params: { id: '123' } });
771
+ *
772
+ * // React Query hooks
773
+ * const { data } = api.useQuery('GET /users/{id}', { params: { id: '123' } });
774
+ * const mutation = api.useMutation('POST /users');
775
+ * \`\`\`
776
+ */
777
+ export function createApi(options: CreateApiOptions) {
778
+ const fetcher = createAuthAwareFetcher<paths, typeof endpointAuth, typeof securitySchemes>({
779
+ baseURL: options.baseURL,
780
+ endpointAuth,
781
+ securitySchemes,
782
+ authStrategies: options.authStrategies,
783
+ onRequest: options.onRequest,
784
+ });
785
+
786
+ const hooks = createEndpointHooks<paths>(fetcher, { queryClient: options.queryClient });
787
+
788
+ return Object.assign(fetcher, hooks);
789
+ }
790
+ ` : `
791
+ // ============================================================
792
+ // API Client Factory
793
+ // ============================================================
794
+
795
+ import { TypedFetcher, type FetcherOptions } from '@geekmidas/client/fetcher';
796
+ import { createEndpointHooks } from '@geekmidas/client/endpoint-hooks';
797
+ import type { QueryClient } from '@tanstack/react-query';
798
+
799
+ /**
800
+ * Options for creating the API client.
801
+ */
802
+ export interface CreateApiOptions extends Omit<FetcherOptions, 'baseURL'> {
803
+ /** Base URL for all API requests (required) */
804
+ baseURL: string;
805
+ /** Optional React Query client instance */
806
+ queryClient?: QueryClient;
807
+ }
808
+
809
+ /**
810
+ * Create a type-safe API client with React Query hooks.
811
+ *
812
+ * @example
813
+ * \`\`\`typescript
814
+ * const api = createApi({
815
+ * baseURL: 'https://api.example.com',
816
+ * });
817
+ *
818
+ * // Imperative fetch
819
+ * const data = await api('GET /health');
820
+ *
821
+ * // React Query hooks
822
+ * const { data } = api.useQuery('GET /health');
823
+ * \`\`\`
824
+ */
825
+ export function createApi(options: CreateApiOptions) {
826
+ const { queryClient, ...fetcherOptions } = options;
827
+ const fetcher = new TypedFetcher<paths>(fetcherOptions);
828
+
829
+ const hooks = createEndpointHooks<paths>(fetcher.request.bind(fetcher), { queryClient });
830
+
831
+ return Object.assign(fetcher.request.bind(fetcher), hooks);
832
+ }
833
+ `;
834
+ return `// Auto-generated by @geekmidas/cli - DO NOT EDIT
835
+ // Generated: ${(/* @__PURE__ */ new Date()).toISOString()}
836
+
837
+ // ============================================================
838
+ // Security Scheme Type
839
+ // ============================================================
840
+
841
+ interface SecuritySchemeObject {
842
+ type: 'apiKey' | 'http' | 'mutualTLS' | 'oauth2' | 'openIdConnect';
843
+ description?: string;
844
+ name?: string;
845
+ in?: 'query' | 'header' | 'cookie';
846
+ scheme?: string;
847
+ bearerFormat?: string;
848
+ flows?: Record<string, unknown>;
849
+ openIdConnectUrl?: string;
850
+ [key: string]: unknown;
851
+ }
852
+
853
+ // ============================================================
854
+ // API Info
855
+ // ============================================================
856
+
857
+ export const apiInfo = {
858
+ title: '${title}',
859
+ version: '${version}',${description ? `\n description: '${description.replace(/'/g, "\\'")}',` : ""}
860
+ } as const;
861
+
862
+ // ============================================================
863
+ // Security Schemes
864
+ // ============================================================
865
+
866
+ /**
867
+ * Available security schemes for this API.
868
+ * Maps authorizer names to OpenAPI security scheme definitions.
869
+ */
870
+ export const securitySchemes = ${JSON.stringify(securitySchemesObj, null, 2).replace(/"([a-zA-Z_$][a-zA-Z0-9_$]*)":/g, "$1:")} as const satisfies Record<string, SecuritySchemeObject>;
871
+
872
+ export type SecuritySchemeId = ${schemeNames || "never"};
873
+
874
+ // ============================================================
875
+ // Endpoint Authentication Map
876
+ // ============================================================
877
+
878
+ /**
879
+ * Runtime map of endpoints to their required authentication scheme.
880
+ * \`null\` indicates a public endpoint (no auth required).
881
+ */
882
+ export const endpointAuth = ${JSON.stringify(endpointAuth, null, 2).replace(/"([^"]+)":/g, "'$1':")} as const satisfies Record<string, SecuritySchemeId | null>;
883
+
884
+ export type EndpointString = keyof typeof endpointAuth;
885
+
886
+ export type AuthenticatedEndpoint = {
887
+ [K in EndpointString]: typeof endpointAuth[K] extends null ? never : K;
888
+ }[EndpointString];
889
+
890
+ export type PublicEndpoint = {
891
+ [K in EndpointString]: typeof endpointAuth[K] extends null ? K : never;
892
+ }[EndpointString];
893
+
894
+ // ============================================================
895
+ // Schema Definitions
896
+ // ============================================================
897
+
898
+ ${schemaInterfaces}
899
+
900
+ // ============================================================
901
+ // OpenAPI Paths
902
+ // ============================================================
903
+
904
+ ${pathsInterface}
905
+ ${createApiSection}
906
+ `;
907
+ }
908
+ };
909
+
910
+ //#endregion
911
+ //#region src/openapi.ts
912
+ /**
913
+ * Fixed output path for generated OpenAPI client (not configurable)
914
+ */
915
+ const OPENAPI_OUTPUT_PATH = "./.gkm/openapi.ts";
916
+ /**
917
+ * Resolve OpenAPI config from GkmConfig
918
+ */
919
+ function resolveOpenApiConfig(config) {
920
+ if (config.openapi === false) return { enabled: false };
921
+ if (config.openapi === true || config.openapi === void 0) return {
922
+ enabled: config.openapi === true,
923
+ title: "API Documentation",
924
+ version: "1.0.0",
925
+ description: "Auto-generated API documentation from endpoints"
926
+ };
927
+ return {
928
+ enabled: config.openapi.enabled !== false,
929
+ title: config.openapi.title || "API Documentation",
930
+ version: config.openapi.version || "1.0.0",
931
+ description: config.openapi.description || "Auto-generated API documentation from endpoints"
932
+ };
933
+ }
934
+ /**
935
+ * Generate OpenAPI spec from endpoints
936
+ * @returns Object with output path and endpoint count, or null if disabled
937
+ */
938
+ async function generateOpenApi(config, options = {}) {
939
+ const logger = options.silent ? { log: () => {} } : console;
940
+ const openApiConfig = resolveOpenApiConfig(config);
941
+ if (!openApiConfig.enabled) return null;
942
+ const endpointGenerator = new EndpointGenerator();
943
+ const loadedEndpoints = await endpointGenerator.load(config.routes);
944
+ if (loadedEndpoints.length === 0) {
945
+ logger.log("No valid endpoints found for OpenAPI generation");
946
+ return null;
947
+ }
948
+ const endpoints = loadedEndpoints.map(({ construct }) => construct);
949
+ const outputPath = join$1(process.cwd(), OPENAPI_OUTPUT_PATH);
950
+ await mkdir(dirname(outputPath), { recursive: true });
951
+ const tsGenerator = new OpenApiTsGenerator();
952
+ const tsContent = await tsGenerator.generate(endpoints, {
953
+ title: openApiConfig.title,
954
+ version: openApiConfig.version,
955
+ description: openApiConfig.description
956
+ });
957
+ await writeFile(outputPath, tsContent);
958
+ logger.log(`📄 OpenAPI client generated: ${OPENAPI_OUTPUT_PATH}`);
959
+ return {
960
+ outputPath,
961
+ endpointCount: loadedEndpoints.length
962
+ };
963
+ }
964
+ async function openapiCommand(options = {}) {
965
+ const logger = console;
966
+ try {
967
+ const config = await loadConfig(options.cwd);
968
+ if (!config.openapi) config.openapi = { enabled: true };
969
+ const result = await generateOpenApi(config);
970
+ if (result) logger.log(`Found ${result.endpointCount} endpoints`);
971
+ } catch (error) {
972
+ throw new Error(`OpenAPI generation failed: ${error.message}`);
973
+ }
974
+ }
975
+
976
+ //#endregion
977
+ export { ConstructGenerator, EndpointGenerator, OPENAPI_OUTPUT_PATH, generateOpenApi, openapiCommand, resolveOpenApiConfig };
978
+ //# sourceMappingURL=openapi--vOy9mo4.mjs.map