@geekmidas/cli 0.10.0 → 0.12.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 (145) hide show
  1. package/README.md +525 -0
  2. package/dist/bundler-DRXCw_YR.mjs +70 -0
  3. package/dist/bundler-DRXCw_YR.mjs.map +1 -0
  4. package/dist/bundler-WsEvH_b2.cjs +71 -0
  5. package/dist/bundler-WsEvH_b2.cjs.map +1 -0
  6. package/dist/{config-C9aXOHBe.cjs → config-AmInkU7k.cjs} +8 -8
  7. package/dist/config-AmInkU7k.cjs.map +1 -0
  8. package/dist/{config-BrkUalUh.mjs → config-DYULeEv8.mjs} +3 -3
  9. package/dist/config-DYULeEv8.mjs.map +1 -0
  10. package/dist/config.cjs +1 -1
  11. package/dist/config.d.cts +1 -1
  12. package/dist/config.d.mts +1 -1
  13. package/dist/config.mjs +1 -1
  14. package/dist/encryption-C8H-38Yy.mjs +42 -0
  15. package/dist/encryption-C8H-38Yy.mjs.map +1 -0
  16. package/dist/encryption-Dyf_r1h-.cjs +44 -0
  17. package/dist/encryption-Dyf_r1h-.cjs.map +1 -0
  18. package/dist/index.cjs +2116 -179
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.mjs +2134 -192
  21. package/dist/index.mjs.map +1 -1
  22. package/dist/{openapi-CZLI4QTr.mjs → openapi-BfFlOBCG.mjs} +801 -38
  23. package/dist/openapi-BfFlOBCG.mjs.map +1 -0
  24. package/dist/{openapi-BeHLKcwP.cjs → openapi-Bt_1FDpT.cjs} +794 -31
  25. package/dist/openapi-Bt_1FDpT.cjs.map +1 -0
  26. package/dist/{openapi-react-query-o5iMi8tz.cjs → openapi-react-query-B-sNWHFU.cjs} +5 -5
  27. package/dist/openapi-react-query-B-sNWHFU.cjs.map +1 -0
  28. package/dist/{openapi-react-query-CcciaVu5.mjs → openapi-react-query-B6XTeGqS.mjs} +5 -5
  29. package/dist/openapi-react-query-B6XTeGqS.mjs.map +1 -0
  30. package/dist/openapi-react-query.cjs +1 -1
  31. package/dist/openapi-react-query.d.cts.map +1 -1
  32. package/dist/openapi-react-query.d.mts.map +1 -1
  33. package/dist/openapi-react-query.mjs +1 -1
  34. package/dist/openapi.cjs +2 -2
  35. package/dist/openapi.d.cts +1 -1
  36. package/dist/openapi.d.cts.map +1 -1
  37. package/dist/openapi.d.mts +1 -1
  38. package/dist/openapi.d.mts.map +1 -1
  39. package/dist/openapi.mjs +2 -2
  40. package/dist/storage-BUYQJgz7.cjs +4 -0
  41. package/dist/storage-BXoJvmv2.cjs +149 -0
  42. package/dist/storage-BXoJvmv2.cjs.map +1 -0
  43. package/dist/storage-C9PU_30f.mjs +101 -0
  44. package/dist/storage-C9PU_30f.mjs.map +1 -0
  45. package/dist/storage-DLJAYxzJ.mjs +3 -0
  46. package/dist/{types-b-vwGpqc.d.cts → types-BR0M2v_c.d.mts} +100 -1
  47. package/dist/types-BR0M2v_c.d.mts.map +1 -0
  48. package/dist/{types-DXgiA1sF.d.mts → types-BhkZc-vm.d.cts} +100 -1
  49. package/dist/types-BhkZc-vm.d.cts.map +1 -0
  50. package/examples/cron-example.ts +27 -27
  51. package/examples/env.ts +27 -27
  52. package/examples/function-example.ts +31 -31
  53. package/examples/gkm.config.json +20 -20
  54. package/examples/gkm.config.ts +8 -8
  55. package/examples/gkm.minimal.config.json +5 -5
  56. package/examples/gkm.production.config.json +25 -25
  57. package/examples/logger.ts +2 -2
  58. package/package.json +6 -6
  59. package/src/__tests__/EndpointGenerator.hooks.spec.ts +191 -191
  60. package/src/__tests__/config.spec.ts +55 -55
  61. package/src/__tests__/loadEnvFiles.spec.ts +93 -93
  62. package/src/__tests__/normalizeHooksConfig.spec.ts +58 -58
  63. package/src/__tests__/openapi-react-query.spec.ts +497 -497
  64. package/src/__tests__/openapi.spec.ts +428 -428
  65. package/src/__tests__/test-helpers.ts +76 -76
  66. package/src/auth/__tests__/credentials.spec.ts +204 -0
  67. package/src/auth/__tests__/index.spec.ts +168 -0
  68. package/src/auth/credentials.ts +187 -0
  69. package/src/auth/index.ts +226 -0
  70. package/src/build/__tests__/index-new.spec.ts +474 -474
  71. package/src/build/__tests__/manifests.spec.ts +333 -333
  72. package/src/build/bundler.ts +141 -0
  73. package/src/build/endpoint-analyzer.ts +236 -0
  74. package/src/build/handler-templates.ts +1253 -0
  75. package/src/build/index.ts +250 -179
  76. package/src/build/manifests.ts +52 -52
  77. package/src/build/providerResolver.ts +145 -145
  78. package/src/build/types.ts +64 -43
  79. package/src/config.ts +39 -39
  80. package/src/deploy/__tests__/docker.spec.ts +111 -0
  81. package/src/deploy/__tests__/dokploy.spec.ts +245 -0
  82. package/src/deploy/__tests__/init.spec.ts +662 -0
  83. package/src/deploy/docker.ts +128 -0
  84. package/src/deploy/dokploy.ts +204 -0
  85. package/src/deploy/index.ts +136 -0
  86. package/src/deploy/init.ts +484 -0
  87. package/src/deploy/types.ts +48 -0
  88. package/src/dev/__tests__/index.spec.ts +266 -266
  89. package/src/dev/index.ts +647 -601
  90. package/src/docker/__tests__/compose.spec.ts +531 -0
  91. package/src/docker/__tests__/templates.spec.ts +280 -0
  92. package/src/docker/compose.ts +273 -0
  93. package/src/docker/index.ts +230 -0
  94. package/src/docker/templates.ts +446 -0
  95. package/src/generators/CronGenerator.ts +72 -72
  96. package/src/generators/EndpointGenerator.ts +699 -398
  97. package/src/generators/FunctionGenerator.ts +84 -84
  98. package/src/generators/Generator.ts +72 -72
  99. package/src/generators/OpenApiTsGenerator.ts +577 -577
  100. package/src/generators/SubscriberGenerator.ts +124 -124
  101. package/src/generators/__tests__/CronGenerator.spec.ts +433 -433
  102. package/src/generators/__tests__/EndpointGenerator.spec.ts +532 -382
  103. package/src/generators/__tests__/FunctionGenerator.spec.ts +244 -244
  104. package/src/generators/__tests__/SubscriberGenerator.spec.ts +397 -382
  105. package/src/generators/index.ts +4 -4
  106. package/src/index.ts +623 -201
  107. package/src/init/__tests__/generators.spec.ts +334 -334
  108. package/src/init/__tests__/init.spec.ts +332 -332
  109. package/src/init/__tests__/utils.spec.ts +89 -89
  110. package/src/init/generators/config.ts +175 -175
  111. package/src/init/generators/docker.ts +41 -41
  112. package/src/init/generators/env.ts +72 -72
  113. package/src/init/generators/index.ts +1 -1
  114. package/src/init/generators/models.ts +64 -64
  115. package/src/init/generators/monorepo.ts +161 -161
  116. package/src/init/generators/package.ts +71 -71
  117. package/src/init/generators/source.ts +6 -6
  118. package/src/init/index.ts +203 -208
  119. package/src/init/templates/api.ts +115 -115
  120. package/src/init/templates/index.ts +75 -75
  121. package/src/init/templates/minimal.ts +98 -98
  122. package/src/init/templates/serverless.ts +89 -89
  123. package/src/init/templates/worker.ts +98 -98
  124. package/src/init/utils.ts +54 -56
  125. package/src/openapi-react-query.ts +194 -194
  126. package/src/openapi.ts +63 -63
  127. package/src/secrets/__tests__/encryption.spec.ts +226 -0
  128. package/src/secrets/__tests__/generator.spec.ts +319 -0
  129. package/src/secrets/__tests__/index.spec.ts +91 -0
  130. package/src/secrets/__tests__/storage.spec.ts +403 -0
  131. package/src/secrets/encryption.ts +91 -0
  132. package/src/secrets/generator.ts +164 -0
  133. package/src/secrets/index.ts +383 -0
  134. package/src/secrets/storage.ts +134 -0
  135. package/src/secrets/types.ts +53 -0
  136. package/src/types.ts +295 -176
  137. package/tsdown.config.ts +11 -8
  138. package/dist/config-BrkUalUh.mjs.map +0 -1
  139. package/dist/config-C9aXOHBe.cjs.map +0 -1
  140. package/dist/openapi-BeHLKcwP.cjs.map +0 -1
  141. package/dist/openapi-CZLI4QTr.mjs.map +0 -1
  142. package/dist/openapi-react-query-CcciaVu5.mjs.map +0 -1
  143. package/dist/openapi-react-query-o5iMi8tz.cjs.map +0 -1
  144. package/dist/types-DXgiA1sF.d.mts.map +0 -1
  145. package/dist/types-b-vwGpqc.d.cts.map +0 -1
@@ -1,7 +1,6 @@
1
- import { loadConfig } from "./config-BrkUalUh.mjs";
2
- import { relative } from "path";
1
+ import { loadConfig } from "./config-DYULeEv8.mjs";
2
+ import { dirname, join, relative } from "node:path";
3
3
  import { mkdir, writeFile } from "node:fs/promises";
4
- import { dirname, join as join$1, relative as relative$1 } from "node:path";
5
4
  import fg from "fast-glob";
6
5
  import kebabCase from "lodash.kebabcase";
7
6
  import { Endpoint } from "@geekmidas/constructs/endpoints";
@@ -41,6 +40,586 @@ var ConstructGenerator = class {
41
40
  }
42
41
  };
43
42
 
43
+ //#endregion
44
+ //#region src/build/endpoint-analyzer.ts
45
+ /**
46
+ * Analyze an endpoint to extract its features
47
+ */
48
+ function analyzeEndpointFeatures(endpoint) {
49
+ return {
50
+ hasAuth: !!endpoint.authorizer,
51
+ hasServices: endpoint.services.length > 0,
52
+ hasDatabase: !!endpoint.databaseService,
53
+ hasBodyValidation: !!endpoint.input?.body,
54
+ hasQueryValidation: !!endpoint.input?.query,
55
+ hasParamValidation: !!endpoint.input?.params,
56
+ hasAudits: (endpoint.audits?.length ?? 0) > 0,
57
+ hasEvents: (endpoint.events?.length ?? 0) > 0,
58
+ hasRateLimit: !!endpoint.rateLimit,
59
+ hasRls: !!endpoint.rlsConfig && !endpoint.rlsBypass,
60
+ hasOutputValidation: !!endpoint.outputSchema
61
+ };
62
+ }
63
+ /**
64
+ * Determine the optimization tier for an endpoint based on its features
65
+ */
66
+ function determineEndpointTier(features) {
67
+ const { hasAuth, hasServices, hasDatabase, hasAudits, hasEvents, hasRateLimit, hasRls } = features;
68
+ if (!hasAuth && !hasServices && !hasDatabase && !hasAudits && !hasEvents && !hasRateLimit && !hasRls) return "minimal";
69
+ if (hasAudits || hasRls || hasRateLimit) return "full";
70
+ return "standard";
71
+ }
72
+ /**
73
+ * Perform complete analysis of an endpoint
74
+ */
75
+ function analyzeEndpoint(endpoint, exportName) {
76
+ const features = analyzeEndpointFeatures(endpoint);
77
+ const tier = determineEndpointTier(features);
78
+ return {
79
+ route: endpoint.route,
80
+ method: endpoint.method,
81
+ exportName,
82
+ features,
83
+ tier,
84
+ serviceNames: endpoint.services.map((s) => s.serviceName),
85
+ databaseServiceName: endpoint.databaseService?.serviceName
86
+ };
87
+ }
88
+ /**
89
+ * Generate a summary of endpoint analysis for logging
90
+ */
91
+ function summarizeAnalysis(analyses) {
92
+ const byTier = {
93
+ minimal: 0,
94
+ standard: 0,
95
+ full: 0
96
+ };
97
+ const byFeature = {
98
+ hasAuth: 0,
99
+ hasServices: 0,
100
+ hasDatabase: 0,
101
+ hasBodyValidation: 0,
102
+ hasQueryValidation: 0,
103
+ hasParamValidation: 0,
104
+ hasAudits: 0,
105
+ hasEvents: 0,
106
+ hasRateLimit: 0,
107
+ hasRls: 0,
108
+ hasOutputValidation: 0
109
+ };
110
+ for (const analysis of analyses) {
111
+ byTier[analysis.tier]++;
112
+ for (const [feature, enabled] of Object.entries(analysis.features)) if (enabled) byFeature[feature]++;
113
+ }
114
+ return {
115
+ total: analyses.length,
116
+ byTier,
117
+ byFeature
118
+ };
119
+ }
120
+
121
+ //#endregion
122
+ //#region src/build/handler-templates.ts
123
+ /**
124
+ * Generate validator references for an endpoint
125
+ */
126
+ function generateValidators(exportName, features) {
127
+ const validators = [];
128
+ if (features.hasBodyValidation) validators.push(`validateBody(${exportName})`);
129
+ if (features.hasQueryValidation) validators.push(`validateQuery(${exportName})`);
130
+ if (features.hasParamValidation) validators.push(`validateParams(${exportName})`);
131
+ return validators.length > 0 ? `\n ${validators.join(",\n ")},` : "";
132
+ }
133
+ /**
134
+ * Generate a minimal handler (near-raw-Hono performance)
135
+ *
136
+ * Used for: Health checks, public endpoints with no services
137
+ */
138
+ function generateMinimalHandler(analysis) {
139
+ const { exportName, features } = analysis;
140
+ const method = analysis.method.toLowerCase();
141
+ const validators = generateValidators(exportName, features);
142
+ const hasValidators = validators.length > 0;
143
+ if (!hasValidators && !features.hasOutputValidation) return `
144
+ // Minimal handler: ${analysis.route} (${analysis.method})
145
+ app.${method}('${analysis.route}', async (c) => {
146
+ const result = await ${exportName}.handler(
147
+ {
148
+ services: {},
149
+ logger,
150
+ body: undefined,
151
+ query: undefined,
152
+ params: undefined,
153
+ session: undefined,
154
+ header: Endpoint.createHeaders(c.req.header()),
155
+ cookie: Endpoint.createCookies(c.req.header().cookie),
156
+ auditor: undefined,
157
+ db: undefined,
158
+ } as any,
159
+ { getMetadata: () => ({}) } as any,
160
+ );
161
+ return c.json(result, ${exportName}.status as any);
162
+ });`;
163
+ return `
164
+ // Minimal handler with validation: ${analysis.route} (${analysis.method})
165
+ app.${method}('${analysis.route}',${validators}
166
+ async (c) => {
167
+ const result = await ${exportName}.handler(
168
+ {
169
+ services: {},
170
+ logger,
171
+ body: ${features.hasBodyValidation ? "(c.req.valid as any)('json')" : "undefined"},
172
+ query: ${features.hasQueryValidation ? "(c.req.valid as any)('query')" : "undefined"},
173
+ params: ${features.hasParamValidation ? "(c.req.valid as any)('param')" : "undefined"},
174
+ session: undefined,
175
+ header: Endpoint.createHeaders(c.req.header()),
176
+ cookie: Endpoint.createCookies(c.req.header().cookie),
177
+ auditor: undefined,
178
+ db: undefined,
179
+ } as any,
180
+ { getMetadata: () => ({}) } as any,
181
+ );
182
+ ${features.hasOutputValidation ? `const output = await ${exportName}.parseOutput(result);
183
+ return c.json(output, ${exportName}.status as any);` : `return c.json(result, ${exportName}.status as any);`}
184
+ }
185
+ );`;
186
+ }
187
+ /**
188
+ * Generate a standard handler (auth and/or services)
189
+ *
190
+ * Used for: Authenticated endpoints, endpoints with services
191
+ */
192
+ function generateStandardHandler(analysis) {
193
+ const { exportName, features } = analysis;
194
+ const method = analysis.method.toLowerCase();
195
+ const validators = generateValidators(exportName, features);
196
+ let serviceResolution = "";
197
+ if (features.hasServices || features.hasDatabase) serviceResolution = `
198
+ const services = await serviceDiscovery.register(${exportName}.services);
199
+ ${features.hasDatabase ? `const db = ${exportName}.databaseService
200
+ ? (await serviceDiscovery.register([${exportName}.databaseService]) as any)[${exportName}.databaseService.serviceName]
201
+ : undefined;` : "const db = undefined;"}`;
202
+ else serviceResolution = `
203
+ const services = {};
204
+ const db = undefined;`;
205
+ let authCode = "";
206
+ if (features.hasAuth) authCode = `
207
+ // Authentication
208
+ const session = await ${exportName}.getSession({
209
+ services,
210
+ logger,
211
+ header,
212
+ cookie,
213
+ ...(db !== undefined && { db }),
214
+ } as any);
215
+
216
+ const isAuthorized = await ${exportName}.authorize({
217
+ header,
218
+ cookie,
219
+ services,
220
+ logger,
221
+ session,
222
+ } as any);
223
+
224
+ if (!isAuthorized) {
225
+ return c.json({ error: 'Unauthorized' }, 401);
226
+ }`;
227
+ let eventCode = "";
228
+ if (features.hasEvents) eventCode = `
229
+ // Publish events on success
230
+ if (Endpoint.isSuccessStatus(${exportName}.status)) {
231
+ await (publishConstructEvents as any)(
232
+ ${exportName},
233
+ result,
234
+ serviceDiscovery,
235
+ logger,
236
+ );
237
+ }`;
238
+ return `
239
+ // Standard handler: ${analysis.route} (${analysis.method})
240
+ app.${method}('${analysis.route}',${validators}
241
+ async (c) => {
242
+ const headerValues = c.req.header();
243
+ const header = Endpoint.createHeaders(headerValues);
244
+ const cookie = Endpoint.createCookies(headerValues.cookie);
245
+ ${serviceResolution}
246
+ ${authCode}
247
+
248
+ const responseBuilder = new ResponseBuilder();
249
+ const result = await ${exportName}.handler(
250
+ {
251
+ services,
252
+ logger,
253
+ body: ${features.hasBodyValidation ? "(c.req.valid as any)('json')" : "undefined"},
254
+ query: ${features.hasQueryValidation ? "(c.req.valid as any)('query')" : "undefined"},
255
+ params: ${features.hasParamValidation ? "(c.req.valid as any)('param')" : "undefined"},
256
+ session: ${features.hasAuth ? "session" : "undefined"},
257
+ header,
258
+ cookie,
259
+ auditor: undefined,
260
+ db,
261
+ } as any,
262
+ responseBuilder,
263
+ );
264
+
265
+ let data = result;
266
+ let metadata = responseBuilder.getMetadata();
267
+
268
+ if (Endpoint.hasMetadata(result)) {
269
+ data = result.data;
270
+ metadata = result.metadata;
271
+ }
272
+
273
+ ${features.hasOutputValidation ? `const output = ${exportName}.outputSchema
274
+ ? await ${exportName}.parseOutput(data)
275
+ : data;` : "const output = data;"}
276
+ ${eventCode}
277
+
278
+ const status = (metadata.status ?? ${exportName}.status) as any;
279
+ return c.json(output, status);
280
+ }
281
+ );`;
282
+ }
283
+ /**
284
+ * Generate validators.ts - Shared validator middleware factories
285
+ */
286
+ function generateValidatorsFile(analyses) {
287
+ const needsBody = analyses.some((a) => a.features.hasBodyValidation);
288
+ const needsQuery = analyses.some((a) => a.features.hasQueryValidation);
289
+ const needsParams = analyses.some((a) => a.features.hasParamValidation);
290
+ if (!needsBody && !needsQuery && !needsParams) return `// No validators needed for this build\nexport {};\n`;
291
+ const exports = [];
292
+ const factories = [];
293
+ if (needsBody) {
294
+ exports.push("validateBody");
295
+ factories.push(`
296
+ export const validateBody = (endpoint: any) =>
297
+ validator('json', async (value, c) => {
298
+ if (!endpoint.input?.body) return undefined;
299
+ const parsed = await Endpoint.validate(endpoint.input.body, value);
300
+ if (parsed.issues) return c.json(parsed.issues, 422);
301
+ return parsed.value;
302
+ });`);
303
+ }
304
+ if (needsQuery) {
305
+ exports.push("validateQuery");
306
+ factories.push(`
307
+ export const validateQuery = (endpoint: any) =>
308
+ validator('query', async (_, c) => {
309
+ if (!endpoint.input?.query) return undefined;
310
+ const rawQuery = Object.fromEntries(new URL(c.req.url).searchParams);
311
+ const parsed = await Endpoint.validate(endpoint.input.query, rawQuery);
312
+ if (parsed.issues) return c.json(parsed.issues, 422);
313
+ return parsed.value;
314
+ });`);
315
+ }
316
+ if (needsParams) {
317
+ exports.push("validateParams");
318
+ factories.push(`
319
+ export const validateParams = (endpoint: any) =>
320
+ validator('param', async (params, c) => {
321
+ if (!endpoint.input?.params) return undefined;
322
+ const parsed = await Endpoint.validate(endpoint.input.params, params);
323
+ if (parsed.issues) return c.json(parsed.issues, 422);
324
+ return parsed.value;
325
+ });`);
326
+ }
327
+ return `/**
328
+ * Generated validator middleware factories
329
+ * Shared across all endpoint tiers that need validation
330
+ */
331
+ import { validator } from 'hono/validator';
332
+ import { Endpoint } from '@geekmidas/constructs/endpoints';
333
+ ${factories.join("\n")}
334
+ `;
335
+ }
336
+ /**
337
+ * Generate a standalone minimal endpoint file
338
+ */
339
+ function generateMinimalEndpointFile(analysis, endpointImport) {
340
+ const { exportName, features } = analysis;
341
+ const needsValidators = features.hasBodyValidation || features.hasQueryValidation || features.hasParamValidation;
342
+ const validatorImport = needsValidators ? generateValidatorImportsForEndpoint(analysis) : "";
343
+ const handler = generateMinimalHandler(analysis);
344
+ return `/**
345
+ * Minimal endpoint: ${analysis.route} (${analysis.method})
346
+ * Near-raw-Hono performance
347
+ */
348
+ import type { Hono } from 'hono';
349
+ import type { Logger } from '@geekmidas/logger';
350
+ import { Endpoint } from '@geekmidas/constructs/endpoints';
351
+ ${validatorImport}
352
+ import { ${exportName} } from '${endpointImport.importPath}';
353
+
354
+ export function setup${capitalize(exportName)}(
355
+ app: Hono,
356
+ logger: Logger,
357
+ ): void {
358
+ ${handler}
359
+ }
360
+ `;
361
+ }
362
+ /**
363
+ * Generate a standalone standard endpoint file
364
+ */
365
+ function generateStandardEndpointFile(analysis, endpointImport) {
366
+ const { exportName, features } = analysis;
367
+ const needsValidators = features.hasBodyValidation || features.hasQueryValidation || features.hasParamValidation;
368
+ const validatorImport = needsValidators ? generateValidatorImportsForEndpoint(analysis) : "";
369
+ const eventsImport = features.hasEvents ? `import { publishConstructEvents } from '@geekmidas/constructs/endpoints';` : "";
370
+ const handler = generateStandardHandler(analysis);
371
+ return `/**
372
+ * Standard endpoint: ${analysis.route} (${analysis.method})
373
+ * Auth and/or services enabled
374
+ */
375
+ import type { Hono } from 'hono';
376
+ import type { Logger } from '@geekmidas/logger';
377
+ import type { ServiceDiscovery } from '@geekmidas/services';
378
+ import { Endpoint, ResponseBuilder } from '@geekmidas/constructs/endpoints';
379
+ ${eventsImport}
380
+ ${validatorImport}
381
+ import { ${exportName} } from '${endpointImport.importPath}';
382
+
383
+ export function setup${capitalize(exportName)}(
384
+ app: Hono,
385
+ serviceDiscovery: ServiceDiscovery<any, any>,
386
+ logger: Logger,
387
+ ): void {
388
+ ${handler}
389
+ }
390
+ `;
391
+ }
392
+ /**
393
+ * Generate a standalone full endpoint file
394
+ */
395
+ function generateFullEndpointFile(analysis, endpointImport) {
396
+ const { exportName } = analysis;
397
+ return `/**
398
+ * Full endpoint: ${analysis.route} (${analysis.method})
399
+ * Complex features: audits, RLS, rate limiting
400
+ */
401
+ import type { Hono } from 'hono';
402
+ import type { ServiceDiscovery } from '@geekmidas/services';
403
+ import { HonoEndpoint } from '@geekmidas/constructs/hono';
404
+ import { ${exportName} } from '${endpointImport.importPath}';
405
+
406
+ export function setup${capitalize(exportName)}(
407
+ app: Hono,
408
+ serviceDiscovery: ServiceDiscovery<any, any>,
409
+ openApiOptions: any,
410
+ ): void {
411
+ HonoEndpoint.addRoutes([${exportName}] as any, serviceDiscovery, app, openApiOptions);
412
+ }
413
+ `;
414
+ }
415
+ /**
416
+ * Generate tier index file that imports and calls all endpoint setup functions
417
+ */
418
+ function generateTierIndexFile(tier, analyses) {
419
+ const tierEndpoints = analyses.filter((a) => a.tier === tier);
420
+ if (tierEndpoints.length === 0) {
421
+ if (tier === "minimal") return `// No minimal-tier endpoints in this build
422
+ import type { Hono } from 'hono';
423
+ import type { Logger } from '@geekmidas/logger';
424
+
425
+ export function setupMinimalEndpoints(
426
+ _app: Hono,
427
+ _logger: Logger,
428
+ ): void {
429
+ // No minimal endpoints
430
+ }
431
+ `;
432
+ if (tier === "standard") return `// No standard-tier endpoints in this build
433
+ import type { Hono } from 'hono';
434
+ import type { Logger } from '@geekmidas/logger';
435
+ import type { ServiceDiscovery } from '@geekmidas/services';
436
+
437
+ export function setupStandardEndpoints(
438
+ _app: Hono,
439
+ _serviceDiscovery: ServiceDiscovery<any, any>,
440
+ _logger: Logger,
441
+ ): void {
442
+ // No standard endpoints
443
+ }
444
+ `;
445
+ return `// No full-tier endpoints in this build
446
+ import type { Hono } from 'hono';
447
+ import type { ServiceDiscovery } from '@geekmidas/services';
448
+
449
+ export function setupFullEndpoints(
450
+ _app: Hono,
451
+ _serviceDiscovery: ServiceDiscovery<any, any>,
452
+ _enableOpenApi: boolean,
453
+ ): void {
454
+ // No full endpoints
455
+ }
456
+ `;
457
+ }
458
+ const imports = tierEndpoints.map((a) => `import { setup${capitalize(a.exportName)} } from './${a.exportName}.js';`).join("\n");
459
+ if (tier === "minimal") {
460
+ const calls$1 = tierEndpoints.map((a) => ` setup${capitalize(a.exportName)}(app, logger);`).join("\n");
461
+ return `/**
462
+ * Minimal-tier endpoint index (${tierEndpoints.length} endpoints)
463
+ * Near-raw-Hono performance
464
+ */
465
+ import type { Hono } from 'hono';
466
+ import type { Logger } from '@geekmidas/logger';
467
+ ${imports}
468
+
469
+ export function setupMinimalEndpoints(
470
+ app: Hono,
471
+ logger: Logger,
472
+ ): void {
473
+ ${calls$1}
474
+ }
475
+ `;
476
+ }
477
+ if (tier === "standard") {
478
+ const calls$1 = tierEndpoints.map((a) => ` setup${capitalize(a.exportName)}(app, serviceDiscovery, logger);`).join("\n");
479
+ return `/**
480
+ * Standard-tier endpoint index (${tierEndpoints.length} endpoints)
481
+ * Auth and/or services enabled
482
+ */
483
+ import type { Hono } from 'hono';
484
+ import type { Logger } from '@geekmidas/logger';
485
+ import type { ServiceDiscovery } from '@geekmidas/services';
486
+ ${imports}
487
+
488
+ export function setupStandardEndpoints(
489
+ app: Hono,
490
+ serviceDiscovery: ServiceDiscovery<any, any>,
491
+ logger: Logger,
492
+ ): void {
493
+ ${calls$1}
494
+ }
495
+ `;
496
+ }
497
+ const calls = tierEndpoints.map((a) => ` setup${capitalize(a.exportName)}(app, serviceDiscovery, openApiOptions);`).join("\n");
498
+ return `/**
499
+ * Full-tier endpoint index (${tierEndpoints.length} endpoints)
500
+ * Complex features: audits, RLS, rate limiting
501
+ */
502
+ import type { Hono } from 'hono';
503
+ import type { ServiceDiscovery } from '@geekmidas/services';
504
+ ${imports}
505
+
506
+ export function setupFullEndpoints(
507
+ app: Hono,
508
+ serviceDiscovery: ServiceDiscovery<any, any>,
509
+ enableOpenApi: boolean,
510
+ ): void {
511
+ const openApiOptions: any = enableOpenApi ? {
512
+ docsPath: '/__docs',
513
+ openApiOptions: {
514
+ title: 'API Documentation',
515
+ version: '1.0.0',
516
+ description: 'Generated API documentation'
517
+ }
518
+ } : { docsPath: false };
519
+
520
+ ${calls}
521
+ }
522
+ `;
523
+ }
524
+ /**
525
+ * Generate validator imports for a single endpoint
526
+ */
527
+ function generateValidatorImportsForEndpoint(analysis) {
528
+ const imports = [];
529
+ if (analysis.features.hasBodyValidation) imports.push("validateBody");
530
+ if (analysis.features.hasQueryValidation) imports.push("validateQuery");
531
+ if (analysis.features.hasParamValidation) imports.push("validateParams");
532
+ if (imports.length === 0) return "";
533
+ return `import { ${imports.join(", ")} } from '../validators.js';`;
534
+ }
535
+ /**
536
+ * Capitalize first letter
537
+ */
538
+ function capitalize(str) {
539
+ return str.charAt(0).toUpperCase() + str.slice(1);
540
+ }
541
+ /**
542
+ * Generate all endpoint files with nested folder structure (per-endpoint files)
543
+ */
544
+ function generateEndpointFilesNested(analyses, endpointImports) {
545
+ const files = {
546
+ "validators.ts": generateValidatorsFile(analyses),
547
+ "minimal/index.ts": generateTierIndexFile("minimal", analyses),
548
+ "standard/index.ts": generateTierIndexFile("standard", analyses),
549
+ "full/index.ts": generateTierIndexFile("full", analyses),
550
+ "index.ts": generateNestedIndexFile(analyses)
551
+ };
552
+ for (const analysis of analyses) {
553
+ const endpointImport = endpointImports.find((i) => i.exportName === analysis.exportName);
554
+ if (!endpointImport) continue;
555
+ const fileName = `${analysis.tier}/${analysis.exportName}.ts`;
556
+ switch (analysis.tier) {
557
+ case "minimal":
558
+ files[fileName] = generateMinimalEndpointFile(analysis, endpointImport);
559
+ break;
560
+ case "standard":
561
+ files[fileName] = generateStandardEndpointFile(analysis, endpointImport);
562
+ break;
563
+ case "full":
564
+ files[fileName] = generateFullEndpointFile(analysis, endpointImport);
565
+ break;
566
+ }
567
+ }
568
+ return files;
569
+ }
570
+ /**
571
+ * Generate index.ts for nested structure
572
+ */
573
+ function generateNestedIndexFile(analyses) {
574
+ const minimalCount = analyses.filter((a) => a.tier === "minimal").length;
575
+ const standardCount = analyses.filter((a) => a.tier === "standard").length;
576
+ const fullCount = analyses.filter((a) => a.tier === "full").length;
577
+ return `/**
578
+ * Generated optimized endpoints
579
+ *
580
+ * Build-time optimization tiers:
581
+ * - minimal: ${minimalCount} endpoints (near-raw-Hono)
582
+ * - standard: ${standardCount} endpoints (auth/services)
583
+ * - full: ${fullCount} endpoints (audits/rls/rate-limit)
584
+ */
585
+ import type { EnvironmentParser } from '@geekmidas/envkit';
586
+ import type { Logger } from '@geekmidas/logger';
587
+ import type { Hono } from 'hono';
588
+ import { ServiceDiscovery } from '@geekmidas/services';
589
+ import { setupMinimalEndpoints } from './minimal/index.js';
590
+ import { setupStandardEndpoints } from './standard/index.js';
591
+ import { setupFullEndpoints } from './full/index.js';
592
+
593
+ export async function setupEndpoints(
594
+ app: Hono,
595
+ envParser: EnvironmentParser<any>,
596
+ logger: Logger,
597
+ enableOpenApi: boolean = false,
598
+ ): Promise<void> {
599
+ const serviceDiscovery = ServiceDiscovery.getInstance(envParser);
600
+
601
+ // Minimal handlers (${minimalCount} endpoints) - near-raw-Hono performance
602
+ setupMinimalEndpoints(app, logger);
603
+
604
+ // Standard handlers (${standardCount} endpoints) - auth/services
605
+ setupStandardEndpoints(app, serviceDiscovery, logger);
606
+
607
+ // Full handlers (${fullCount} endpoints) - audits/rls/rate-limit
608
+ setupFullEndpoints(app, serviceDiscovery, enableOpenApi);
609
+
610
+ // Add Swagger UI if OpenAPI is enabled
611
+ if (enableOpenApi) {
612
+ try {
613
+ const { swaggerUI } = await import('@hono/swagger-ui');
614
+ app.get('/__docs/ui', swaggerUI({ url: '/__docs' }));
615
+ } catch {
616
+ // @hono/swagger-ui not installed, skip Swagger UI
617
+ }
618
+ }
619
+ }
620
+ `;
621
+ }
622
+
44
623
  //#endregion
45
624
  //#region src/generators/EndpointGenerator.ts
46
625
  var EndpointGenerator = class extends ConstructGenerator {
@@ -59,19 +638,19 @@ var EndpointGenerator = class extends ConstructGenerator {
59
638
  routes.push({
60
639
  path: "*",
61
640
  method: "ALL",
62
- handler: relative$1(process.cwd(), appFile),
641
+ handler: relative(process.cwd(), appFile),
63
642
  authorizer: "none"
64
643
  });
65
644
  logger.log(`Generated server with ${constructs.length} endpoints${enableOpenApi ? " (OpenAPI enabled)" : ""}`);
66
645
  } else if (provider === "aws-lambda") {
67
- const routesDir = join$1(outputDir, "routes");
646
+ const routesDir = join(outputDir, "routes");
68
647
  await mkdir(routesDir, { recursive: true });
69
648
  for (const { key, construct, path } of constructs) {
70
649
  const handlerFile = await this.generateHandlerFile(routesDir, path.relative, key, "aws-apigatewayv2", construct, context);
71
650
  const routeInfo = {
72
651
  path: construct._path,
73
652
  method: construct.method,
74
- handler: relative$1(process.cwd(), handlerFile).replace(/\.ts$/, ".handler"),
653
+ handler: relative(process.cwd(), handlerFile).replace(/\.ts$/, ".handler"),
75
654
  timeout: construct.timeout,
76
655
  memorySize: construct.memorySize,
77
656
  environment: await construct.getEnvironment(),
@@ -85,7 +664,7 @@ var EndpointGenerator = class extends ConstructGenerator {
85
664
  const routeInfo = {
86
665
  path: construct._path,
87
666
  method: construct.method,
88
- handler: relative$1(process.cwd(), handlerFile).replace(/\.ts$/, ".handler"),
667
+ handler: relative(process.cwd(), handlerFile).replace(/\.ts$/, ".handler"),
89
668
  timeout: construct.timeout,
90
669
  memorySize: construct.memorySize,
91
670
  environment: await construct.getEnvironment(),
@@ -98,10 +677,10 @@ var EndpointGenerator = class extends ConstructGenerator {
98
677
  }
99
678
  async generateHandlerFile(outputDir, sourceFile, exportName, provider, _endpoint, context) {
100
679
  const handlerFileName = `${exportName}.ts`;
101
- const handlerPath = join$1(outputDir, handlerFileName);
102
- const relativePath = relative$1(dirname(handlerPath), sourceFile);
680
+ const handlerPath = join(outputDir, handlerFileName);
681
+ const relativePath = relative(dirname(handlerPath), sourceFile);
103
682
  const importPath = relativePath.replace(/\.ts$/, ".js");
104
- const relativeEnvParserPath = relative$1(dirname(handlerPath), context.envParserPath);
683
+ const relativeEnvParserPath = relative(dirname(handlerPath), context.envParserPath);
105
684
  let content;
106
685
  switch (provider) {
107
686
  case "aws-apigatewayv1":
@@ -118,25 +697,26 @@ var EndpointGenerator = class extends ConstructGenerator {
118
697
  await writeFile(handlerPath, content);
119
698
  return handlerPath;
120
699
  }
121
- async generateEndpointsFile(outputDir, endpoints, _context) {
700
+ async generateEndpointsFile(outputDir, endpoints, context) {
122
701
  const endpointsFileName = "endpoints.ts";
123
- const endpointsPath = join$1(outputDir, endpointsFileName);
702
+ const endpointsPath = join(outputDir, endpointsFileName);
124
703
  const importsByFile = /* @__PURE__ */ new Map();
125
704
  for (const { path, key } of endpoints) {
126
- const relativePath = relative$1(dirname(endpointsPath), path.relative);
705
+ const relativePath = relative(dirname(endpointsPath), path.relative);
127
706
  const importPath = relativePath.replace(/\.ts$/, ".js");
128
707
  if (!importsByFile.has(importPath)) importsByFile.set(importPath, []);
129
- importsByFile.get(importPath).push(key);
708
+ importsByFile.get(importPath)?.push(key);
130
709
  }
131
- const imports = Array.from(importsByFile.entries()).map(([importPath, exports]) => `import { ${exports.join(", ")} } from '${importPath}';`).join("\n");
710
+ const endpointImports = Array.from(importsByFile.entries()).map(([importPath, exports]) => `import { ${exports.join(", ")} } from '${importPath}';`).join("\n");
132
711
  const allExportNames = endpoints.map(({ key }) => key);
712
+ if (context.production?.enabled && context.production.optimizedHandlers) return this.generateOptimizedEndpointsFile(endpointsPath, endpoints, endpointImports, allExportNames);
133
713
  const content = `import type { EnvironmentParser } from '@geekmidas/envkit';
134
714
  import type { Logger } from '@geekmidas/logger';
135
715
  import { HonoEndpoint } from '@geekmidas/constructs/hono';
136
716
  import { Endpoint } from '@geekmidas/constructs/endpoints';
137
717
  import { ServiceDiscovery } from '@geekmidas/services';
138
718
  import type { Hono } from 'hono';
139
- ${imports}
719
+ ${endpointImports}
140
720
 
141
721
  const endpoints: Endpoint<any, any, any, any, any, any, any, any, any, any, any, any, any, any>[] = [
142
722
  ${allExportNames.join(",\n ")}
@@ -148,10 +728,7 @@ export async function setupEndpoints(
148
728
  logger: Logger,
149
729
  enableOpenApi: boolean = true,
150
730
  ): Promise<void> {
151
- const serviceDiscovery = ServiceDiscovery.getInstance(
152
- logger,
153
- envParser
154
- );
731
+ const serviceDiscovery = ServiceDiscovery.getInstance(envParser);
155
732
 
156
733
  // Configure OpenAPI options based on enableOpenApi flag
157
734
  const openApiOptions: any = enableOpenApi ? {
@@ -179,11 +756,49 @@ export async function setupEndpoints(
179
756
  await writeFile(endpointsPath, content);
180
757
  return endpointsPath;
181
758
  }
759
+ /**
760
+ * Generate optimized endpoints files with nested folder structure (per-endpoint files)
761
+ */
762
+ async generateOptimizedEndpointsFile(endpointsPath, endpoints, _endpointImports, _allExportNames) {
763
+ const logger = console;
764
+ const outputDir = dirname(endpointsPath);
765
+ const endpointsDir = join(outputDir, "endpoints");
766
+ await mkdir(join(endpointsDir, "minimal"), { recursive: true });
767
+ await mkdir(join(endpointsDir, "standard"), { recursive: true });
768
+ await mkdir(join(endpointsDir, "full"), { recursive: true });
769
+ const analyses = endpoints.map(({ key, construct }) => analyzeEndpoint(construct, key));
770
+ const endpointImports = endpoints.map(({ key, path }) => {
771
+ const tierDir = join(endpointsDir, "standard");
772
+ const relativePath = relative(tierDir, path.relative);
773
+ const importPath = relativePath.replace(/\.ts$/, ".js");
774
+ return {
775
+ exportName: key,
776
+ importPath
777
+ };
778
+ });
779
+ const summary = summarizeAnalysis(analyses);
780
+ logger.log(`\n📊 Endpoint Analysis:`);
781
+ logger.log(` Total: ${summary.total} endpoints`);
782
+ logger.log(` - Minimal (near-raw-Hono): ${summary.byTier.minimal} endpoints`);
783
+ logger.log(` - Standard (auth/services): ${summary.byTier.standard} endpoints`);
784
+ logger.log(` - Full (audits/rls/rate-limit): ${summary.byTier.full} endpoints`);
785
+ const files = generateEndpointFilesNested(analyses, endpointImports);
786
+ for (const [filename, content] of Object.entries(files)) {
787
+ const filePath = join(endpointsDir, filename);
788
+ await mkdir(dirname(filePath), { recursive: true });
789
+ await writeFile(filePath, content);
790
+ }
791
+ const endpointFiles = Object.keys(files).filter((f) => !f.endsWith("index.ts") && !f.endsWith("validators.ts")).length;
792
+ const indexFiles = Object.keys(files).filter((f) => f.endsWith("index.ts")).length;
793
+ logger.log(` Generated ${endpointFiles} endpoint files + ${indexFiles} index files + validators.ts`);
794
+ return join(endpointsDir, "index.ts");
795
+ }
182
796
  async generateAppFile(outputDir, context) {
797
+ if (context.production?.enabled) return this.generateProductionAppFile(outputDir, context);
183
798
  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);
799
+ const appPath = join(outputDir, appFileName);
800
+ const relativeLoggerPath = relative(dirname(appPath), context.loggerPath);
801
+ const relativeEnvParserPath = relative(dirname(appPath), context.envParserPath);
187
802
  const telescopeEnabled = context.telescope?.enabled;
188
803
  const telescopeWebSocketEnabled = context.telescope?.websocket;
189
804
  const usesExternalTelescope = !!context.telescope?.telescopePath;
@@ -191,15 +806,15 @@ export async function setupEndpoints(
191
806
  const usesExternalStudio = !!context.studio?.studioPath;
192
807
  let telescopeImports = "";
193
808
  if (telescopeEnabled) if (usesExternalTelescope) {
194
- const relativeTelescopePath = relative$1(dirname(appPath), context.telescope.telescopePath);
195
- telescopeImports = `import ${context.telescope.telescopeImportPattern} from '${relativeTelescopePath}';
809
+ const relativeTelescopePath = relative(dirname(appPath), context.telescope?.telescopePath);
810
+ telescopeImports = `import ${context.telescope?.telescopeImportPattern} from '${relativeTelescopePath}';
196
811
  import { createMiddleware, createUI } from '@geekmidas/telescope/hono';`;
197
812
  } else telescopeImports = `import { Telescope, InMemoryStorage } from '@geekmidas/telescope';
198
813
  import { createMiddleware, createUI } from '@geekmidas/telescope/hono';`;
199
814
  let studioImports = "";
200
815
  if (studioEnabled) if (usesExternalStudio) {
201
- const relativeStudioPath = relative$1(dirname(appPath), context.studio.studioPath);
202
- studioImports = `import ${context.studio.studioImportPattern} from '${relativeStudioPath}';
816
+ const relativeStudioPath = relative(dirname(appPath), context.studio?.studioPath);
817
+ studioImports = `import ${context.studio?.studioImportPattern} from '${relativeStudioPath}';
203
818
  import { createStudioApp } from '@geekmidas/studio/server/hono';`;
204
819
  } else studioImports = `// Studio requires a configured instance - use studio config path
205
820
  // import { createStudioApp } from '@geekmidas/studio/server/hono';`;
@@ -207,7 +822,7 @@ import { createStudioApp } from '@geekmidas/studio/server/hono';`;
207
822
  let beforeSetupCall = "";
208
823
  let afterSetupCall = "";
209
824
  if (context.hooks?.serverHooksPath) {
210
- const relativeHooksPath = relative$1(dirname(appPath), context.hooks.serverHooksPath);
825
+ const relativeHooksPath = relative(dirname(appPath), context.hooks.serverHooksPath);
211
826
  hooksImports = `import * as serverHooks from '${relativeHooksPath}';`;
212
827
  beforeSetupCall = `
213
828
  // Call beforeSetup hook if defined
@@ -228,7 +843,7 @@ import { createStudioApp } from '@geekmidas/studio/server/hono';`;
228
843
  const { createNodeWebSocket } = await import('@hono/node-ws');
229
844
  const { injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app: honoApp });
230
845
  // Add WebSocket route directly to main app (sub-app routes don't support WS upgrade)
231
- honoApp.get('${context.telescope.path}/ws', upgradeWebSocket(() => ({
846
+ honoApp.get('${context.telescope?.path}/ws', upgradeWebSocket(() => ({
232
847
  onOpen: (_event: Event, ws: any) => {
233
848
  telescope.addWsClient(ws);
234
849
  },
@@ -261,16 +876,16 @@ ${telescopeWebSocketSetupCode}
261
876
 
262
877
  // Mount telescope UI
263
878
  const telescopeUI = createUI(telescope);
264
- honoApp.route('${context.telescope.path}', telescopeUI);
879
+ honoApp.route('${context.telescope?.path}', telescopeUI);
265
880
  `;
266
881
  else telescopeSetup = `
267
882
  // Setup Telescope for debugging/monitoring
268
- const telescopeStorage = new InMemoryStorage({ maxEntries: ${context.telescope.maxEntries} });
883
+ const telescopeStorage = new InMemoryStorage({ maxEntries: ${context.telescope?.maxEntries} });
269
884
  const telescope = new Telescope({
270
885
  enabled: true,
271
- path: '${context.telescope.path}',
272
- ignorePatterns: ${JSON.stringify(context.telescope.ignore)},
273
- recordBody: ${context.telescope.recordBody},
886
+ path: '${context.telescope?.path}',
887
+ ignorePatterns: ${JSON.stringify(context.telescope?.ignore)},
888
+ recordBody: ${context.telescope?.recordBody},
274
889
  storage: telescopeStorage,
275
890
  });
276
891
  ${telescopeWebSocketSetupCode}
@@ -279,13 +894,13 @@ ${telescopeWebSocketSetupCode}
279
894
 
280
895
  // Mount telescope UI
281
896
  const telescopeUI = createUI(telescope);
282
- honoApp.route('${context.telescope.path}', telescopeUI);
897
+ honoApp.route('${context.telescope?.path}', telescopeUI);
283
898
  `;
284
899
  let studioSetup = "";
285
900
  if (studioEnabled && usesExternalStudio) studioSetup = `
286
901
  // Mount Studio data browser UI
287
902
  const studioApp = createStudioApp(studio);
288
- honoApp.route('${context.studio.path}', studioApp);
903
+ honoApp.route('${context.studio?.path}', studioApp);
289
904
  `;
290
905
  const content = `/**
291
906
  * Generated server application
@@ -414,6 +1029,154 @@ export const handler = adapter.handler;
414
1029
  export const handler = ${exportName};
415
1030
  `;
416
1031
  }
1032
+ /**
1033
+ * Generate a production-optimized app.ts file
1034
+ * No dev tools (Telescope, Studio, WebSocket), includes health checks and graceful shutdown
1035
+ */
1036
+ async generateProductionAppFile(outputDir, context) {
1037
+ const appFileName = "app.ts";
1038
+ const appPath = join(outputDir, appFileName);
1039
+ const relativeLoggerPath = relative(dirname(appPath), context.loggerPath);
1040
+ const relativeEnvParserPath = relative(dirname(appPath), context.envParserPath);
1041
+ const production = context.production;
1042
+ const healthCheckPath = production.healthCheck;
1043
+ const enableGracefulShutdown = production.gracefulShutdown;
1044
+ const enableOpenApi = production.openapi;
1045
+ const includeSubscribers = production.subscribers === "include";
1046
+ let hooksImports = "";
1047
+ let beforeSetupCall = "";
1048
+ let afterSetupCall = "";
1049
+ if (context.hooks?.serverHooksPath) {
1050
+ const relativeHooksPath = relative(dirname(appPath), context.hooks.serverHooksPath);
1051
+ hooksImports = `import * as serverHooks from '${relativeHooksPath}';`;
1052
+ beforeSetupCall = `
1053
+ // Call beforeSetup hook if defined
1054
+ if (typeof serverHooks.beforeSetup === 'function') {
1055
+ await serverHooks.beforeSetup(honoApp, { envParser, logger });
1056
+ }
1057
+ `;
1058
+ afterSetupCall = `
1059
+ // Call afterSetup hook if defined
1060
+ if (typeof serverHooks.afterSetup === 'function') {
1061
+ await serverHooks.afterSetup(honoApp, { envParser, logger });
1062
+ }
1063
+ `;
1064
+ }
1065
+ const subscriberSetup = includeSubscribers ? `
1066
+ // Start subscribers in background
1067
+ await setupSubscribers(envParser, logger).catch((error) => {
1068
+ logger.error({ error }, 'Failed to start subscribers');
1069
+ });
1070
+ ` : "";
1071
+ const subscriberImport = includeSubscribers ? `import { setupSubscribers } from './subscribers.js';` : "";
1072
+ const gracefulShutdownCode = enableGracefulShutdown ? `
1073
+ // Graceful shutdown handling
1074
+ let isShuttingDown = false;
1075
+
1076
+ const shutdown = async () => {
1077
+ if (isShuttingDown) return;
1078
+ isShuttingDown = true;
1079
+ logger.info('Graceful shutdown initiated');
1080
+ // Allow in-flight requests to complete (30s timeout)
1081
+ setTimeout(() => process.exit(0), 30000);
1082
+ };
1083
+
1084
+ process.on('SIGTERM', shutdown);
1085
+ process.on('SIGINT', shutdown);
1086
+ ` : "";
1087
+ const endpointsImportPath = production.optimizedHandlers ? "./endpoints/index.js" : "./endpoints.js";
1088
+ const content = `/**
1089
+ * Generated production server application
1090
+ *
1091
+ * This is a production-optimized build without dev tools.
1092
+ * - No Telescope debugging dashboard
1093
+ * - No Studio database browser
1094
+ * - No WebSocket updates
1095
+ * - Includes health checks and graceful shutdown
1096
+ */
1097
+ import { Hono } from 'hono';
1098
+ import type { Hono as HonoType } from 'hono';
1099
+ import { setupEndpoints } from '${endpointsImportPath}';
1100
+ ${subscriberImport}
1101
+ import ${context.envParserImportPattern} from '${relativeEnvParserPath}';
1102
+ import ${context.loggerImportPattern} from '${relativeLoggerPath}';
1103
+ ${hooksImports}
1104
+
1105
+ export interface ServerApp {
1106
+ app: HonoType;
1107
+ start: (options?: {
1108
+ port?: number;
1109
+ serve: (app: HonoType, port: number) => void | Promise<void>;
1110
+ }) => Promise<void>;
1111
+ }
1112
+
1113
+ /**
1114
+ * Create and configure the production Hono application
1115
+ */
1116
+ export async function createApp(app?: HonoType): Promise<ServerApp> {
1117
+ const honoApp = app || new Hono();
1118
+
1119
+ // Health check endpoint (always first)
1120
+ honoApp.get('${healthCheckPath}', (c) => c.json({ status: 'ok', timestamp: Date.now() }));
1121
+ honoApp.get('/ready', (c) => c.json({ ready: true }));
1122
+ ${beforeSetupCall}
1123
+ // Setup HTTP endpoints (OpenAPI: ${enableOpenApi})
1124
+ await setupEndpoints(honoApp, envParser, logger, ${enableOpenApi});
1125
+ ${afterSetupCall}
1126
+ return {
1127
+ app: honoApp,
1128
+ async start(options) {
1129
+ if (!options?.serve) {
1130
+ throw new Error(
1131
+ 'serve function is required. Pass a serve function for your runtime:\\n' +
1132
+ ' - Bun: (app, port) => Bun.serve({ port, fetch: app.fetch })\\n' +
1133
+ ' - Node: (app, port) => serve({ fetch: app.fetch, port })'
1134
+ );
1135
+ }
1136
+
1137
+ const port = options.port ?? Number(process.env.PORT) ?? 3000;
1138
+ ${gracefulShutdownCode}${subscriberSetup}
1139
+ logger.info({ port }, 'Starting production server');
1140
+
1141
+ // Start HTTP server using provided serve function
1142
+ await options.serve(honoApp, port);
1143
+
1144
+ logger.info({ port }, 'Production server started');
1145
+ }
1146
+ };
1147
+ }
1148
+
1149
+ // Default export for convenience
1150
+ export default createApp;
1151
+ `;
1152
+ await writeFile(appPath, content);
1153
+ await this.generateProductionServerEntry(outputDir);
1154
+ return appPath;
1155
+ }
1156
+ /**
1157
+ * Generate production server.ts entry point
1158
+ */
1159
+ async generateProductionServerEntry(outputDir) {
1160
+ const serverPath = join(outputDir, "server.ts");
1161
+ const content = `#!/usr/bin/env node
1162
+ /**
1163
+ * Production server entry point
1164
+ * Generated by 'gkm build --provider server --production'
1165
+ */
1166
+ import { serve } from '@hono/node-server';
1167
+ import { createApp } from './app.js';
1168
+
1169
+ const port = Number(process.env.PORT) || 3000;
1170
+
1171
+ const { app, start } = await createApp();
1172
+
1173
+ await start({
1174
+ port,
1175
+ serve: (app, port) => serve({ fetch: app.fetch, port }),
1176
+ });
1177
+ `;
1178
+ await writeFile(serverPath, content);
1179
+ }
417
1180
  };
418
1181
 
419
1182
  //#endregion
@@ -946,7 +1709,7 @@ async function generateOpenApi(config, options = {}) {
946
1709
  return null;
947
1710
  }
948
1711
  const endpoints = loadedEndpoints.map(({ construct }) => construct);
949
- const outputPath = join$1(process.cwd(), OPENAPI_OUTPUT_PATH);
1712
+ const outputPath = join(process.cwd(), OPENAPI_OUTPUT_PATH);
950
1713
  await mkdir(dirname(outputPath), { recursive: true });
951
1714
  const tsGenerator = new OpenApiTsGenerator();
952
1715
  const tsContent = await tsGenerator.generate(endpoints, {
@@ -975,4 +1738,4 @@ async function openapiCommand(options = {}) {
975
1738
 
976
1739
  //#endregion
977
1740
  export { ConstructGenerator, EndpointGenerator, OPENAPI_OUTPUT_PATH, generateOpenApi, openapiCommand, resolveOpenApiConfig };
978
- //# sourceMappingURL=openapi-CZLI4QTr.mjs.map
1741
+ //# sourceMappingURL=openapi-BfFlOBCG.mjs.map