#!/usr/bin/env node import { spawn } from "node:child_process"; import { mkdir } from "node:fs/promises"; import { join, resolve } from "node:path"; import { DatabaseSync } from "node:sqlite"; const [, , lockName, ...command] = process.argv; const lockRoot = resolve(process.cwd(), ".cache", "rea-command-locks"); const signalExitCodes = { SIGINT: 130, SIGTERM: 143 }; let database; let child; let forceTimer; let shutdownSignal; process.on("SIGINT", () => handleSignal("SIGINT")); process.on("SIGTERM", () => handleSignal("SIGTERM")); process.on("disconnect", handleDisconnect); process.on("message", (message) => { if (message === "SIGINT" || message === "SIGTERM") handleSignal(message); }); if (!process.connected) { // The public wrapper can disappear before ESM startup installs listeners. // A disconnected supervisor has no authority to start a new command. process.exitCode = signalExitCodes.SIGTERM; } else if ( typeof lockName !== "string" || !/^[a-z][a-z0-9-]*$/u.test(lockName) || command.length === 0 ) { console.error("Usage: run-exclusive.mjs [args...]"); process.exitCode = 64; disconnectParent(); } else { await run(); } async function run() { try { await mkdir(lockRoot, { recursive: true }); // SQLite owns acquisition and OS-backed lock release, including process // death. Keep the database pathname stable; deleting it would split owners. database = new DatabaseSync(join(lockRoot, `${lockName}.sqlite`)); try { database.exec("BEGIN EXCLUSIVE"); } catch (cause) { if (isBusyError(cause)) throw new Error( `Another ${lockName} command is already running. Wait for it to finish.`, { cause }, ); throw cause; } if (shutdownSignal === undefined) { const result = await runCommand(); process.exitCode = result.code ?? 1; } } catch (cause) { console.error(cause instanceof Error ? cause.message : String(cause)); process.exitCode = 1; } finally { database?.close(); clearTimeout(forceTimer); if (shutdownSignal !== undefined) process.exitCode = signalExitCodes[shutdownSignal]; disconnectParent(); } } function runCommand() { return new Promise((resolveResult, reject) => { child = spawn(command[0], command.slice(1), { cwd: process.cwd(), env: process.env, shell: process.platform === "win32", stdio: "inherit", }); child.once("error", reject); child.once("close", (code) => { // During POSIX cancellation the group leader must stay alive, retaining // both its group identity and SQLite ownership until the entire group is // killed. A completed command can still have uncooperative descendants. if (shutdownSignal === undefined || process.platform === "win32") resolveResult({ code }); }); }); } function disconnectParent() { process.off("disconnect", handleDisconnect); if (process.connected) process.disconnect(); } function handleDisconnect() { handleSignal("SIGTERM"); } function handleSignal(signal) { if (shutdownSignal !== undefined) return; shutdownSignal = signal; if (child === undefined || process.platform === "win32") return; // Establish cancellation before signalling the group. This supervisor is // its live leader, so the group identifier cannot be reused during cleanup. process.kill(-process.pid, signal); forceTimer = setTimeout(() => process.kill(-process.pid, "SIGKILL"), 1_000); } function isBusyError(cause) { return ( typeof cause === "object" && cause !== null && "code" in cause && cause.code === "ERR_SQLITE_ERROR" && "errcode" in cause && (cause.errcode === 5 || cause.errcode === 6) ); }