@botpress/adk-cli
Version:
Command-line interface for the Botpress Agent Development Kit (ADK)
110 lines (107 loc) • 3.18 kB
JavaScript
// @bun
import {
CONSOLE_LOCK_FILE,
CONSOLE_PORT_FILE,
CONSOLE_SOCKET_PATH,
isAdkDevConsolePort
} from "./chunk-sgj6770p.js";
// src/server/console-control.ts
import { existsSync, unlinkSync } from "fs";
var FETCH_TIMEOUT_MS = 1e4;
function isProcessAlive(pid) {
try {
process.kill(pid, 0);
return true;
} catch {
return false;
}
}
async function shutdownConsole(port) {
try {
const res = await fetch(`http://localhost:${port}/api/console/shutdown`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({}),
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
});
if (res.status === 404) {
return { result: "unavailable" };
}
if (!res.ok) {
const body = await res.json().catch(() => null);
return {
result: "failed",
error: body?.message ?? `Shutdown request returned HTTP ${res.status}`
};
}
return { result: "success" };
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
if (msg.includes("Connection refused") || msg.includes("ECONNREFUSED") || msg.includes("TimeoutError")) {
return { result: "success" };
}
return { result: "failed", error: msg };
}
}
async function waitForConsoleDead(port, timeoutMs = 4000) {
const start = Date.now();
while (Date.now() - start < timeoutMs) {
const alive = await isAdkDevConsolePort(port).catch(() => false);
if (!alive)
return true;
await new Promise((resolve) => setTimeout(resolve, 250));
}
return false;
}
async function forceKillConsole(pid) {
if (pid <= 0 || !isProcessAlive(pid))
return { killed: false };
try {
process.kill(pid, "SIGTERM");
} catch {
return { killed: false };
}
const start = Date.now();
while (Date.now() - start < 3000) {
if (!isProcessAlive(pid))
return { killed: true, signal: "SIGTERM" };
await new Promise((resolve) => setTimeout(resolve, 200));
}
try {
process.kill(pid, "SIGKILL");
} catch {
return { killed: !isProcessAlive(pid), signal: "SIGTERM" };
}
await new Promise((resolve) => setTimeout(resolve, 500));
return { killed: !isProcessAlive(pid), signal: "SIGKILL" };
}
function cleanupConsoleFiles() {
const removed = [];
for (const [filePath, label] of [
[CONSOLE_PORT_FILE, "console.port"],
[CONSOLE_SOCKET_PATH, "console.sock"],
[CONSOLE_LOCK_FILE, "console.lock"]
]) {
if (existsSync(filePath)) {
try {
unlinkSync(filePath);
removed.push(label);
} catch {}
}
}
return removed;
}
async function shutdownDevConsole(opts) {
const { port, pid } = opts;
const shutdown = await shutdownConsole(port);
if (shutdown.result === "success" && await waitForConsoleDead(port)) {
cleanupConsoleFiles();
return { stopped: true, method: "graceful" };
}
if (pid && pid > 0 && (await forceKillConsole(pid)).killed) {
cleanupConsoleFiles();
return { stopped: true, method: "forced" };
}
return { stopped: false, method: "failed" };
}
export { shutdownConsole, waitForConsoleDead, forceKillConsole, cleanupConsoleFiles, shutdownDevConsole };