| 295 | } |
| 296 | |
| 297 | async function runCommand(command: string, cwd: string) { |
| 298 | const COMMAND_TIMEOUT_MS = 5 * 60 * 1000; |
| 299 | |
| 300 | const start = Date.now(); |
| 301 | |
| 302 | return await new Promise<Metric.CommandExecution>((resolve) => { |
| 303 | const child = spawn(command, { |
| 304 | cwd, |
| 305 | shell: true, |
| 306 | env: { |
| 307 | ...process.env, |
| 308 | CI: process.env.CI ?? "1", |
| 309 | }, |
| 310 | stdio: ["ignore", "pipe", "pipe"], |
| 311 | }); |
| 312 | |
| 313 | let stdout = ""; |
| 314 | let stderr = ""; |
| 315 | let errorMessage: string | undefined; |
| 316 | let timeout: NodeJS.Timeout | undefined; |
| 317 | let settled = false; |
| 318 | |
| 319 | timeout = setTimeout(() => { |
| 320 | errorMessage = `Timed out after ${COMMAND_TIMEOUT_MS}ms`; |
| 321 | child.kill("SIGKILL"); |
| 322 | }, COMMAND_TIMEOUT_MS); |
| 323 | |
| 324 | child.stdout?.on("data", (chunk) => { |
| 325 | stdout += chunk.toString(); |
| 326 | }); |
| 327 | |
| 328 | child.stderr?.on("data", (chunk) => { |
| 329 | stderr += chunk.toString(); |
| 330 | }); |
| 331 | |
| 332 | child.on("error", (error) => { |
| 333 | errorMessage = error.message; |
| 334 | }); |
| 335 | |
| 336 | child.on("close", (code) => { |
| 337 | const exitCode = typeof code === "number" ? code : null; |
| 338 | if (settled) return; |
| 339 | settled = true; |
| 340 | if (timeout) clearTimeout(timeout); |
| 341 | |
| 342 | const runtimeMs = Date.now() - start; |
| 343 | const success = exitCode === 0 && !errorMessage; |
| 344 | |
| 345 | resolve({ |
| 346 | command, |
| 347 | success, |
| 348 | exitCode, |
| 349 | stdout, |
| 350 | stderr, |
| 351 | runtimeMs, |
| 352 | errorMessage, |
| 353 | }); |
| 354 | }); |