|
1 | 1 | import { Command } from 'commander'; |
2 | 2 | import path from 'path'; |
3 | 3 | import fs from 'fs'; |
| 4 | +import net from 'net'; |
4 | 5 | import chalk from 'chalk'; |
5 | 6 | import { bundleRequire } from 'bundle-require'; |
6 | 7 |
|
| 8 | +// Helper to find available port |
| 9 | +const getAvailablePort = async (startPort: number): Promise<number> => { |
| 10 | + const isPortAvailable = (port: number): Promise<boolean> => { |
| 11 | + return new Promise((resolve) => { |
| 12 | + const server = net.createServer(); |
| 13 | + server.once('error', (err: any) => { |
| 14 | + resolve(false); |
| 15 | + }); |
| 16 | + server.once('listening', () => { |
| 17 | + server.close(() => resolve(true)); |
| 18 | + }); |
| 19 | + server.listen(port); |
| 20 | + }); |
| 21 | + }; |
| 22 | + |
| 23 | + let port = startPort; |
| 24 | + while (!(await isPortAvailable(port))) { |
| 25 | + port++; |
| 26 | + if (port > startPort + 100) { |
| 27 | + throw new Error(`Could not find an available port starting from ${startPort}`); |
| 28 | + } |
| 29 | + } |
| 30 | + return port; |
| 31 | +}; |
| 32 | + |
7 | 33 | export const serveCommand = new Command('serve') |
8 | 34 | .description('Start ObjectStack server with plugins from configuration') |
9 | 35 | .argument('[config]', 'Configuration file path', 'objectstack.config.ts') |
10 | 36 | .option('-p, --port <port>', 'Server port', '3000') |
11 | 37 | .option('--no-server', 'Skip starting HTTP server plugin') |
12 | 38 | .action(async (configPath, options) => { |
| 39 | + let port = parseInt(options.port); |
| 40 | + try { |
| 41 | + const availablePort = await getAvailablePort(port); |
| 42 | + if (availablePort !== port) { |
| 43 | + port = availablePort; |
| 44 | + } |
| 45 | + } catch (e) { |
| 46 | + // Ignore error and try with original port, or let it fail later |
| 47 | + } |
| 48 | + |
13 | 49 | console.log(chalk.bold(`\n🚀 ObjectStack Server`)); |
14 | 50 | console.log(chalk.dim(`------------------------`)); |
15 | 51 | console.log(`📂 Config: ${chalk.blue(configPath)}`); |
16 | | - console.log(`🌐 Port: ${chalk.blue(options.port)}`); |
| 52 | + if (parseInt(options.port) !== port) { |
| 53 | + console.log(`🌐 Port: ${chalk.blue(port)} ${chalk.yellow(`(requested: ${options.port} in use)`)}`); |
| 54 | + } else { |
| 55 | + console.log(`🌐 Port: ${chalk.blue(port)}`); |
| 56 | + } |
17 | 57 | console.log(''); |
18 | 58 |
|
| 59 | + |
19 | 60 | const absolutePath = path.resolve(process.cwd(), configPath); |
20 | 61 |
|
21 | 62 | if (!fs.existsSync(absolutePath)) { |
@@ -69,9 +110,9 @@ export const serveCommand = new Command('serve') |
69 | 110 | if (options.server !== false) { |
70 | 111 | try { |
71 | 112 | const { HonoServerPlugin } = await import('@objectstack/plugin-hono-server'); |
72 | | - const serverPlugin = new HonoServerPlugin({ port: parseInt(options.port) }); |
| 113 | + const serverPlugin = new HonoServerPlugin({ port }); |
73 | 114 | kernel.use(serverPlugin); |
74 | | - console.log(chalk.green(` ✓ Registered HTTP server plugin (port: ${options.port})`)); |
| 115 | + console.log(chalk.green(` ✓ Registered HTTP server plugin (port: ${port})`)); |
75 | 116 | } catch (e: any) { |
76 | 117 | console.warn(chalk.yellow(` ⚠ HTTP server plugin not available: ${e.message}`)); |
77 | 118 | } |
|
0 commit comments