(options: { timeoutMs: number } = { timeoutMs: 60_000 })
| 153 | } |
| 154 | |
| 155 | async start(options: { timeoutMs: number } = { timeoutMs: 60_000 }): Promise<DevServerHandle> { |
| 156 | if (this.handle) { |
| 157 | return this.handle; |
| 158 | } |
| 159 | |
| 160 | const port = await getFreeHashPort(this.params.appPath); |
| 161 | const url = buildDevServerUrl(port); |
| 162 | const { executable, args, needsDoubleDash } = parseCommandWithPortSupport(this.params.runCommand); |
| 163 | const normalizedExecutable = normalizeExecutable(executable); |
| 164 | const commandArgs = needsDoubleDash ? [...args, '--', '--port', String(port)] : [...args, '--port', String(port)]; |
| 165 | |
| 166 | logger.printInfoLog(`Starting dev server: ${normalizedExecutable} ${commandArgs.join(' ')}`); |
| 167 | |
| 168 | const child = spawn(normalizedExecutable, commandArgs, { |
| 169 | cwd: this.params.appPath, |
| 170 | stdio: ['ignore', 'pipe', 'pipe'], |
| 171 | env: { |
| 172 | ...process.env, |
| 173 | PORT: String(port), |
| 174 | FORCE_COLOR: '1', |
| 175 | }, |
| 176 | }); |
| 177 | |
| 178 | const maxBytes = 1_000_000; |
| 179 | let out = ''; |
| 180 | const push = (chunk: Buffer) => { |
| 181 | out += chunk.toString('utf-8'); |
| 182 | if (out.length > maxBytes) { |
| 183 | out = out.slice(out.length - maxBytes); |
| 184 | } |
| 185 | }; |
| 186 | child.stdout?.on('data', push); |
| 187 | child.stderr?.on('data', push); |
| 188 | |
| 189 | // If our process exits, try to stop the child to avoid orphans. |
| 190 | process.once('exit', () => { |
| 191 | terminateChildProcess(child); |
| 192 | }); |
| 193 | |
| 194 | const ready = await waitForServerReady(url, options.timeoutMs); |
| 195 | if (!ready) { |
| 196 | const tail = out.trim(); |
| 197 | terminateChildProcess(child); |
| 198 | throw new Error(`Dev server did not become ready at ${url} within ${options.timeoutMs}ms.\n${tail}`); |
| 199 | } |
| 200 | |
| 201 | this.handle = { |
| 202 | child, |
| 203 | port, |
| 204 | url, |
| 205 | outputTail: () => out.trim(), |
| 206 | }; |
| 207 | |
| 208 | logger.printSuccessLog(`Dev server ready at ${url}`); |
| 209 | return this.handle; |
| 210 | } |
| 211 | |
| 212 | async stop(): Promise<void> { |
no test coverage detected