core/adapters/tool.adapter.ts

core/adapters/tool.adapter.ts is a file in GovLab Quality. 106 lines of code and 18 definitions.

import type {
    AdvisoryContext,
    AdvisoryToolSpec,
    RunnerContext,
    ScanSpec,
    SelectableToolSpec,
    ToolCall,
    ToolExit,
} from "#types/tool.types";
import { EMPTY_RESULT, findingsResult, fixedResult, toolError, toolFailure } from "#core/factories/finding.factory";
import type { Finding, PassOutcome, RunResult } from "#types/finding.types";
import { exitOf, failedWhenEmpty, passOf, terminalOf } from "#core/classifiers/invocation.classifier";
import { signalKilled, statusIn } from "#core/predicates/failure.predicate";
import { electedMissing } from "#configuration/strings/tool.strings";
import { gatingAdvisory } from "#core/factories/tool.factory";
import { spawnTool } from "#core/adapters/invocation.adapter";
import { writeToolFile } from "#core/persistence/tool.persistence";

export const passTool = function passTool(advisory: AdvisoryContext, call: ToolCall, spec: ScanSpec): PassOutcome {
    const result = spawnTool(call.bin, call.args, { cwd: call.cwd, encoding: "utf8", env: call.env, shell: false });
    return passOf(advisory, result, spec);
};

export const scanTool = function scanTool(advisory: AdvisoryContext, call: ToolCall, spec: ScanSpec): RunResult {
    const outcome = passTool(advisory, call, spec);
    return outcome.terminal ? outcome.result : findingsResult(outcome.findings);
};

export const scanTargets = function scanTargets(
    targets: readonly string[],
    scanOne: (target: string) => PassOutcome | null,
): RunResult {
    const findings: Finding[] = [];
    for (const target of targets) {
        const outcome = scanOne(target);
        if (outcome?.terminal === true) {
            return outcome.result;
        }
        findings.push(...(outcome?.findings ?? []));
    }
    return findingsResult(findings);
};

export const fixCycle = function fixCycle(
    reported: PassOutcome,
    shouldFix: (findings: readonly Finding[]) => boolean,
    applyFix: (findings: Finding[]) => RunResult,
): RunResult {
    if (reported.terminal) {
        return reported.result;
    }
    return shouldFix(reported.findings) ? applyFix(reported.findings) : findingsResult(reported.findings);
};

export const residualOf = function residualOf(reported: readonly Finding[], outcome: PassOutcome): RunResult {
    return outcome.terminal ? outcome.result : fixedResult(reported, outcome.findings);
};

export const standardScan = function standardScan(
    advisory: AdvisoryContext,
    statuses: ReadonlySet<number>,
    parse: ScanSpec["parse"],
): ScanSpec {
    return { failed: failedWhenEmpty(statuses), failure: (exit) => toolError(advisory, exit), parse };
};

export const messageScan = function messageScan(
    advisory: AdvisoryContext,
    statuses: ReadonlySet<number>,
    parse: ScanSpec["parse"],
    message: (exit: ToolExit) => string,
): ScanSpec {
    return { failed: failedWhenEmpty(statuses), failure: (exit) => toolFailure(advisory, message(exit)), parse };
};

export const runAdvisory = function runAdvisory(spec: AdvisoryToolSpec, context: RunnerContext): RunResult {
    const result = spawnTool(spec.tool, spec.argsFor(context), {
        cwd: spec.cwdFor(context),
        encoding: "utf8",
        shell: false,
    });
    if (result.error || signalKilled(result.status) || !spec.statusOk(result.status)) {
        return EMPTY_RESULT;
    }
    const output = spec.stream === "stderr" ? result.stderr : result.stdout;
    return findingsResult(spec.parse(output, context.ecosystem));
};

export const runSelectable = async function runSelectable(
    spec: SelectableToolSpec,
    context: RunnerContext,
): Promise<RunResult> {
    const advisory = context.elected === false;
    const advisoryContext = gatingAdvisory(context, spec.tool, electedMissing(spec.tool));
    const configFile =
        context.elected === true
            ? writeToolFile(context.root, spec.configFilename, await spec.loadConfig(context.root))
            : null;
    const result = spawnTool(spec.tool, spec.argsFor(configFile, context), {
        cwd: spec.cwdFor(context),
        encoding: "utf8",
        shell: false,
    });
    const terminal = terminalOf(advisoryContext, result);
    if (terminal !== null) {
        return advisory ? EMPTY_RESULT : terminal;
    }
    if (!spec.statusOk(result.status)) {
        return advisory ? EMPTY_RESULT : toolError(advisoryContext, exitOf(result));
    }
    return findingsResult(spec.parse(result.stdout, context.ecosystem, advisory));
};

export const statusOkIn = function statusOkIn(statuses: ReadonlySet<number>): (status: number | null) => boolean {
    return (status) => statusIn(statuses, status);
};