core/adapters/tool.jscpd.adapter.ts
core/adapters/tool.jscpd.adapter.ts is a file in GovLab Quality. 62 lines of code and 20 definitions.
import { existsSync, renameSync, rmSync } from "node:fs";
import { findingsResult, signalDeathResult } from "#core/factories/finding.factory";
import { numberField, stringArrayField, stringArrayFieldOr, stringField } from "#core/selectors/record.selector";
import type { RunResult } from "#types/finding.types";
import type { RunnerContext } from "#types/tool.types";
import { createRequire } from "node:module";
import { defineTool } from "#core/registries/tool.registry";
import { loadGovlabConfig } from "#core/loaders/config.loader";
import { parseJscpdOutput } from "#core/parsers/tool.jscpd.parser";
import path from "node:path";
import process from "node:process";
import { relativePath } from "@ssot/paths";
import { sectionOf } from "#core/selectors/config.selector";
import { signalKilled } from "#core/predicates/failure.predicate";
import { spawnTool } from "#core/adapters/invocation.adapter";
import { withMasterExclude } from "#core/selectors/exclusions.selector";
import { writeToolJson } from "#core/persistence/tool.persistence";
const SECTION = "jscpd";
const CONFIG_FILE = "jscpd.json";
const REPORTER_FILE = "jscpd-report.json";
const DEFAULT_THRESHOLD = 0;
const DEFAULT_MIN_TOKENS = 50;
const DEFAULT_MIN_LINES = 5;
const DEFAULT_FORMATS = ["typescript", "javascript", "css", "html"];
const REPORTERS = ["json"];
const moduleRequire = createRequire(import.meta.url);
export const govlabJscpdConfig = async (consumerRoot: string = process.cwd()): Promise<Record<string, unknown>> => {
const config = await loadGovlabConfig(consumerRoot);
const section = sectionOf(config, SECTION);
return {
absolute: section["absolute"] !== false,
format: stringArrayFieldOr(section, "format", DEFAULT_FORMATS),
ignore: withMasterExclude(config, stringArrayField(section, "ignore")),
minLines: numberField(section, "minLines", DEFAULT_MIN_LINES),
minTokens: numberField(section, "minTokens", DEFAULT_MIN_TOKENS),
output: stringField(section, "output", relativePath("govlabHost.reports")),
reporters: REPORTERS,
threshold: numberField(section, "threshold", DEFAULT_THRESHOLD),
};
};
const jscpdBin = function jscpdBin(): string {
return path.resolve(path.dirname(moduleRequire.resolve("jscpd/package.json")), "run-jscpd.js");
};
const settleArtifact = function settleArtifact(root: string): void {
const declared = path.join(root, relativePath("govlabHost.reports.duplication"));
const written = path.join(path.dirname(declared), REPORTER_FILE);
if (existsSync(written)) {
rmSync(declared, { force: true });
renameSync(written, declared);
}
};
const runJscpd = async function runJscpd(context: RunnerContext): Promise<RunResult> {
const configFile = await writeToolJson(context.root, CONFIG_FILE, await govlabJscpdConfig(context.root));
const args = [jscpdBin(), "--config", configFile, "--reporters", "json", "--silent", ...context.paths];
const result = spawnTool(process.execPath, args, { cwd: context.root, encoding: "utf8", shell: false });
settleArtifact(context.root);
if (signalKilled(result.status)) {
return signalDeathResult({ ecosystem: context.ecosystem, root: context.root, tool: SECTION }, result.signal);
}
return findingsResult(parseJscpdOutput(result.stdout, context.ecosystem));
};
defineTool({ ecosystems: ["javascript", "typescript", "css", "html"], run: runJscpd, tool: SECTION });