@geekmidas/cli 0.2.4 → 0.4.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 +488 -71
- package/dist/{EndpointGenerator-C73wNoih.cjs → EndpointGenerator-BxNCkus4.cjs} +60 -6
- package/dist/EndpointGenerator-BxNCkus4.cjs.map +1 -0
- package/dist/{EndpointGenerator-CWh18d92.mjs → EndpointGenerator-CzDhG7Or.mjs} +60 -6
- package/dist/EndpointGenerator-CzDhG7Or.mjs.map +1 -0
- package/dist/OpenApiTsGenerator-NBNEoaeO.cjs +501 -0
- package/dist/OpenApiTsGenerator-NBNEoaeO.cjs.map +1 -0
- package/dist/OpenApiTsGenerator-q3aWNkuM.mjs +495 -0
- package/dist/OpenApiTsGenerator-q3aWNkuM.mjs.map +1 -0
- package/dist/build/index.cjs +4 -4
- package/dist/build/index.mjs +4 -4
- package/dist/build/manifests.cjs +3 -2
- package/dist/build/manifests.mjs +2 -2
- package/dist/{build-BVng9MQX.cjs → build-CWtHnJMQ.cjs} +19 -17
- package/dist/build-CWtHnJMQ.cjs.map +1 -0
- package/dist/{build-BqexeI-W.mjs → build-DyDgu_D1.mjs} +20 -18
- package/dist/build-DyDgu_D1.mjs.map +1 -0
- package/dist/{config-U-mdW-7Y.mjs → config-AFmFKmU0.mjs} +3 -3
- package/dist/config-AFmFKmU0.mjs.map +1 -0
- package/dist/{config-D1EpSGk6.cjs → config-BVIJpAsa.cjs} +3 -3
- package/dist/config-BVIJpAsa.cjs.map +1 -0
- package/dist/config.cjs +1 -1
- package/dist/config.mjs +1 -1
- package/dist/dev/index.cjs +5 -4
- package/dist/dev/index.mjs +4 -4
- package/dist/{dev-DbtyToc7.cjs → dev-CgDYC4o8.cjs} +95 -31
- package/dist/dev-CgDYC4o8.cjs.map +1 -0
- package/dist/{dev-DnGYXuMn.mjs → dev-CpA8AQPX.mjs} +90 -32
- package/dist/dev-CpA8AQPX.mjs.map +1 -0
- package/dist/generators/EndpointGenerator.cjs +1 -1
- package/dist/generators/EndpointGenerator.mjs +1 -1
- package/dist/generators/OpenApiTsGenerator.cjs +3 -0
- package/dist/generators/OpenApiTsGenerator.mjs +3 -0
- package/dist/generators/index.cjs +1 -1
- package/dist/generators/index.mjs +1 -1
- package/dist/index.cjs +16 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +16 -10
- package/dist/index.mjs.map +1 -1
- package/dist/manifests-C2eMoMUm.mjs +68 -0
- package/dist/manifests-C2eMoMUm.mjs.map +1 -0
- package/dist/manifests-CK1VV_pM.cjs +80 -0
- package/dist/manifests-CK1VV_pM.cjs.map +1 -0
- package/dist/openapi-DRTRGhTt.mjs +50 -0
- package/dist/openapi-DRTRGhTt.mjs.map +1 -0
- package/dist/openapi-DhK4b0lB.cjs +56 -0
- package/dist/openapi-DhK4b0lB.cjs.map +1 -0
- package/dist/openapi.cjs +4 -3
- package/dist/openapi.mjs +4 -3
- package/docs/OPENAPI_TYPESCRIPT_DESIGN.md +408 -0
- package/docs/manifest-refactor-design.md +287 -0
- package/package.json +10 -3
- package/src/__tests__/openapi.spec.ts +78 -63
- package/src/build/__tests__/index-new.spec.ts +43 -72
- package/src/build/__tests__/manifests.spec.ts +346 -0
- package/src/build/index.ts +59 -62
- package/src/build/manifests.ts +85 -13
- package/src/build/types.ts +13 -2
- package/src/config.ts +4 -2
- package/src/dev/__tests__/index.spec.ts +98 -1
- package/src/dev/index.ts +152 -25
- package/src/generators/EndpointGenerator.ts +69 -5
- package/src/generators/OpenApiTsGenerator.ts +798 -0
- package/src/index.ts +6 -3
- package/src/openapi.ts +36 -15
- package/src/types.ts +23 -7
- package/dist/EndpointGenerator-C73wNoih.cjs.map +0 -1
- package/dist/EndpointGenerator-CWh18d92.mjs.map +0 -1
- package/dist/build-BVng9MQX.cjs.map +0 -1
- package/dist/build-BqexeI-W.mjs.map +0 -1
- package/dist/config-D1EpSGk6.cjs.map +0 -1
- package/dist/config-U-mdW-7Y.mjs.map +0 -1
- package/dist/dev-DbtyToc7.cjs.map +0 -1
- package/dist/dev-DnGYXuMn.mjs.map +0 -1
- package/dist/manifests-BrJXpHrf.mjs +0 -21
- package/dist/manifests-BrJXpHrf.mjs.map +0 -1
- package/dist/manifests-D0saShvH.cjs +0 -27
- package/dist/manifests-D0saShvH.cjs.map +0 -1
- package/dist/openapi-BTHbPrxS.mjs +0 -36
- package/dist/openapi-BTHbPrxS.mjs.map +0 -1
- package/dist/openapi-CewcfoRH.cjs +0 -42
- package/dist/openapi-CewcfoRH.cjs.map +0 -1
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { createServer } from 'node:net';
|
|
2
2
|
import { describe, expect, it } from 'vitest';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
findAvailablePort,
|
|
5
|
+
isPortAvailable,
|
|
6
|
+
normalizeTelescopeConfig,
|
|
7
|
+
} from '../index';
|
|
4
8
|
|
|
5
9
|
/**
|
|
6
10
|
* Helper to occupy a port for testing
|
|
@@ -206,3 +210,96 @@ describe('devCommand edge cases', () => {
|
|
|
206
210
|
});
|
|
207
211
|
});
|
|
208
212
|
});
|
|
213
|
+
|
|
214
|
+
describe('normalizeTelescopeConfig', () => {
|
|
215
|
+
it('should return undefined when config is false', () => {
|
|
216
|
+
const result = normalizeTelescopeConfig(false);
|
|
217
|
+
expect(result).toBeUndefined();
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('should return default config when config is true', () => {
|
|
221
|
+
const result = normalizeTelescopeConfig(true);
|
|
222
|
+
expect(result).toEqual({
|
|
223
|
+
enabled: true,
|
|
224
|
+
path: '/__telescope',
|
|
225
|
+
ignore: [],
|
|
226
|
+
recordBody: true,
|
|
227
|
+
maxEntries: 1000,
|
|
228
|
+
websocket: true,
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('should return default config when config is undefined', () => {
|
|
233
|
+
const result = normalizeTelescopeConfig(undefined);
|
|
234
|
+
expect(result).toEqual({
|
|
235
|
+
enabled: true,
|
|
236
|
+
path: '/__telescope',
|
|
237
|
+
ignore: [],
|
|
238
|
+
recordBody: true,
|
|
239
|
+
maxEntries: 1000,
|
|
240
|
+
websocket: true,
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it('should return undefined when config.enabled is false', () => {
|
|
245
|
+
const result = normalizeTelescopeConfig({ enabled: false });
|
|
246
|
+
expect(result).toBeUndefined();
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('should merge custom config with defaults', () => {
|
|
250
|
+
const result = normalizeTelescopeConfig({
|
|
251
|
+
path: '/__debug',
|
|
252
|
+
ignore: ['/health', '/metrics'],
|
|
253
|
+
recordBody: false,
|
|
254
|
+
maxEntries: 500,
|
|
255
|
+
});
|
|
256
|
+
expect(result).toEqual({
|
|
257
|
+
enabled: true,
|
|
258
|
+
path: '/__debug',
|
|
259
|
+
ignore: ['/health', '/metrics'],
|
|
260
|
+
recordBody: false,
|
|
261
|
+
maxEntries: 500,
|
|
262
|
+
websocket: true,
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('should use defaults for missing config values', () => {
|
|
267
|
+
const result = normalizeTelescopeConfig({
|
|
268
|
+
path: '/__custom',
|
|
269
|
+
});
|
|
270
|
+
expect(result).toEqual({
|
|
271
|
+
enabled: true,
|
|
272
|
+
path: '/__custom',
|
|
273
|
+
ignore: [],
|
|
274
|
+
recordBody: true,
|
|
275
|
+
maxEntries: 1000,
|
|
276
|
+
websocket: true,
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it('should handle empty object config', () => {
|
|
281
|
+
const result = normalizeTelescopeConfig({});
|
|
282
|
+
expect(result).toEqual({
|
|
283
|
+
enabled: true,
|
|
284
|
+
path: '/__telescope',
|
|
285
|
+
ignore: [],
|
|
286
|
+
recordBody: true,
|
|
287
|
+
maxEntries: 1000,
|
|
288
|
+
websocket: true,
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('should allow disabling websocket', () => {
|
|
293
|
+
const result = normalizeTelescopeConfig({
|
|
294
|
+
websocket: false,
|
|
295
|
+
});
|
|
296
|
+
expect(result).toEqual({
|
|
297
|
+
enabled: true,
|
|
298
|
+
path: '/__telescope',
|
|
299
|
+
ignore: [],
|
|
300
|
+
recordBody: true,
|
|
301
|
+
maxEntries: 1000,
|
|
302
|
+
websocket: false,
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
});
|
package/src/dev/index.ts
CHANGED
|
@@ -3,8 +3,9 @@ import { mkdir } from 'node:fs/promises';
|
|
|
3
3
|
import { createServer } from 'node:net';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import chokidar from 'chokidar';
|
|
6
|
+
import fg from 'fast-glob';
|
|
6
7
|
import { resolveProviders } from '../build/providerResolver';
|
|
7
|
-
import type { BuildContext } from '../build/types';
|
|
8
|
+
import type { BuildContext, NormalizedTelescopeConfig } from '../build/types';
|
|
8
9
|
import { loadConfig } from '../config';
|
|
9
10
|
import {
|
|
10
11
|
CronGenerator,
|
|
@@ -12,7 +13,12 @@ import {
|
|
|
12
13
|
FunctionGenerator,
|
|
13
14
|
SubscriberGenerator,
|
|
14
15
|
} from '../generators';
|
|
15
|
-
import type {
|
|
16
|
+
import type {
|
|
17
|
+
GkmConfig,
|
|
18
|
+
LegacyProvider,
|
|
19
|
+
Runtime,
|
|
20
|
+
TelescopeConfig,
|
|
21
|
+
} from '../types';
|
|
16
22
|
|
|
17
23
|
const logger = console;
|
|
18
24
|
|
|
@@ -62,6 +68,38 @@ export async function findAvailablePort(
|
|
|
62
68
|
);
|
|
63
69
|
}
|
|
64
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Normalize telescope configuration
|
|
73
|
+
* @internal Exported for testing
|
|
74
|
+
*/
|
|
75
|
+
export function normalizeTelescopeConfig(
|
|
76
|
+
config: GkmConfig['telescope'],
|
|
77
|
+
): NormalizedTelescopeConfig | undefined {
|
|
78
|
+
if (config === false) {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Default to enabled in development mode
|
|
83
|
+
const isEnabled =
|
|
84
|
+
config === true || config === undefined || config.enabled !== false;
|
|
85
|
+
|
|
86
|
+
if (!isEnabled) {
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const telescopeConfig: TelescopeConfig =
|
|
91
|
+
typeof config === 'object' ? config : {};
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
enabled: true,
|
|
95
|
+
path: telescopeConfig.path ?? '/__telescope',
|
|
96
|
+
ignore: telescopeConfig.ignore ?? [],
|
|
97
|
+
recordBody: telescopeConfig.recordBody ?? true,
|
|
98
|
+
maxEntries: telescopeConfig.maxEntries ?? 1000,
|
|
99
|
+
websocket: telescopeConfig.websocket ?? true,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
65
103
|
export interface DevOptions {
|
|
66
104
|
port?: number;
|
|
67
105
|
enableOpenApi?: boolean;
|
|
@@ -102,11 +140,18 @@ export async function devCommand(options: DevOptions): Promise<void> {
|
|
|
102
140
|
? '{ logger }'
|
|
103
141
|
: `{ ${loggerName} as logger }`;
|
|
104
142
|
|
|
143
|
+
// Normalize telescope configuration
|
|
144
|
+
const telescope = normalizeTelescopeConfig(config.telescope);
|
|
145
|
+
if (telescope) {
|
|
146
|
+
logger.log(`🔭 Telescope enabled at ${telescope.path}`);
|
|
147
|
+
}
|
|
148
|
+
|
|
105
149
|
const buildContext: BuildContext = {
|
|
106
150
|
envParserPath,
|
|
107
151
|
envParserImportPattern,
|
|
108
152
|
loggerPath,
|
|
109
153
|
loggerImportPattern,
|
|
154
|
+
telescope,
|
|
110
155
|
};
|
|
111
156
|
|
|
112
157
|
// Build initial version
|
|
@@ -117,31 +162,70 @@ export async function devCommand(options: DevOptions): Promise<void> {
|
|
|
117
162
|
resolved.enableOpenApi,
|
|
118
163
|
);
|
|
119
164
|
|
|
165
|
+
// Determine runtime (default to node)
|
|
166
|
+
const runtime: Runtime = config.runtime ?? 'node';
|
|
167
|
+
|
|
120
168
|
// Start the dev server
|
|
121
169
|
const devServer = new DevServer(
|
|
122
170
|
resolved.providers[0] as LegacyProvider,
|
|
123
171
|
options.port || 3000,
|
|
124
172
|
resolved.enableOpenApi,
|
|
173
|
+
telescope,
|
|
174
|
+
runtime,
|
|
125
175
|
);
|
|
126
176
|
|
|
127
177
|
await devServer.start();
|
|
128
178
|
|
|
129
179
|
// Watch for file changes
|
|
180
|
+
const envParserFile = config.envParser.split('#')[0];
|
|
181
|
+
const loggerFile = config.logger.split('#')[0];
|
|
182
|
+
|
|
130
183
|
const watchPatterns = [
|
|
131
184
|
config.routes,
|
|
132
185
|
...(config.functions ? [config.functions] : []),
|
|
133
186
|
...(config.crons ? [config.crons] : []),
|
|
134
187
|
...(config.subscribers ? [config.subscribers] : []),
|
|
135
|
-
config
|
|
136
|
-
|
|
188
|
+
// Add .ts extension if not present for config files
|
|
189
|
+
envParserFile.endsWith('.ts') ? envParserFile : `${envParserFile}.ts`,
|
|
190
|
+
loggerFile.endsWith('.ts') ? loggerFile : `${loggerFile}.ts`,
|
|
137
191
|
].flat();
|
|
138
192
|
|
|
139
|
-
|
|
193
|
+
// Normalize patterns - remove leading ./ when using cwd option
|
|
194
|
+
const normalizedPatterns = watchPatterns.map((p) =>
|
|
195
|
+
p.startsWith('./') ? p.slice(2) : p,
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
logger.log(`👀 Watching for changes in: ${normalizedPatterns.join(', ')}`);
|
|
199
|
+
|
|
200
|
+
// Resolve glob patterns to actual files (chokidar 4.x doesn't support globs)
|
|
201
|
+
const resolvedFiles = await fg(normalizedPatterns, {
|
|
202
|
+
cwd: process.cwd(),
|
|
203
|
+
absolute: false,
|
|
204
|
+
onlyFiles: true,
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Also watch the directories for new files
|
|
208
|
+
const dirsToWatch = [
|
|
209
|
+
...new Set(resolvedFiles.map((f) => f.split('/').slice(0, -1).join('/'))),
|
|
210
|
+
];
|
|
211
|
+
|
|
212
|
+
logger.log(
|
|
213
|
+
`📁 Found ${resolvedFiles.length} files in ${dirsToWatch.length} directories`,
|
|
214
|
+
);
|
|
140
215
|
|
|
141
|
-
const watcher = chokidar.watch(
|
|
216
|
+
const watcher = chokidar.watch([...resolvedFiles, ...dirsToWatch], {
|
|
142
217
|
ignored: /(^|[\/\\])\../, // ignore dotfiles
|
|
143
218
|
persistent: true,
|
|
144
219
|
ignoreInitial: true,
|
|
220
|
+
cwd: process.cwd(),
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
watcher.on('ready', () => {
|
|
224
|
+
logger.log('🔍 File watcher ready');
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
watcher.on('error', (error) => {
|
|
228
|
+
logger.error('❌ Watcher error:', error);
|
|
145
229
|
});
|
|
146
230
|
|
|
147
231
|
let rebuildTimeout: NodeJS.Timeout | null = null;
|
|
@@ -229,6 +313,8 @@ class DevServer {
|
|
|
229
313
|
private provider: LegacyProvider,
|
|
230
314
|
private requestedPort: number,
|
|
231
315
|
private enableOpenApi: boolean,
|
|
316
|
+
private telescope?: NormalizedTelescopeConfig,
|
|
317
|
+
private runtime: Runtime = 'node',
|
|
232
318
|
) {
|
|
233
319
|
this.actualPort = requestedPort;
|
|
234
320
|
}
|
|
@@ -260,12 +346,14 @@ class DevServer {
|
|
|
260
346
|
logger.log(`\n✨ Starting server on port ${this.actualPort}...`);
|
|
261
347
|
|
|
262
348
|
// Start the server using tsx (TypeScript execution)
|
|
349
|
+
// Use detached: true so we can kill the entire process tree
|
|
263
350
|
this.serverProcess = spawn(
|
|
264
351
|
'npx',
|
|
265
352
|
['tsx', serverEntryPath, '--port', this.actualPort.toString()],
|
|
266
353
|
{
|
|
267
354
|
stdio: 'inherit',
|
|
268
355
|
env: { ...process.env, NODE_ENV: 'development' },
|
|
356
|
+
detached: true,
|
|
269
357
|
},
|
|
270
358
|
);
|
|
271
359
|
|
|
@@ -292,19 +380,39 @@ class DevServer {
|
|
|
292
380
|
`📚 API Docs available at http://localhost:${this.actualPort}/docs`,
|
|
293
381
|
);
|
|
294
382
|
}
|
|
383
|
+
if (this.telescope) {
|
|
384
|
+
logger.log(
|
|
385
|
+
`🔭 Telescope available at http://localhost:${this.actualPort}${this.telescope.path}`,
|
|
386
|
+
);
|
|
387
|
+
}
|
|
295
388
|
}
|
|
296
389
|
}
|
|
297
390
|
|
|
298
391
|
async stop(): Promise<void> {
|
|
299
392
|
if (this.serverProcess && this.isRunning) {
|
|
300
|
-
this.serverProcess.
|
|
393
|
+
const pid = this.serverProcess.pid;
|
|
394
|
+
|
|
395
|
+
// Kill the entire process group (negative PID kills the group)
|
|
396
|
+
if (pid) {
|
|
397
|
+
try {
|
|
398
|
+
process.kill(-pid, 'SIGTERM');
|
|
399
|
+
} catch {
|
|
400
|
+
// Process might already be dead
|
|
401
|
+
}
|
|
402
|
+
}
|
|
301
403
|
|
|
302
404
|
// Wait for process to exit
|
|
303
405
|
await new Promise<void>((resolve) => {
|
|
304
406
|
const timeout = setTimeout(() => {
|
|
305
|
-
|
|
407
|
+
if (pid) {
|
|
408
|
+
try {
|
|
409
|
+
process.kill(-pid, 'SIGKILL');
|
|
410
|
+
} catch {
|
|
411
|
+
// Process might already be dead
|
|
412
|
+
}
|
|
413
|
+
}
|
|
306
414
|
resolve();
|
|
307
|
-
},
|
|
415
|
+
}, 3000);
|
|
308
416
|
|
|
309
417
|
this.serverProcess?.on('exit', () => {
|
|
310
418
|
clearTimeout(timeout);
|
|
@@ -318,7 +426,21 @@ class DevServer {
|
|
|
318
426
|
}
|
|
319
427
|
|
|
320
428
|
async restart(): Promise<void> {
|
|
429
|
+
const portToReuse = this.actualPort;
|
|
321
430
|
await this.stop();
|
|
431
|
+
|
|
432
|
+
// Wait for port to be released (up to 3 seconds)
|
|
433
|
+
let attempts = 0;
|
|
434
|
+
while (attempts < 30) {
|
|
435
|
+
if (await isPortAvailable(portToReuse)) {
|
|
436
|
+
break;
|
|
437
|
+
}
|
|
438
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
439
|
+
attempts++;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Force reuse the same port
|
|
443
|
+
this.requestedPort = portToReuse;
|
|
322
444
|
await this.start();
|
|
323
445
|
}
|
|
324
446
|
|
|
@@ -333,6 +455,24 @@ class DevServer {
|
|
|
333
455
|
join(dirname(serverPath), 'app.js'),
|
|
334
456
|
);
|
|
335
457
|
|
|
458
|
+
const serveCode =
|
|
459
|
+
this.runtime === 'bun'
|
|
460
|
+
? `Bun.serve({
|
|
461
|
+
port,
|
|
462
|
+
fetch: app.fetch,
|
|
463
|
+
});`
|
|
464
|
+
: `const { serve } = await import('@hono/node-server');
|
|
465
|
+
const server = serve({
|
|
466
|
+
fetch: app.fetch,
|
|
467
|
+
port,
|
|
468
|
+
});
|
|
469
|
+
// Inject WebSocket support if available
|
|
470
|
+
const injectWs = (app as any).__injectWebSocket;
|
|
471
|
+
if (injectWs) {
|
|
472
|
+
injectWs(server);
|
|
473
|
+
console.log('🔌 Telescope real-time updates enabled');
|
|
474
|
+
}`;
|
|
475
|
+
|
|
336
476
|
const content = `#!/usr/bin/env node
|
|
337
477
|
/**
|
|
338
478
|
* Development server entry point
|
|
@@ -344,27 +484,14 @@ const port = process.argv.includes('--port')
|
|
|
344
484
|
? Number.parseInt(process.argv[process.argv.indexOf('--port') + 1])
|
|
345
485
|
: 3000;
|
|
346
486
|
|
|
347
|
-
|
|
487
|
+
// createApp is async to support optional WebSocket setup
|
|
488
|
+
const { app, start } = await createApp(undefined, ${this.enableOpenApi});
|
|
348
489
|
|
|
349
490
|
// Start the server
|
|
350
491
|
start({
|
|
351
492
|
port,
|
|
352
493
|
serve: async (app, port) => {
|
|
353
|
-
|
|
354
|
-
if (typeof Bun !== 'undefined') {
|
|
355
|
-
// Bun runtime
|
|
356
|
-
Bun.serve({
|
|
357
|
-
port,
|
|
358
|
-
fetch: app.fetch,
|
|
359
|
-
});
|
|
360
|
-
} else {
|
|
361
|
-
// Node.js runtime with @hono/node-server
|
|
362
|
-
const { serve } = await import('@hono/node-server');
|
|
363
|
-
serve({
|
|
364
|
-
fetch: app.fetch,
|
|
365
|
-
port,
|
|
366
|
-
});
|
|
367
|
-
}
|
|
494
|
+
${serveCode}
|
|
368
495
|
},
|
|
369
496
|
}).catch((error) => {
|
|
370
497
|
console.error('Failed to start server:', error);
|
|
@@ -249,7 +249,7 @@ export class EndpointGenerator extends ConstructGenerator<
|
|
|
249
249
|
any
|
|
250
250
|
>
|
|
251
251
|
>[],
|
|
252
|
-
|
|
252
|
+
_context: BuildContext,
|
|
253
253
|
): Promise<string> {
|
|
254
254
|
const endpointsFileName = 'endpoints.ts';
|
|
255
255
|
const endpointsPath = join(outputDir, endpointsFileName);
|
|
@@ -333,6 +333,69 @@ export function setupEndpoints(
|
|
|
333
333
|
context.envParserPath,
|
|
334
334
|
);
|
|
335
335
|
|
|
336
|
+
// Generate telescope imports and setup if enabled
|
|
337
|
+
const telescopeEnabled = context.telescope?.enabled;
|
|
338
|
+
const telescopeWebSocketEnabled = context.telescope?.websocket;
|
|
339
|
+
const telescopeImports = telescopeEnabled
|
|
340
|
+
? `import { Telescope, InMemoryStorage } from '@geekmidas/telescope';
|
|
341
|
+
import { createMiddleware, createUI } from '@geekmidas/telescope/hono';`
|
|
342
|
+
: '';
|
|
343
|
+
|
|
344
|
+
const telescopeWebSocketSetupCode = telescopeWebSocketEnabled
|
|
345
|
+
? `
|
|
346
|
+
// Setup WebSocket for real-time telescope updates
|
|
347
|
+
try {
|
|
348
|
+
const { createNodeWebSocket } = await import('@hono/node-ws');
|
|
349
|
+
const { injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app: honoApp });
|
|
350
|
+
// Add WebSocket route directly to main app (sub-app routes don't support WS upgrade)
|
|
351
|
+
honoApp.get('${context.telescope!.path}/ws', upgradeWebSocket(() => ({
|
|
352
|
+
onOpen: (_event: Event, ws: any) => {
|
|
353
|
+
telescope.addWsClient(ws);
|
|
354
|
+
},
|
|
355
|
+
onClose: (_event: Event, ws: any) => {
|
|
356
|
+
telescope.removeWsClient(ws);
|
|
357
|
+
},
|
|
358
|
+
onMessage: (event: MessageEvent, ws: any) => {
|
|
359
|
+
try {
|
|
360
|
+
const data = JSON.parse(event.data);
|
|
361
|
+
if (data.type === 'ping') {
|
|
362
|
+
ws.send(JSON.stringify({ type: 'pong' }));
|
|
363
|
+
}
|
|
364
|
+
} catch {
|
|
365
|
+
// Ignore invalid messages
|
|
366
|
+
}
|
|
367
|
+
},
|
|
368
|
+
})));
|
|
369
|
+
// Store injectWebSocket for server entry to call after serve()
|
|
370
|
+
(honoApp as any).__injectWebSocket = injectWebSocket;
|
|
371
|
+
logger.info('Telescope WebSocket enabled');
|
|
372
|
+
} catch (e) {
|
|
373
|
+
logger.warn({ error: e }, 'WebSocket support not available - install @hono/node-ws for real-time updates');
|
|
374
|
+
}
|
|
375
|
+
`
|
|
376
|
+
: '';
|
|
377
|
+
|
|
378
|
+
const telescopeSetup = telescopeEnabled
|
|
379
|
+
? `
|
|
380
|
+
// Setup Telescope for debugging/monitoring
|
|
381
|
+
const telescopeStorage = new InMemoryStorage({ maxEntries: ${context.telescope!.maxEntries} });
|
|
382
|
+
const telescope = new Telescope({
|
|
383
|
+
enabled: true,
|
|
384
|
+
path: '${context.telescope!.path}',
|
|
385
|
+
ignorePatterns: ${JSON.stringify(context.telescope!.ignore)},
|
|
386
|
+
recordBody: ${context.telescope!.recordBody},
|
|
387
|
+
storage: telescopeStorage,
|
|
388
|
+
});
|
|
389
|
+
${telescopeWebSocketSetupCode}
|
|
390
|
+
// Add telescope middleware (before endpoints to capture all requests)
|
|
391
|
+
honoApp.use('*', createMiddleware(telescope));
|
|
392
|
+
|
|
393
|
+
// Mount telescope UI
|
|
394
|
+
const telescopeUI = createUI(telescope);
|
|
395
|
+
honoApp.route('${context.telescope!.path}', telescopeUI);
|
|
396
|
+
`
|
|
397
|
+
: '';
|
|
398
|
+
|
|
336
399
|
const content = `/**
|
|
337
400
|
* Generated server application
|
|
338
401
|
*
|
|
@@ -346,6 +409,7 @@ import { setupEndpoints } from './endpoints.js';
|
|
|
346
409
|
import { setupSubscribers } from './subscribers.js';
|
|
347
410
|
import ${context.envParserImportPattern} from '${relativeEnvParserPath}';
|
|
348
411
|
import ${context.loggerImportPattern} from '${relativeLoggerPath}';
|
|
412
|
+
${telescopeImports}
|
|
349
413
|
|
|
350
414
|
export interface ServerApp {
|
|
351
415
|
app: HonoType;
|
|
@@ -366,7 +430,7 @@ export interface ServerApp {
|
|
|
366
430
|
* // With Bun
|
|
367
431
|
* import { createApp } from './.gkm/server/app.js';
|
|
368
432
|
*
|
|
369
|
-
* const { app, start } = createApp();
|
|
433
|
+
* const { app, start } = await createApp();
|
|
370
434
|
*
|
|
371
435
|
* await start({
|
|
372
436
|
* port: 3000,
|
|
@@ -380,7 +444,7 @@ export interface ServerApp {
|
|
|
380
444
|
* import { serve } from '@hono/node-server';
|
|
381
445
|
* import { createApp } from './.gkm/server/app.js';
|
|
382
446
|
*
|
|
383
|
-
* const { app, start } = createApp();
|
|
447
|
+
* const { app, start } = await createApp();
|
|
384
448
|
*
|
|
385
449
|
* await start({
|
|
386
450
|
* port: 3000,
|
|
@@ -389,9 +453,9 @@ export interface ServerApp {
|
|
|
389
453
|
* }
|
|
390
454
|
* });
|
|
391
455
|
*/
|
|
392
|
-
export function createApp(app?: HonoType, enableOpenApi: boolean = true): ServerApp {
|
|
456
|
+
export async function createApp(app?: HonoType, enableOpenApi: boolean = true): Promise<ServerApp> {
|
|
393
457
|
const honoApp = app || new Hono();
|
|
394
|
-
|
|
458
|
+
${telescopeSetup}
|
|
395
459
|
// Setup HTTP endpoints
|
|
396
460
|
setupEndpoints(honoApp, envParser, logger, enableOpenApi);
|
|
397
461
|
|