core/steps/catalog.step.ts
core/steps/catalog.step.ts is a file in GovLab Quality. 79 lines of code and 24 definitions.
import type { CatalogRule, CatalogWriter, ToolTally } from "#types/catalog.types";
import { readFileSync, readdirSync } from "node:fs";
import { recordAt, recordsAt, stringField } from "#core/selectors/record.selector";
import { absolutePath } from "@ssot/paths";
import { coverageMarkdown } from "#core/formatters/catalog.formatter";
import { defineStep } from "#core/registries/step.registry";
import { jsonRecord } from "#core/parsers/record.parser";
import path from "node:path";
const JSON_SUFFIX = ".json";
const INDEX_FILE = "index.generated.json";
const COVERAGE_FILE = "coverage.generated.md";
const UNKNOWN = "unknown";
interface SourceCatalog {
rules: CatalogRule[];
tool: string;
version: string;
}
const isCatalogRule = function isCatalogRule(value: Record<string, unknown>): value is CatalogRule {
return typeof value["ruleId"] === "string";
};
const readSource = function readSource(file: string): SourceCatalog {
const parsed = jsonRecord(readFileSync(file, "utf8"));
const summary = recordAt(parsed, "summary");
const rules = recordsAt(parsed, "rules").filter(isCatalogRule);
const [first] = rules;
const version = stringField(summary, "toolVersion") || (first ? stringField(first, "toolVersion") : "") || UNKNOWN;
return { rules, tool: stringField(summary, "tool") || path.basename(file, JSON_SUFFIX), version };
};
const sourceFiles = function sourceFiles(): string[] {
return ["govlab.quality.catalog.data", "govlab.quality.catalog.generated"].flatMap((key) => {
const dir = absolutePath(key);
return readdirSync(dir)
.filter((name) => name.endsWith(JSON_SUFFIX))
.map((name) => path.join(dir, name));
});
};
const tallyOf = function tallyOf(source: SourceCatalog): ToolTally {
const ecosystems = [...new Set(source.rules.map((rule) => stringField(rule, "ecosystem") || UNKNOWN))];
const ecosystem = ecosystems.length === 1 ? (ecosystems[0] ?? UNKNOWN) : `multi(${String(ecosystems.length)})`;
return { ecosystem, rules: source.rules.length, tool: source.tool, version: source.version };
};
const byEcosystemOf = function byEcosystemOf(rules: readonly CatalogRule[]): Record<string, number> {
const counts: Record<string, number> = {};
for (const rule of rules) {
const ecosystem = stringField(rule, "ecosystem") || UNKNOWN;
counts[ecosystem] = (counts[ecosystem] ?? 0) + 1;
}
return counts;
};
const writeIndex = async function writeIndex(
writer: CatalogWriter,
sources: readonly SourceCatalog[],
rules: readonly CatalogRule[],
): Promise<void> {
const byEcosystem = byEcosystemOf(rules);
const tools = sources.map(tallyOf).toSorted((a, b) => b.rules - a.rules);
const index = {
byEcosystem,
ecosystems: Object.keys(byEcosystem).length,
tools,
totalRules: rules.length,
totalTools: tools.length,
};
await writer.json(absolutePath("govlab.quality.generated", INDEX_FILE), index);
await writer.markdown(absolutePath("govlab.quality.generated", COVERAGE_FILE), coverageMarkdown(index));
};
defineStep({
gives: ["rules"],
name: "catalog",
needs: [],
run: async (_state, writer) => {
const sources = sourceFiles()
.map(readSource)
.toSorted((a, b) => a.tool.localeCompare(b.tool));
const rules = sources.flatMap((source) => source.rules.map((rule): CatalogRule => ({ ...rule })));
await writeIndex(writer, sources, rules);
return { rules };
},
});