core/loaders/package.loader.ts
core/loaders/package.loader.ts is a file in GovLab Patterns. 106 lines of code and 27 definitions.
import type { DirScan, Pruned } from "#types/package.types";
import { type Dirent, existsSync, readFileSync, readdirSync } from "node:fs";
import { join, resolve, sep } from "node:path";
import { detectLanguage } from "@govlab/code-parse";
import { isRecord } from "#core/predicates/record.predicate";
import process from "node:process";
import { unreadableManifest } from "#configuration/strings/report.strings";
const MANIFEST = "_manifest.json";
const PACKAGE = "package.json";
const TEST_PREFIX = "test_";
const NON_CODE: ReadonlySet<string> = new Set(["json", "yaml", "toml", "css", "html"]);
const TEST_DIRS: ReadonlySet<string> = new Set(["tests", "test", "__tests__", "spec", "testdata"]);
const TEST_MARKERS: readonly string[] = [".test.", ".spec.", ".stories.", "_test."];
const isShapedManifest = function isShapedManifest(value: unknown): boolean {
return (
isRecord(value) &&
typeof value["label"] === "string" &&
typeof value["maturity"] === "string" &&
isRecord(value["visibility"])
);
};
const readJsonSafe = function readJsonSafe(path: string): unknown {
if (!existsSync(path)) {
return null;
}
try {
return JSON.parse(readFileSync(path, "utf8"));
} catch (error) {
process.stderr.write(unreadableManifest(path, String(error)));
return null;
}
};
export const discoverModules = function discoverModules(root: string, pruned: Pruned): string[] {
const found: string[] = [];
const stack: string[] = [root];
let dir = stack.pop();
while (dir !== undefined) {
if (isShapedManifest(readJsonSafe(join(dir, MANIFEST)))) {
found.push(resolve(dir));
}
const parent = dir;
stack.push(
...readdirSync(parent, { withFileTypes: true })
.filter((entry) => entry.isDirectory())
.map((entry) => join(parent, entry.name))
.filter((child) => !pruned(child)),
);
dir = stack.pop();
}
return found
.filter((one) => !found.some((other) => other !== one && other.startsWith(one + sep)))
.sort((a, b) => a.localeCompare(b));
};
export const buildPackageMap = function buildPackageMap(modules: readonly string[]): Map<string, string> {
return new Map(
modules.flatMap((dir): [string, string][] => {
const parsed = readJsonSafe(join(dir, PACKAGE));
return isRecord(parsed) && typeof parsed["name"] === "string" ? [[parsed["name"], dir]] : [];
}),
);
};
const isCodeFile = function isCodeFile(name: string): boolean {
const language = detectLanguage(name);
return language !== null && !NON_CODE.has(language);
};
const isTestFile = function isTestFile(name: string): boolean {
return TEST_MARKERS.some((marker) => name.includes(marker)) || name.startsWith(TEST_PREFIX);
};
const isDescendable = function isDescendable(entry: Dirent, full: string, pruned: Pruned): boolean {
return entry.isDirectory() && !pruned(full) && !TEST_DIRS.has(entry.name) && !existsSync(join(full, MANIFEST));
};
const bucketFor = function bucketFor(entry: Dirent, full: string, scan: DirScan, pruned: Pruned): string[] | null {
if (isDescendable(entry, full, pruned)) {
return scan.dirs;
}
if (!entry.isFile() || !isCodeFile(entry.name)) {
return null;
}
return isTestFile(entry.name) ? scan.tests : scan.files;
};
const scanDir = function scanDir(dir: string, pruned: Pruned): DirScan {
const scan: DirScan = { dirs: [], files: [], tests: [] };
for (const entry of readdirSync(dir, { withFileTypes: true })) {
const full = join(dir, entry.name);
bucketFor(entry, full, scan, pruned)?.push(full);
}
return scan;
};
const walk = function walk(moduleDir: string, pruned: Pruned, pick: (scan: DirScan) => string[]): string[] {
const out: string[] = [];
const stack: string[] = [moduleDir];
let dir = stack.pop();
while (dir !== undefined) {
const scan = scanDir(dir, pruned);
out.push(...pick(scan));
stack.push(...scan.dirs);
dir = stack.pop();
}
return out.sort((a, b) => a.localeCompare(b));
};
export const sourceFiles = function sourceFiles(moduleDir: string, pruned: Pruned): string[] {
return walk(moduleDir, pruned, (scan) => scan.files);
};
export const testFilesOf = function testFilesOf(moduleDir: string, pruned: Pruned): string[] {
return walk(moduleDir, pruned, (scan) => scan.tests);
};