#!/usr/bin/env node
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function __assignType(fn, args) {
    fn.__type = args;
    return fn;
}
const child_process_1 = require("child_process");
const fs_1 = require("fs");
const crypto_1 = require("crypto");
const os_1 = require("os");
const path_1 = require("path");
const promises_1 = require("timers/promises");
process.env.APP_ENV = process.env.APP_ENV || 'development';
function findProjectRoot() {
    let dir = process.cwd();
    while (true) {
        if ((0, fs_1.existsSync)((0, path_1.join)(dir, 'package.json')))
            return dir;
        const parent = (0, path_1.dirname)(dir);
        if (parent === dir) {
            console.error('Could not find package.json in any parent directory');
            process.exit(1);
        }
        dir = parent;
    }
}
findProjectRoot.__type = ['findProjectRoot', 'P&/!'];
const projectDir = findProjectRoot();
const tscPath = require.resolve('typescript/bin/tsc');
// --- Dev state coordination ---
const projectHash = (0, crypto_1.createHash)('md5').update(projectDir).digest('hex').substring(0, 12);
const devLockFile = (0, path_1.join)((0, os_1.tmpdir)(), `dksf-dev-${projectHash}.lock`);
const devStateFile = (0, path_1.join)((0, os_1.tmpdir)(), `dksf-dev-${projectHash}.json`);
const __ΩDevState = ['ready', 'pids', 'DevState', 'P)4!\'F4"Mw#y'];
function isPidAlive(pid) {
    try {
        process.kill(pid, 0);
        return true;
    }
    catch {
        return false;
    }
}
isPidAlive.__type = ['pid', 'isPidAlive', 'P\'2!)/"'];
function readDevState() {
    try {
        return JSON.parse((0, fs_1.readFileSync)(devStateFile, 'utf-8'));
    }
    catch {
        return null;
    }
}
readDevState.__type = [() => __ΩDevState, 'readDevState', 'PPn!,J/"'];
function writeDevState(state) {
    (0, fs_1.writeFileSync)(devStateFile, JSON.stringify(state));
}
writeDevState.__type = [() => __ΩDevState, 'state', 'writeDevState', 'Pn!2"$/#'];
function registerDevPid() {
    const state = readDevState() ?? { ready: true, pids: [] };
    state.pids = state.pids.filter(isPidAlive);
    if (!state.pids.includes(process.pid))
        state.pids.push(process.pid);
    writeDevState(state);
}
registerDevPid.__type = ['registerDevPid', 'P$/!'];
function unregisterDevPid() {
    const state = readDevState();
    if (!state)
        return;
    state.pids = state.pids.filter(__assignType(p => p !== process.pid && isPidAlive(p), ['p', '', 'P"2!"/"']));
    if (state.pids.length === 0) {
        try {
            (0, fs_1.unlinkSync)(devStateFile);
        }
        catch {
            // ignore
        }
    }
    else {
        writeDevState(state);
    }
}
unregisterDevPid.__type = ['unregisterDevPid', 'P$/!'];
function isDevRunning() {
    const state = readDevState();
    return !!state?.ready && state.pids.some(isPidAlive);
}
isDevRunning.__type = ['isDevRunning', 'P)/!'];
function tryAcquireBuildLock(retries = 5) {
    if (retries <= 0)
        return false;
    try {
        (0, fs_1.writeFileSync)(devLockFile, String(process.pid), { flag: 'wx' });
        return true;
    }
    catch (err) {
        if (err.code !== 'EEXIST')
            throw err;
        try {
            const pid = parseInt((0, fs_1.readFileSync)(devLockFile, 'utf-8'));
            if (!isPidAlive(pid)) {
                (0, fs_1.unlinkSync)(devLockFile);
                return tryAcquireBuildLock(retries - 1);
            }
        }
        catch {
            return tryAcquireBuildLock(retries - 1);
        }
        return false;
    }
}
tryAcquireBuildLock.__type = ['retries', 'tryAcquireBuildLock', 'P"2!)/"'];
function releaseBuildLock() {
    try {
        (0, fs_1.unlinkSync)(devLockFile);
    }
    catch {
        // ignore
    }
}
releaseBuildLock.__type = ['releaseBuildLock', 'P$/!'];
// --- Core operations ---
function extractTsconfigArg(args) {
    for (let i = 0; i < args.length; i++) {
        if (args[i] === '-p' || args[i] === '--tsconfig') {
            const val = args[i + 1];
            args.splice(i, 2);
            return val;
        }
        if (args[i].startsWith('-p=') || args[i].startsWith('--tsconfig=')) {
            const val = args[i].split('=', 2)[1];
            args.splice(i, 1);
            return val;
        }
    }
    return undefined;
}
extractTsconfigArg.__type = ['args', 'extractTsconfigArg', 'P&F2!P&-J/"'];
function clean() {
    console.log('Cleaning dist/ directory...');
    const distDir = (0, path_1.join)(projectDir, 'dist');
    const devconsoleDir = (0, path_1.join)(distDir, 'devconsole');
    const hasDevconsole = (0, fs_1.existsSync)(devconsoleDir);
    const devconsoleBackup = (0, path_1.join)(projectDir, '.devconsole-backup');
    if (hasDevconsole) {
        (0, fs_1.renameSync)(devconsoleDir, devconsoleBackup);
    }
    (0, fs_1.rmSync)(distDir, { recursive: true, force: true });
    if (hasDevconsole) {
        (0, fs_1.mkdirSync)(distDir, { recursive: true });
        (0, fs_1.renameSync)(devconsoleBackup, devconsoleDir);
    }
}
clean.__type = ['clean', 'P$/!'];
function tsc(tsconfig) {
    const result = (0, child_process_1.spawnSync)(process.execPath, [tscPath, '-p', tsconfig], {
        stdio: 'inherit',
        cwd: projectDir
    });
    if (result.status !== 0)
        process.exit(result.status ?? 1);
}
tsc.__type = ['tsconfig', 'tsc', 'P&2!$/"'];
function startTscWatchWithReadySignal(tsconfig) {
    const child = (0, child_process_1.spawn)(process.execPath, [tscPath, '-w', '--preserveWatchOutput', '-p', tsconfig], {
        stdio: ['inherit', 'pipe', 'inherit'],
        cwd: projectDir
    });
    const ready = (Promise.Ω = [['$']], new Promise(__assignType((resolve, reject) => {
        let resolved = false;
        child.stdout.on('data', __assignType((data) => {
            process.stdout.write(data);
            if (!resolved && data.toString().includes('Watching for file changes')) {
                resolved = true;
                resolve();
            }
        }, ['data', '', 'P!2!"/"']));
        child.on('close', __assignType(code => {
            if (!resolved)
                reject(new Error(`tsc exited with code ${code}`));
        }, ['code', '', 'P"2!"/"']));
    }, ['resolve', 'reject', '', 'P"2!"2""/#'])));
    return { child, ready };
}
startTscWatchWithReadySignal.__type = ['tsconfig', 'child', 'ready', 'startTscWatchWithReadySignal', 'P&2!P!4"$`4#M/$'];
async function performLockedWatchBuild(tsconfig) {
    console.log('Starting dev build...');
    clean();
    const { child, ready } = startTscWatchWithReadySignal(tsconfig);
    await ready;
    console.log('Dev build ready.');
    writeDevState({ ready: true, pids: [] });
    releaseBuildLock();
    return child;
}
performLockedWatchBuild.__type = ['tsconfig', 'performLockedWatchBuild', 'P&2!!`/"'];
// Returns tsc watch child process if this process is the builder, null otherwise
async function ensureDevBuild(tsconfig) {
    if (isDevRunning()) {
        console.log('Using existing dev build from another process.');
        return null;
    }
    if (tryAcquireBuildLock()) {
        return performLockedWatchBuild(tsconfig);
    }
    // Wait for another builder to finish
    console.log('Waiting for another dksf-dev process to finish building...');
    while (true) {
        await (0, promises_1.setTimeout)(200);
        if (readDevState()?.ready) {
            console.log('Build ready.');
            return null;
        }
        try {
            const pid = parseInt((0, fs_1.readFileSync)(devLockFile, 'utf-8'));
            if (!isPidAlive(pid)) {
                if (tryAcquireBuildLock()) {
                    return performLockedWatchBuild(tsconfig);
                }
            }
        }
        catch {
            if (readDevState()?.ready)
                return null;
            if (tryAcquireBuildLock()) {
                return performLockedWatchBuild(tsconfig);
            }
        }
    }
}
ensureDevBuild.__type = ['tsconfig', 'ensureDevBuild', 'P&2!P!,J`/"'];
// --- Subcommands ---
function cmdClean() {
    clean();
}
cmdClean.__type = ['cmdClean', 'P$/!'];
function cmdBuild(args) {
    const watch = args.includes('--watch');
    const tsconfig = extractTsconfigArg(args) ?? 'tsconfig.json';
    if (isDevRunning()) {
        console.log('Skipping clean due to running dev server.');
    }
    else {
        clean();
    }
    console.log('Building...');
    if (watch) {
        const child = (0, child_process_1.spawn)(process.execPath, [tscPath, '-w', '--preserveWatchOutput', '-p', tsconfig], {
            stdio: 'inherit',
            cwd: projectDir
        });
        process.on('SIGINT', () => { });
        process.on('SIGTERM', () => child.kill('SIGTERM'));
        child.on('close', __assignType(code => process.exit(code ?? 0), ['code', '', 'P"2!"/"']));
    }
    else {
        tsc(tsconfig);
        console.log('Build complete.');
    }
}
cmdBuild.__type = ['args', 'cmdBuild', 'P&F2!$/"'];
async function cmdRun(args) {
    const ddIdx = args.indexOf('--');
    const ownArgs = ddIdx >= 0 ? args.slice(0, ddIdx) : args;
    const childArgs = ddIdx >= 0 ? args.slice(ddIdx + 1) : ['server:start'];
    const debug = ownArgs.includes('--debug');
    const tsconfig = extractTsconfigArg(ownArgs) ?? 'tsconfig.json';
    const script = ownArgs.find(__assignType(a => !a.startsWith('-'), ['a', '', 'P"2!"/"'])) ?? '.';
    const tscChild = await ensureDevBuild(tsconfig);
    registerDevPid();
    process.on('exit', unregisterDevPid);
    const nodeArgs = ['--enable-source-maps', '--watch', '--watch-preserve-output'];
    const port = process.env.PORT;
    const inspectFlag = debug ? '--inspect-brk' : '--inspect';
    nodeArgs.push(port ? `${inspectFlag}=${parseInt(port) + 1000}` : inspectFlag);
    nodeArgs.push(script, ...childArgs);
    const child = (0, child_process_1.spawn)(process.execPath, nodeArgs, {
        stdio: 'inherit',
        cwd: projectDir
    });
    process.on('SIGINT', () => { });
    process.on('SIGTERM', () => {
        child.kill('SIGTERM');
        tscChild?.kill('SIGTERM');
    });
    child.on('close', __assignType(code => {
        tscChild?.kill();
        process.exit(code ?? 0);
    }, ['code', '', 'P"2!"/"']));
}
cmdRun.__type = ['args', 'cmdRun', 'P&F2!$`/"'];
function cmdMigrate(subcmd, args) {
    const debug = args.includes('--debug');
    const tsconfig = extractTsconfigArg(args) ?? 'tsconfig.json';
    if (!isDevRunning()) {
        console.log('Building...');
        clean();
        tsc(tsconfig);
        console.log('Build complete.');
    }
    else {
        console.log('Using existing dev build from another process.');
    }
    const deepkitCmd = subcmd === 'create'
        ? 'migration:create'
        : subcmd === 'reset'
            ? 'migration:reset'
            : subcmd === 'charset'
                ? 'migration:charset'
                : 'migration:run';
    const childArgs = [deepkitCmd];
    if (subcmd === 'create' || subcmd === 'charset') {
        const remaining = args.filter(__assignType(a => a !== '--debug', ['a', '', 'P"2!"/"']));
        childArgs.push(...remaining);
    }
    const inspectFlag = debug ? '--inspect-brk=9226' : '--inspect=9226';
    const child = (0, child_process_1.spawn)(process.execPath, ['--enable-source-maps', inspectFlag, '.', ...childArgs], {
        stdio: 'inherit',
        cwd: projectDir
    });
    child.on('close', __assignType(code => process.exit(code ?? 0), ['code', '', 'P"2!"/"']));
}
cmdMigrate.__type = ['subcmd', 'args', 'cmdMigrate', 'P&2!&F2"$/#'];
function cmdRepl(args) {
    const debug = args.includes('--debug');
    const tsconfig = extractTsconfigArg(args) ?? 'tsconfig.json';
    if (!isDevRunning()) {
        console.log('Building...');
        clean();
        tsc(tsconfig);
        console.log('Build complete.');
    }
    else {
        console.log('Using existing dev build from another process.');
    }
    const inspectFlag = debug ? '--inspect-brk=9227' : '--inspect=9227';
    const child = (0, child_process_1.spawn)(process.execPath, ['--enable-source-maps', inspectFlag, '.', 'repl'], {
        stdio: 'inherit',
        cwd: projectDir
    });
    child.on('close', __assignType(code => process.exit(code ?? 0), ['code', '', 'P"2!"/"']));
}
cmdRepl.__type = ['args', 'cmdRepl', 'P&F2!$/"'];
function cmdTest(args) {
    const debug = args.includes('--debug');
    const tsconfig = extractTsconfigArg(args) ?? 'tsconfig.test.json';
    const testArgs = args.filter(__assignType(a => a !== '--debug', ['a', '', 'P"2!"/"']));
    if (isDevRunning()) {
        console.log('Skipping clean due to running dev server.');
    }
    else {
        clean();
    }
    console.log('Building...');
    tsc(tsconfig);
    console.log('Build complete.');
    const inspectFlag = debug ? '--inspect-brk=9268' : '--inspect=9268';
    const child = (0, child_process_1.spawn)(process.execPath, [inspectFlag, (0, path_1.join)(__dirname, 'dksf-test.js'), ...testArgs], {
        stdio: 'inherit',
        cwd: projectDir
    });
    child.on('close', __assignType(code => process.exit(code ?? 0), ['code', '', 'P"2!"/"']));
}
cmdTest.__type = ['args', 'cmdTest', 'P&F2!$/"'];
// --- Entry ---
async function main() {
    const [cmd, ...args] = process.argv.slice(2);
    switch (cmd) {
        case 'clean':
            cmdClean();
            return;
        case 'build':
            cmdBuild(args);
            return;
        case 'run':
            await cmdRun(args);
            return;
        case 'migrate':
            cmdMigrate('run', args);
            return;
        case 'migrate:create':
            cmdMigrate('create', args);
            return;
        case 'migrate:reset':
            cmdMigrate('reset', args);
            return;
        case 'migrate:charset':
            cmdMigrate('charset', args);
            return;
        case 'repl':
            cmdRepl(args);
            return;
        case 'test':
            cmdTest(args);
            return;
        default:
            console.error(`Usage: dksf-dev <command> [options]

Commands:
  clean                        Remove the dist/ directory
  build [--watch]              Clean and build (--watch for watch mode)
  run [--debug] [script] [--]  Clean, build, and start with node --watch
  migrate [--debug]            Clean, build (if needed), and run migrations
  migrate:create [--debug]     Clean, build (if needed), and create a migration
  migrate:reset [--debug]      Clean, build (if needed), and reset migrations
  migrate:charset [--debug]    Clean, build (if needed), and standardize charset/collation
  repl [--debug]               Clean, build (if needed), and start a REPL
  test [--debug] [args...]     Clean, build tests, and run dksf-test

Common options:
  -p, --tsconfig <file>  TypeScript config file (default: tsconfig.json)

Run options:
  --debug      Use --inspect-brk instead of --inspect
  <script>     Entrypoint to run (default: ".", uses package.json "main")
  -- <args>    Arguments passed to the child process (default: server:start)

Environment:
  PORT         When set, inspect port is PORT+1000 (default: 9229)`);
            process.exit(1);
    }
}
main.__type = ['main', 'P$`/!'];
main().catch(__assignType(err => {
    console.error(err);
    process.exit(1);
}, ['err', '', 'P"2!"/"']));
//# sourceMappingURL=dksf-dev.js.map