core/lifecycle/invocation.lifecycle.ts

core/lifecycle/invocation.lifecycle.ts is a file in GovLab Quality. 62 lines of code and 13 definitions.

import type { ChildProcess } from "node:child_process";
import { execFileSync } from "node:child_process";
import process from "node:process";

const WINDOWS = "win32";
const SIGNAL_EXIT_BASE = 128;
const TERMINAL_SIGNALS: Readonly<Record<string, number>> = { SIGHUP: 1, SIGINT: 2, SIGTERM: 15 };

const live = new Set<ChildProcess>();
const hook = { installed: false };

const stillRunning = function stillRunning(child: ChildProcess): boolean {
    return child.pid !== undefined && child.exitCode === null && child.signalCode === null;
};

export const killTree = function killTree(child: ChildProcess): void {
    if (!stillRunning(child)) {
        return;
    }
    if (process.platform !== WINDOWS) {
        child.kill("SIGKILL");
        return;
    }
    try {
        execFileSync("taskkill", ["/PID", String(child.pid), "/T", "/F"], { stdio: "ignore" });
    } catch (error) {
        if (!child.kill("SIGKILL") && stillRunning(child)) {
            throw error;
        }
    }
};

export const supervisedCount = function supervisedCount(): number {
    return live.size;
};

export const reapSupervised = function reapSupervised(): number {
    const reaped = [...live].filter(stillRunning).length;
    for (const child of live) {
        killTree(child);
    }
    live.clear();
    return reaped;
};

const hookParentExit = function hookParentExit(): void {
    if (hook.installed) {
        return;
    }
    hook.installed = true;
    process.on("exit", () => {
        reapSupervised();
    });
    for (const [signal, number] of Object.entries(TERMINAL_SIGNALS)) {
        process.once(signal, () => {
            reapSupervised();
            process.exit(SIGNAL_EXIT_BASE + number);
        });
    }
};

export const superviseChild = function superviseChild(child: ChildProcess): void {
    hookParentExit();
    live.add(child);
    const release = (): void => {
        live.delete(child);
    };
    child.on("close", release);
    child.on("error", release);
};