"use strict";
/**
 * Ref-based baseline gather — produces a `CurrentScan` for a git
 * ref by checking it out into a temporary worktree and running the
 * analyzer pipeline there.
 *
 * # When this runs
 *
 * `mode === 'ref-based'` (see `./modes.ts`). The guardrail check
 * needs a "prior side" to diff against; in committed modes the
 * prior side comes from `.dxkit/baselines/<name>.json`, but in
 * ref-based mode no file is committed — the prior side is
 * recomputed on the fly from a git ref (default
 * `origin/<default-branch>`).
 *
 * # Mechanics
 *
 * 1. Resolve `ref` to a commit SHA. Failure here surfaces a
 *    `RefBaselineError` with one of three actionable hints:
 *      - Shallow clone → `git fetch --unshallow` / CI fetch-depth
 *      - Ref doesn't exist → `git fetch origin` or fix policy
 *      - Local-only ref → push it or use a remote-tracking ref
 * 2. `git worktree add --detach <tempDir> <sha>`. The worktree is a
 *    full checkout of the source tree at that SHA — but NOT a
 *    package-manager install, so dep-vuln scanners that read
 *    `node_modules` directly will see degraded results. The
 *    dxkit dep scanners use lockfiles (`package-lock.json`,
 *    `Pipfile.lock`, etc.) which ARE in the worktree, so coverage
 *    survives the gap.
 * 3. Run `gatherCurrentScan` against the worktree directory. Same
 *    pipeline as the live current scan — same producer registry,
 *    same envelope shape — so the matcher diffs apples-to-apples.
 * 4. Clean up the worktree on the way out (try/finally).
 *
 * # Why a generic `withRefWorktree` helper
 *
 * The worktree setup + cleanup pattern is reusable. Future modes-
 * aware tooling (e.g., a `vyuh-dxkit baseline diff <refA> <refB>`
 * subcommand) can compose `withRefWorktree` directly instead of
 * re-deriving the temp-dir + cleanup dance. `gatherFromRef` is a
 * thin specialization for the guardrail-check use case.
 *
 * # Failure semantics
 *
 * Recoverable failures (ref unreachable, worktree-add fails) throw
 * `RefBaselineError` with a `hint` field the CLI renders in plain
 * prose. Unrecoverable failures (the gather pipeline itself
 * crashes) propagate up the original Error subclass — they're not
 * specific to ref-based mode and live with the existing error
 * handling in the orchestrator.
 */
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.RefBaselineError = void 0;
exports.resolveRefToSha = resolveRefToSha;
exports.isShallowRepo = isShallowRepo;
exports.withRefWorktree = withRefWorktree;
exports.mirrorSaltFile = mirrorSaltFile;
exports.gatherFromRef = gatherFromRef;
const child_process_1 = require("child_process");
const fs_1 = require("fs");
const os_1 = require("os");
const path = __importStar(require("path"));
const create_1 = require("./create");
/**
 * Recoverable error from the ref-based gather path. Carries an
 * actionable `hint` the CLI surfaces verbatim so customers don't
 * have to interpret raw git output. Inherits from `Error` so
 * existing catch-by-Error code keeps working.
 */
class RefBaselineError extends Error {
    hint;
    constructor(message, hint) {
        super(message);
        this.name = 'RefBaselineError';
        this.hint = hint;
    }
}
exports.RefBaselineError = RefBaselineError;
/**
 * Resolve a ref to a commit SHA via `git rev-parse --verify
 * <ref>^{commit}`. Returns null when the ref isn't reachable (the
 * caller surfaces the appropriate hint based on shallow-clone /
 * remote-only state).
 */
function resolveRefToSha(cwd, ref) {
    try {
        const out = (0, child_process_1.execFileSync)('git', ['rev-parse', '--verify', `${ref}^{commit}`], {
            cwd,
            encoding: 'utf-8',
            stdio: ['ignore', 'pipe', 'pipe'],
        }).trim();
        return out || null;
    }
    catch {
        return null;
    }
}
/**
 * Whether the current working tree was cloned shallowly. Drives
 * the hint surfaced when a ref isn't reachable: a CI clone with
 * `fetch-depth: 1` won't have the baseline ref's history, and the
 * fix is `fetch-depth: 0`, not pushing the missing ref.
 */
function isShallowRepo(cwd) {
    try {
        const out = (0, child_process_1.execFileSync)('git', ['rev-parse', '--is-shallow-repository'], {
            cwd,
            encoding: 'utf-8',
            stdio: ['ignore', 'pipe', 'pipe'],
        }).trim();
        return out === 'true';
    }
    catch {
        return false;
    }
}
/**
 * Build the right `RefBaselineError` for an unreachable ref. The
 * hint is the actionable next step, not a tautology — shallow
 * clones get fetch-depth advice, otherwise we suggest configuring
 * a different ref.
 */
function unreachableRefError(cwd, ref) {
    if (isShallowRepo(cwd)) {
        return new RefBaselineError(`Cannot resolve baseline ref ${ref}: this is a shallow clone.`, 'Run `git fetch --unshallow` locally, or set `fetch-depth: 0` in your CI checkout step.');
    }
    return new RefBaselineError(`Cannot resolve baseline ref ${ref}.`, `Run \`git fetch origin\`, push the ref upstream, or set \`baseline.ref\` in .dxkit/policy.json to an existing ref.`);
}
/**
 * Check out `ref` into a temporary worktree, run `fn` with the
 * worktree path, and tear down the worktree on the way out.
 *
 * Always cleans up — even when `fn` throws. The cleanup tolerates
 * `git worktree remove` failures (e.g., dirty worktree from a
 * partial gather) by falling back to `rm -rf` on the temp dir.
 */
async function withRefWorktree(opts, fn) {
    const sha = resolveRefToSha(opts.cwd, opts.ref);
    if (sha === null)
        throw unreachableRefError(opts.cwd, opts.ref);
    // mkdtempSync returns an empty dir; git worktree add wants the
    // target path NOT to exist (or to be empty). Use a fresh subdir
    // inside the temp parent so git creates it cleanly.
    const tempBase = (0, fs_1.mkdtempSync)(path.join((0, os_1.tmpdir)(), 'dxkit-ref-'));
    const worktreePath = path.join(tempBase, 'baseline');
    let worktreeAdded = false;
    try {
        (0, child_process_1.execFileSync)('git', ['worktree', 'add', '--detach', worktreePath, sha], {
            cwd: opts.cwd,
            stdio: ['ignore', 'pipe', 'pipe'],
        });
        worktreeAdded = true;
        // Mirror file-mode salt into the worktree so secret-HMAC entries
        // pair across prior/current sides. Env-var + deterministic modes
        // resolve identically across cwd + worktree (env inheritance +
        // shared initial-commit SHA); file mode is the one that drifts
        // because `.dxkit/salt` is gitignored and so isn't part of the
        // checkout. The copy is no-op when the file doesn't exist.
        mirrorSaltFile(opts.cwd, worktreePath);
        return await fn(worktreePath);
    }
    catch (err) {
        if (err instanceof RefBaselineError)
            throw err;
        if (!worktreeAdded) {
            // The worktree-add itself failed. Surface a clean error
            // instead of bubbling the raw stderr.
            throw new RefBaselineError(`Failed to set up baseline worktree at ${opts.ref}.`, `Check that 'git worktree' is available and that ${tempBase} is writable.`);
        }
        throw err;
    }
    finally {
        if (worktreeAdded) {
            try {
                (0, child_process_1.execFileSync)('git', ['worktree', 'remove', '--force', worktreePath], {
                    cwd: opts.cwd,
                    stdio: ['ignore', 'pipe', 'pipe'],
                });
            }
            catch {
                // git worktree remove can fail if the worktree dir was
                // already cleaned externally. The rmSync below recovers.
            }
        }
        try {
            (0, fs_1.rmSync)(tempBase, { recursive: true, force: true });
        }
        catch {
            // Best-effort cleanup of the temp parent. A stale temp dir
            // is preferable to surfacing a misleading error if the gather
            // already succeeded.
        }
    }
}
/**
 * Copy `.dxkit/salt` from `srcCwd` into `dstCwd` when present.
 * Public for testing — production callers reach this through
 * `withRefWorktree`. The directory is created on demand; absent
 * source files are silently skipped (env-var + deterministic salt
 * modes both work without the file).
 */
function mirrorSaltFile(srcCwd, dstCwd) {
    const src = path.join(srcCwd, '.dxkit', 'salt');
    if (!(0, fs_1.existsSync)(src))
        return;
    const dstDir = path.join(dstCwd, '.dxkit');
    (0, fs_1.mkdirSync)(dstDir, { recursive: true });
    (0, fs_1.copyFileSync)(src, path.join(dstDir, 'salt'));
}
/**
 * Run `gatherCurrentScan` against a temporary worktree checked out
 * to `ref`. Returns the same shape as a live gather — the matcher
 * doesn't care which side was the worktree, only that both sides
 * are `CurrentScan` envelopes.
 *
 * Per-tool degradation note: dep-vuln scanners may report less
 * coverage in the worktree because `node_modules` (and analogous
 * install artifacts) are typically gitignored and so don't exist
 * in the worktree. The lockfile-driven scanners dxkit prefers
 * survive the gap; `npm audit`-style probes do not.
 */
async function gatherFromRef(opts) {
    return withRefWorktree({ cwd: opts.cwd, ref: opts.ref }, async (worktreePath) => {
        return (0, create_1.gatherCurrentScan)({ cwd: worktreePath, verbose: opts.verbose });
    });
}
//# sourceMappingURL=ref-baseline.js.map