| 64 | const DEFAULT_MAX_PORT_ATTEMPTS = 100; |
| 65 | |
| 66 | export async function startServer(opts: ServerOptions): Promise<ServerHandle> { |
| 67 | const webDist = path.resolve(opts.webDistPath ?? DEFAULT_WEB_DIST); |
| 68 | const compiled = (opts.routes ?? []).map(compileRoute); |
| 69 | const startPort = opts.port ?? DEFAULT_START_PORT; |
| 70 | const maxPortAttempts = opts.maxPortAttempts ?? DEFAULT_MAX_PORT_ATTEMPTS; |
| 71 | |
| 72 | for (let i = 0; i < maxPortAttempts; i++) { |
| 73 | const port = startPort + i; |
| 74 | const server = http.createServer((req, res) => { |
| 75 | handleRequest(req, res, webDist, compiled).catch((err) => { |
| 76 | const msg = err instanceof Error ? err.message : String(err); |
| 77 | process.stderr.write(`request handler error: ${msg}\n`); |
| 78 | if (!res.headersSent) { |
| 79 | res.writeHead(500, { "Content-Type": "text/plain" }); |
| 80 | } |
| 81 | res.end("Internal Server Error"); |
| 82 | }); |
| 83 | }); |
| 84 | |
| 85 | try { |
| 86 | await listen(server, port); |
| 87 | return { |
| 88 | port, |
| 89 | close: () => |
| 90 | new Promise<void>((resolve, reject) => { |
| 91 | server.close((err) => (err ? reject(err) : resolve())); |
| 92 | }), |
| 93 | }; |
| 94 | } catch (err) { |
| 95 | if (!isPortUnavailable(err)) throw err; |
| 96 | } |
| 97 | } |
| 98 | |
| 99 | throw new Error( |
| 100 | `Could not find a free port in range ${startPort}-${startPort + maxPortAttempts - 1}`, |
| 101 | ); |
| 102 | } |
| 103 | |
| 104 | function listen(server: http.Server, port: number): Promise<void> { |
| 105 | return new Promise<void>((resolve, reject) => { |