# core/coordinators/page.coordinator.ts

> 148 lines of code and 24 definitions.

Tree: Bane's Lab Build Scripts
Language: typescript
Layer: application
Canonical: https://banes-lab.com/anatomy/build-scripts#file-build-scripts-core-coordinators-page-coordinator-ts
Source text: https://banes-lab.com/source/build-scripts/core/coordinators/page.coordinator.ts.txt

Listed in [core/coordinators](https://banes-lab.com/api/source/build-scripts/core/coordinators.md), after [core/coordinators/ontology.coordinator.ts](https://banes-lab.com/source/build-scripts/core/coordinators/ontology.coordinator.ts.md) and before [core/coordinators/payload.coordinator.ts](https://banes-lab.com/source/build-scripts/core/coordinators/payload.coordinator.ts.md).

## Definitions

- `prerenderSite` (lexical_declaration, line 76, exported)
- `sourceRoutesOf` (lexical_declaration, line 57)
- `JSON_INDENT` (lexical_declaration, line 55)
- `web` (lexical_declaration, line 58)
- `licenses` (lexical_declaration, line 59)
- `trees` (lexical_declaration, line 60)
- `routes` (lexical_declaration, line 61)
- `loaded` (lexical_declaration, line 82, exported)
- `registry` (lexical_declaration, line 83, exported)
- `discovery` (lexical_declaration, line 85, exported)
- `stamped` (lexical_declaration, line 86, exported)
- `pageSitemap` (lexical_declaration, line 88, exported)
- `security` (lexical_declaration, line 90, exported)
- `{ files, journal, records, unresolved }` (lexical_declaration, line 96, exported)
- `catalog` (lexical_declaration, line 102, exported)
- `leaves` (lexical_declaration, line 106, exported)
- `sources` (lexical_declaration, line 114, exported)
- `body` (lexical_declaration, line 115, exported)
- `sourceSitemaps` (lexical_declaration, line 116, exported)
- `recordBody` (lexical_declaration, line 121, exported)
- `recordSitemaps` (lexical_declaration, line 122, exported)
- `selector` (lexical_declaration, line 127, exported)
- `sections` (lexical_declaration, line 128, exported)
- `sectionSitemaps` (lexical_declaration, line 132, exported)

## Contained in

- [core/coordinators](https://banes-lab.com/anatomy/build-scripts/folder-build-scripts-core-coordinators.md)

## Uses

- [core/converters/learning.converter.ts](https://banes-lab.com/source/build-scripts/core/converters/learning.converter.ts.md)
- [core/converters/source.segment.converter.ts](https://banes-lab.com/source/build-scripts/core/converters/source.segment.converter.ts.md)
- [core/factories/server.factory.ts](https://banes-lab.com/source/build-scripts/core/factories/server.factory.ts.md)
- [core/loaders/source.loader.ts](https://banes-lab.com/source/build-scripts/core/loaders/source.loader.ts.md)
- [core/persistence/journal.persistence.ts](https://banes-lab.com/source/build-scripts/core/persistence/journal.persistence.ts.md)
- [core/persistence/page.persistence.ts](https://banes-lab.com/source/build-scripts/core/persistence/page.persistence.ts.md)
- [core/resolvers/catalog.resolver.ts](https://banes-lab.com/source/build-scripts/core/resolvers/catalog.resolver.ts.md)

## Linked from

- [core/converters](https://banes-lab.com/anatomy/build-scripts/folder-build-scripts-core-converters.md)
- [core/factories](https://banes-lab.com/anatomy/build-scripts/folder-build-scripts-core-factories.md)
- [core/loaders](https://banes-lab.com/anatomy/build-scripts/folder-build-scripts-core-loaders.md)
- [core/persistence](https://banes-lab.com/anatomy/build-scripts/folder-build-scripts-core-persistence.md)
- [core/resolvers](https://banes-lab.com/anatomy/build-scripts/folder-build-scripts-core-resolvers.md)

## Source

```typescript
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,
        };
    });
};
```
