2026-08-16 11:03:25 +02:00
|
|
|
import { spawn } from "node:child_process";
|
|
|
|
|
import net from "node:net";
|
|
|
|
|
|
|
|
|
|
export function isPortFree(host: string, port: number): Promise<boolean> {
|
|
|
|
|
return new Promise((resolve) => {
|
|
|
|
|
const socket = net.connect({ host, port, timeout: 500 });
|
|
|
|
|
socket.once("connect", () => {
|
|
|
|
|
socket.destroy();
|
|
|
|
|
resolve(false);
|
|
|
|
|
});
|
|
|
|
|
socket.once("timeout", () => {
|
|
|
|
|
socket.destroy();
|
|
|
|
|
resolve(true);
|
|
|
|
|
});
|
|
|
|
|
socket.once("error", () => {
|
|
|
|
|
resolve(true);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function waitUntilReady(
|
|
|
|
|
url: string,
|
|
|
|
|
timeoutMs: number,
|
|
|
|
|
intervalMs = 400,
|
|
|
|
|
): Promise<boolean> {
|
|
|
|
|
const deadline = Date.now() + timeoutMs;
|
|
|
|
|
while (Date.now() < deadline) {
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch(url, { signal: AbortSignal.timeout(1500) });
|
|
|
|
|
if (response.status === 200) return true;
|
|
|
|
|
} catch {
|
|
|
|
|
// not ready yet
|
|
|
|
|
}
|
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-16 12:29:55 +02:00
|
|
|
/** Single-shot check (not a poll loop, unlike `waitUntilReady`) for whether a triggershell server
|
|
|
|
|
* is already listening at `url` - used to decide whether `run` can go through the REST/WS API. */
|
|
|
|
|
export async function isServerReachable(url: string): Promise<boolean> {
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch(`${url}/api/healthz`, {
|
|
|
|
|
signal: AbortSignal.timeout(1000),
|
|
|
|
|
});
|
|
|
|
|
return response.status === 200;
|
|
|
|
|
} catch {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-16 11:03:25 +02:00
|
|
|
export function openBrowser(url: string): void {
|
|
|
|
|
const command =
|
2026-08-16 13:55:25 +02:00
|
|
|
process.platform === "darwin"
|
|
|
|
|
? "open"
|
|
|
|
|
: process.platform === "win32"
|
|
|
|
|
? "start"
|
|
|
|
|
: "xdg-open";
|
2026-08-16 11:03:25 +02:00
|
|
|
const args = process.platform === "win32" ? ["", url] : [url];
|
|
|
|
|
try {
|
2026-08-16 13:55:25 +02:00
|
|
|
spawn(command, args, {
|
|
|
|
|
detached: true,
|
|
|
|
|
stdio: "ignore",
|
|
|
|
|
shell: process.platform === "win32",
|
|
|
|
|
}).unref();
|
2026-08-16 11:03:25 +02:00
|
|
|
} catch {
|
|
|
|
|
// best-effort - not fatal if no browser opener is available
|
|
|
|
|
}
|
|
|
|
|
}
|