"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.gitleaksProvider = void 0;
exports.gatherGitleaksResult = gatherGitleaksResult;
/**
 * Gitleaks integration — secret scanning with 800+ patterns.
 *
 * Exposes one gather helper — `gatherGitleaksResult(cwd)` — returning a
 * typed outcome with either a `SecretsResult` envelope or the reason
 * scanning was skipped. Consumed by the capability provider
 * (`gitleaksProvider`) and by the Layer 2 legacy-field reshape path in
 * `tools/parallel.ts`. Memoized per-cwd so both callers share one
 * invocation per analyzer run.
 */
const fs = __importStar(require("fs"));
const os = __importStar(require("os"));
const path = __importStar(require("path"));
const runner_1 = require("./runner");
const tool_registry_1 = require("./tool-registry");
const exclusions_1 = require("./exclusions");
const paths_1 = require("./paths");
const suppressions_1 = require("./suppressions");
/**
 * Per-cwd memoization of the gitleaks outcome. Gitleaks is a ~1-5s shell
 * invocation; memoizing ensures the Layer 2 reshape path + the capability
 * dispatcher's `gitleaksProvider` both hit the same computed outcome
 * within one `analyzeHealth` call.
 *
 * Cache is module-scoped and not invalidated automatically — safe for
 * dxkit's one-shot CLI shape (single cwd per process) and for the one
 * analyzer that exercises two paths to the same cwd (parallel.ts +
 * gatherCapabilityReport). Future long-running modes (diff, daemon)
 * that re-analyze the same cwd will need a clear-cache seam here.
 */
const gitleaksOutcomeCache = new Map();
/**
 * Single source of truth for secret-scanning via gitleaks. Consumed by
 * `gitleaksProvider` (capability dispatcher) and by the Layer 2 legacy
 * reshape in `tools/parallel.ts` — both paths share the memoized
 * per-cwd outcome so gitleaks shells out at most once per analyzer run.
 */
function gatherGitleaksResult(cwd) {
    const cached = gitleaksOutcomeCache.get(cwd);
    if (cached)
        return cached;
    const outcome = computeGitleaksOutcome(cwd);
    gitleaksOutcomeCache.set(cwd, outcome);
    return outcome;
}
function computeGitleaksOutcome(cwd) {
    const gitleaksCmd = findGitleaks(cwd);
    if (!gitleaksCmd)
        return { kind: 'unavailable', reason: 'not installed' };
    // Run gitleaks with JSON report (--no-git scans files, not git history).
    // Invoked via `runFileSync` (no shell) with an args array so the
    // source path and report path need no quoting — single-quoted shell
    // strings + `2>/dev/null` are POSIX-only and silently produced no
    // report under Windows' cmd.exe. The temp report lives under
    // `os.tmpdir()` rather than a hardcoded `/tmp`, which doesn't exist
    // on Windows.
    const reportPath = path.join(os.tmpdir(), `dxkit-gitleaks-${Date.now()}.json`);
    (0, runner_1.runFileSync)(gitleaksCmd, [
        'detect',
        '--source',
        cwd,
        '--report-format',
        'json',
        '--report-path',
        reportPath,
        '--no-git',
        '--exit-code',
        '0',
    ], cwd, 120000);
    // Read the report file directly. Pre-fix this used `run('cat
    // <path>')` which routed through execSync — large reports on
    // enterprise codebases would exceed the 1MB default maxBuffer and
    // silently return empty (same bug class as jscpd.ts). Direct file
    // read sidesteps the buffer entirely; gitleaks reports on
    // enterprise repos can reach MB-range when many findings are
    // surfaced.
    let reportRaw;
    try {
        reportRaw = fs.readFileSync(reportPath, 'utf-8');
    }
    catch {
        reportRaw = '';
    }
    // Best-effort cleanup; failure is non-fatal (the OS reaps tmpdir).
    try {
        fs.unlinkSync(reportPath);
    }
    catch {
        /* file already gone or never written — fine */
    }
    if (!reportRaw)
        return { kind: 'unavailable', reason: 'no output' };
    let parsed;
    try {
        parsed = JSON.parse(reportRaw);
    }
    catch {
        return { kind: 'unavailable', reason: 'parse error' };
    }
    if (!Array.isArray(parsed)) {
        // gitleaks returned non-array JSON (malformed); treat as zero findings.
        const envelope = {
            schemaVersion: 1,
            tool: 'gitleaks',
            findings: [],
            suppressedCount: 0,
        };
        return { kind: 'success', envelope, suppressedCount: 0, rawSecrets: [] };
    }
    const combined = parsed.map((f) => ({
        finding: {
            file: (0, paths_1.toProjectRelative)(cwd, f.File),
            line: f.StartLine,
            rule: f.RuleID,
            severity: f.RuleID.includes('private-key') ? 'critical' : 'high',
            title: f.Description,
        },
        secret: f.Secret,
    }));
    // Gitleaks --no-git scans everything on disk (ignores .gitignore), so
    // we re-apply the resolved exclusion set via isExcludedPath().
    const filteredCombined = combined.filter((c) => !(0, exclusions_1.isExcludedPath)(cwd, c.finding.file));
    // Apply `.dxkit-suppressions.json` so known-false positives don't count.
    const suppressions = (0, suppressions_1.loadSuppressions)(cwd);
    const { kept, suppressed } = (0, suppressions_1.applySuppressions)(filteredCombined, suppressions.gitleaks, (c) => c.finding.rule, (c) => c.finding.file);
    const envelope = {
        schemaVersion: 1,
        tool: 'gitleaks',
        findings: kept.map((c) => c.finding),
        suppressedCount: suppressed.length,
    };
    const rawSecrets = kept.map((c) => ({
        file: c.finding.file,
        line: c.finding.line,
        rule: c.finding.rule,
        secret: c.secret,
    }));
    return { kind: 'success', envelope, suppressedCount: suppressed.length, rawSecrets };
}
/**
 * Capability-shaped provider. Register in
 * `src/languages/capabilities/global.ts:GLOBAL_CAPABILITIES` so the
 * dispatcher picks it up via `providersFor(SECRETS)`.
 */
exports.gitleaksProvider = {
    source: 'gitleaks',
    async gather(cwd) {
        const outcome = gatherGitleaksResult(cwd);
        return outcome.kind === 'success' ? outcome.envelope : null;
    },
};
function findGitleaks(cwd) {
    const status = (0, tool_registry_1.findTool)(tool_registry_1.TOOL_DEFS.gitleaks, cwd);
    return status.available ? status.path : null;
}
//# sourceMappingURL=gitleaks.js.map