// Planu — Env Var Scanner: file scanning logic (SPEC-025)
// Scans source files for env var usage and detects hardcoded secrets.
import { readFile, readdir } from 'node:fs/promises';
import { join, extname } from 'node:path';
// === Constants ===
export const SCAN_EXTENSIONS = new Set([
    '.ts',
    '.tsx',
    '.js',
    '.jsx',
    '.py',
    '.go',
    '.rs',
    '.java',
    '.rb',
    '.php',
]);
export const IGNORE_DIRS = new Set([
    'node_modules',
    'dist',
    'build',
    '.git',
    'vendor',
    '__pycache__',
    '.next',
    'coverage',
    'target',
]);
// === Directory and file scanning ===
export async function scanDirectory(dirPath, files, depth = 0) {
    if (depth > 5) {
        return;
    }
    let entries;
    try {
        entries = await readdir(dirPath);
    }
    catch {
        return;
    }
    for (const entry of entries) {
        if (IGNORE_DIRS.has(entry)) {
            continue;
        }
        const fullPath = join(dirPath, entry);
        const ext = extname(entry);
        if (SCAN_EXTENSIONS.has(ext)) {
            files.push(fullPath);
        }
        else if (!ext) {
            // Might be a directory — try recursing
            /* v8 ignore next 1 */
            await scanDirectory(fullPath, files, depth + 1);
        }
    }
}
export async function scanFileForEnvVars(filePath, patterns) {
    const found = [];
    try {
        const content = await readFile(filePath, 'utf-8');
        const lines = content.split('\n');
        lines.forEach((line, idx) => {
            for (const pattern of patterns) {
                pattern.lastIndex = 0;
                let match;
                while ((match = pattern.exec(line)) !== null) {
                    // Capture group 1 or 2 depending on pattern
                    const varName = match[2] ?? match[1];
                    if (varName && /^[A-Z_][A-Z0-9_]*$/.test(varName)) {
                        found.push({ name: varName, file: filePath, line: idx + 1 });
                    }
                }
            }
        });
    }
    catch {
        // File read error — skip
    }
    return found;
}
export async function scanFileForSecrets(filePath, secretPatterns, projectPath) {
    const found = [];
    // Never scan .env files themselves or test files
    if (filePath.includes('.env') || filePath.includes('.test.') || filePath.includes('.spec.')) {
        return found;
    }
    try {
        const content = await readFile(filePath, 'utf-8');
        const lines = content.split('\n');
        const relPath = filePath.replace(projectPath + '/', '');
        lines.forEach((line, idx) => {
            for (const pattern of secretPatterns) {
                pattern.lastIndex = 0;
                if (pattern.test(line)) {
                    found.push({
                        file: relPath,
                        line: idx + 1,
                        variableName: extractVarNameFromLine(line),
                        severity: 'critical',
                        instruction: 'Remove this hardcoded secret immediately. Use environment variables instead. ' +
                            'Rotate the credential if it was ever committed to git.',
                    });
                    break; // Only report once per line
                }
            }
        });
    }
    catch {
        // File read error — skip
    }
    return found;
}
export function extractVarNameFromLine(line) {
    const match = /(?:const|let|var|val)?\s*(\w+)\s*[=:]/.exec(line.trim());
    return match?.[1] ?? 'unknown_variable';
}
//# sourceMappingURL=scanner.js.map