|
1 | 1 | #!/usr/bin/env node |
2 | 2 |
|
3 | | -import { spawnSync } from "node:child_process"; |
4 | | -import { constants as fsConstants, mkdtempSync, readFileSync, rmSync } from "node:fs"; |
| 3 | +import { spawn } from "node:child_process"; |
| 4 | +import { constants as fsConstants } from "node:fs"; |
5 | 5 | import { access } from "node:fs/promises"; |
6 | | -import os from "node:os"; |
7 | 6 | import path from "node:path"; |
8 | 7 | import process from "node:process"; |
9 | 8 | import { fileURLToPath } from "node:url"; |
10 | 9 |
|
11 | 10 | const ROOT_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); |
| 11 | +const READINESS_LINE = "Server running on http://localhost:3001"; |
| 12 | +const STARTUP_TIMEOUT_MS = 15_000; |
12 | 13 |
|
13 | 14 | function resolveBinaryPath() { |
14 | 15 | if (process.platform === "linux") { |
@@ -46,47 +47,76 @@ function resolveBinaryPath() { |
46 | 47 | throw new Error(`Unsupported smoke test platform: ${process.platform}/${process.arch}`); |
47 | 48 | } |
48 | 49 |
|
49 | | -function shellQuote(value) { |
50 | | - return `'${value.replace(/'/g, `'\\''`)}'`; |
51 | | -} |
52 | | - |
53 | 50 | async function main() { |
54 | 51 | const binaryPath = resolveBinaryPath(); |
55 | 52 | await access(binaryPath, fsConstants.X_OK); |
56 | 53 |
|
57 | | - const tmpDir = mkdtempSync(path.join(os.tmpdir(), "openlinear-sidecar-smoke-")); |
58 | | - const logPath = path.join(tmpDir, "sidecar.log"); |
59 | | - |
60 | | - try { |
61 | | - const command = [ |
62 | | - `timeout 15s ${shellQuote(binaryPath)} > ${shellQuote(logPath)} 2>&1 || true`, |
63 | | - `cat ${shellQuote(logPath)}`, |
64 | | - `grep -q 'Server running on http://localhost:3001' ${shellQuote(logPath)}`, |
65 | | - ].join("; "); |
66 | | - |
67 | | - const result = spawnSync("bash", ["-lc", command], { |
| 54 | + await new Promise((resolve, reject) => { |
| 55 | + const child = spawn(binaryPath, [], { |
68 | 56 | cwd: ROOT_DIR, |
69 | 57 | env: { ...process.env }, |
70 | | - encoding: "utf8", |
| 58 | + stdio: ["ignore", "pipe", "pipe"], |
71 | 59 | }); |
72 | 60 |
|
73 | | - if (result.stdout) { |
74 | | - process.stdout.write(result.stdout); |
75 | | - } |
| 61 | + let settled = false; |
| 62 | + let output = ""; |
76 | 63 |
|
77 | | - if (result.stderr) { |
78 | | - process.stderr.write(result.stderr); |
79 | | - } |
| 64 | + const settle = (callback) => { |
| 65 | + if (settled) { |
| 66 | + return; |
| 67 | + } |
80 | 68 |
|
81 | | - if (result.status !== 0) { |
82 | | - const logOutput = readFileSync(logPath, "utf8"); |
83 | | - throw new Error( |
84 | | - `Sidecar smoke test failed before readiness. Exit code: ${result.status}\n${logOutput}`, |
85 | | - ); |
86 | | - } |
87 | | - } finally { |
88 | | - rmSync(tmpDir, { recursive: true, force: true }); |
89 | | - } |
| 69 | + settled = true; |
| 70 | + clearTimeout(timeoutId); |
| 71 | + |
| 72 | + if (!child.killed) { |
| 73 | + child.kill("SIGTERM"); |
| 74 | + } |
| 75 | + |
| 76 | + callback(); |
| 77 | + }; |
| 78 | + |
| 79 | + const onChunk = (chunk, writer) => { |
| 80 | + const text = chunk.toString(); |
| 81 | + output += text; |
| 82 | + writer.write(text); |
| 83 | + |
| 84 | + if (output.includes(READINESS_LINE)) { |
| 85 | + settle(resolve); |
| 86 | + } |
| 87 | + }; |
| 88 | + |
| 89 | + child.stdout?.on("data", (chunk) => onChunk(chunk, process.stdout)); |
| 90 | + child.stderr?.on("data", (chunk) => onChunk(chunk, process.stderr)); |
| 91 | + |
| 92 | + child.on("error", (error) => { |
| 93 | + settle(() => reject(error)); |
| 94 | + }); |
| 95 | + |
| 96 | + child.on("exit", (code, signal) => { |
| 97 | + if (settled) { |
| 98 | + return; |
| 99 | + } |
| 100 | + |
| 101 | + settle(() => { |
| 102 | + reject( |
| 103 | + new Error( |
| 104 | + `Sidecar smoke test failed before readiness. Exit code: ${code ?? "null"}, signal: ${signal ?? "null"}\n${output}`, |
| 105 | + ), |
| 106 | + ); |
| 107 | + }); |
| 108 | + }); |
| 109 | + |
| 110 | + const timeoutId = setTimeout(() => { |
| 111 | + settle(() => { |
| 112 | + reject( |
| 113 | + new Error( |
| 114 | + `Sidecar smoke test timed out after ${STARTUP_TIMEOUT_MS}ms before readiness.\n${output}`, |
| 115 | + ), |
| 116 | + ); |
| 117 | + }); |
| 118 | + }, STARTUP_TIMEOUT_MS); |
| 119 | + }); |
90 | 120 | } |
91 | 121 |
|
92 | 122 | main().catch((error) => { |
|
0 commit comments