core/coordinators/document.coordinator.ts
core/coordinators/document.coordinator.ts is a file in GovLab Docs. 163 lines of code and 39 definitions.
import type {
CheckOptions,
DocChecksDeps,
DocModule,
ModuleOutput,
ModuleSelection,
Spec,
WorkspaceMap,
} from "#types/document.output.types";
import { DRIFT_CODES, MANIFEST_FILE, README_FILE } from "#configuration/constants/document.constants";
import {
checkSummary,
duplicatePrinciple,
generateSummary,
healedCount,
manifestMissing,
noDocsBlock,
noManifest,
writtenLabel,
} from "#configuration/strings/package.strings";
import { driftFinding, healed } from "#configuration/strings/drift.strings";
import { print, printErr } from "#core/reporters/base.reporter";
import { readJsonSafe, readTextSafe } from "#core/loaders/base.loader";
import type { CheckResult } from "#types/finding.types";
import type { ManifestModule } from "#types/manifest.types";
import type { ModuleDocs } from "#types/readme.types";
import { ROOT } from "@ssot/paths";
import { analyzabilityMessages } from "#core/validators/barrel.validator";
import { checkSpec } from "#core/coordinators/drift.coordinator";
import { governanceMessages } from "#core/validators/readme.validator";
import { isPlainRecord } from "#core/predicates/record.predicate";
import { membersWithoutManifest } from "#core/loaders/package.loader";
import { persistWorkspaceMap } from "#core/persistence/document.persistence";
import { resolve } from "node:path";
import { staleChartsMessages } from "#core/validators/figure.validator";
const EMPTY_RESULT: CheckResult = { errors: [], heals: [] };
const reportChecks = function reportChecks(result: CheckResult, fix: boolean): void {
for (const message of result.errors) {
printErr(message);
}
for (const message of result.heals) {
print(message);
}
print(checkSummary(result.errors.length, fix ? healedCount(result.heals.length) : ""));
};
const selectModules = function selectModules(
all: readonly ManifestModule[],
selection: ModuleSelection,
): ManifestModule[] {
const { only } = selection;
if (selection.workspaceMapOnly) {
return [];
}
return only === null ? [...all] : all.filter((module) => only.has(module.relPath));
};
const includesWorkspaceMap = function includesWorkspaceMap(selection: ModuleSelection): boolean {
return selection.workspaceMapOnly || selection.only === null;
};
const merge = function merge(results: readonly CheckResult[]): CheckResult {
return { errors: results.flatMap((result) => result.errors), heals: results.flatMap((result) => result.heals) };
};
export class DocChecks {
private readonly discoverAll: () => ManifestModule[];
private readonly engine: ModuleDocs;
private readonly generatedPrefix: string;
private readonly moduleOutputs: (module: DocModule) => Promise<ModuleOutput>;
private readonly workspaceMapTarget: () => Promise<WorkspaceMap | null>;
private readonly writeSpec: (spec: Spec, content: string) => void;
public constructor(deps: DocChecksDeps) {
this.discoverAll = deps.discoverAll;
this.engine = deps.engine;
this.generatedPrefix = deps.generatedPrefix;
this.moduleOutputs = deps.moduleOutputs;
this.workspaceMapTarget = deps.workspaceMapTarget;
this.writeSpec = deps.writeSpec;
}
public async checkModules(options: CheckOptions): Promise<number> {
const { fix } = options;
const perModule = await Promise.all(
selectModules(this.discoverAll(), options).map(async (module) => this.checkOneModule(module, fix)),
);
const mapResult = includesWorkspaceMap(options) ? await this.workspaceMapCheck(fix) : EMPTY_RESULT;
const coverage = { errors: membersWithoutManifest(ROOT).map(manifestMissing), heals: [] };
const result = merge([coverage, ...perModule, mapResult]);
reportChecks(result, fix);
return result.errors.length;
}
public async generateAll(selection: ModuleSelection): Promise<void> {
const counts = await Promise.all(
selectModules(this.discoverAll(), selection).map(async (module) =>
this.writeSpecs((await this.moduleOutputs(module)).specs),
),
);
const mapRel = includesWorkspaceMap(selection) ? await this.writeWorkspaceMap() : null;
if (mapRel !== null) {
print(writtenLabel(mapRel));
}
const total = counts.reduce((sum, count) => sum + count, mapRel === null ? 0 : 1);
print(generateSummary(total));
}
public async writeOne(targetDir: string): Promise<void> {
const dir = resolve(targetDir);
const manifest = readJsonSafe(resolve(dir, MANIFEST_FILE));
if (!isPlainRecord(manifest)) {
throw new Error(noManifest(targetDir));
}
if (manifest.docs === undefined) {
throw new Error(noDocsBlock(targetDir));
}
const { specs } = await this.moduleOutputs({ dir, label: targetDir, manifest, relPath: targetDir });
await this.writeSpecs(specs);
}
private async checkOneModule(module: ManifestModule, fix: boolean): Promise<CheckResult> {
const { specs, shared } = await this.moduleOutputs(module);
const onDiskReadme = readTextSafe(resolve(module.dir, README_FILE)) ?? "";
const specResults = await Promise.all(specs.map(async (spec) => checkSpec(spec, fix, this.writeSpec)));
const governance = governanceMessages(this.engine, module, {
generatedPrefix: this.generatedPrefix,
onDiskReadme,
});
return {
errors: [
...governance,
...specResults.flatMap((result) => result.errors),
...analyzabilityMessages(module, shared.isAggregate),
...staleChartsMessages(module, shared.hasCharts),
],
heals: specResults.flatMap((result) => result.heals),
};
}
private async writeWorkspaceMap(): Promise<string | null> {
const map = await this.workspaceMapTarget();
if (map === null) {
return null;
}
persistWorkspaceMap(map);
return map.rel;
}
private async workspaceMapDrift(fix: boolean): Promise<CheckResult> {
const map = await this.workspaceMapTarget();
if (map === null || map.content === (readTextSafe(map.path) ?? "")) {
return EMPTY_RESULT;
}
if (!fix) {
return { errors: [driftFinding(map.rel, DRIFT_CODES.doc)], heals: [] };
}
persistWorkspaceMap(map);
return { errors: [], heals: [healed(map.rel)] };
}
private async workspaceMapCheck(fix: boolean): Promise<CheckResult> {
const ontology = { errors: this.engine.ontologyDuplicates().map(duplicatePrinciple), heals: [] };
return merge([ontology, await this.workspaceMapDrift(fix)]);
}
private async writeSpecs(specs: readonly Spec[]): Promise<number> {
await Promise.all(
specs.map(async (spec) => {
this.writeSpec(spec, await spec.produce(readTextSafe(spec.path) ?? ""));
print(writtenLabel(spec.label));
}),
);
return specs.length;
}
}