@geekmidas/cli 0.10.0 → 0.13.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.
- package/README.md +525 -0
- package/dist/bundler-B1qy9b-j.cjs +112 -0
- package/dist/bundler-B1qy9b-j.cjs.map +1 -0
- package/dist/bundler-DskIqW2t.mjs +111 -0
- package/dist/bundler-DskIqW2t.mjs.map +1 -0
- package/dist/{config-C9aXOHBe.cjs → config-AmInkU7k.cjs} +8 -8
- package/dist/config-AmInkU7k.cjs.map +1 -0
- package/dist/{config-BrkUalUh.mjs → config-DYULeEv8.mjs} +3 -3
- package/dist/config-DYULeEv8.mjs.map +1 -0
- package/dist/config.cjs +1 -1
- package/dist/config.d.cts +1 -1
- package/dist/config.d.mts +1 -1
- package/dist/config.mjs +1 -1
- package/dist/encryption-C8H-38Yy.mjs +42 -0
- package/dist/encryption-C8H-38Yy.mjs.map +1 -0
- package/dist/encryption-Dyf_r1h-.cjs +44 -0
- package/dist/encryption-Dyf_r1h-.cjs.map +1 -0
- package/dist/index.cjs +2123 -179
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +2141 -192
- package/dist/index.mjs.map +1 -1
- package/dist/{openapi-CZLI4QTr.mjs → openapi-BfFlOBCG.mjs} +801 -38
- package/dist/openapi-BfFlOBCG.mjs.map +1 -0
- package/dist/{openapi-BeHLKcwP.cjs → openapi-Bt_1FDpT.cjs} +794 -31
- package/dist/openapi-Bt_1FDpT.cjs.map +1 -0
- package/dist/{openapi-react-query-o5iMi8tz.cjs → openapi-react-query-B-sNWHFU.cjs} +5 -5
- package/dist/openapi-react-query-B-sNWHFU.cjs.map +1 -0
- package/dist/{openapi-react-query-CcciaVu5.mjs → openapi-react-query-B6XTeGqS.mjs} +5 -5
- package/dist/openapi-react-query-B6XTeGqS.mjs.map +1 -0
- package/dist/openapi-react-query.cjs +1 -1
- package/dist/openapi-react-query.d.cts.map +1 -1
- package/dist/openapi-react-query.d.mts.map +1 -1
- package/dist/openapi-react-query.mjs +1 -1
- package/dist/openapi.cjs +2 -2
- package/dist/openapi.d.cts +1 -1
- package/dist/openapi.d.cts.map +1 -1
- package/dist/openapi.d.mts +1 -1
- package/dist/openapi.d.mts.map +1 -1
- package/dist/openapi.mjs +2 -2
- package/dist/storage-BOOpAF8N.cjs +5 -0
- package/dist/storage-Bj1E26lU.cjs +187 -0
- package/dist/storage-Bj1E26lU.cjs.map +1 -0
- package/dist/storage-kSxTjkNb.mjs +133 -0
- package/dist/storage-kSxTjkNb.mjs.map +1 -0
- package/dist/storage-tgZSUnKl.mjs +3 -0
- package/dist/{types-b-vwGpqc.d.cts → types-BR0M2v_c.d.mts} +100 -1
- package/dist/types-BR0M2v_c.d.mts.map +1 -0
- package/dist/{types-DXgiA1sF.d.mts → types-BhkZc-vm.d.cts} +100 -1
- package/dist/types-BhkZc-vm.d.cts.map +1 -0
- package/examples/cron-example.ts +27 -27
- package/examples/env.ts +27 -27
- package/examples/function-example.ts +31 -31
- package/examples/gkm.config.json +20 -20
- package/examples/gkm.config.ts +8 -8
- package/examples/gkm.minimal.config.json +5 -5
- package/examples/gkm.production.config.json +25 -25
- package/examples/logger.ts +2 -2
- package/package.json +6 -6
- package/src/__tests__/EndpointGenerator.hooks.spec.ts +191 -191
- package/src/__tests__/config.spec.ts +55 -55
- package/src/__tests__/loadEnvFiles.spec.ts +93 -93
- package/src/__tests__/normalizeHooksConfig.spec.ts +58 -58
- package/src/__tests__/openapi-react-query.spec.ts +497 -497
- package/src/__tests__/openapi.spec.ts +428 -428
- package/src/__tests__/test-helpers.ts +76 -76
- package/src/auth/__tests__/credentials.spec.ts +204 -0
- package/src/auth/__tests__/index.spec.ts +168 -0
- package/src/auth/credentials.ts +187 -0
- package/src/auth/index.ts +226 -0
- package/src/build/__tests__/bundler.spec.ts +444 -0
- package/src/build/__tests__/index-new.spec.ts +474 -474
- package/src/build/__tests__/manifests.spec.ts +333 -333
- package/src/build/bundler.ts +210 -0
- package/src/build/endpoint-analyzer.ts +236 -0
- package/src/build/handler-templates.ts +1253 -0
- package/src/build/index.ts +260 -179
- package/src/build/manifests.ts +52 -52
- package/src/build/providerResolver.ts +145 -145
- package/src/build/types.ts +64 -43
- package/src/config.ts +39 -39
- package/src/deploy/__tests__/docker.spec.ts +111 -0
- package/src/deploy/__tests__/dokploy.spec.ts +245 -0
- package/src/deploy/__tests__/init.spec.ts +662 -0
- package/src/deploy/docker.ts +128 -0
- package/src/deploy/dokploy.ts +204 -0
- package/src/deploy/index.ts +136 -0
- package/src/deploy/init.ts +484 -0
- package/src/deploy/types.ts +48 -0
- package/src/dev/__tests__/index.spec.ts +266 -266
- package/src/dev/index.ts +647 -601
- package/src/docker/__tests__/compose.spec.ts +531 -0
- package/src/docker/__tests__/templates.spec.ts +280 -0
- package/src/docker/compose.ts +273 -0
- package/src/docker/index.ts +230 -0
- package/src/docker/templates.ts +446 -0
- package/src/generators/CronGenerator.ts +72 -72
- package/src/generators/EndpointGenerator.ts +699 -398
- package/src/generators/FunctionGenerator.ts +84 -84
- package/src/generators/Generator.ts +72 -72
- package/src/generators/OpenApiTsGenerator.ts +577 -577
- package/src/generators/SubscriberGenerator.ts +124 -124
- package/src/generators/__tests__/CronGenerator.spec.ts +433 -433
- package/src/generators/__tests__/EndpointGenerator.spec.ts +532 -382
- package/src/generators/__tests__/FunctionGenerator.spec.ts +244 -244
- package/src/generators/__tests__/SubscriberGenerator.spec.ts +397 -382
- package/src/generators/index.ts +4 -4
- package/src/index.ts +623 -201
- package/src/init/__tests__/generators.spec.ts +334 -334
- package/src/init/__tests__/init.spec.ts +332 -332
- package/src/init/__tests__/utils.spec.ts +89 -89
- package/src/init/generators/config.ts +175 -175
- package/src/init/generators/docker.ts +41 -41
- package/src/init/generators/env.ts +72 -72
- package/src/init/generators/index.ts +1 -1
- package/src/init/generators/models.ts +64 -64
- package/src/init/generators/monorepo.ts +161 -161
- package/src/init/generators/package.ts +71 -71
- package/src/init/generators/source.ts +6 -6
- package/src/init/index.ts +203 -208
- package/src/init/templates/api.ts +115 -115
- package/src/init/templates/index.ts +75 -75
- package/src/init/templates/minimal.ts +98 -98
- package/src/init/templates/serverless.ts +89 -89
- package/src/init/templates/worker.ts +98 -98
- package/src/init/utils.ts +54 -56
- package/src/openapi-react-query.ts +194 -194
- package/src/openapi.ts +63 -63
- package/src/secrets/__tests__/encryption.spec.ts +226 -0
- package/src/secrets/__tests__/generator.spec.ts +319 -0
- package/src/secrets/__tests__/index.spec.ts +91 -0
- package/src/secrets/__tests__/storage.spec.ts +611 -0
- package/src/secrets/encryption.ts +91 -0
- package/src/secrets/generator.ts +164 -0
- package/src/secrets/index.ts +383 -0
- package/src/secrets/storage.ts +192 -0
- package/src/secrets/types.ts +53 -0
- package/src/types.ts +295 -176
- package/tsdown.config.ts +11 -8
- package/dist/config-BrkUalUh.mjs.map +0 -1
- package/dist/config-C9aXOHBe.cjs.map +0 -1
- package/dist/openapi-BeHLKcwP.cjs.map +0 -1
- package/dist/openapi-CZLI4QTr.mjs.map +0 -1
- package/dist/openapi-react-query-CcciaVu5.mjs.map +0 -1
- package/dist/openapi-react-query-o5iMi8tz.cjs.map +0 -1
- package/dist/types-DXgiA1sF.d.mts.map +0 -1
- 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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
16
|
+
ConstructGenerator,
|
|
17
|
+
type GeneratedConstruct,
|
|
18
|
+
type GeneratorOptions,
|
|
10
19
|
} from './Generator';
|
|
11
20
|
|
|
12
21
|
export class EndpointGenerator extends ConstructGenerator<
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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
|
-
${
|
|
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
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
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
|
-
|
|
366
|
-
|
|
479
|
+
} else {
|
|
480
|
+
telescopeImports = `import { Telescope, InMemoryStorage } from '@geekmidas/telescope';
|
|
367
481
|
import { createMiddleware, createUI } from '@geekmidas/telescope/hono';`;
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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
|
-
|
|
382
|
-
|
|
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
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
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
|
|
571
|
+
honoApp.route('${context.telescope?.path}', telescopeUI);
|
|
458
572
|
`;
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
573
|
+
} else {
|
|
574
|
+
// Create inline telescope instance
|
|
575
|
+
telescopeSetup = `
|
|
462
576
|
// Setup Telescope for debugging/monitoring
|
|
463
|
-
const telescopeStorage = new InMemoryStorage({ maxEntries: ${context.telescope
|
|
577
|
+
const telescopeStorage = new InMemoryStorage({ maxEntries: ${context.telescope?.maxEntries} });
|
|
464
578
|
const telescope = new Telescope({
|
|
465
579
|
enabled: true,
|
|
466
|
-
path: '${context.telescope
|
|
467
|
-
ignorePatterns: ${JSON.stringify(context.telescope
|
|
468
|
-
recordBody: ${context.telescope
|
|
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
|
|
591
|
+
honoApp.route('${context.telescope?.path}', telescopeUI);
|
|
478
592
|
`;
|
|
479
|
-
|
|
480
|
-
|
|
593
|
+
}
|
|
594
|
+
}
|
|
481
595
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
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
|
|
602
|
+
honoApp.route('${context.studio?.path}', studioApp);
|
|
489
603
|
`;
|
|
490
|
-
|
|
604
|
+
}
|
|
491
605
|
|
|
492
|
-
|
|
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
|
-
|
|
704
|
+
await writeFile(appPath, content);
|
|
591
705
|
|
|
592
|
-
|
|
593
|
-
|
|
706
|
+
return appPath;
|
|
707
|
+
}
|
|
594
708
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
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
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
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
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
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
|
}
|