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