"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.__tsfTypeAliases = exports.TSF_DEV_RUNNER_PID_ENV = exports.TSF_DEV_RUN_ID_ENV = exports.TSF_DEV_STATE_FILE_ENV = void 0;
exports.getDevStatePaths = getDevStatePaths;
exports.readDevState = readDevState;
exports.updateDevState = updateDevState;
exports.pruneDevState = pruneDevState;
exports.registerDevRun = registerDevRun;
exports.unregisterDevRun = unregisterDevRun;
exports.publishDevConsoleEndpoint = publishDevConsoleEndpoint;
exports.getLoopbackDevConsoleUrl = getLoopbackDevConsoleUrl;
exports.isPidAlive = isPidAlive;
const node_crypto_1 = require("node:crypto");
const node_fs_1 = require("node:fs");
const node_os_1 = require("node:os");
const node_path_1 = require("node:path");
exports.TSF_DEV_STATE_FILE_ENV = 'TSF_DEV_STATE_FILE';
exports.TSF_DEV_RUN_ID_ENV = 'TSF_DEV_RUN_ID';
exports.TSF_DEV_RUNNER_PID_ENV = 'TSF_DEV_RUNNER_PID';
function getDevStatePaths(projectDir) {
    const canonicalDir = canonicalProjectDirectory(projectDir);
    const projectHash = (0, node_crypto_1.createHash)('md5').update(canonicalDir).digest('hex').slice(0, 12);
    return {
        stateFile: (0, node_path_1.resolve)((0, node_os_1.tmpdir)(), `tsf-dev-${projectHash}.json`),
        buildLockFile: (0, node_path_1.resolve)((0, node_os_1.tmpdir)(), `tsf-dev-${projectHash}.lock`)
    };
}
function readDevState(stateFile) {
    try {
        return normalizeDevState(JSON.parse((0, node_fs_1.readFileSync)(stateFile, 'utf8')));
    }
    catch {
        return undefined;
    }
}
function updateDevState(stateFile, update) {
    return withStateLock(stateFile, () => {
        const state = readDevState(stateFile) ?? emptyDevState();
        update(state);
        writeDevState(stateFile, state);
        return state;
    });
}
function pruneDevState(stateFile) {
    if (!(0, node_fs_1.existsSync)(stateFile))
        return undefined;
    return updateDevState(stateFile, state => {
        state.pids = state.pids.filter(isPidAlive);
        for (const [runId, run] of Object.entries(state.runs)) {
            const runnerAlive = isPidAlive(run.runnerPid);
            const appAlive = isPidAlive(run.appPid);
            if (!appAlive) {
                delete run.appPid;
                delete run.devConsoleUrl;
            }
            if (!runnerAlive && !appAlive)
                delete state.runs[runId];
        }
    });
}
function registerDevRun(stateFile, run) {
    updateDevState(stateFile, state => {
        state.pids = state.pids.filter(isPidAlive);
        if (!state.pids.includes(run.runnerPid))
            state.pids.push(run.runnerPid);
        state.runs[run.runId] = run;
    });
}
function unregisterDevRun(stateFile, runId, runnerPid) {
    updateDevState(stateFile, state => {
        const run = state.runs[runId];
        if (run?.runnerPid === runnerPid)
            delete state.runs[runId];
        state.pids = state.pids.filter(pid => pid !== runnerPid && isPidAlive(pid));
    });
}
function publishDevConsoleEndpoint(server) {
    const stateFile = process.env[exports.TSF_DEV_STATE_FILE_ENV];
    const runId = process.env[exports.TSF_DEV_RUN_ID_ENV];
    const runnerPid = Number(process.env[exports.TSF_DEV_RUNNER_PID_ENV]);
    const devConsoleUrl = getLoopbackDevConsoleUrl(server);
    if (!stateFile || !runId || !Number.isSafeInteger(runnerPid) || runnerPid <= 0 || !devConsoleUrl)
        return undefined;
    const appPid = process.pid;
    const now = Date.now();
    updateDevState(stateFile, state => {
        const existing = state.runs[runId];
        state.pids = state.pids.filter(isPidAlive);
        if (!state.pids.includes(runnerPid))
            state.pids.push(runnerPid);
        state.runs[runId] = {
            runId,
            runnerPid,
            appPid,
            script: existing?.script ?? '.',
            command: existing?.command ?? [],
            devConsoleUrl,
            startedAt: existing?.startedAt ?? now,
            updatedAt: now
        };
    });
    return () => {
        updateDevState(stateFile, state => {
            const run = state.runs[runId];
            if (run?.appPid !== appPid)
                return;
            delete run.appPid;
            delete run.devConsoleUrl;
            run.updatedAt = Date.now();
        });
    };
}
function getLoopbackDevConsoleUrl(server) {
    const address = server.address();
    if (!address || typeof address === 'string')
        return undefined;
    let host;
    switch (address.address) {
        case '::':
        case '::1':
            host = '[::1]';
            break;
        case '0.0.0.0':
        case '127.0.0.1':
        case '::ffff:127.0.0.1':
            host = '127.0.0.1';
            break;
        default:
            return undefined;
    }
    return `ws://${host}:${address.port}/_devconsole/ws`;
}
function isPidAlive(pid) {
    if (!pid || !Number.isSafeInteger(pid) || pid <= 0)
        return false;
    try {
        process.kill(pid, 0);
        return true;
    }
    catch {
        return false;
    }
}
function emptyDevState() {
    return { version: 2, ready: false, pids: [], runs: {} };
}
function normalizeDevState(value) {
    if (!value || typeof value !== 'object')
        throw new Error('Invalid tsf-dev state');
    const raw = value;
    const pids = Array.isArray(raw.pids) ? raw.pids.filter(pid => Number.isSafeInteger(pid) && pid > 0) : [];
    const runs = {};
    if (raw.runs && typeof raw.runs === 'object') {
        for (const [runId, candidate] of Object.entries(raw.runs)) {
            if (!candidate || typeof candidate !== 'object')
                continue;
            const run = candidate;
            if (!Number.isSafeInteger(run.runnerPid) || !run.runnerPid || run.runnerPid <= 0)
                continue;
            runs[runId] = {
                runId,
                runnerPid: run.runnerPid,
                appPid: Number.isSafeInteger(run.appPid) && run.appPid > 0 ? run.appPid : undefined,
                script: typeof run.script === 'string' ? run.script : '.',
                command: Array.isArray(run.command) ? run.command.filter(item => typeof item === 'string') : [],
                devConsoleUrl: typeof run.devConsoleUrl === 'string' ? run.devConsoleUrl : undefined,
                startedAt: typeof run.startedAt === 'number' ? run.startedAt : 0,
                updatedAt: typeof run.updatedAt === 'number' ? run.updatedAt : 0
            };
        }
    }
    return {
        version: 2,
        ready: raw.ready === true,
        pids,
        runs
    };
}
function writeDevState(stateFile, state) {
    (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(stateFile), { recursive: true });
    const temporary = `${stateFile}.${process.pid}.${(0, node_crypto_1.randomUUID)()}.tmp`;
    try {
        (0, node_fs_1.writeFileSync)(temporary, JSON.stringify(state), { mode: 0o600 });
        (0, node_fs_1.renameSync)(temporary, stateFile);
    }
    finally {
        try {
            (0, node_fs_1.unlinkSync)(temporary);
        }
        catch {
            // The atomic rename normally removes the temporary path.
        }
    }
}
function withStateLock(stateFile, callback) {
    const lockFile = `${stateFile}.state-lock`;
    const startedAt = Date.now();
    while (true) {
        try {
            (0, node_fs_1.writeFileSync)(lockFile, String(process.pid), { flag: 'wx', mode: 0o600 });
            break;
        }
        catch (error) {
            if (error.code !== 'EEXIST')
                throw error;
            removeStaleLock(lockFile);
            if (Date.now() - startedAt >= 2000)
                throw new Error(`Timed out waiting for tsf-dev state lock: ${lockFile}`);
            sleepSync(10);
        }
    }
    try {
        return callback();
    }
    finally {
        try {
            (0, node_fs_1.unlinkSync)(lockFile);
        }
        catch {
            // Ignore cleanup races with stale-lock recovery.
        }
    }
}
function removeStaleLock(lockFile) {
    try {
        const owner = (0, node_fs_1.readFileSync)(lockFile, 'utf8').trim();
        const ownerPid = Number(owner);
        if (owner && isPidAlive(ownerPid))
            return;
        if (!owner && Date.now() - (0, node_fs_1.statSync)(lockFile).mtimeMs < 100)
            return;
        (0, node_fs_1.unlinkSync)(lockFile);
    }
    catch {
        try {
            (0, node_fs_1.unlinkSync)(lockFile);
        }
        catch {
            // Another process may have released it already.
        }
    }
}
function sleepSync(milliseconds) {
    Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, milliseconds);
}
function canonicalProjectDirectory(projectDir) {
    try {
        return (0, node_fs_1.realpathSync)(projectDir);
    }
    catch {
        return (0, node_path_1.resolve)(projectDir);
    }
}
exports.__tsfTypeAliases = JSON.parse("[1,{\"DevRunState\":{\"kind\":18,\"typeName\":\"DevRunState\",\"types\":[{\"kind\":20,\"name\":\"runId\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"runnerPid\",\"type\":{\"kind\":7},\"optional\":false},{\"kind\":20,\"name\":\"appPid\",\"type\":{\"kind\":12,\"types\":[{\"kind\":7},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"script\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"command\",\"type\":{\"kind\":14,\"type\":{\"kind\":6}},\"optional\":false},{\"kind\":20,\"name\":\"devConsoleUrl\",\"type\":{\"kind\":12,\"types\":[{\"kind\":6},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"startedAt\",\"type\":{\"kind\":7},\"optional\":false},{\"kind\":20,\"name\":\"updatedAt\",\"type\":{\"kind\":7},\"optional\":false}]},\"DevState\":{\"kind\":18,\"typeName\":\"DevState\",\"types\":[{\"kind\":20,\"name\":\"version\",\"type\":{\"kind\":10,\"literal\":2},\"optional\":false},{\"kind\":20,\"name\":\"ready\",\"type\":{\"kind\":8},\"optional\":false},{\"kind\":20,\"name\":\"pids\",\"type\":{\"kind\":14,\"type\":{\"kind\":7}},\"optional\":false},{\"kind\":20,\"name\":\"runs\",\"type\":{\"kind\":18,\"typeName\":\"Record\",\"utilityType\":\"Record\",\"typeArguments\":[{\"kind\":6},{\"kind\":18,\"typeName\":\"DevRunState\",\"types\":[{\"kind\":20,\"name\":\"runId\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"runnerPid\",\"type\":{\"kind\":7},\"optional\":false},{\"kind\":20,\"name\":\"appPid\",\"type\":{\"kind\":12,\"types\":[{\"kind\":7},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"script\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"command\",\"type\":{\"kind\":14,\"type\":{\"kind\":6}},\"optional\":false},{\"kind\":20,\"name\":\"devConsoleUrl\",\"type\":{\"kind\":12,\"types\":[{\"kind\":6},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"startedAt\",\"type\":{\"kind\":7},\"optional\":false},{\"kind\":20,\"name\":\"updatedAt\",\"type\":{\"kind\":7},\"optional\":false}]}],\"index\":{\"kind\":18,\"typeName\":\"DevRunState\",\"types\":[{\"kind\":20,\"name\":\"runId\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"runnerPid\",\"type\":{\"kind\":7},\"optional\":false},{\"kind\":20,\"name\":\"appPid\",\"type\":{\"kind\":12,\"types\":[{\"kind\":7},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"script\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"command\",\"type\":{\"kind\":14,\"type\":{\"kind\":6}},\"optional\":false},{\"kind\":20,\"name\":\"devConsoleUrl\",\"type\":{\"kind\":12,\"types\":[{\"kind\":6},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"startedAt\",\"type\":{\"kind\":7},\"optional\":false},{\"kind\":20,\"name\":\"updatedAt\",\"type\":{\"kind\":7},\"optional\":false}]},\"types\":[]},\"optional\":false}]},\"DevStatePaths\":{\"kind\":18,\"typeName\":\"DevStatePaths\",\"types\":[{\"kind\":20,\"name\":\"stateFile\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"buildLockFile\",\"type\":{\"kind\":6},\"optional\":false}]}}]")[1];
//# sourceMappingURL=dev-state.js.map