@zap-js/client 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/README.md +310 -24
  2. package/bin/zap +0 -0
  3. package/bin/zap-codegen +0 -0
  4. package/dist/cli/commands/build.d.ts +11 -0
  5. package/dist/cli/commands/build.js +282 -0
  6. package/dist/cli/commands/codegen.d.ts +8 -0
  7. package/dist/cli/commands/codegen.js +95 -0
  8. package/dist/cli/commands/dev.d.ts +20 -0
  9. package/dist/cli/commands/dev.js +78 -0
  10. package/dist/cli/commands/new.d.ts +9 -0
  11. package/dist/cli/commands/new.js +307 -0
  12. package/dist/cli/commands/routes-old.d.ts +9 -0
  13. package/dist/cli/commands/routes-old.js +106 -0
  14. package/dist/cli/commands/routes.d.ts +11 -0
  15. package/dist/cli/commands/routes.js +280 -0
  16. package/dist/cli/commands/serve.d.ts +17 -0
  17. package/dist/cli/commands/serve.js +386 -0
  18. package/dist/cli/index.d.ts +2 -0
  19. package/dist/cli/index.js +76 -0
  20. package/dist/cli/utils/index.d.ts +2 -0
  21. package/dist/cli/utils/index.js +2 -0
  22. package/dist/cli/utils/logger.d.ts +84 -0
  23. package/dist/cli/utils/logger.js +181 -0
  24. package/dist/cli/utils/port-finder.d.ts +8 -0
  25. package/dist/cli/utils/port-finder.js +48 -0
  26. package/dist/dev-server/codegen-runner.d.ts +41 -0
  27. package/dist/dev-server/codegen-runner.js +172 -0
  28. package/dist/dev-server/hot-reload.d.ts +72 -0
  29. package/dist/dev-server/hot-reload.js +280 -0
  30. package/dist/dev-server/index.d.ts +8 -0
  31. package/dist/dev-server/index.js +8 -0
  32. package/dist/dev-server/route-scanner.d.ts +71 -0
  33. package/dist/dev-server/route-scanner.js +114 -0
  34. package/dist/dev-server/rust-builder.d.ts +66 -0
  35. package/dist/dev-server/rust-builder.js +286 -0
  36. package/dist/dev-server/server.d.ts +147 -0
  37. package/dist/dev-server/server.js +658 -0
  38. package/dist/dev-server/vite-proxy.d.ts +56 -0
  39. package/dist/dev-server/vite-proxy.js +212 -0
  40. package/dist/dev-server/watcher.d.ts +48 -0
  41. package/dist/dev-server/watcher.js +127 -0
  42. package/dist/router/codegen-enhanced.d.ts +5 -0
  43. package/dist/router/codegen-enhanced.js +275 -0
  44. package/dist/router/codegen.d.ts +17 -0
  45. package/dist/router/codegen.js +654 -0
  46. package/dist/router/index.d.ts +16 -0
  47. package/dist/router/index.js +19 -0
  48. package/dist/router/scanner.d.ts +86 -0
  49. package/dist/router/scanner.js +689 -0
  50. package/dist/router/ssg.d.ts +115 -0
  51. package/dist/router/ssg.js +202 -0
  52. package/dist/router/types.d.ts +124 -0
  53. package/dist/router/types.js +9 -0
  54. package/dist/router/watch.d.ts +38 -0
  55. package/dist/router/watch.js +135 -0
  56. package/dist/runtime/csrf.d.ts +146 -0
  57. package/dist/runtime/csrf.js +166 -0
  58. package/dist/runtime/error-boundary.d.ts +129 -0
  59. package/dist/runtime/error-boundary.js +287 -0
  60. package/dist/runtime/hooks.d.ts +83 -0
  61. package/dist/runtime/hooks.js +96 -0
  62. package/dist/runtime/index.d.ts +229 -0
  63. package/dist/runtime/index.js +449 -0
  64. package/dist/runtime/ipc-client.d.ts +144 -0
  65. package/dist/runtime/ipc-client.js +621 -0
  66. package/dist/runtime/logger.d.ts +71 -0
  67. package/dist/runtime/logger.js +164 -0
  68. package/dist/runtime/middleware.d.ts +66 -0
  69. package/dist/runtime/middleware.js +114 -0
  70. package/dist/runtime/process-manager.d.ts +51 -0
  71. package/dist/runtime/process-manager.js +207 -0
  72. package/dist/runtime/router-simple.d.ts +98 -0
  73. package/dist/runtime/router-simple.js +330 -0
  74. package/dist/runtime/router.d.ts +103 -0
  75. package/dist/runtime/router.js +435 -0
  76. package/dist/runtime/rpc-client.d.ts +35 -0
  77. package/dist/runtime/rpc-client.js +140 -0
  78. package/dist/runtime/streaming-utils.d.ts +86 -0
  79. package/dist/runtime/streaming-utils.js +150 -0
  80. package/dist/runtime/types.d.ts +465 -0
  81. package/dist/runtime/types.js +60 -0
  82. package/dist/runtime/websockets-utils.d.ts +50 -0
  83. package/dist/runtime/websockets-utils.js +92 -0
  84. package/package.json +30 -20
  85. package/index.js +0 -29
  86. package/internal/cli/package.json +0 -46
  87. package/internal/cli/tsconfig.tsbuildinfo +0 -1
  88. package/internal/dev-server/node_modules/ora/index.d.ts +0 -332
  89. package/internal/dev-server/node_modules/ora/index.js +0 -416
  90. package/internal/dev-server/node_modules/ora/license +0 -9
  91. package/internal/dev-server/node_modules/ora/node_modules/string-width/index.d.ts +0 -36
  92. package/internal/dev-server/node_modules/ora/node_modules/string-width/index.js +0 -65
  93. package/internal/dev-server/node_modules/ora/node_modules/string-width/license +0 -9
  94. package/internal/dev-server/node_modules/ora/node_modules/string-width/node_modules/emoji-regex/LICENSE-MIT.txt +0 -20
  95. package/internal/dev-server/node_modules/ora/node_modules/string-width/node_modules/emoji-regex/README.md +0 -107
  96. package/internal/dev-server/node_modules/ora/node_modules/string-width/node_modules/emoji-regex/index.d.ts +0 -3
  97. package/internal/dev-server/node_modules/ora/node_modules/string-width/node_modules/emoji-regex/index.js +0 -4
  98. package/internal/dev-server/node_modules/ora/node_modules/string-width/node_modules/emoji-regex/index.mjs +0 -4
  99. package/internal/dev-server/node_modules/ora/node_modules/string-width/node_modules/emoji-regex/package.json +0 -46
  100. package/internal/dev-server/node_modules/ora/node_modules/string-width/package.json +0 -60
  101. package/internal/dev-server/node_modules/ora/node_modules/string-width/readme.md +0 -62
  102. package/internal/dev-server/node_modules/ora/package.json +0 -66
  103. package/internal/dev-server/node_modules/ora/readme.md +0 -325
  104. package/internal/dev-server/package.json +0 -41
  105. package/internal/router/package.json +0 -28
  106. package/internal/runtime/package.json +0 -41
  107. package/internal/runtime/src/error-boundary.tsx +0 -476
  108. package/internal/runtime/src/router-simple.tsx +0 -640
  109. package/internal/runtime/src/router.tsx +0 -771
  110. package/internal/runtime/tsconfig.tsbuildinfo +0 -1
  111. package/src/errors.js +0 -33
  112. package/src/logger.js +0 -10
  113. package/src/middleware.js +0 -32
  114. package/src/router.js +0 -41
  115. package/src/types.js +0 -39
@@ -0,0 +1,658 @@
1
+ import { EventEmitter } from 'events';
2
+ import path from 'path';
3
+ import { tmpdir } from 'os';
4
+ import { pathToFileURL } from 'url';
5
+ import { FileWatcher } from './watcher.js';
6
+ import { RustBuilder } from './rust-builder.js';
7
+ import { ViteProxy } from './vite-proxy.js';
8
+ import { CodegenRunner } from './codegen-runner.js';
9
+ import { HotReloadServer } from './hot-reload.js';
10
+ import { RouteScannerRunner } from './route-scanner.js';
11
+ import { ProcessManager, IpcServer } from '../runtime/index.js';
12
+ import { cliLogger } from '../cli/utils/logger.js';
13
+ // Register tsx loader for TypeScript imports
14
+ // This must be called before any dynamic imports of .ts files
15
+ let tsxRegistered = false;
16
+ async function ensureTsxRegistered() {
17
+ if (tsxRegistered)
18
+ return;
19
+ try {
20
+ const tsx = await import('tsx/esm/api');
21
+ tsx.register();
22
+ tsxRegistered = true;
23
+ }
24
+ catch {
25
+ // tsx not available, TypeScript imports won't work
26
+ }
27
+ }
28
+ /**
29
+ * DevServer - Unified development server orchestrator
30
+ *
31
+ * Coordinates all development components:
32
+ * - Rust backend compilation with file watching
33
+ * - Vite frontend dev server
34
+ * - Automatic TypeScript binding generation
35
+ * - Hot reload signaling
36
+ *
37
+ * Workflow:
38
+ * 1. Initial build of Rust backend
39
+ * 2. Generate TypeScript bindings
40
+ * 3. Start Vite dev server
41
+ * 4. Start hot reload WebSocket server
42
+ * 5. Watch for file changes and orchestrate rebuilds
43
+ */
44
+ export class DevServer extends EventEmitter {
45
+ constructor(config) {
46
+ super();
47
+ // Runtime components for Rust server + IPC
48
+ this.processManager = null;
49
+ this.ipcServer = null;
50
+ this.socketPath = '';
51
+ this.currentRouteTree = null;
52
+ this.registeredHandlers = new Map(); // handlerId -> filePath
53
+ // Timing
54
+ this.startTime = 0;
55
+ this.config = {
56
+ rustPort: 3000,
57
+ vitePort: 5173,
58
+ hotReloadPort: 3001,
59
+ logLevel: 'info',
60
+ release: false,
61
+ skipInitialBuild: false,
62
+ openBrowser: false,
63
+ ...config,
64
+ };
65
+ // If binaryPath is provided, skip initial build
66
+ if (this.config.binaryPath) {
67
+ this.config.skipInitialBuild = true;
68
+ }
69
+ this.state = {
70
+ phase: 'starting',
71
+ rustReady: false,
72
+ viteReady: false,
73
+ lastError: null,
74
+ };
75
+ // Initialize components
76
+ this.watcher = new FileWatcher({
77
+ rootDir: this.config.projectDir,
78
+ });
79
+ this.rustBuilder = new RustBuilder({
80
+ projectDir: this.config.projectDir,
81
+ release: this.config.release,
82
+ bin: 'zap',
83
+ binaryPath: this.config.binaryPath,
84
+ });
85
+ this.viteProxy = new ViteProxy({
86
+ projectDir: this.config.projectDir,
87
+ port: this.config.vitePort,
88
+ });
89
+ this.codegenRunner = new CodegenRunner({
90
+ projectDir: this.config.projectDir,
91
+ codegenBinary: this.config.codegenBinaryPath,
92
+ });
93
+ this.hotReloadServer = new HotReloadServer({
94
+ port: this.config.hotReloadPort,
95
+ });
96
+ this.routeScanner = new RouteScannerRunner({
97
+ projectDir: this.config.projectDir,
98
+ });
99
+ this.setupEventHandlers();
100
+ }
101
+ /**
102
+ * Set up event handlers for all components
103
+ */
104
+ setupEventHandlers() {
105
+ // File watcher events
106
+ this.watcher.on('rust', (event) => this.handleRustChange(event));
107
+ this.watcher.on('typescript', (event) => this.handleTypeScriptChange(event));
108
+ this.watcher.on('config', (event) => this.handleConfigChange(event));
109
+ this.watcher.on('error', (err) => this.log('error', `Watcher error: ${err.message}`));
110
+ // Rust builder events
111
+ this.rustBuilder.on('build-start', () => {
112
+ this.state.phase = 'rebuilding';
113
+ this.emit('rust-build-start');
114
+ });
115
+ this.rustBuilder.on('build-complete', (result) => {
116
+ this.emit('rust-build-complete', result);
117
+ });
118
+ this.rustBuilder.on('error', (msg) => {
119
+ this.log('error', msg);
120
+ });
121
+ this.rustBuilder.on('warning', (msg) => {
122
+ this.log('warn', msg);
123
+ });
124
+ // Vite events
125
+ this.viteProxy.on('ready', (port) => {
126
+ this.state.viteReady = true;
127
+ this.emit('vite-ready', port);
128
+ });
129
+ this.viteProxy.on('error', (err) => {
130
+ this.log('error', `Vite error: ${err.message}`);
131
+ });
132
+ this.viteProxy.on('hmr-update', () => {
133
+ this.emit('hmr-update');
134
+ });
135
+ // Hot reload events
136
+ this.hotReloadServer.on('client-connected', () => {
137
+ this.log('debug', 'Hot reload client connected');
138
+ });
139
+ // Codegen events
140
+ this.codegenRunner.on('complete', (result) => {
141
+ if (result.success) {
142
+ this.log('info', 'TypeScript bindings regenerated');
143
+ }
144
+ });
145
+ // Route scanner events
146
+ this.routeScanner.on('routes-changed', async (tree) => {
147
+ this.log('info', `Routes updated (${tree.routes.length} pages, ${tree.apiRoutes.length} API)`);
148
+ this.hotReloadServer.reload('routes', []);
149
+ // Restart Rust server to pick up new routes
150
+ try {
151
+ await this.restartRustServer();
152
+ }
153
+ catch (err) {
154
+ const message = err instanceof Error ? err.message : String(err);
155
+ this.log('error', `Failed to restart Rust server: ${message}`);
156
+ }
157
+ });
158
+ this.routeScanner.on('error', (err) => {
159
+ this.log('warn', `Route scanner error: ${err.message}`);
160
+ });
161
+ }
162
+ /**
163
+ * Start the development server
164
+ */
165
+ async start() {
166
+ this.startTime = Date.now();
167
+ cliLogger.header('ZapJS Dev Server');
168
+ try {
169
+ // Phase 1: Initial Rust build
170
+ if (!this.config.skipInitialBuild) {
171
+ await this.buildRust();
172
+ }
173
+ // Phase 2: Generate TypeScript bindings
174
+ await this.runCodegen();
175
+ // Phase 2.5: Scan routes
176
+ const routeTree = await this.scanRoutes();
177
+ this.currentRouteTree = routeTree;
178
+ // Phase 3: Start Rust HTTP server with IPC
179
+ await this.startRustServer(routeTree);
180
+ // Phase 4: Start other servers in parallel
181
+ await Promise.all([
182
+ this.startHotReloadServer(),
183
+ this.startViteServer(),
184
+ ]);
185
+ // Phase 5: Start file watcher and route watcher
186
+ this.startWatcher();
187
+ await this.startRouteWatcher();
188
+ // Ready!
189
+ this.state.phase = 'ready';
190
+ this.printReadyMessage();
191
+ // Open browser if requested
192
+ if (this.config.openBrowser) {
193
+ this.openBrowser();
194
+ }
195
+ }
196
+ catch (err) {
197
+ this.state.phase = 'error';
198
+ this.state.lastError = err instanceof Error ? err.message : String(err);
199
+ throw err;
200
+ }
201
+ }
202
+ /**
203
+ * Stop all servers immediately
204
+ */
205
+ async stop() {
206
+ if (this.state.phase === 'stopped')
207
+ return;
208
+ this.state.phase = 'stopped';
209
+ cliLogger.newline();
210
+ cliLogger.warn('Shutting down...');
211
+ // Kill Rust server first (most important)
212
+ if (this.processManager) {
213
+ this.processManager.stop();
214
+ this.processManager = null;
215
+ }
216
+ // Stop IPC
217
+ if (this.ipcServer) {
218
+ this.ipcServer.stop();
219
+ this.ipcServer = null;
220
+ }
221
+ // Stop other components
222
+ try {
223
+ this.watcher.stop();
224
+ this.viteProxy.stop();
225
+ this.hotReloadServer.stop();
226
+ this.routeScanner.stopWatching();
227
+ this.rustBuilder.cancel();
228
+ }
229
+ catch {
230
+ // Ignore errors during cleanup
231
+ }
232
+ cliLogger.success('Goodbye!');
233
+ cliLogger.newline();
234
+ process.exit(0);
235
+ }
236
+ /**
237
+ * Build Rust backend
238
+ */
239
+ async buildRust() {
240
+ cliLogger.spinner('rust-build', 'Building Rust backend...');
241
+ const result = await this.rustBuilder.build();
242
+ if (result.success) {
243
+ const duration = (result.duration / 1000).toFixed(2);
244
+ cliLogger.succeedSpinner('rust-build', `Rust build complete (${duration}s)`);
245
+ this.state.rustReady = true;
246
+ if (result.warnings.length > 0) {
247
+ cliLogger.warn(`${result.warnings.length} warnings`);
248
+ }
249
+ }
250
+ else {
251
+ cliLogger.failSpinner('rust-build', 'Rust build failed');
252
+ for (const error of result.errors) {
253
+ cliLogger.error(error);
254
+ }
255
+ throw new Error('Rust build failed');
256
+ }
257
+ }
258
+ /**
259
+ * Run codegen to generate TypeScript bindings
260
+ */
261
+ async runCodegen() {
262
+ cliLogger.spinner('codegen', 'Generating TypeScript bindings...');
263
+ const success = await this.codegenRunner.run();
264
+ if (success) {
265
+ cliLogger.succeedSpinner('codegen', 'TypeScript bindings generated');
266
+ }
267
+ else {
268
+ cliLogger.warn('Codegen skipped (binary not found)');
269
+ }
270
+ }
271
+ /**
272
+ * Scan routes directory and generate route tree
273
+ */
274
+ async scanRoutes() {
275
+ if (!this.routeScanner.hasRoutesDir()) {
276
+ this.log('debug', 'No routes directory found');
277
+ return null;
278
+ }
279
+ cliLogger.spinner('routes', 'Scanning routes...');
280
+ const tree = await this.routeScanner.scan();
281
+ if (tree) {
282
+ cliLogger.succeedSpinner('routes', `Found ${tree.routes.length} pages, ${tree.apiRoutes.length} API routes`);
283
+ return tree;
284
+ }
285
+ else {
286
+ cliLogger.warn('Route scanning skipped');
287
+ return null;
288
+ }
289
+ }
290
+ /**
291
+ * Start the Rust HTTP server with IPC for TypeScript handlers
292
+ */
293
+ async startRustServer(routeTree) {
294
+ cliLogger.spinner('rust-server', 'Starting Rust HTTP server...');
295
+ try {
296
+ // Generate unique socket path for this dev session
297
+ this.socketPath = path.join(tmpdir(), `zap-dev-${Date.now()}-${Math.random().toString(36).substring(7)}.sock`);
298
+ // Create and start IPC server first
299
+ this.ipcServer = new IpcServer(this.socketPath);
300
+ await this.ipcServer.start();
301
+ this.log('debug', `IPC server listening on ${this.socketPath}`);
302
+ // Load and register route handlers
303
+ const routes = await this.loadRouteHandlers(routeTree);
304
+ // Build Rust server configuration
305
+ const config = this.buildRustConfig(routes);
306
+ // Get binary path
307
+ const binaryPath = this.rustBuilder.getBinaryPath();
308
+ this.log('debug', `Using Rust binary: ${binaryPath}`);
309
+ // Create process manager and start Rust server
310
+ this.processManager = new ProcessManager(binaryPath, this.socketPath);
311
+ await this.processManager.start(config, this.config.logLevel || 'info');
312
+ this.state.rustReady = true;
313
+ cliLogger.succeedSpinner('rust-server', `Rust server ready on port ${this.config.rustPort}`);
314
+ }
315
+ catch (err) {
316
+ cliLogger.failSpinner('rust-server', 'Failed to start Rust server');
317
+ const message = err instanceof Error ? err.message : String(err);
318
+ this.log('error', `Rust server error: ${message}`);
319
+ throw err;
320
+ }
321
+ }
322
+ /**
323
+ * Load TypeScript route handlers and register them with IPC server
324
+ */
325
+ async loadRouteHandlers(routeTree) {
326
+ const routes = [];
327
+ if (!routeTree || !routeTree.apiRoutes || routeTree.apiRoutes.length === 0) {
328
+ console.log('[routes] No API routes to register');
329
+ return routes;
330
+ }
331
+ // Register tsx loader for TypeScript imports
332
+ await ensureTsxRegistered();
333
+ console.log(`[routes] Loading ${routeTree.apiRoutes.length} API route handlers...`);
334
+ for (const apiRoute of routeTree.apiRoutes) {
335
+ try {
336
+ // Construct the full file path
337
+ const routeFilePath = path.join(this.config.projectDir, 'routes', apiRoute.relativePath);
338
+ console.log(`[routes] Loading: ${routeFilePath}`);
339
+ // Dynamic import the route module using file URL for ESM compatibility
340
+ const fileUrl = pathToFileURL(routeFilePath).href;
341
+ const routeModule = await import(fileUrl);
342
+ console.log(`[routes] Loaded module, exports: ${Object.keys(routeModule).join(', ')}`);
343
+ // Check for each HTTP method handler
344
+ const methods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'];
345
+ for (const method of methods) {
346
+ if (routeModule[method]) {
347
+ const handlerId = `handler_${method}_${apiRoute.urlPath.replace(/\//g, '_').replace(/:/g, '')}`;
348
+ console.log(`[routes] Registering handler: ${handlerId} for ${method} ${apiRoute.urlPath}`);
349
+ // Register handler with IPC server
350
+ this.ipcServer.registerHandler(handlerId, async (req) => {
351
+ console.log(`[handler] Received request: ${method} ${apiRoute.urlPath}`);
352
+ console.log(`[handler] Request data:`, JSON.stringify(req, null, 2));
353
+ try {
354
+ // Call the route handler
355
+ console.log(`[handler] Calling handler function...`);
356
+ const result = await routeModule[method](req);
357
+ console.log(`[handler] Handler returned:`, JSON.stringify(result, null, 2));
358
+ // Convert result to IPC response format
359
+ const response = this.formatHandlerResponse(result);
360
+ console.log(`[handler] Formatted response:`, JSON.stringify(response, null, 2));
361
+ return response;
362
+ }
363
+ catch (err) {
364
+ const message = err instanceof Error ? err.message : String(err);
365
+ const stack = err instanceof Error ? err.stack : '';
366
+ console.error(`[handler] ERROR in ${method} ${apiRoute.urlPath}: ${message}`);
367
+ console.error(`[handler] Stack: ${stack}`);
368
+ return {
369
+ status: 500,
370
+ headers: { 'content-type': 'application/json' },
371
+ body: JSON.stringify({ error: 'Internal Server Error', message }),
372
+ };
373
+ }
374
+ });
375
+ // Track handler mapping
376
+ this.registeredHandlers.set(handlerId, routeFilePath);
377
+ // Add route to config
378
+ routes.push({
379
+ method,
380
+ path: apiRoute.urlPath,
381
+ handler_id: handlerId,
382
+ is_typescript: true,
383
+ });
384
+ console.log(`[routes] ✓ Registered: ${method} ${apiRoute.urlPath} -> ${handlerId}`);
385
+ }
386
+ }
387
+ }
388
+ catch (err) {
389
+ const message = err instanceof Error ? err.message : String(err);
390
+ console.error(`[routes] Failed to load ${apiRoute.relativePath}: ${message}`);
391
+ }
392
+ }
393
+ console.log(`[routes] Total registered: ${routes.length} API handlers`);
394
+ return routes;
395
+ }
396
+ /**
397
+ * Format handler result into IPC response
398
+ */
399
+ formatHandlerResponse(result) {
400
+ // Handle Response object
401
+ if (result instanceof Response) {
402
+ return {
403
+ status: result.status,
404
+ headers: Object.fromEntries(result.headers.entries()),
405
+ body: '', // Will need to await text() but keeping sync for now
406
+ };
407
+ }
408
+ // Handle string
409
+ if (typeof result === 'string') {
410
+ return {
411
+ status: 200,
412
+ headers: { 'content-type': 'text/plain' },
413
+ body: result,
414
+ };
415
+ }
416
+ // Handle object (JSON)
417
+ if (typeof result === 'object' && result !== null) {
418
+ return {
419
+ status: 200,
420
+ headers: { 'content-type': 'application/json' },
421
+ body: JSON.stringify(result),
422
+ };
423
+ }
424
+ // Default
425
+ return {
426
+ status: 200,
427
+ headers: { 'content-type': 'text/plain' },
428
+ body: String(result),
429
+ };
430
+ }
431
+ /**
432
+ * Build Rust server configuration from routes
433
+ */
434
+ buildRustConfig(routes) {
435
+ return {
436
+ port: this.config.rustPort,
437
+ hostname: '127.0.0.1',
438
+ ipc_socket_path: this.socketPath,
439
+ routes,
440
+ static_files: [],
441
+ middleware: {
442
+ enable_cors: true,
443
+ enable_logging: true,
444
+ enable_compression: false,
445
+ },
446
+ health_check_path: '/health',
447
+ metrics_path: '/metrics',
448
+ };
449
+ }
450
+ /**
451
+ * Restart Rust server (called when routes change)
452
+ */
453
+ async restartRustServer() {
454
+ this.log('info', 'Restarting Rust server...');
455
+ // Stop existing server
456
+ if (this.processManager) {
457
+ await this.processManager.stop();
458
+ }
459
+ if (this.ipcServer) {
460
+ await this.ipcServer.stop();
461
+ }
462
+ // Clear registered handlers
463
+ this.registeredHandlers.clear();
464
+ // Re-scan routes and restart
465
+ const routeTree = await this.scanRoutes();
466
+ this.currentRouteTree = routeTree;
467
+ await this.startRustServer(routeTree);
468
+ }
469
+ /**
470
+ * Start the route file watcher
471
+ */
472
+ async startRouteWatcher() {
473
+ if (!this.routeScanner.hasRoutesDir()) {
474
+ return;
475
+ }
476
+ await this.routeScanner.startWatching();
477
+ this.log('debug', 'Route watcher started');
478
+ }
479
+ /**
480
+ * Start the hot reload WebSocket server
481
+ */
482
+ async startHotReloadServer() {
483
+ await this.hotReloadServer.start();
484
+ this.log('debug', `Hot reload server on port ${this.config.hotReloadPort}`);
485
+ }
486
+ /**
487
+ * Start the Vite dev server
488
+ */
489
+ async startViteServer() {
490
+ cliLogger.spinner('vite', 'Starting Vite dev server...');
491
+ try {
492
+ await this.viteProxy.start();
493
+ cliLogger.succeedSpinner('vite', `Vite ready on port ${this.viteProxy.getPort()}`);
494
+ }
495
+ catch (err) {
496
+ cliLogger.warn('Vite not available (frontend only)');
497
+ this.log('debug', `Vite error: ${err}`);
498
+ }
499
+ }
500
+ /**
501
+ * Start the file watcher
502
+ */
503
+ startWatcher() {
504
+ this.watcher.start();
505
+ this.log('debug', 'File watcher started');
506
+ }
507
+ /**
508
+ * Handle Rust file changes
509
+ */
510
+ async handleRustChange(event) {
511
+ const relativePath = path.relative(this.config.projectDir, event.path);
512
+ cliLogger.newline();
513
+ cliLogger.info(`[${event.type}] ${relativePath}`);
514
+ cliLogger.spinner('rust-rebuild', 'Rebuilding Rust...');
515
+ const result = await this.rustBuilder.build();
516
+ if (result.success) {
517
+ const duration = (result.duration / 1000).toFixed(2);
518
+ cliLogger.succeedSpinner('rust-rebuild', `Rust rebuild complete (${duration}s)`);
519
+ // Regenerate bindings
520
+ await this.codegenRunner.run();
521
+ // Signal hot reload
522
+ this.hotReloadServer.reload('rust', [relativePath]);
523
+ this.state.phase = 'ready';
524
+ }
525
+ else {
526
+ cliLogger.failSpinner('rust-rebuild', 'Rust build failed');
527
+ for (const error of result.errors.slice(0, 3)) {
528
+ cliLogger.error(error);
529
+ }
530
+ // Notify clients of error
531
+ this.hotReloadServer.notifyError(result.errors.join('\n'));
532
+ this.state.phase = 'error';
533
+ }
534
+ }
535
+ /**
536
+ * Handle TypeScript file changes
537
+ */
538
+ async handleTypeScriptChange(event) {
539
+ const relativePath = path.relative(this.config.projectDir, event.path);
540
+ this.log('debug', `[${event.type}] ${relativePath}`);
541
+ // Vite HMR handles TypeScript changes automatically
542
+ // Just emit the event for logging
543
+ this.emit('typescript-change', event);
544
+ }
545
+ /**
546
+ * Handle config file changes
547
+ */
548
+ async handleConfigChange(event) {
549
+ const relativePath = path.relative(this.config.projectDir, event.path);
550
+ cliLogger.newline();
551
+ cliLogger.warn(`[config] ${relativePath}`);
552
+ // For significant config changes, restart might be needed
553
+ if (relativePath.includes('Cargo.toml')) {
554
+ cliLogger.warn('Cargo.toml changed - rebuilding...');
555
+ await this.handleRustChange(event);
556
+ }
557
+ else if (relativePath.includes('vite.config')) {
558
+ cliLogger.warn('Vite config changed - restarting Vite...');
559
+ await this.viteProxy.restart();
560
+ }
561
+ }
562
+ /**
563
+ * Print the ready message
564
+ */
565
+ printReadyMessage() {
566
+ const elapsed = ((Date.now() - this.startTime) / 1000).toFixed(2);
567
+ cliLogger.newline();
568
+ cliLogger.success(`Dev server ready in ${elapsed}s`);
569
+ cliLogger.newline();
570
+ cliLogger.info('Servers:');
571
+ cliLogger.listItem(`API: http://127.0.0.1:${this.config.rustPort}`, '➜');
572
+ if (this.viteProxy.getPort()) {
573
+ cliLogger.listItem(`Frontend: http://127.0.0.1:${this.viteProxy.getPort()}`, '➜');
574
+ }
575
+ cliLogger.listItem(`Hot Reload: ws://127.0.0.1:${this.config.hotReloadPort}`, '➜');
576
+ cliLogger.newline();
577
+ cliLogger.info('Press Ctrl+C to stop');
578
+ cliLogger.newline();
579
+ // Show keyboard shortcuts
580
+ cliLogger.info('Shortcuts:');
581
+ console.log(' r - Rebuild Rust');
582
+ console.log(' c - Regenerate codegen');
583
+ console.log(' q - Quit');
584
+ cliLogger.newline();
585
+ // Setup keyboard input
586
+ this.setupKeyboardInput();
587
+ }
588
+ /**
589
+ * Set up keyboard input handling
590
+ */
591
+ setupKeyboardInput() {
592
+ if (process.stdin.isTTY) {
593
+ process.stdin.setRawMode(true);
594
+ process.stdin.resume();
595
+ process.stdin.setEncoding('utf8');
596
+ process.stdin.on('data', async (key) => {
597
+ // Ctrl+C
598
+ if (key === '\u0003') {
599
+ await this.stop();
600
+ process.exit(0);
601
+ }
602
+ // 'r' - rebuild
603
+ if (key === 'r') {
604
+ cliLogger.newline();
605
+ cliLogger.info('Manual rebuild triggered...');
606
+ await this.rustBuilder.build();
607
+ }
608
+ // 'c' - codegen
609
+ if (key === 'c') {
610
+ cliLogger.newline();
611
+ cliLogger.info('Regenerating bindings...');
612
+ await this.codegenRunner.run();
613
+ }
614
+ // 'q' - quit
615
+ if (key === 'q') {
616
+ await this.stop();
617
+ process.exit(0);
618
+ }
619
+ });
620
+ }
621
+ }
622
+ /**
623
+ * Open the browser
624
+ */
625
+ openBrowser() {
626
+ const url = this.viteProxy.getPort()
627
+ ? `http://127.0.0.1:${this.viteProxy.getPort()}`
628
+ : `http://127.0.0.1:${this.config.rustPort}`;
629
+ import('child_process').then(({ exec }) => {
630
+ const command = process.platform === 'darwin' ? 'open' :
631
+ process.platform === 'win32' ? 'start' : 'xdg-open';
632
+ exec(`${command} ${url}`);
633
+ });
634
+ }
635
+ /**
636
+ * Log a message with the appropriate level
637
+ */
638
+ log(level, message) {
639
+ const levels = { debug: 0, info: 1, warn: 2, error: 3 };
640
+ const configLevel = levels[this.config.logLevel || 'info'];
641
+ const msgLevel = levels[level];
642
+ if (msgLevel >= configLevel) {
643
+ const prefix = {
644
+ debug: '[debug]',
645
+ info: '[info]',
646
+ warn: '[warn]',
647
+ error: '[error]',
648
+ }[level];
649
+ console.log(`${prefix} ${message}`);
650
+ }
651
+ }
652
+ /**
653
+ * Get current server state
654
+ */
655
+ getState() {
656
+ return { ...this.state };
657
+ }
658
+ }