"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.splitPackageScriptCommands = splitPackageScriptCommands;
exports.runPackageScriptCommand = runPackageScriptCommand;
exports.runRepositoryPackageBinary = runRepositoryPackageBinary;
exports.runInlineNodeCommand = runInlineNodeCommand;
exports.runLocalNodeTests = runLocalNodeTests;
exports.resolveFilteredWorkspacePackageRoot = resolveFilteredWorkspacePackageRoot;
exports.runLocalNodeScriptCommand = runLocalNodeScriptCommand;
exports.runNodeScriptInProcess = runNodeScriptInProcess;
exports.stripNodeShebang = stripNodeShebang;
exports.tokenizePackageScriptCommand = tokenizePackageScriptCommand;
exports.formatPreflightFailureOutput = formatPreflightFailureOutput;
// Responsibility-focused extraction from index-build-preflight.ts.
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = __importDefault(require("node:path"));
const node_child_process_1 = require("node:child_process");
const node_module_1 = require("node:module");
const index_build_preflight_evidence_1 = require("./index-build-preflight-evidence");
function splitPackageScriptCommands(script) {
    return script
        .split('&&')
        .map((command) => command.trim())
        .filter((command) => command.length > 0);
}
function runPackageScriptCommand(repositoryRoot, command, seenScripts) {
    const args = tokenizePackageScriptCommand(command);
    const executable = args[0];
    if (executable === 'node') {
        runInlineNodeCommand(repositoryRoot, args.slice(1));
        return;
    }
    if (executable === 'pnpm' && args[1] === 'run' && typeof args[2] === 'string') {
        (0, index_build_preflight_evidence_1.runPackageScript)(repositoryRoot, args[2], seenScripts);
        return;
    }
    if (executable === 'pnpm'
        && args[1] === '--filter'
        && typeof args[2] === 'string'
        && typeof args[3] === 'string'
        && args.length === 4) {
        const workspacePackageRoot = resolveFilteredWorkspacePackageRoot(repositoryRoot, args[2]);
        (0, index_build_preflight_evidence_1.runPackageScript)(workspacePackageRoot, args[3], seenScripts);
        return;
    }
    if (executable === 'tsc') {
        (0, node_child_process_1.execFileSync)(process.execPath, [require.resolve('typescript/bin/tsc'), ...args.slice(1)], {
            cwd: repositoryRoot,
            encoding: 'utf8',
            stdio: 'pipe',
            env: process.env,
        });
        return;
    }
    if (executable === 'eslint') {
        runRepositoryPackageBinary({
            repositoryRoot,
            packageName: 'eslint',
            binaryName: 'eslint',
            args: args.slice(1),
        });
        return;
    }
    throw new Error(`Unsupported release preflight package-script command: ${command}`);
}
function runRepositoryPackageBinary(options) {
    const repositoryRequire = (0, node_module_1.createRequire)(node_path_1.default.join(options.repositoryRoot, 'package.json'));
    const packageJsonPath = repositoryRequire.resolve(`${options.packageName}/package.json`);
    const packageRoot = node_path_1.default.dirname(packageJsonPath);
    const packageJson = JSON.parse(node_fs_1.default.readFileSync(packageJsonPath, 'utf8'));
    const declaredBinaryPath = typeof packageJson.bin === 'string'
        ? packageJson.bin
        : packageJson.bin?.[options.binaryName];
    if (typeof declaredBinaryPath !== 'string') {
        throw new Error(`Unsupported release preflight package binary: ${options.binaryName}`);
    }
    const relativeBinaryPath = declaredBinaryPath;
    const binaryPath = node_path_1.default.resolve(packageRoot, relativeBinaryPath);
    const relativePath = node_path_1.default.relative(packageRoot, binaryPath);
    if (relativePath === '' ||
        relativePath === '..' ||
        relativePath.startsWith(`..${node_path_1.default.sep}`) ||
        node_path_1.default.isAbsolute(relativePath) ||
        !node_fs_1.default.existsSync(binaryPath) ||
        !node_fs_1.default.statSync(binaryPath).isFile()) {
        throw new Error(`Unsupported release preflight package binary path: ${relativeBinaryPath}`);
    }
    (0, node_child_process_1.execFileSync)(process.execPath, [binaryPath, ...options.args], {
        cwd: options.repositoryRoot,
        encoding: 'utf8',
        stdio: 'pipe',
        env: process.env,
    });
}
function runInlineNodeCommand(repositoryRoot, args) {
    if (args[0] === '--test') {
        runLocalNodeTests(repositoryRoot, args.slice(1));
        return;
    }
    if (args[0] !== '-e' || typeof args[1] !== 'string') {
        runLocalNodeScriptCommand(repositoryRoot, args);
        return;
    }
    const previousCwd = process.cwd();
    const previousExit = process.exit;
    try {
        process.chdir(repositoryRoot);
        process.exit = ((code) => {
            const normalizedCode = typeof code === 'number' ? code : Number(code ?? 0);
            const exitError = new Error(`Inline node command exited with code ${normalizedCode}`);
            exitError.status = normalizedCode;
            throw exitError;
        });
        try {
            // Execute release preflight package scripts in-process so sandboxed test repositories do not need child-process execution permission.
            new Function('require', 'process', 'console', args[1])(require, process, console);
        }
        catch (error) {
            if (typeof error.status === 'number' && error.status === 0) {
                return;
            }
            throw error;
        }
    }
    finally {
        process.exit = previousExit;
        process.chdir(previousCwd);
    }
}
function runLocalNodeTests(repositoryRoot, patterns) {
    if (patterns.length === 0 || patterns.some((pattern) => pattern.startsWith('-'))) {
        throw new Error(`Unsupported release preflight node test command: node --test ${patterns.join(' ')}`);
    }
    const testPaths = patterns.flatMap((pattern) => expandRepositoryLocalGlob(repositoryRoot, pattern));
    if (testPaths.length === 0) {
        throw new Error(`Release preflight node test command matched no files: ${patterns.join(' ')}`);
    }
    const environment = { ...process.env };
    delete environment.NODE_TEST_CONTEXT;
    (0, node_child_process_1.execFileSync)(process.execPath, ['--test', ...testPaths], {
        cwd: repositoryRoot,
        encoding: 'utf8',
        stdio: 'pipe',
        env: environment,
    });
}
function resolveFilteredWorkspacePackageRoot(repositoryRoot, selector) {
    const workspacePath = node_path_1.default.join(repositoryRoot, 'pnpm-workspace.yaml');
    if (!node_fs_1.default.existsSync(workspacePath)) {
        throw new Error(`Unsupported release preflight workspace filter without pnpm-workspace.yaml: ${selector}`);
    }
    const patterns = node_fs_1.default.readFileSync(workspacePath, 'utf8').split(/\r?\n/u).flatMap((line) => {
        const match = line.match(/^\s*-\s+["']?([^"'#]+?)["']?\s*$/u);
        return match === null ? [] : [match[1].trim()];
    });
    const packageRoots = patterns.flatMap((pattern) => resolveWorkspacePattern(repositoryRoot, pattern));
    const matches = [...new Set(packageRoots)].filter((packageRoot) => {
        const packageJsonPath = node_path_1.default.join(packageRoot, 'package.json');
        if (!node_fs_1.default.existsSync(packageJsonPath))
            return false;
        const packageJson = JSON.parse(node_fs_1.default.readFileSync(packageJsonPath, 'utf8'));
        return packageJson.name === selector;
    });
    if (matches.length !== 1) {
        throw new Error(`Release preflight workspace filter '${selector}' resolved ${matches.length} packages; expected exactly one.`);
    }
    return matches[0];
}
function resolveWorkspacePattern(repositoryRoot, pattern) {
    if (pattern === '.')
        return [repositoryRoot];
    if (pattern.includes('*') && !pattern.endsWith('/*')) {
        throw new Error(`Unsupported release preflight workspace pattern: ${pattern}`);
    }
    const rawBase = pattern.endsWith('/*') ? pattern.slice(0, -2) : pattern;
    const basePath = resolveContainedRepositoryPath(repositoryRoot, rawBase, 'workspace pattern');
    if (!pattern.endsWith('/*'))
        return [basePath];
    if (!node_fs_1.default.existsSync(basePath) || !node_fs_1.default.statSync(basePath).isDirectory())
        return [];
    return node_fs_1.default.readdirSync(basePath, { withFileTypes: true })
        .filter((entry) => entry.isDirectory())
        .map((entry) => node_path_1.default.join(basePath, entry.name));
}
function expandRepositoryLocalGlob(repositoryRoot, pattern) {
    const normalizedPattern = pattern.split(node_path_1.default.sep).join('/');
    const directoryPart = node_path_1.default.posix.dirname(normalizedPattern);
    const basenamePattern = node_path_1.default.posix.basename(normalizedPattern);
    const directoryPath = resolveContainedRepositoryPath(repositoryRoot, directoryPart, 'node test pattern');
    if (!node_fs_1.default.existsSync(directoryPath) || !node_fs_1.default.statSync(directoryPath).isDirectory())
        return [];
    if (!/[?*]/u.test(basenamePattern)) {
        const filePath = resolveContainedRepositoryPath(repositoryRoot, normalizedPattern, 'node test path');
        return node_fs_1.default.existsSync(filePath) && node_fs_1.default.statSync(filePath).isFile()
            ? [node_path_1.default.relative(repositoryRoot, filePath)]
            : [];
    }
    const expression = new RegExp(`^${basenamePattern
        .replace(/[.+^${}()|[\]\\]/gu, '\\$&')
        .replaceAll('*', '.*')
        .replaceAll('?', '.')}$`, 'u');
    return node_fs_1.default.readdirSync(directoryPath, { withFileTypes: true })
        .filter((entry) => entry.isFile() && expression.test(entry.name))
        .map((entry) => node_path_1.default.relative(repositoryRoot, node_path_1.default.join(directoryPath, entry.name)))
        .sort((left, right) => left.localeCompare(right, 'en'));
}
function resolveContainedRepositoryPath(repositoryRoot, relativePath, label) {
    const resolvedPath = node_path_1.default.resolve(repositoryRoot, relativePath);
    const repositoryRelativePath = node_path_1.default.relative(repositoryRoot, resolvedPath);
    if (repositoryRelativePath === '..'
        || repositoryRelativePath.startsWith(`..${node_path_1.default.sep}`)
        || node_path_1.default.isAbsolute(repositoryRelativePath)) {
        throw new Error(`Unsupported release preflight ${label} outside repository: ${relativePath}`);
    }
    return resolvedPath;
}
function runLocalNodeScriptCommand(repositoryRoot, args) {
    const scriptPath = args[0];
    if (typeof scriptPath !== 'string' || scriptPath.trim().length === 0) {
        throw new Error(`Unsupported release preflight node command: node ${args.join(' ')}`);
    }
    if (scriptPath.startsWith('-')) {
        throw new Error(`Unsupported release preflight node command: node ${args.join(' ')}`);
    }
    const resolvedScriptPath = node_path_1.default.resolve(repositoryRoot, scriptPath);
    const relativeScriptPath = node_path_1.default.relative(repositoryRoot, resolvedScriptPath);
    if (relativeScriptPath === '' ||
        relativeScriptPath === '..' ||
        relativeScriptPath.startsWith(`..${node_path_1.default.sep}`) ||
        node_path_1.default.isAbsolute(relativeScriptPath)) {
        throw new Error(`Unsupported release preflight node script outside repository: ${scriptPath}`);
    }
    if (!node_fs_1.default.existsSync(resolvedScriptPath) || !node_fs_1.default.statSync(resolvedScriptPath).isFile()) {
        throw new Error(`Unsupported release preflight node script missing: ${scriptPath}`);
    }
    (0, node_child_process_1.execFileSync)(process.execPath, [resolvedScriptPath, ...args.slice(1)], {
        cwd: repositoryRoot,
        env: process.env,
        stdio: 'inherit',
    });
}
function runNodeScriptInProcess(options) {
    const previousCwd = process.cwd();
    const previousArgv = process.argv;
    const previousExit = process.exit;
    try {
        process.chdir(options.repositoryRoot);
        process.argv = [process.execPath, options.scriptPath, ...options.args];
        process.exit = ((code) => {
            const normalizedCode = typeof code === 'number' ? code : Number(code ?? 0);
            const exitError = new Error(`Node script exited with code ${normalizedCode}`);
            exitError.status = normalizedCode;
            throw exitError;
        });
        try {
            const scriptSource = stripNodeShebang(node_fs_1.default.readFileSync(options.scriptPath, 'utf8'));
            const scriptModule = {
                exports: {},
            };
            new Function('require', 'module', 'exports', 'process', 'console', '__filename', '__dirname', scriptSource)((0, node_module_1.createRequire)(options.scriptPath), scriptModule, scriptModule.exports, process, console, options.scriptPath, node_path_1.default.dirname(options.scriptPath));
        }
        catch (error) {
            if (typeof error.status === 'number' && error.status === 0) {
                return;
            }
            throw error;
        }
    }
    finally {
        process.exit = previousExit;
        process.argv = previousArgv;
        process.chdir(previousCwd);
    }
}
function stripNodeShebang(source) {
    if (!source.startsWith('#!')) {
        return source;
    }
    const lineBreakIndex = source.indexOf('\n');
    if (lineBreakIndex === -1) {
        return '';
    }
    return source.slice(lineBreakIndex + 1);
}
function tokenizePackageScriptCommand(command) {
    const tokens = [];
    let current = '';
    let quote = null;
    for (let index = 0; index < command.length; index += 1) {
        const character = command[index];
        if (quote !== null) {
            if (character === quote) {
                quote = null;
            }
            else if (character === '\\' &&
                quote === '"' &&
                index + 1 < command.length &&
                ['"', '\\', '$', '`'].includes(command[index + 1])) {
                index += 1;
                current += command[index];
            }
            else {
                current += character;
            }
            continue;
        }
        if (character === '"' || character === "'") {
            quote = character;
            continue;
        }
        if (/\s/.test(character)) {
            if (current.length > 0) {
                tokens.push(current);
                current = '';
            }
            continue;
        }
        current += character;
    }
    if (quote !== null) {
        throw new Error(`Unterminated quote in package script command: ${command}`);
    }
    if (current.length > 0) {
        tokens.push(current);
    }
    return tokens;
}
function formatPreflightFailureOutput(error) {
    const output = [];
    const maybeOutput = error;
    if (typeof maybeOutput.message === 'string' && maybeOutput.message.trim().length > 0) {
        output.push(`Error: ${maybeOutput.message.trim()}`);
    }
    if (typeof maybeOutput.status === 'number') {
        output.push(`Exit code: ${maybeOutput.status}`);
    }
    if (typeof maybeOutput.stderr === 'string' && maybeOutput.stderr.trim().length > 0) {
        output.push(`stderr: ${maybeOutput.stderr.trim()}`);
    }
    if (typeof maybeOutput.stdout === 'string' && maybeOutput.stdout.trim().length > 0) {
        output.push(`stdout: ${maybeOutput.stdout.trim()}`);
    }
    return output;
}
