core/coordinators/page.coordinator.ts

core/coordinators/page.coordinator.ts is a file in Bane's Lab Build Scripts. 148 lines of code and 24 definitions.

import {
    CATALOG_SITEMAP_ROUTE,
    JSON_ROUTE,
    MARKDOWN_EXTENSION,
    SEARCH_KEY_FILE,
    SECTION_PART_WEIGHT,
    SECURITY_KEY_FILE,
    SECURITY_TXT_FILE,
    SOURCE_PART_CHARS,
} from "#configuration/constants/site.constants";
import type {
    Loaded,
    Prerendered,
    RecordBodyRenderer,
    RecordRegistry,
    SectionSelector,
    SourceBodyRenderer,
} from "#types/page.types";
import { sourceSizeOf, sourceTextOf } from "#core/loaders/source.loader";
import {
    writeDiscovery,
    writePages,
    writeRecordPages,
    writeSectionPages,
    writeSitemapIndex,
    writeSitemapParts,
    writeSourcePages,
    writeText,
} from "#core/persistence/page.persistence";
import { ANATOMY_PAGE } from "@banes-lab/web/ids/page.ids";
import { ANATOMY_TREE_DECLARATIONS } from "@banes-lab/web/registries/anatomy.tree.registry";
import type { BuiltOntology } from "#types/ontology.types";
import { COMPANY_EMAIL } from "@banes-lab/web/strings/company.strings";
import { RUNNER_MODULES } from "#configuration/constants/loader.constants";
import type { SourceRoute } from "#types/source.types";
import { buildCatalog } from "#core/coordinators/catalog.coordinator";
import { closureReport } from "#core/resolvers/catalog.resolver";
import { dirname } from "node:path";
import { discover } from "#core/converters/site.converter";
import { distinctSections } from "#core/converters/section.page.converter";
import { loadFrom } from "#core/loaders/site.loader";
import { loadWebModules } from "#core/loaders/catalog.loader";
import { mkdirSync } from "node:fs";
import { persistJournal } from "#core/persistence/journal.persistence";
import { renderSecurityTxt } from "#core/formatters/site.formatter";
import { signText } from "#core/adapters/security.adapter";
import { sourcePartsOf } from "#core/converters/source.segment.converter";
import { sourceRoutes } from "#core/converters/source.route.converter";
import { stampLedger } from "#core/persistence/route.persistence";
import { textOf } from "@ssot/secrets";
import { withModuleServer } from "#core/factories/server.factory";
import { writeCatalog } from "#core/persistence/catalog.persistence";
import { writeVerbatim } from "@govlab/canonical-write";

const JSON_INDENT = 4;

const sourceRoutesOf = async function sourceRoutesOf(loaded: Loaded): Promise<readonly SourceRoute[]> {
    const web = await loadWebModules(loaded.runner);
    const licenses = new Map(ANATOMY_TREE_DECLARATIONS.map((declaration) => [declaration.tab, declaration.license]));
    const trees = web.anatomy.ANATOMY_TREES.map((tree) => ({ ...tree, label: web.source.treeLabelOf(tree.tab) }));
    const routes = sourceRoutes(trees, {
        languageOf: web.source.languageOf,
        license: (tab) => licenses.get(tab) ?? null,
        localPath: web.folder.localPath,
        nodeRoute: web.source.nodeRoute,
        sourceTitle: web.source.sourceTitle,
        tabPath: (tab) => loaded.links.tabLink(ANATOMY_PAGE, tab),
    });
    return routes.flatMap((route) =>
        route.text !== null && sourceSizeOf(route.text) > SOURCE_PART_CHARS
            ? sourcePartsOf(route, sourceTextOf(route.text) ?? "", SOURCE_PART_CHARS)
            : [route],
    );
};

export const prerenderSite = async function prerenderSite(
    root: string,
    outDir: string,
    ontology: BuiltOntology,
): Promise<Prerendered> {
    return withModuleServer(root, async (server) => {
        const loaded = await loadFrom(server);
        const registry = await loaded.runner.import<RecordRegistry>(RUNNER_MODULES.recordRegistry.path);
        registry.registerRecords([...ontology.collections.values()].flatMap((index) => Object.keys(index)));
        const discovery = await discover(loaded);
        const stamped = await stampLedger(discovery, new Date());
        await writePages(outDir, loaded, discovery, stamped.dates);
        const pageSitemap = writeDiscovery(outDir, discovery, stamped);
        writeText(outDir, SEARCH_KEY_FILE, textOf("INDEXNOW_KEY"));
        const security = signText(
            textOf("SECURITY_PGP_KEY"),
            renderSecurityTxt(discovery.site, COMPANY_EMAIL, new Date()),
        );
        writeText(outDir, SECURITY_TXT_FILE, security.signed);
        writeText(outDir, SECURITY_KEY_FILE, security.publicKey);
        const { files, journal, records, unresolved } = await buildCatalog(
            loaded.runner,
            discovery,
            loaded.exportSections,
            ontology,
        );
        const catalog = writeCatalog(outDir, files);
        await persistJournal(journal);
        mkdirSync(dirname(closureReport()), { recursive: true });
        writeVerbatim(closureReport(), JSON.stringify(unresolved, null, JSON_INDENT));
        const leaves = [...files.keys()].filter(
            (address) => !address.startsWith(JSON_ROUTE) && address.endsWith(MARKDOWN_EXTENSION),
        );
        writeSitemapParts(
            outDir,
            CATALOG_SITEMAP_ROUTE,
            leaves.toSorted((left, right) => left.localeCompare(right)).map((address) => discovery.site + address),
        );
        const sources = await sourceRoutesOf(loaded);
        const body = await loaded.runner.import<SourceBodyRenderer>(RUNNER_MODULES.sourceBody.path);
        const sourceSitemaps = writeSourcePages(
            outDir,
            { body, main: loaded.main, renderer: loaded.renderer, site: discovery.site },
            sources,
        );
        const recordBody = await loaded.runner.import<RecordBodyRenderer>(RUNNER_MODULES.recordBody.path);
        const recordSitemaps = writeRecordPages(
            outDir,
            { body: recordBody, main: loaded.main, renderer: loaded.renderer, site: discovery.site },
            records,
        );
        const selector = await loaded.runner.import<SectionSelector>(RUNNER_MODULES.sectionSelector.path);
        const sections = distinctSections(
            discovery.pages.flatMap((page) => selector.sectionSeedsOf(page.id, SECTION_PART_WEIGHT)),
            new Set(records.map((record) => record.title)),
        );
        const sectionSitemaps = writeSectionPages(
            outDir,
            { main: loaded.main, renderer: loaded.renderer, site: discovery.site },
            sections,
        );
        writeSitemapIndex(outDir, discovery.site, [
            pageSitemap,
            ...[...sourceSitemaps, ...recordSitemaps, ...sectionSitemaps].map(
                (route): readonly [string, null] => [route, null],
            ),
        ]);
        return {
            catalog,
            records: records.length,
            routes: discovery.routes.length,
            sections: sections.length,
            sources: sources.length,
        };
    });
};