@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,289 +1,309 @@
1
1
  import { mkdir, writeFile } from 'node:fs/promises';
2
2
  import { dirname, join, relative } from 'node:path';
3
3
  import { Endpoint } from '@geekmidas/constructs/endpoints';
4
+ import {
5
+ analyzeEndpoint,
6
+ type EndpointAnalysis,
7
+ summarizeAnalysis,
8
+ } from '../build/endpoint-analyzer';
9
+ import {
10
+ type EndpointImportInfo,
11
+ generateEndpointFilesNested,
12
+ } from '../build/handler-templates';
4
13
  import type { BuildContext } from '../build/types';
5
14
  import type { LegacyProvider, RouteInfo } from '../types';
6
15
  import {
7
- ConstructGenerator,
8
- type GeneratedConstruct,
9
- type GeneratorOptions,
16
+ ConstructGenerator,
17
+ type GeneratedConstruct,
18
+ type GeneratorOptions,
10
19
  } from './Generator';
11
20
 
12
21
  export class EndpointGenerator extends ConstructGenerator<
13
- Endpoint<
14
- any,
15
- any,
16
- any,
17
- any,
18
- any,
19
- any,
20
- any,
21
- any,
22
- any,
23
- any,
24
- any,
25
- any,
26
- any,
27
- any
28
- >,
29
- RouteInfo[]
22
+ Endpoint<
23
+ any,
24
+ any,
25
+ any,
26
+ any,
27
+ any,
28
+ any,
29
+ any,
30
+ any,
31
+ any,
32
+ any,
33
+ any,
34
+ any,
35
+ any,
36
+ any
37
+ >,
38
+ RouteInfo[]
30
39
  > {
31
- isConstruct(
32
- value: any,
33
- ): value is Endpoint<
34
- any,
35
- any,
36
- any,
37
- any,
38
- any,
39
- any,
40
- any,
41
- any,
42
- any,
43
- any,
44
- any,
45
- any,
46
- any,
47
- any
48
- > {
49
- return Endpoint.isEndpoint(value);
50
- }
51
-
52
- async build(
53
- context: BuildContext,
54
- constructs: GeneratedConstruct<
55
- Endpoint<
56
- any,
57
- any,
58
- any,
59
- any,
60
- any,
61
- any,
62
- any,
63
- any,
64
- any,
65
- any,
66
- any,
67
- any,
68
- any,
69
- any
70
- >
71
- >[],
72
- outputDir: string,
73
- options?: GeneratorOptions,
74
- ): Promise<RouteInfo[]> {
75
- const provider = options?.provider || 'aws-apigatewayv2';
76
- const enableOpenApi = options?.enableOpenApi || false;
77
- const logger = console;
78
- const routes: RouteInfo[] = [];
79
-
80
- if (constructs.length === 0) {
81
- return routes;
82
- }
83
-
84
- if (provider === 'server') {
85
- // Generate endpoints.ts and app.ts
86
- await this.generateEndpointsFile(outputDir, constructs, context);
87
- const appFile = await this.generateAppFile(outputDir, context);
88
-
89
- routes.push({
90
- path: '*',
91
- method: 'ALL',
92
- handler: relative(process.cwd(), appFile),
93
- authorizer: 'none',
94
- });
95
-
96
- logger.log(
97
- `Generated server with ${constructs.length} endpoints${enableOpenApi ? ' (OpenAPI enabled)' : ''}`,
98
- );
99
- } else if (provider === 'aws-lambda') {
100
- // For aws-lambda, create routes subdirectory
101
- const routesDir = join(outputDir, 'routes');
102
- await mkdir(routesDir, { recursive: true });
103
-
104
- // Generate individual handlers for API Gateway routes
105
- for (const { key, construct, path } of constructs) {
106
- const handlerFile = await this.generateHandlerFile(
107
- routesDir,
108
- path.relative,
109
- key,
110
- 'aws-apigatewayv2',
111
- construct,
112
- context,
113
- );
114
-
115
- const routeInfo: RouteInfo = {
116
- path: construct._path,
117
- method: construct.method,
118
- handler: relative(process.cwd(), handlerFile).replace(
119
- /\.ts$/,
120
- '.handler',
121
- ),
122
- timeout: construct.timeout,
123
- memorySize: construct.memorySize,
124
- environment: await construct.getEnvironment(),
125
- authorizer: construct.authorizer?.name ?? 'none',
126
- };
127
-
128
- routes.push(routeInfo);
129
- logger.log(
130
- `Generated handler for ${routeInfo.method} ${routeInfo.path}`,
131
- );
132
- }
133
- } else {
134
- // Generate individual handler files for AWS API Gateway providers
135
- for (const { key, construct, path } of constructs) {
136
- const handlerFile = await this.generateHandlerFile(
137
- outputDir,
138
- path.relative,
139
- key,
140
- provider,
141
- construct,
142
- context,
143
- );
144
-
145
- const routeInfo: RouteInfo = {
146
- path: construct._path,
147
- method: construct.method,
148
- handler: relative(process.cwd(), handlerFile).replace(
149
- /\.ts$/,
150
- '.handler',
151
- ),
152
- timeout: construct.timeout,
153
- memorySize: construct.memorySize,
154
- environment: await construct.getEnvironment(),
155
- authorizer: construct.authorizer?.name ?? 'none',
156
- };
157
-
158
- routes.push(routeInfo);
159
- logger.log(
160
- `Generated handler for ${routeInfo.method} ${routeInfo.path}`,
161
- );
162
- }
163
- }
164
-
165
- return routes;
166
- }
167
-
168
- private async generateHandlerFile(
169
- outputDir: string,
170
- sourceFile: string,
171
- exportName: string,
172
- provider: LegacyProvider,
173
- _endpoint: Endpoint<
174
- any,
175
- any,
176
- any,
177
- any,
178
- any,
179
- any,
180
- any,
181
- any,
182
- any,
183
- any,
184
- any,
185
- any,
186
- any,
187
- any
188
- >,
189
- context: BuildContext,
190
- ): Promise<string> {
191
- const handlerFileName = `${exportName}.ts`;
192
- const handlerPath = join(outputDir, handlerFileName);
193
-
194
- const relativePath = relative(dirname(handlerPath), sourceFile);
195
- const importPath = relativePath.replace(/\.ts$/, '.js');
196
-
197
- const relativeEnvParserPath = relative(
198
- dirname(handlerPath),
199
- context.envParserPath,
200
- );
201
-
202
- let content: string;
203
-
204
- switch (provider) {
205
- case 'aws-apigatewayv1':
206
- content = this.generateAWSApiGatewayV1Handler(
207
- importPath,
208
- exportName,
209
- relativeEnvParserPath,
210
- context.envParserImportPattern,
211
- );
212
- break;
213
- case 'aws-apigatewayv2':
214
- content = this.generateAWSApiGatewayV2Handler(
215
- importPath,
216
- exportName,
217
- relativeEnvParserPath,
218
- context.envParserImportPattern,
219
- );
220
- break;
221
- case 'server':
222
- content = this.generateServerHandler(importPath, exportName);
223
- break;
224
- default:
225
- throw new Error(`Unsupported provider: ${provider}`);
226
- }
227
-
228
- await writeFile(handlerPath, content);
229
- return handlerPath;
230
- }
231
-
232
- private async generateEndpointsFile(
233
- outputDir: string,
234
- endpoints: GeneratedConstruct<
235
- Endpoint<
236
- any,
237
- any,
238
- any,
239
- any,
240
- any,
241
- any,
242
- any,
243
- any,
244
- any,
245
- any,
246
- any,
247
- any,
248
- any,
249
- any
250
- >
251
- >[],
252
- _context: BuildContext,
253
- ): Promise<string> {
254
- const endpointsFileName = 'endpoints.ts';
255
- const endpointsPath = join(outputDir, endpointsFileName);
256
-
257
- // Group imports by file
258
- const importsByFile = new Map<string, string[]>();
259
-
260
- for (const { path, key } of endpoints) {
261
- const relativePath = relative(dirname(endpointsPath), path.relative);
262
- const importPath = relativePath.replace(/\.ts$/, '.js');
263
-
264
- if (!importsByFile.has(importPath)) {
265
- importsByFile.set(importPath, []);
266
- }
267
- importsByFile.get(importPath)!.push(key);
268
- }
269
-
270
- // Generate import statements
271
- const imports = Array.from(importsByFile.entries())
272
- .map(
273
- ([importPath, exports]) =>
274
- `import { ${exports.join(', ')} } from '${importPath}';`,
275
- )
276
- .join('\n');
277
-
278
- const allExportNames = endpoints.map(({ key }) => key);
279
-
280
- const content = `import type { EnvironmentParser } from '@geekmidas/envkit';
40
+ isConstruct(
41
+ value: any,
42
+ ): value is Endpoint<
43
+ any,
44
+ any,
45
+ any,
46
+ any,
47
+ any,
48
+ any,
49
+ any,
50
+ any,
51
+ any,
52
+ any,
53
+ any,
54
+ any,
55
+ any,
56
+ any
57
+ > {
58
+ return Endpoint.isEndpoint(value);
59
+ }
60
+
61
+ async build(
62
+ context: BuildContext,
63
+ constructs: GeneratedConstruct<
64
+ Endpoint<
65
+ any,
66
+ any,
67
+ any,
68
+ any,
69
+ any,
70
+ any,
71
+ any,
72
+ any,
73
+ any,
74
+ any,
75
+ any,
76
+ any,
77
+ any,
78
+ any
79
+ >
80
+ >[],
81
+ outputDir: string,
82
+ options?: GeneratorOptions,
83
+ ): Promise<RouteInfo[]> {
84
+ const provider = options?.provider || 'aws-apigatewayv2';
85
+ const enableOpenApi = options?.enableOpenApi || false;
86
+ const logger = console;
87
+ const routes: RouteInfo[] = [];
88
+
89
+ if (constructs.length === 0) {
90
+ return routes;
91
+ }
92
+
93
+ if (provider === 'server') {
94
+ // Generate endpoints.ts and app.ts
95
+ await this.generateEndpointsFile(outputDir, constructs, context);
96
+ const appFile = await this.generateAppFile(outputDir, context);
97
+
98
+ routes.push({
99
+ path: '*',
100
+ method: 'ALL',
101
+ handler: relative(process.cwd(), appFile),
102
+ authorizer: 'none',
103
+ });
104
+
105
+ logger.log(
106
+ `Generated server with ${constructs.length} endpoints${enableOpenApi ? ' (OpenAPI enabled)' : ''}`,
107
+ );
108
+ } else if (provider === 'aws-lambda') {
109
+ // For aws-lambda, create routes subdirectory
110
+ const routesDir = join(outputDir, 'routes');
111
+ await mkdir(routesDir, { recursive: true });
112
+
113
+ // Generate individual handlers for API Gateway routes
114
+ for (const { key, construct, path } of constructs) {
115
+ const handlerFile = await this.generateHandlerFile(
116
+ routesDir,
117
+ path.relative,
118
+ key,
119
+ 'aws-apigatewayv2',
120
+ construct,
121
+ context,
122
+ );
123
+
124
+ const routeInfo: RouteInfo = {
125
+ path: construct._path,
126
+ method: construct.method,
127
+ handler: relative(process.cwd(), handlerFile).replace(
128
+ /\.ts$/,
129
+ '.handler',
130
+ ),
131
+ timeout: construct.timeout,
132
+ memorySize: construct.memorySize,
133
+ environment: await construct.getEnvironment(),
134
+ authorizer: construct.authorizer?.name ?? 'none',
135
+ };
136
+
137
+ routes.push(routeInfo);
138
+ logger.log(
139
+ `Generated handler for ${routeInfo.method} ${routeInfo.path}`,
140
+ );
141
+ }
142
+ } else {
143
+ // Generate individual handler files for AWS API Gateway providers
144
+ for (const { key, construct, path } of constructs) {
145
+ const handlerFile = await this.generateHandlerFile(
146
+ outputDir,
147
+ path.relative,
148
+ key,
149
+ provider,
150
+ construct,
151
+ context,
152
+ );
153
+
154
+ const routeInfo: RouteInfo = {
155
+ path: construct._path,
156
+ method: construct.method,
157
+ handler: relative(process.cwd(), handlerFile).replace(
158
+ /\.ts$/,
159
+ '.handler',
160
+ ),
161
+ timeout: construct.timeout,
162
+ memorySize: construct.memorySize,
163
+ environment: await construct.getEnvironment(),
164
+ authorizer: construct.authorizer?.name ?? 'none',
165
+ };
166
+
167
+ routes.push(routeInfo);
168
+ logger.log(
169
+ `Generated handler for ${routeInfo.method} ${routeInfo.path}`,
170
+ );
171
+ }
172
+ }
173
+
174
+ return routes;
175
+ }
176
+
177
+ private async generateHandlerFile(
178
+ outputDir: string,
179
+ sourceFile: string,
180
+ exportName: string,
181
+ provider: LegacyProvider,
182
+ _endpoint: Endpoint<
183
+ any,
184
+ any,
185
+ any,
186
+ any,
187
+ any,
188
+ any,
189
+ any,
190
+ any,
191
+ any,
192
+ any,
193
+ any,
194
+ any,
195
+ any,
196
+ any
197
+ >,
198
+ context: BuildContext,
199
+ ): Promise<string> {
200
+ const handlerFileName = `${exportName}.ts`;
201
+ const handlerPath = join(outputDir, handlerFileName);
202
+
203
+ const relativePath = relative(dirname(handlerPath), sourceFile);
204
+ const importPath = relativePath.replace(/\.ts$/, '.js');
205
+
206
+ const relativeEnvParserPath = relative(
207
+ dirname(handlerPath),
208
+ context.envParserPath,
209
+ );
210
+
211
+ let content: string;
212
+
213
+ switch (provider) {
214
+ case 'aws-apigatewayv1':
215
+ content = this.generateAWSApiGatewayV1Handler(
216
+ importPath,
217
+ exportName,
218
+ relativeEnvParserPath,
219
+ context.envParserImportPattern,
220
+ );
221
+ break;
222
+ case 'aws-apigatewayv2':
223
+ content = this.generateAWSApiGatewayV2Handler(
224
+ importPath,
225
+ exportName,
226
+ relativeEnvParserPath,
227
+ context.envParserImportPattern,
228
+ );
229
+ break;
230
+ case 'server':
231
+ content = this.generateServerHandler(importPath, exportName);
232
+ break;
233
+ default:
234
+ throw new Error(`Unsupported provider: ${provider}`);
235
+ }
236
+
237
+ await writeFile(handlerPath, content);
238
+ return handlerPath;
239
+ }
240
+
241
+ private async generateEndpointsFile(
242
+ outputDir: string,
243
+ endpoints: GeneratedConstruct<
244
+ Endpoint<
245
+ any,
246
+ any,
247
+ any,
248
+ any,
249
+ any,
250
+ any,
251
+ any,
252
+ any,
253
+ any,
254
+ any,
255
+ any,
256
+ any,
257
+ any,
258
+ any
259
+ >
260
+ >[],
261
+ context: BuildContext,
262
+ ): Promise<string> {
263
+ const endpointsFileName = 'endpoints.ts';
264
+ const endpointsPath = join(outputDir, endpointsFileName);
265
+
266
+ // Group imports by file
267
+ const importsByFile = new Map<string, string[]>();
268
+
269
+ for (const { path, key } of endpoints) {
270
+ const relativePath = relative(dirname(endpointsPath), path.relative);
271
+ const importPath = relativePath.replace(/\.ts$/, '.js');
272
+
273
+ if (!importsByFile.has(importPath)) {
274
+ importsByFile.set(importPath, []);
275
+ }
276
+ importsByFile.get(importPath)?.push(key);
277
+ }
278
+
279
+ // Generate import statements for endpoints
280
+ const endpointImports = Array.from(importsByFile.entries())
281
+ .map(
282
+ ([importPath, exports]) =>
283
+ `import { ${exports.join(', ')} } from '${importPath}';`,
284
+ )
285
+ .join('\n');
286
+
287
+ const allExportNames = endpoints.map(({ key }) => key);
288
+
289
+ // Check if we should use optimized handler generation
290
+ if (context.production?.enabled && context.production.optimizedHandlers) {
291
+ return this.generateOptimizedEndpointsFile(
292
+ endpointsPath,
293
+ endpoints,
294
+ endpointImports,
295
+ allExportNames,
296
+ );
297
+ }
298
+
299
+ // Standard generation (development or optimizedHandlers: false)
300
+ const content = `import type { EnvironmentParser } from '@geekmidas/envkit';
281
301
  import type { Logger } from '@geekmidas/logger';
282
302
  import { HonoEndpoint } from '@geekmidas/constructs/hono';
283
303
  import { Endpoint } from '@geekmidas/constructs/endpoints';
284
304
  import { ServiceDiscovery } from '@geekmidas/services';
285
305
  import type { Hono } from 'hono';
286
- ${imports}
306
+ ${endpointImports}
287
307
 
288
308
  const endpoints: Endpoint<any, any, any, any, any, any, any, any, any, any, any, any, any, any>[] = [
289
309
  ${allExportNames.join(',\n ')}
@@ -295,10 +315,7 @@ export async function setupEndpoints(
295
315
  logger: Logger,
296
316
  enableOpenApi: boolean = true,
297
317
  ): Promise<void> {
298
- const serviceDiscovery = ServiceDiscovery.getInstance(
299
- logger,
300
- envParser
301
- );
318
+ const serviceDiscovery = ServiceDiscovery.getInstance(envParser);
302
319
 
303
320
  // Configure OpenAPI options based on enableOpenApi flag
304
321
  const openApiOptions: any = enableOpenApi ? {
@@ -324,98 +341,195 @@ export async function setupEndpoints(
324
341
  }
325
342
  `;
326
343
 
327
- await writeFile(endpointsPath, content);
328
-
329
- return endpointsPath;
330
- }
331
-
332
- private async generateAppFile(
333
- outputDir: string,
334
- context: BuildContext,
335
- ): Promise<string> {
336
- const appFileName = 'app.ts';
337
- const appPath = join(outputDir, appFileName);
338
-
339
- const relativeLoggerPath = relative(dirname(appPath), context.loggerPath);
340
-
341
- const relativeEnvParserPath = relative(
342
- dirname(appPath),
343
- context.envParserPath,
344
- );
345
-
346
- // Generate telescope imports and setup if enabled
347
- const telescopeEnabled = context.telescope?.enabled;
348
- const telescopeWebSocketEnabled = context.telescope?.websocket;
349
- const usesExternalTelescope = !!context.telescope?.telescopePath;
350
-
351
- // Generate studio imports and setup if enabled
352
- const studioEnabled = context.studio?.enabled;
353
- const usesExternalStudio = !!context.studio?.studioPath;
354
-
355
- // Generate imports based on whether telescope is external or inline
356
- let telescopeImports = '';
357
- if (telescopeEnabled) {
358
- if (usesExternalTelescope) {
359
- const relativeTelescopePath = relative(
360
- dirname(appPath),
361
- context.telescope!.telescopePath!,
362
- );
363
- telescopeImports = `import ${context.telescope!.telescopeImportPattern} from '${relativeTelescopePath}';
344
+ await writeFile(endpointsPath, content);
345
+
346
+ return endpointsPath;
347
+ }
348
+
349
+ /**
350
+ * Generate optimized endpoints files with nested folder structure (per-endpoint files)
351
+ */
352
+ private async generateOptimizedEndpointsFile(
353
+ endpointsPath: string,
354
+ endpoints: GeneratedConstruct<
355
+ Endpoint<
356
+ any,
357
+ any,
358
+ any,
359
+ any,
360
+ any,
361
+ any,
362
+ any,
363
+ any,
364
+ any,
365
+ any,
366
+ any,
367
+ any,
368
+ any,
369
+ any
370
+ >
371
+ >[],
372
+ _endpointImports: string,
373
+ _allExportNames: string[],
374
+ ): Promise<string> {
375
+ const logger = console;
376
+ const outputDir = dirname(endpointsPath);
377
+
378
+ // Create endpoints subdirectory with tier folders
379
+ const endpointsDir = join(outputDir, 'endpoints');
380
+ await mkdir(join(endpointsDir, 'minimal'), { recursive: true });
381
+ await mkdir(join(endpointsDir, 'standard'), { recursive: true });
382
+ await mkdir(join(endpointsDir, 'full'), { recursive: true });
383
+
384
+ // Analyze each endpoint
385
+ const analyses: EndpointAnalysis[] = endpoints.map(({ key, construct }) =>
386
+ analyzeEndpoint(construct, key),
387
+ );
388
+
389
+ // Build endpoint import info with correct relative paths from each tier folder
390
+ // Use paths relative to the tier folder (e.g., endpoints/standard/)
391
+ const endpointImports: EndpointImportInfo[] = endpoints.map(
392
+ ({ key, path }) => {
393
+ // Calculate relative path from tier folder (one level deeper than endpointsDir)
394
+ const tierDir = join(endpointsDir, 'standard'); // Use any tier as reference - same depth
395
+ const relativePath = relative(tierDir, path.relative);
396
+ const importPath = relativePath.replace(/\.ts$/, '.js');
397
+ return { exportName: key, importPath };
398
+ },
399
+ );
400
+
401
+ // Log analysis summary
402
+ const summary = summarizeAnalysis(analyses);
403
+ logger.log(`\n📊 Endpoint Analysis:`);
404
+ logger.log(` Total: ${summary.total} endpoints`);
405
+ logger.log(
406
+ ` - Minimal (near-raw-Hono): ${summary.byTier.minimal} endpoints`,
407
+ );
408
+ logger.log(
409
+ ` - Standard (auth/services): ${summary.byTier.standard} endpoints`,
410
+ );
411
+ logger.log(
412
+ ` - Full (audits/rls/rate-limit): ${summary.byTier.full} endpoints`,
413
+ );
414
+
415
+ // Generate files with nested structure (per-endpoint files)
416
+ const files = generateEndpointFilesNested(analyses, endpointImports);
417
+
418
+ // Write each file, creating directories as needed
419
+ for (const [filename, content] of Object.entries(files)) {
420
+ const filePath = join(endpointsDir, filename);
421
+ await mkdir(dirname(filePath), { recursive: true });
422
+ await writeFile(filePath, content);
423
+ }
424
+
425
+ // Count files by type
426
+ const endpointFiles = Object.keys(files).filter(
427
+ (f) => !f.endsWith('index.ts') && !f.endsWith('validators.ts'),
428
+ ).length;
429
+ const indexFiles = Object.keys(files).filter((f) =>
430
+ f.endsWith('index.ts'),
431
+ ).length;
432
+
433
+ logger.log(
434
+ ` Generated ${endpointFiles} endpoint files + ${indexFiles} index files + validators.ts`,
435
+ );
436
+
437
+ // Return path to index file
438
+ return join(endpointsDir, 'index.ts');
439
+ }
440
+
441
+ private async generateAppFile(
442
+ outputDir: string,
443
+ context: BuildContext,
444
+ ): Promise<string> {
445
+ // Use production generator if in production mode
446
+ if (context.production?.enabled) {
447
+ return this.generateProductionAppFile(outputDir, context);
448
+ }
449
+
450
+ const appFileName = 'app.ts';
451
+ const appPath = join(outputDir, appFileName);
452
+
453
+ const relativeLoggerPath = relative(dirname(appPath), context.loggerPath);
454
+
455
+ const relativeEnvParserPath = relative(
456
+ dirname(appPath),
457
+ context.envParserPath,
458
+ );
459
+
460
+ // Generate telescope imports and setup if enabled
461
+ const telescopeEnabled = context.telescope?.enabled;
462
+ const telescopeWebSocketEnabled = context.telescope?.websocket;
463
+ const usesExternalTelescope = !!context.telescope?.telescopePath;
464
+
465
+ // Generate studio imports and setup if enabled
466
+ const studioEnabled = context.studio?.enabled;
467
+ const usesExternalStudio = !!context.studio?.studioPath;
468
+
469
+ // Generate imports based on whether telescope is external or inline
470
+ let telescopeImports = '';
471
+ if (telescopeEnabled) {
472
+ if (usesExternalTelescope) {
473
+ const relativeTelescopePath = relative(
474
+ dirname(appPath),
475
+ context.telescope?.telescopePath!,
476
+ );
477
+ telescopeImports = `import ${context.telescope?.telescopeImportPattern} from '${relativeTelescopePath}';
364
478
  import { createMiddleware, createUI } from '@geekmidas/telescope/hono';`;
365
- } else {
366
- telescopeImports = `import { Telescope, InMemoryStorage } from '@geekmidas/telescope';
479
+ } else {
480
+ telescopeImports = `import { Telescope, InMemoryStorage } from '@geekmidas/telescope';
367
481
  import { createMiddleware, createUI } from '@geekmidas/telescope/hono';`;
368
- }
369
- }
370
-
371
- // Generate imports for studio
372
- let studioImports = '';
373
- if (studioEnabled) {
374
- if (usesExternalStudio) {
375
- const relativeStudioPath = relative(
376
- dirname(appPath),
377
- context.studio!.studioPath!,
378
- );
379
- studioImports = `import ${context.studio!.studioImportPattern} from '${relativeStudioPath}';
482
+ }
483
+ }
484
+
485
+ // Generate imports for studio
486
+ let studioImports = '';
487
+ if (studioEnabled) {
488
+ if (usesExternalStudio) {
489
+ const relativeStudioPath = relative(
490
+ dirname(appPath),
491
+ context.studio?.studioPath!,
492
+ );
493
+ studioImports = `import ${context.studio?.studioImportPattern} from '${relativeStudioPath}';
380
494
  import { createStudioApp } from '@geekmidas/studio/server/hono';`;
381
- } else {
382
- studioImports = `// Studio requires a configured instance - use studio config path
495
+ } else {
496
+ studioImports = `// Studio requires a configured instance - use studio config path
383
497
  // import { createStudioApp } from '@geekmidas/studio/server/hono';`;
384
- }
385
- }
386
-
387
- // Generate imports for server hooks
388
- let hooksImports = '';
389
- let beforeSetupCall = '';
390
- let afterSetupCall = '';
391
- if (context.hooks?.serverHooksPath) {
392
- const relativeHooksPath = relative(
393
- dirname(appPath),
394
- context.hooks.serverHooksPath,
395
- );
396
- hooksImports = `import * as serverHooks from '${relativeHooksPath}';`;
397
- beforeSetupCall = `
498
+ }
499
+ }
500
+
501
+ // Generate imports for server hooks
502
+ let hooksImports = '';
503
+ let beforeSetupCall = '';
504
+ let afterSetupCall = '';
505
+ if (context.hooks?.serverHooksPath) {
506
+ const relativeHooksPath = relative(
507
+ dirname(appPath),
508
+ context.hooks.serverHooksPath,
509
+ );
510
+ hooksImports = `import * as serverHooks from '${relativeHooksPath}';`;
511
+ beforeSetupCall = `
398
512
  // Call beforeSetup hook if defined
399
513
  if (typeof serverHooks.beforeSetup === 'function') {
400
514
  await serverHooks.beforeSetup(honoApp, { envParser, logger });
401
515
  }
402
516
  `;
403
- afterSetupCall = `
517
+ afterSetupCall = `
404
518
  // Call afterSetup hook if defined
405
519
  if (typeof serverHooks.afterSetup === 'function') {
406
520
  await serverHooks.afterSetup(honoApp, { envParser, logger });
407
521
  }
408
522
  `;
409
- }
523
+ }
410
524
 
411
- const telescopeWebSocketSetupCode = telescopeWebSocketEnabled
412
- ? `
525
+ const telescopeWebSocketSetupCode = telescopeWebSocketEnabled
526
+ ? `
413
527
  // Setup WebSocket for real-time telescope updates
414
528
  try {
415
529
  const { createNodeWebSocket } = await import('@hono/node-ws');
416
530
  const { injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app: honoApp });
417
531
  // Add WebSocket route directly to main app (sub-app routes don't support WS upgrade)
418
- honoApp.get('${context.telescope!.path}/ws', upgradeWebSocket(() => ({
532
+ honoApp.get('${context.telescope?.path}/ws', upgradeWebSocket(() => ({
419
533
  onOpen: (_event: Event, ws: any) => {
420
534
  telescope.addWsClient(ws);
421
535
  },
@@ -440,32 +554,32 @@ import { createStudioApp } from '@geekmidas/studio/server/hono';`;
440
554
  logger.warn({ error: e }, 'WebSocket support not available - install @hono/node-ws for real-time updates');
441
555
  }
442
556
  `
443
- : '';
444
-
445
- // Generate telescope setup - either use external instance or create inline
446
- let telescopeSetup = '';
447
- if (telescopeEnabled) {
448
- if (usesExternalTelescope) {
449
- // Use external telescope instance - no need to create one
450
- telescopeSetup = `
557
+ : '';
558
+
559
+ // Generate telescope setup - either use external instance or create inline
560
+ let telescopeSetup = '';
561
+ if (telescopeEnabled) {
562
+ if (usesExternalTelescope) {
563
+ // Use external telescope instance - no need to create one
564
+ telescopeSetup = `
451
565
  ${telescopeWebSocketSetupCode}
452
566
  // Add telescope middleware (before endpoints to capture all requests)
453
567
  honoApp.use('*', createMiddleware(telescope));
454
568
 
455
569
  // Mount telescope UI
456
570
  const telescopeUI = createUI(telescope);
457
- honoApp.route('${context.telescope!.path}', telescopeUI);
571
+ honoApp.route('${context.telescope?.path}', telescopeUI);
458
572
  `;
459
- } else {
460
- // Create inline telescope instance
461
- telescopeSetup = `
573
+ } else {
574
+ // Create inline telescope instance
575
+ telescopeSetup = `
462
576
  // Setup Telescope for debugging/monitoring
463
- const telescopeStorage = new InMemoryStorage({ maxEntries: ${context.telescope!.maxEntries} });
577
+ const telescopeStorage = new InMemoryStorage({ maxEntries: ${context.telescope?.maxEntries} });
464
578
  const telescope = new Telescope({
465
579
  enabled: true,
466
- path: '${context.telescope!.path}',
467
- ignorePatterns: ${JSON.stringify(context.telescope!.ignore)},
468
- recordBody: ${context.telescope!.recordBody},
580
+ path: '${context.telescope?.path}',
581
+ ignorePatterns: ${JSON.stringify(context.telescope?.ignore)},
582
+ recordBody: ${context.telescope?.recordBody},
469
583
  storage: telescopeStorage,
470
584
  });
471
585
  ${telescopeWebSocketSetupCode}
@@ -474,22 +588,22 @@ ${telescopeWebSocketSetupCode}
474
588
 
475
589
  // Mount telescope UI
476
590
  const telescopeUI = createUI(telescope);
477
- honoApp.route('${context.telescope!.path}', telescopeUI);
591
+ honoApp.route('${context.telescope?.path}', telescopeUI);
478
592
  `;
479
- }
480
- }
593
+ }
594
+ }
481
595
 
482
- // Generate studio setup - requires external instance
483
- let studioSetup = '';
484
- if (studioEnabled && usesExternalStudio) {
485
- studioSetup = `
596
+ // Generate studio setup - requires external instance
597
+ let studioSetup = '';
598
+ if (studioEnabled && usesExternalStudio) {
599
+ studioSetup = `
486
600
  // Mount Studio data browser UI
487
601
  const studioApp = createStudioApp(studio);
488
- honoApp.route('${context.studio!.path}', studioApp);
602
+ honoApp.route('${context.studio?.path}', studioApp);
489
603
  `;
490
- }
604
+ }
491
605
 
492
- const content = `/**
606
+ const content = `/**
493
607
  * Generated server application
494
608
  *
495
609
  * ⚠️ WARNING: This is for LOCAL DEVELOPMENT ONLY
@@ -587,18 +701,18 @@ ${afterSetupCall}
587
701
  export default createApp;
588
702
  `;
589
703
 
590
- await writeFile(appPath, content);
704
+ await writeFile(appPath, content);
591
705
 
592
- return appPath;
593
- }
706
+ return appPath;
707
+ }
594
708
 
595
- private generateAWSApiGatewayV1Handler(
596
- importPath: string,
597
- exportName: string,
598
- envParserPath: string,
599
- envParserImportPattern: string,
600
- ): string {
601
- return `import { AmazonApiGatewayV1Endpoint } from '@geekmidas/constructs/aws';
709
+ private generateAWSApiGatewayV1Handler(
710
+ importPath: string,
711
+ exportName: string,
712
+ envParserPath: string,
713
+ envParserImportPattern: string,
714
+ ): string {
715
+ return `import { AmazonApiGatewayV1Endpoint } from '@geekmidas/constructs/aws';
602
716
  import { ${exportName} } from '${importPath}';
603
717
  import ${envParserImportPattern} from '${envParserPath}';
604
718
 
@@ -606,15 +720,15 @@ const adapter = new AmazonApiGatewayV1Endpoint(envParser, ${exportName});
606
720
 
607
721
  export const handler = adapter.handler;
608
722
  `;
609
- }
610
-
611
- private generateAWSApiGatewayV2Handler(
612
- importPath: string,
613
- exportName: string,
614
- envParserPath: string,
615
- envParserImportPattern: string,
616
- ): string {
617
- return `import { AmazonApiGatewayV2Endpoint } from '@geekmidas/constructs/aws';
723
+ }
724
+
725
+ private generateAWSApiGatewayV2Handler(
726
+ importPath: string,
727
+ exportName: string,
728
+ envParserPath: string,
729
+ envParserImportPattern: string,
730
+ ): string {
731
+ return `import { AmazonApiGatewayV2Endpoint } from '@geekmidas/constructs/aws';
618
732
  import { ${exportName} } from '${importPath}';
619
733
  import ${envParserImportPattern} from '${envParserPath}';
620
734
 
@@ -622,16 +736,203 @@ const adapter = new AmazonApiGatewayV2Endpoint(envParser, ${exportName});
622
736
 
623
737
  export const handler = adapter.handler;
624
738
  `;
625
- }
739
+ }
626
740
 
627
- private generateServerHandler(
628
- importPath: string,
629
- exportName: string,
630
- ): string {
631
- return `import { ${exportName} } from '${importPath}';
741
+ private generateServerHandler(
742
+ importPath: string,
743
+ exportName: string,
744
+ ): string {
745
+ return `import { ${exportName} } from '${importPath}';
632
746
 
633
747
  // Server handler - implement based on your server framework
634
748
  export const handler = ${exportName};
635
749
  `;
750
+ }
751
+
752
+ /**
753
+ * Generate a production-optimized app.ts file
754
+ * No dev tools (Telescope, Studio, WebSocket), includes health checks and graceful shutdown
755
+ */
756
+ private async generateProductionAppFile(
757
+ outputDir: string,
758
+ context: BuildContext,
759
+ ): Promise<string> {
760
+ const appFileName = 'app.ts';
761
+ const appPath = join(outputDir, appFileName);
762
+
763
+ const relativeLoggerPath = relative(dirname(appPath), context.loggerPath);
764
+ const relativeEnvParserPath = relative(
765
+ dirname(appPath),
766
+ context.envParserPath,
767
+ );
768
+
769
+ const production = context.production!;
770
+ const healthCheckPath = production.healthCheck;
771
+ const enableGracefulShutdown = production.gracefulShutdown;
772
+ const enableOpenApi = production.openapi;
773
+ const includeSubscribers = production.subscribers === 'include';
774
+
775
+ // Generate imports for server hooks
776
+ let hooksImports = '';
777
+ let beforeSetupCall = '';
778
+ let afterSetupCall = '';
779
+ if (context.hooks?.serverHooksPath) {
780
+ const relativeHooksPath = relative(
781
+ dirname(appPath),
782
+ context.hooks.serverHooksPath,
783
+ );
784
+ hooksImports = `import * as serverHooks from '${relativeHooksPath}';`;
785
+ beforeSetupCall = `
786
+ // Call beforeSetup hook if defined
787
+ if (typeof serverHooks.beforeSetup === 'function') {
788
+ await serverHooks.beforeSetup(honoApp, { envParser, logger });
636
789
  }
790
+ `;
791
+ afterSetupCall = `
792
+ // Call afterSetup hook if defined
793
+ if (typeof serverHooks.afterSetup === 'function') {
794
+ await serverHooks.afterSetup(honoApp, { envParser, logger });
795
+ }
796
+ `;
797
+ }
798
+
799
+ // Subscriber setup code
800
+ const subscriberSetup = includeSubscribers
801
+ ? `
802
+ // Start subscribers in background
803
+ await setupSubscribers(envParser, logger).catch((error) => {
804
+ logger.error({ error }, 'Failed to start subscribers');
805
+ });
806
+ `
807
+ : '';
808
+
809
+ const subscriberImport = includeSubscribers
810
+ ? `import { setupSubscribers } from './subscribers.js';`
811
+ : '';
812
+
813
+ // Graceful shutdown code
814
+ const gracefulShutdownCode = enableGracefulShutdown
815
+ ? `
816
+ // Graceful shutdown handling
817
+ let isShuttingDown = false;
818
+
819
+ const shutdown = async () => {
820
+ if (isShuttingDown) return;
821
+ isShuttingDown = true;
822
+ logger.info('Graceful shutdown initiated');
823
+ // Allow in-flight requests to complete (30s timeout)
824
+ setTimeout(() => process.exit(0), 30000);
825
+ };
826
+
827
+ process.on('SIGTERM', shutdown);
828
+ process.on('SIGINT', shutdown);
829
+ `
830
+ : '';
831
+
832
+ // Use endpoints/index.js for optimized builds, endpoints.js otherwise
833
+ const endpointsImportPath = production.optimizedHandlers
834
+ ? './endpoints/index.js'
835
+ : './endpoints.js';
836
+
837
+ const content = `/**
838
+ * Generated production server application
839
+ *
840
+ * This is a production-optimized build without dev tools.
841
+ * - No Telescope debugging dashboard
842
+ * - No Studio database browser
843
+ * - No WebSocket updates
844
+ * - Includes health checks and graceful shutdown
845
+ */
846
+ import { Hono } from 'hono';
847
+ import type { Hono as HonoType } from 'hono';
848
+ import { setupEndpoints } from '${endpointsImportPath}';
849
+ ${subscriberImport}
850
+ import ${context.envParserImportPattern} from '${relativeEnvParserPath}';
851
+ import ${context.loggerImportPattern} from '${relativeLoggerPath}';
852
+ ${hooksImports}
853
+
854
+ export interface ServerApp {
855
+ app: HonoType;
856
+ start: (options?: {
857
+ port?: number;
858
+ serve: (app: HonoType, port: number) => void | Promise<void>;
859
+ }) => Promise<void>;
860
+ }
861
+
862
+ /**
863
+ * Create and configure the production Hono application
864
+ */
865
+ export async function createApp(app?: HonoType): Promise<ServerApp> {
866
+ const honoApp = app || new Hono();
867
+
868
+ // Health check endpoint (always first)
869
+ honoApp.get('${healthCheckPath}', (c) => c.json({ status: 'ok', timestamp: Date.now() }));
870
+ honoApp.get('/ready', (c) => c.json({ ready: true }));
871
+ ${beforeSetupCall}
872
+ // Setup HTTP endpoints (OpenAPI: ${enableOpenApi})
873
+ await setupEndpoints(honoApp, envParser, logger, ${enableOpenApi});
874
+ ${afterSetupCall}
875
+ return {
876
+ app: honoApp,
877
+ async start(options) {
878
+ if (!options?.serve) {
879
+ throw new Error(
880
+ 'serve function is required. Pass a serve function for your runtime:\\n' +
881
+ ' - Bun: (app, port) => Bun.serve({ port, fetch: app.fetch })\\n' +
882
+ ' - Node: (app, port) => serve({ fetch: app.fetch, port })'
883
+ );
884
+ }
885
+
886
+ const port = options.port ?? Number(process.env.PORT) ?? 3000;
887
+ ${gracefulShutdownCode}${subscriberSetup}
888
+ logger.info({ port }, 'Starting production server');
889
+
890
+ // Start HTTP server using provided serve function
891
+ await options.serve(honoApp, port);
892
+
893
+ logger.info({ port }, 'Production server started');
894
+ }
895
+ };
896
+ }
897
+
898
+ // Default export for convenience
899
+ export default createApp;
900
+ `;
901
+
902
+ await writeFile(appPath, content);
903
+
904
+ // Also generate the production server entry point
905
+ await this.generateProductionServerEntry(outputDir);
906
+
907
+ return appPath;
908
+ }
909
+
910
+ /**
911
+ * Generate production server.ts entry point
912
+ */
913
+ private async generateProductionServerEntry(
914
+ outputDir: string,
915
+ ): Promise<void> {
916
+ const serverPath = join(outputDir, 'server.ts');
917
+
918
+ const content = `#!/usr/bin/env node
919
+ /**
920
+ * Production server entry point
921
+ * Generated by 'gkm build --provider server --production'
922
+ */
923
+ import { serve } from '@hono/node-server';
924
+ import { createApp } from './app.js';
925
+
926
+ const port = Number(process.env.PORT) || 3000;
927
+
928
+ const { app, start } = await createApp();
929
+
930
+ await start({
931
+ port,
932
+ serve: (app, port) => serve({ fetch: app.fetch, port }),
933
+ });
934
+ `;
935
+
936
+ await writeFile(serverPath, content);
937
+ }
637
938
  }