"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MAX_ZIP_SIZE_BYTES = void 0;
exports.isPythonRuntime = isPythonRuntime;
exports.isNodeRuntime = isNodeRuntime;
exports.resolveCodeLocation = resolveCodeLocation;
exports.resolveProjectPaths = resolveProjectPaths;
exports.ensureDirClean = ensureDirClean;
exports.copySourceTree = copySourceTree;
exports.ensureBinaryAvailable = ensureBinaryAvailable;
exports.runCommand = runCommand;
exports.createZipFromDir = createZipFromDir;
exports.enforceZipSizeLimit = enforceZipSizeLimit;
exports.resolveProjectPathsSync = resolveProjectPathsSync;
exports.resolveNodeProjectPaths = resolveNodeProjectPaths;
exports.resolveNodeProjectPathsSync = resolveNodeProjectPathsSync;
exports.ensureDirCleanSync = ensureDirCleanSync;
exports.copySourceTreeSync = copySourceTreeSync;
exports.ensureBinaryAvailableSync = ensureBinaryAvailableSync;
exports.createZipFromDirSync = createZipFromDirSync;
exports.enforceZipSizeLimitSync = enforceZipSizeLimitSync;
exports.convertWindowsScriptsToLinux = convertWindowsScriptsToLinux;
exports.convertWindowsScriptsToLinuxSync = convertWindowsScriptsToLinuxSync;
const constants_1 = require("../constants");
const types_1 = require("../errors/types");
const platform_1 = require("../utils/platform");
const subprocess_1 = require("../utils/subprocess");
const fflate_1 = require("fflate");
const fs_1 = require("fs");
const promises_1 = require("fs/promises");
const path_1 = require("path");
const promises_2 = require("stream/promises");
// ============================================================================
// Runtime Type Detection
// ============================================================================
/**
 * Check if the runtime version is a Python runtime.
 */
function isPythonRuntime(runtimeVersion) {
    return runtimeVersion.startsWith('PYTHON_');
}
/**
 * Check if the runtime version is a Node/TypeScript runtime.
 */
function isNodeRuntime(runtimeVersion) {
    return runtimeVersion.startsWith('NODE_');
}
const EXCLUDED_ENTRIES = new Set(['.git', '.venv', '__pycache__', '.pytest_cache', '.DS_Store', 'node_modules']);
exports.MAX_ZIP_SIZE_BYTES = 250 * 1024 * 1024;
/**
 * Resolve CodeLocation path relative to repository root
 * @param codeLocation Path from AgentEnvSpec.Runtime.CodeLocation
 * @param configBaseDir Path to agentcore directory
 * @returns Absolute path to code location
 */
function resolveCodeLocation(codeLocation, configBaseDir) {
    if ((0, path_1.isAbsolute)(codeLocation)) {
        // Absolute paths allowed but discouraged for checked-in configs
        return codeLocation;
    }
    // Resolve relative to repository root (parent of agentcore/)
    // If configBaseDir is /home/user/myproject/agentcore
    // then repository root is /home/user/myproject/
    const repositoryRoot = (0, path_1.dirname)(configBaseDir);
    return (0, path_1.resolve)(repositoryRoot, codeLocation);
}
async function pathExists(path) {
    try {
        await (0, promises_1.stat)(path);
        return true;
    }
    catch (error) {
        if (error.code === 'ENOENT') {
            return false;
        }
        throw error;
    }
}
async function findUp(fileName, startDir) {
    let current = (0, path_1.resolve)(startDir);
    const { root } = (0, path_1.parse)(current);
    while (true) {
        const candidate = (0, path_1.join)(current, fileName);
        if (await pathExists(candidate)) {
            return candidate;
        }
        if (current === root) {
            return null;
        }
        current = (0, path_1.dirname)(current);
    }
}
async function resolveProjectPaths(options = {}, agentName) {
    const startDir = options.projectRoot ? (0, path_1.resolve)(options.projectRoot) : process.cwd();
    const candidatePyproject = options.pyprojectPath
        ? (0, path_1.resolve)(options.pyprojectPath)
        : await findUp('pyproject.toml', startDir);
    if (!candidatePyproject || !(await pathExists(candidatePyproject))) {
        throw new types_1.MissingProjectFileError(options.pyprojectPath ?? (0, path_1.join)(startDir, 'pyproject.toml'));
    }
    const pyprojectPath = candidatePyproject;
    const projectRoot = options.projectRoot ? (0, path_1.resolve)(options.projectRoot) : (0, path_1.dirname)(pyprojectPath);
    const srcDir = (0, path_1.resolve)(projectRoot, options.srcDir ?? '.');
    const artifactDir = (0, path_1.resolve)(options.artifactDir ?? (0, path_1.join)(projectRoot, constants_1.CONFIG_DIR));
    // Simplified staging structure: <artifactDir>/<name>/staging
    const name = agentName ?? 'default';
    const buildDir = (0, path_1.join)(artifactDir, name);
    const stagingDir = (0, path_1.join)(buildDir, 'staging');
    const artifactsDir = artifactDir;
    return {
        projectRoot,
        srcDir,
        pyprojectPath,
        artifactDir,
        buildDir,
        stagingDir,
        artifactsDir,
    };
}
async function ensureDirClean(dir) {
    await (0, promises_1.rm)(dir, { recursive: true, force: true });
    await (0, promises_1.mkdir)(dir, { recursive: true });
}
async function copyEntry(source, destination, rootDir) {
    const stats = await (0, promises_1.stat)(source);
    if (stats.isDirectory()) {
        await (0, promises_1.mkdir)(destination, { recursive: true });
        const entries = await (0, promises_1.readdir)(source);
        for (const entry of entries) {
            if (EXCLUDED_ENTRIES.has(entry)) {
                continue;
            }
            if (entry === constants_1.CONFIG_DIR && (0, path_1.resolve)(source) === (0, path_1.resolve)(rootDir)) {
                continue;
            }
            await copyEntry((0, path_1.join)(source, entry), (0, path_1.join)(destination, entry), rootDir);
        }
        return;
    }
    const readStream = (0, fs_1.createReadStream)(source);
    const writeStream = (0, fs_1.createWriteStream)(destination);
    await (0, promises_2.pipeline)(readStream, writeStream);
}
async function copySourceTree(srcDir, destination) {
    if (!(await pathExists(srcDir))) {
        throw new types_1.MissingProjectFileError(srcDir);
    }
    await copyEntry(srcDir, destination, srcDir);
}
async function ensureBinaryAvailable(binary, installHint) {
    const checks = [
        () => (platform_1.isWindows ? (0, subprocess_1.checkSubprocess)('where', [binary]) : (0, subprocess_1.checkSubprocess)('which', [binary])),
        () => (0, subprocess_1.checkSubprocess)(binary, ['--version']),
        () => (0, subprocess_1.checkSubprocess)(binary, ['-v']),
    ];
    for (const check of checks) {
        if (await check()) {
            return;
        }
    }
    throw new types_1.MissingDependencyError(binary, installHint);
}
async function runCommand(command, args, cwd) {
    await (0, subprocess_1.runSubprocess)(command, args, { cwd });
}
async function createZipFromDir(sourceDir, outputZip) {
    await (0, promises_1.rm)(outputZip, { force: true });
    await (0, promises_1.mkdir)((0, path_1.dirname)(outputZip), { recursive: true });
    const files = await collectFiles(sourceDir, sourceDir);
    const zipped = (0, fflate_1.zipSync)(files);
    await (0, promises_1.writeFile)(outputZip, zipped);
}
async function collectFiles(directory, rootDir, basePath = '') {
    const result = {};
    const entries = await (0, promises_1.readdir)(directory, { withFileTypes: true });
    for (const entry of entries) {
        if (EXCLUDED_ENTRIES.has(entry.name))
            continue;
        if (entry.name === constants_1.CONFIG_DIR && (0, path_1.resolve)(directory) === (0, path_1.resolve)(rootDir))
            continue;
        const fullPath = (0, path_1.join)(directory, entry.name);
        const zipPath = basePath ? `${basePath}/${entry.name}` : entry.name;
        if (entry.isDirectory()) {
            Object.assign(result, await collectFiles(fullPath, rootDir, zipPath));
        }
        else if (entry.isFile()) {
            result[zipPath] = [await (0, promises_1.readFile)(fullPath), { level: 6 }];
        }
    }
    return result;
}
async function enforceZipSizeLimit(zipPath) {
    const { size } = await (0, promises_1.stat)(zipPath);
    if (size > exports.MAX_ZIP_SIZE_BYTES) {
        throw new types_1.ArtifactSizeError(exports.MAX_ZIP_SIZE_BYTES, size);
    }
    return size;
}
function pathExistsSync(path) {
    return (0, fs_1.existsSync)(path);
}
function findUpSync(fileName, startDir) {
    let current = (0, path_1.resolve)(startDir);
    const { root } = (0, path_1.parse)(current);
    while (true) {
        const candidate = (0, path_1.join)(current, fileName);
        if (pathExistsSync(candidate)) {
            return candidate;
        }
        if (current === root) {
            return null;
        }
        current = (0, path_1.dirname)(current);
    }
}
function resolveProjectPathsSync(options = {}, agentName) {
    const startDir = options.projectRoot ? (0, path_1.resolve)(options.projectRoot) : process.cwd();
    const candidatePyproject = options.pyprojectPath
        ? (0, path_1.resolve)(options.pyprojectPath)
        : findUpSync('pyproject.toml', startDir);
    if (!candidatePyproject || !pathExistsSync(candidatePyproject)) {
        throw new types_1.MissingProjectFileError(options.pyprojectPath ?? (0, path_1.join)(startDir, 'pyproject.toml'));
    }
    const pyprojectPath = candidatePyproject;
    const projectRoot = options.projectRoot ? (0, path_1.resolve)(options.projectRoot) : (0, path_1.dirname)(pyprojectPath);
    const srcDir = (0, path_1.resolve)(projectRoot, options.srcDir ?? '.');
    const artifactDir = (0, path_1.resolve)(options.artifactDir ?? (0, path_1.join)(projectRoot, constants_1.CONFIG_DIR));
    // Simplified staging structure: <artifactDir>/<name>/staging
    const name = agentName ?? 'default';
    const buildDir = (0, path_1.join)(artifactDir, name);
    const stagingDir = (0, path_1.join)(buildDir, 'staging');
    const artifactsDir = artifactDir;
    return {
        projectRoot,
        srcDir,
        pyprojectPath,
        artifactDir,
        buildDir,
        stagingDir,
        artifactsDir,
    };
}
/**
 * Resolve filesystem paths for a Node.js/TypeScript agent project during packaging.
 *
 * Locates the nearest package.json (equivalent of pyproject.toml for Python agents),
 * then derives:
 * - projectRoot: directory containing package.json
 * - srcDir: source directory (defaults to projectRoot)
 * - artifactDir: where CDK config and build outputs live (agentcore/ directory)
 * - buildDir/stagingDir: per-agent temp directories used during CodeZip packaging
 *
 * Note: `pyprojectPath` in the return type is reused from the Python ResolvedPaths
 * interface — for Node projects it points to package.json.
 */
async function resolveNodeProjectPaths(options = {}, agentName) {
    const startDir = options.projectRoot ? (0, path_1.resolve)(options.projectRoot) : process.cwd();
    const candidatePackageJson = await findUp('package.json', startDir);
    if (!candidatePackageJson || !(await pathExists(candidatePackageJson))) {
        throw new types_1.MissingProjectFileError((0, path_1.join)(startDir, 'package.json'));
    }
    const projectRoot = options.projectRoot ? (0, path_1.resolve)(options.projectRoot) : (0, path_1.dirname)(candidatePackageJson);
    const srcDir = (0, path_1.resolve)(projectRoot, options.srcDir ?? '.');
    const artifactDir = (0, path_1.resolve)(options.artifactDir ?? (0, path_1.join)(projectRoot, constants_1.CONFIG_DIR));
    const name = agentName ?? 'default';
    const buildDir = (0, path_1.join)(artifactDir, name);
    const stagingDir = (0, path_1.join)(buildDir, 'staging');
    const artifactsDir = artifactDir;
    return {
        projectRoot,
        srcDir,
        pyprojectPath: candidatePackageJson,
        artifactDir,
        buildDir,
        stagingDir,
        artifactsDir,
    };
}
/** Synchronous version of resolveNodeProjectPaths — used in contexts where async is not available. */
function resolveNodeProjectPathsSync(options = {}, agentName) {
    const startDir = options.projectRoot ? (0, path_1.resolve)(options.projectRoot) : process.cwd();
    const candidatePackageJson = findUpSync('package.json', startDir);
    if (!candidatePackageJson || !pathExistsSync(candidatePackageJson)) {
        throw new types_1.MissingProjectFileError((0, path_1.join)(startDir, 'package.json'));
    }
    const projectRoot = options.projectRoot ? (0, path_1.resolve)(options.projectRoot) : (0, path_1.dirname)(candidatePackageJson);
    const srcDir = (0, path_1.resolve)(projectRoot, options.srcDir ?? '.');
    const artifactDir = (0, path_1.resolve)(options.artifactDir ?? (0, path_1.join)(projectRoot, constants_1.CONFIG_DIR));
    const name = agentName ?? 'default';
    const buildDir = (0, path_1.join)(artifactDir, name);
    const stagingDir = (0, path_1.join)(buildDir, 'staging');
    const artifactsDir = artifactDir;
    return {
        projectRoot,
        srcDir,
        pyprojectPath: candidatePackageJson,
        artifactDir,
        buildDir,
        stagingDir,
        artifactsDir,
    };
}
function ensureDirCleanSync(dir) {
    (0, fs_1.rmSync)(dir, { recursive: true, force: true });
    (0, fs_1.mkdirSync)(dir, { recursive: true });
}
function copyEntrySync(source, destination, rootDir) {
    const stats = (0, fs_1.statSync)(source);
    if (stats.isDirectory()) {
        (0, fs_1.mkdirSync)(destination, { recursive: true });
        const entries = (0, fs_1.readdirSync)(source);
        for (const entry of entries) {
            if (EXCLUDED_ENTRIES.has(entry)) {
                continue;
            }
            if (entry === constants_1.CONFIG_DIR && (0, path_1.resolve)(source) === (0, path_1.resolve)(rootDir)) {
                continue;
            }
            copyEntrySync((0, path_1.join)(source, entry), (0, path_1.join)(destination, entry), rootDir);
        }
        return;
    }
    (0, fs_1.copyFileSync)(source, destination);
}
function copySourceTreeSync(srcDir, destination) {
    if (!pathExistsSync(srcDir)) {
        throw new types_1.MissingProjectFileError(srcDir);
    }
    copyEntrySync(srcDir, destination, srcDir);
}
function ensureBinaryAvailableSync(binary, installHint) {
    const checks = [
        () => (platform_1.isWindows ? (0, subprocess_1.checkSubprocessSync)('where', [binary]) : (0, subprocess_1.checkSubprocessSync)('which', [binary])),
        () => (0, subprocess_1.checkSubprocessSync)(binary, ['--version']),
        () => (0, subprocess_1.checkSubprocessSync)(binary, ['-v']),
    ];
    for (const check of checks) {
        if (check()) {
            return;
        }
    }
    throw new types_1.MissingDependencyError(binary, installHint);
}
function collectFilesSync(directory, rootDir, basePath = '') {
    const result = {};
    const entries = (0, fs_1.readdirSync)(directory, { withFileTypes: true });
    for (const entry of entries) {
        if (EXCLUDED_ENTRIES.has(entry.name))
            continue;
        if (entry.name === constants_1.CONFIG_DIR && (0, path_1.resolve)(directory) === (0, path_1.resolve)(rootDir))
            continue;
        const fullPath = (0, path_1.join)(directory, entry.name);
        const zipPath = basePath ? `${basePath}/${entry.name}` : entry.name;
        if (entry.isDirectory()) {
            Object.assign(result, collectFilesSync(fullPath, rootDir, zipPath));
        }
        else if (entry.isFile()) {
            result[zipPath] = [(0, fs_1.readFileSync)(fullPath), { level: 6 }];
        }
    }
    return result;
}
function createZipFromDirSync(sourceDir, outputZip) {
    (0, fs_1.rmSync)(outputZip, { force: true });
    (0, fs_1.mkdirSync)((0, path_1.dirname)(outputZip), { recursive: true });
    const files = collectFilesSync(sourceDir, sourceDir);
    const zipped = (0, fflate_1.zipSync)(files);
    (0, fs_1.writeFileSync)(outputZip, zipped);
}
function enforceZipSizeLimitSync(zipPath) {
    const { size } = (0, fs_1.statSync)(zipPath);
    if (size > exports.MAX_ZIP_SIZE_BYTES) {
        throw new types_1.ArtifactSizeError(exports.MAX_ZIP_SIZE_BYTES, size);
    }
    return size;
}
/**
 * Generates a Linux-compatible shell script for a Python console entry point.
 * This is needed when packaging on Windows for Linux targets, as uv generates
 * .exe files based on the host OS rather than the target platform.
 */
function generateLinuxConsoleScript(modulePath, funcName) {
    return `#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import re
import sys
from ${modulePath} import ${funcName}
if __name__ == '__main__':
    sys.argv[0] = re.sub(r'(-script\\.pyw|\\.exe)?$', '', sys.argv[0])
    sys.exit(${funcName}())
`;
}
/**
 * Known console script entry points that need to be converted from Windows .exe
 * to Linux shell scripts when cross-compiling.
 * Format: { 'script-name': ['module.path', 'function_name'] }
 */
const KNOWN_CONSOLE_SCRIPTS = {
    'opentelemetry-instrument': ['opentelemetry.instrumentation.auto_instrumentation', 'run'],
    'opentelemetry-bootstrap': ['opentelemetry.instrumentation.bootstrap', 'run'],
};
/**
 * Checks if a shebang line contains a hardcoded absolute path that won't work in Lambda.
 */
function isHardcodedShebang(shebang) {
    return /^#!\/(?:Users|home|opt|var|tmp)\/.*python/.test(shebang);
}
/**
 * Rewrites shebangs in console scripts to use portable #!/usr/bin/env python3.
 * This handles scripts installed on macOS/Linux that have hardcoded absolute paths.
 */
async function rewriteUnixShebangs(stagingDir) {
    const binDir = (0, path_1.join)(stagingDir, 'bin');
    if (!(await pathExists(binDir))) {
        return;
    }
    const entries = await (0, promises_1.readdir)(binDir);
    const portableShebang = '#!/usr/bin/env python3';
    for (const entry of entries) {
        const scriptPath = (0, path_1.join)(binDir, entry);
        const stats = await (0, promises_1.stat)(scriptPath);
        if (!stats.isFile()) {
            continue;
        }
        const content = await (0, promises_1.readFile)(scriptPath, 'utf-8');
        const firstLine = content.split('\n')[0] ?? '';
        if (firstLine && isHardcodedShebang(firstLine)) {
            const newContent = content.replace(firstLine, portableShebang);
            await (0, promises_1.writeFile)(scriptPath, newContent, { mode: 0o755 });
        }
    }
}
/**
 * Prepares console scripts for Linux deployment by:
 * - On Windows: Converting .exe files to Linux shell scripts
 * - On macOS/Linux: Rewriting hardcoded shebang paths to portable ones
 *
 * @param stagingDir The directory containing installed Python packages
 */
async function convertWindowsScriptsToLinux(stagingDir) {
    if (platform_1.isWindows) {
        const binDir = (0, path_1.join)(stagingDir, 'bin');
        if (!(await pathExists(binDir))) {
            return;
        }
        const entries = await (0, promises_1.readdir)(binDir);
        for (const entry of entries) {
            if (!entry.endsWith('.exe')) {
                continue;
            }
            const scriptName = entry.slice(0, -4); // Remove .exe extension
            const entryPoint = KNOWN_CONSOLE_SCRIPTS[scriptName];
            if (entryPoint) {
                const [modulePath, funcName] = entryPoint;
                const exePath = (0, path_1.join)(binDir, entry);
                const scriptPath = (0, path_1.join)(binDir, scriptName);
                // Remove the Windows .exe file
                await (0, promises_1.unlink)(exePath);
                // Create Linux-compatible shell script
                const scriptContent = generateLinuxConsoleScript(modulePath, funcName);
                await (0, promises_1.writeFile)(scriptPath, scriptContent, { mode: 0o755 });
            }
        }
    }
    else {
        await rewriteUnixShebangs(stagingDir);
    }
}
/**
 * Synchronous version of rewriteUnixShebangs.
 */
function rewriteUnixShebangsSync(stagingDir) {
    const binDir = (0, path_1.join)(stagingDir, 'bin');
    if (!pathExistsSync(binDir)) {
        return;
    }
    const entries = (0, fs_1.readdirSync)(binDir);
    const portableShebang = '#!/usr/bin/env python3';
    for (const entry of entries) {
        const scriptPath = (0, path_1.join)(binDir, entry);
        const stats = (0, fs_1.statSync)(scriptPath);
        if (!stats.isFile()) {
            continue;
        }
        const content = (0, fs_1.readFileSync)(scriptPath, 'utf-8');
        const firstLine = content.split('\n')[0] ?? '';
        if (firstLine && isHardcodedShebang(firstLine)) {
            const newContent = content.replace(firstLine, portableShebang);
            (0, fs_1.writeFileSync)(scriptPath, newContent, { mode: 0o755 });
        }
    }
}
/**
 * Synchronous version of convertWindowsScriptsToLinux.
 */
function convertWindowsScriptsToLinuxSync(stagingDir) {
    if (platform_1.isWindows) {
        const binDir = (0, path_1.join)(stagingDir, 'bin');
        if (!pathExistsSync(binDir)) {
            return;
        }
        const entries = (0, fs_1.readdirSync)(binDir);
        for (const entry of entries) {
            if (!entry.endsWith('.exe')) {
                continue;
            }
            const scriptName = entry.slice(0, -4); // Remove .exe extension
            const entryPoint = KNOWN_CONSOLE_SCRIPTS[scriptName];
            if (entryPoint) {
                const [modulePath, funcName] = entryPoint;
                const exePath = (0, path_1.join)(binDir, entry);
                const scriptPath = (0, path_1.join)(binDir, scriptName);
                // Remove the Windows .exe file
                (0, fs_1.unlinkSync)(exePath);
                // Create Linux-compatible shell script
                const scriptContent = generateLinuxConsoleScript(modulePath, funcName);
                (0, fs_1.writeFileSync)(scriptPath, scriptContent, { mode: 0o755 });
            }
        }
    }
    else {
        rewriteUnixShebangsSync(stagingDir);
    }
}
//# sourceMappingURL=helpers.js.map