chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,209 @@
|
||||
import fs from "node:fs";
|
||||
import net from "node:net";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import process from "node:process";
|
||||
import { spawn } from "node:child_process";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { createBrokerEndpoint, parseBrokerEndpoint } from "./broker-endpoint.mjs";
|
||||
import { resolveStateDir } from "./state.mjs";
|
||||
|
||||
export const PID_FILE_ENV = "CODEX_COMPANION_APP_SERVER_PID_FILE";
|
||||
export const LOG_FILE_ENV = "CODEX_COMPANION_APP_SERVER_LOG_FILE";
|
||||
const BROKER_STATE_FILE = "broker.json";
|
||||
|
||||
export function createBrokerSessionDir(prefix = "cxc-") {
|
||||
return fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
||||
}
|
||||
|
||||
function connectToEndpoint(endpoint) {
|
||||
const target = parseBrokerEndpoint(endpoint);
|
||||
return net.createConnection({ path: target.path });
|
||||
}
|
||||
|
||||
export async function waitForBrokerEndpoint(endpoint, timeoutMs = 2000) {
|
||||
const start = Date.now();
|
||||
while (Date.now() - start < timeoutMs) {
|
||||
const ready = await new Promise((resolve) => {
|
||||
const socket = connectToEndpoint(endpoint);
|
||||
socket.on("connect", () => {
|
||||
socket.end();
|
||||
resolve(true);
|
||||
});
|
||||
socket.on("error", () => resolve(false));
|
||||
});
|
||||
if (ready) {
|
||||
return true;
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export async function sendBrokerShutdown(endpoint) {
|
||||
await new Promise((resolve) => {
|
||||
const socket = connectToEndpoint(endpoint);
|
||||
socket.setEncoding("utf8");
|
||||
socket.on("connect", () => {
|
||||
socket.write(`${JSON.stringify({ id: 1, method: "broker/shutdown", params: {} })}\n`);
|
||||
});
|
||||
socket.on("data", () => {
|
||||
socket.end();
|
||||
resolve();
|
||||
});
|
||||
socket.on("error", resolve);
|
||||
socket.on("close", resolve);
|
||||
});
|
||||
}
|
||||
|
||||
export function spawnBrokerProcess({ scriptPath, cwd, endpoint, pidFile, logFile, env = process.env }) {
|
||||
const logFd = fs.openSync(logFile, "a");
|
||||
const child = spawn(process.execPath, [scriptPath, "serve", "--endpoint", endpoint, "--cwd", cwd, "--pid-file", pidFile], {
|
||||
cwd,
|
||||
env,
|
||||
detached: true,
|
||||
stdio: ["ignore", logFd, logFd]
|
||||
});
|
||||
child.unref();
|
||||
fs.closeSync(logFd);
|
||||
return child;
|
||||
}
|
||||
|
||||
function resolveBrokerStateFile(cwd) {
|
||||
return path.join(resolveStateDir(cwd), BROKER_STATE_FILE);
|
||||
}
|
||||
|
||||
export function loadBrokerSession(cwd) {
|
||||
const stateFile = resolveBrokerStateFile(cwd);
|
||||
if (!fs.existsSync(stateFile)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(stateFile, "utf8"));
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function saveBrokerSession(cwd, session) {
|
||||
const stateDir = resolveStateDir(cwd);
|
||||
fs.mkdirSync(stateDir, { recursive: true });
|
||||
fs.writeFileSync(resolveBrokerStateFile(cwd), `${JSON.stringify(session, null, 2)}\n`, "utf8");
|
||||
}
|
||||
|
||||
export function clearBrokerSession(cwd) {
|
||||
const stateFile = resolveBrokerStateFile(cwd);
|
||||
if (fs.existsSync(stateFile)) {
|
||||
fs.unlinkSync(stateFile);
|
||||
}
|
||||
}
|
||||
|
||||
async function isBrokerEndpointReady(endpoint) {
|
||||
if (!endpoint) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return await waitForBrokerEndpoint(endpoint, 150);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function ensureBrokerSession(cwd, options = {}) {
|
||||
const existing = loadBrokerSession(cwd);
|
||||
if (existing && (await isBrokerEndpointReady(existing.endpoint))) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
if (existing) {
|
||||
teardownBrokerSession({
|
||||
endpoint: existing.endpoint ?? null,
|
||||
pidFile: existing.pidFile ?? null,
|
||||
logFile: existing.logFile ?? null,
|
||||
sessionDir: existing.sessionDir ?? null,
|
||||
pid: existing.pid ?? null,
|
||||
killProcess: options.killProcess ?? null
|
||||
});
|
||||
clearBrokerSession(cwd);
|
||||
}
|
||||
|
||||
const sessionDir = createBrokerSessionDir();
|
||||
const endpointFactory = options.createBrokerEndpoint ?? createBrokerEndpoint;
|
||||
const endpoint = endpointFactory(sessionDir, options.platform);
|
||||
const pidFile = path.join(sessionDir, "broker.pid");
|
||||
const logFile = path.join(sessionDir, "broker.log");
|
||||
const scriptPath =
|
||||
options.scriptPath ??
|
||||
fileURLToPath(new URL("../app-server-broker.mjs", import.meta.url));
|
||||
|
||||
const child = spawnBrokerProcess({
|
||||
scriptPath,
|
||||
cwd,
|
||||
endpoint,
|
||||
pidFile,
|
||||
logFile,
|
||||
env: options.env ?? process.env
|
||||
});
|
||||
|
||||
const ready = await waitForBrokerEndpoint(endpoint, options.timeoutMs ?? 2000);
|
||||
if (!ready) {
|
||||
teardownBrokerSession({
|
||||
endpoint,
|
||||
pidFile,
|
||||
logFile,
|
||||
sessionDir,
|
||||
pid: child.pid ?? null,
|
||||
killProcess: options.killProcess ?? null
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
const session = {
|
||||
endpoint,
|
||||
pidFile,
|
||||
logFile,
|
||||
sessionDir,
|
||||
pid: child.pid ?? null
|
||||
};
|
||||
saveBrokerSession(cwd, session);
|
||||
return session;
|
||||
}
|
||||
|
||||
export function teardownBrokerSession({ endpoint = null, pidFile, logFile, sessionDir = null, pid = null, killProcess = null }) {
|
||||
if (Number.isFinite(pid) && killProcess) {
|
||||
try {
|
||||
killProcess(pid);
|
||||
} catch {
|
||||
// Ignore missing or already-exited broker processes.
|
||||
}
|
||||
}
|
||||
|
||||
if (pidFile && fs.existsSync(pidFile)) {
|
||||
fs.unlinkSync(pidFile);
|
||||
}
|
||||
|
||||
if (logFile && fs.existsSync(logFile)) {
|
||||
fs.unlinkSync(logFile);
|
||||
}
|
||||
|
||||
if (endpoint) {
|
||||
try {
|
||||
const target = parseBrokerEndpoint(endpoint);
|
||||
if (target.kind === "unix" && fs.existsSync(target.path)) {
|
||||
fs.unlinkSync(target.path);
|
||||
}
|
||||
} catch {
|
||||
// Ignore malformed or already-removed broker endpoints during teardown.
|
||||
}
|
||||
}
|
||||
|
||||
const resolvedSessionDir = sessionDir ?? (pidFile ? path.dirname(pidFile) : logFile ? path.dirname(logFile) : null);
|
||||
if (resolvedSessionDir && fs.existsSync(resolvedSessionDir)) {
|
||||
try {
|
||||
fs.rmdirSync(resolvedSessionDir);
|
||||
} catch {
|
||||
// Ignore non-empty or missing directories.
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user