// engine/config-health/ci-checker.ts — SPEC-042
// CI/Build configuration health checks (language-agnostic).
import { join } from 'node:path';
import { glob } from 'glob';
import { collectProjectCommands, fileExists, readFileSafe } from './ci-commands-collector.js';
export { checkVersionConsistency } from './ci-version-checker.js';
// ---------------------------------------------------------------------------
// Check: CI commands referencing non-existent scripts
// ---------------------------------------------------------------------------
function extractCiRunCommands(content) {
    const commands = [];
    const runPattern = /^\s*-?\s*run\s*:\s*(.+)$/gm;
    let match;
    while ((match = runPattern.exec(content)) !== null) {
        const cmd = match[1]?.trim() ?? '';
        if (cmd) {
            commands.push(cmd);
        }
    }
    const scriptPattern = /^\s*-\s+([\w./]+\s+[\w./]+.*|[\w./]+\.sh.*)$/gm;
    while ((match = scriptPattern.exec(content)) !== null) {
        const cmd = match[1]?.trim() ?? '';
        if (cmd && !cmd.startsWith('uses:') && !cmd.startsWith('name:')) {
            commands.push(cmd);
        }
    }
    return commands;
}
export async function checkCiCommands(projectPath) {
    const findings = [];
    const ciFiles = [];
    try {
        const ghWorkflows = await glob('.github/workflows/*.{yml,yaml}', {
            cwd: projectPath,
            nodir: true,
        });
        ciFiles.push(...ghWorkflows);
    }
    catch {
        /* v8 ignore next 1 */
        // ignore
    }
    const gitlabCi = await readFileSafe(join(projectPath, '.gitlab-ci.yml'));
    if (gitlabCi) {
        ciFiles.push('.gitlab-ci.yml');
    }
    const bitbucketPipelines = await readFileSafe(join(projectPath, 'bitbucket-pipelines.yml'));
    if (bitbucketPipelines) {
        ciFiles.push('bitbucket-pipelines.yml');
    }
    if (ciFiles.length === 0) {
        return findings;
    }
    const projectCmds = await collectProjectCommands(projectPath);
    for (const ciFile of ciFiles) {
        const content = await readFileSafe(join(projectPath, ciFile));
        if (!content) {
            continue;
        }
        for (const cmd of extractCiRunCommands(content)) {
            const npmRunMatch = /(?:npm run|pnpm run|yarn run)\s+([\w:-]+)/.exec(cmd);
            if (npmRunMatch?.[1]) {
                const scriptName = npmRunMatch[1];
                if (!projectCmds.packageScripts.has(scriptName)) {
                    findings.push({
                        severity: 'error',
                        checker: 'ci-commands',
                        file: ciFile,
                        message: `CI step runs 'npm run ${scriptName}' but '${scriptName}' is not in package.json scripts`,
                        fix: `Add '${scriptName}' to package.json scripts or fix the CI command`,
                    });
                }
                continue;
            }
            const makeMatch = /^make\s+([\w-]+)/.exec(cmd);
            if (makeMatch?.[1]) {
                const target = makeMatch[1];
                if (!projectCmds.makefileTargets.has(target)) {
                    findings.push({
                        severity: 'warning',
                        checker: 'ci-commands',
                        file: ciFile,
                        message: `CI step runs 'make ${target}' but target '${target}' not found in Makefile`,
                        fix: `Add '${target}' target to Makefile or fix the CI command`,
                    });
                }
                continue;
            }
            const shellMatch = /(?:bash|sh)\s+(scripts\/[\w./]+\.sh)/.exec(cmd);
            if (shellMatch?.[1]) {
                const scriptPath = shellMatch[1];
                const exists = await fileExists(join(projectPath, scriptPath));
                if (!exists) {
                    findings.push({
                        severity: 'error',
                        checker: 'ci-commands',
                        file: ciFile,
                        message: `CI step runs '${scriptPath}' but the file does not exist`,
                        fix: `Create '${scriptPath}' or fix the CI command`,
                    });
                }
            }
        }
    }
    return findings;
}
// ---------------------------------------------------------------------------
// Check: build artifacts depending on config files not copied to dist
// ---------------------------------------------------------------------------
export async function checkBuildArtifacts(projectPath) {
    const findings = [];
    const hasDist = await fileExists(join(projectPath, 'dist', 'index.js'));
    const hasBuildSrc = await fileExists(join(projectPath, 'src', 'index.ts'));
    if (!hasBuildSrc) {
        return findings;
    }
    let configJsonFiles;
    try {
        configJsonFiles = await glob('src/config/**/*.json', { cwd: projectPath, nodir: true });
    }
    catch {
        /* v8 ignore next 1 */
        return findings;
    }
    if (configJsonFiles.length === 0) {
        return findings;
    }
    const pkgContent = await readFileSafe(join(projectPath, 'package.json'));
    let buildScript = '';
    if (pkgContent) {
        try {
            const pkg = JSON.parse(pkgContent);
            buildScript = pkg.scripts?.build ?? '';
        }
        catch {
            // ignore
        }
    }
    const hasCopyStep = buildScript.includes('cp ') ||
        buildScript.includes('copyfiles') ||
        buildScript.includes('cpy ') ||
        buildScript.includes('ncp ') ||
        buildScript.includes('config');
    if (hasDist && !hasCopyStep) {
        for (const configFile of configJsonFiles) {
            const distPath = configFile.replace(/^src\//, 'dist/');
            const existsInDist = await fileExists(join(projectPath, distPath));
            if (!existsInDist) {
                findings.push({
                    severity: 'error',
                    checker: 'build-artifacts',
                    file: configFile,
                    message: `Config file '${configFile}' is not present in '${distPath}' — will cause runtime crash when deployed`,
                    fix: `Add a copy step to the build script: e.g., 'cp -r src/config dist/config' or use 'copyfiles' package`,
                });
            }
        }
    }
    else if (!hasDist && !hasCopyStep) {
        findings.push({
            severity: 'warning',
            checker: 'build-artifacts',
            file: 'package.json',
            message: `${configJsonFiles.length} JSON config file(s) in src/config/ detected but build script has no copy step`,
            fix: `Add 'cp -r src/config dist/config' or similar to your build script to ensure configs are available at runtime`,
        });
    }
    return findings;
}
//# sourceMappingURL=ci-checker.js.map