core/persistence/page.persistence.ts
core/persistence/page.persistence.ts is a file in Bane's Lab Build Scripts. 195 lines of code and 25 definitions.
import {
FULL_TEXT_ROUTE,
PAGE_SITEMAP_ROUTE,
RECORD_SITEMAP_ROUTE,
SECTION_SITEMAP_ROUTE,
SITEMAP_ROUTE,
SOURCE_SITEMAP_ROUTE,
} from "#configuration/constants/site.constants";
import type {
PageSource,
RecordPageSource,
SectionPageSource,
SiteSource,
SourcePageSource,
} from "#types/page.types";
import type { RecordSubject, SectionSubject } from "@banes-lab/web/types/page.types.js";
import { datedRoutes, latestStamp } from "#core/converters/route.converter";
import { dirname, join } from "node:path";
import { fullTextParts, fullTextRoutes } from "#core/selectors/site.selector";
import { jsonPath, markdownFileOf } from "#core/resolvers/route.resolver";
import { mapSettled, rewriteEach } from "#core/adapters/document.adapter";
import { mkdirSync, readFileSync, rmSync } from "node:fs";
import { renderFullText, renderMarkdownPage, renderPageHtml } from "#core/formatters/page.formatter";
import {
renderLlms,
renderRobots,
renderRoutePayload,
renderSitemap,
renderSitemapIndex,
renderSitemapParts,
} from "#core/formatters/site.formatter";
import type { Discovery } from "#types/site.types";
import { HOME_PAGE } from "@banes-lab/web/ids/page.ids";
import type { SourceRoute } from "#types/source.types";
import type { StampedRoutes } from "#types/route.types";
import { documentPages } from "#core/converters/section.converter";
import { fileForPath } from "#core/resolvers/page.resolver";
import { isSourceFilePage } from "#core/predicates/route.predicate";
import { llmsAppendix } from "#core/formatters/catalog.formatter";
import { pageFromPath } from "@banes-lab/web/core/assets/link.assets.ts";
import { routesOf } from "#core/converters/page.converter";
import { sourceTextOf } from "#core/loaders/source.loader";
import { writeVerbatim } from "@govlab/canonical-write";
const INDEX_FILE = "index.html";
const MISSING_FILE = "404.html";
const MISSING_PAGE = "404";
const ROBOTS_FILE = "robots.txt";
const LLMS_FILE = "llms.txt";
const JSON_EXTENSION = ".json";
const PRECOMPRESSED_EXTENSIONS = [".br", ".gz"];
const SLASH = "/";
const writePage = function writePage(outDir: string, file: string, html: string): void {
const target = join(outDir, file);
mkdirSync(dirname(target), { recursive: true });
writeVerbatim(target, html);
for (const extension of PRECOMPRESSED_EXTENSIONS) {
rmSync(target + extension, { force: true });
}
};
export const writePages = async function writePages(
outDir: string,
loaded: PageSource & Pick<SiteSource, "ids">,
discovery: Discovery,
dates: StampedRoutes["dates"],
template = readFileSync(join(outDir, INDEX_FILE), "utf8"),
): Promise<void> {
await mapSettled(datedRoutes(routesOf(loaded, discovery), dates), (route) => {
writePage(outDir, fileForPath(route.path), renderPageHtml(loaded, template, route));
});
writePage(
outDir,
MISSING_FILE,
renderPageHtml(loaded, template, { page: MISSING_PAGE, path: SLASH + MISSING_PAGE }),
);
};
export const writeSourcePages = function writeSourcePages(
outDir: string,
loaded: SourcePageSource,
routes: readonly SourceRoute[],
template = readFileSync(join(outDir, INDEX_FILE), "utf8"),
): readonly string[] {
rewriteEach(
template,
routes,
(document, route) => {
loaded.renderer.applyHead(document, loaded.renderer.sourceHead(route.subject));
const whole = route.text === null ? null : sourceTextOf(route.text);
const text = whole === null || route.slice === undefined ? whole : whole.slice(...route.slice);
document.getElementById(loaded.main)?.replaceChildren(loaded.body.renderSourceBody(route, text));
},
(route, html) => {
writePage(outDir, fileForPath(route.subject.path), html);
},
);
const locations = routes
.filter((route) => isSourceFilePage(route.subject.path))
.map((route) => loaded.site + route.subject.path);
return writeSitemapParts(outDir, SOURCE_SITEMAP_ROUTE, locations);
};
export const writeRecordPages = function writeRecordPages(
outDir: string,
loaded: RecordPageSource,
records: readonly RecordSubject[],
template = readFileSync(join(outDir, INDEX_FILE), "utf8"),
): readonly string[] {
rewriteEach(
template,
records,
(document, record) => {
loaded.renderer.applyHead(document, loaded.renderer.recordHead(record));
const page = pageFromPath(record.collection.path, HOME_PAGE);
const rows =
record.section === null
? null
: loaded.renderer.renderStaticSection(page, record.collection.path, record.section);
const body = loaded.body.renderRecordBody(record);
if (rows !== null) {
body.append(rows);
}
document.getElementById(loaded.main)?.replaceChildren(body);
},
(record, html) => {
writePage(outDir, fileForPath(record.path), html);
},
);
return writeSitemapParts(
outDir,
RECORD_SITEMAP_ROUTE,
records.map((record) => loaded.site + record.path),
);
};
export const writeSectionPages = function writeSectionPages(
outDir: string,
loaded: SectionPageSource,
sections: readonly SectionSubject[],
template = readFileSync(join(outDir, INDEX_FILE), "utf8"),
): readonly string[] {
rewriteEach(
template,
sections,
(document, section) => {
loaded.renderer.applyHead(document, loaded.renderer.sectionHead(section));
document.getElementById(loaded.main)?.replaceChildren(loaded.renderer.renderSectionPage(section));
},
(section, html) => {
writePage(outDir, fileForPath(section.path), html);
},
);
return writeSitemapParts(
outDir,
SECTION_SITEMAP_ROUTE,
sections.map((section) => loaded.site + section.path),
);
};
export const writeText = function writeText(outDir: string, file: string, text: string): void {
const target = join(outDir, file);
mkdirSync(dirname(target), { recursive: true });
writeVerbatim(target, text);
};
export const writeSitemapParts = function writeSitemapParts(
outDir: string,
route: string,
locations: readonly string[],
): readonly string[] {
return renderSitemapParts(route, locations).map((part) => {
writeText(outDir, part.route.slice(1), part.body);
return part.route;
});
};
export const writeSitemapIndex = function writeSitemapIndex(
outDir: string,
site: string,
sitemaps: readonly (readonly [string, string | null])[],
): void {
writeText(outDir, SITEMAP_ROUTE.slice(1), renderSitemapIndex(site, sitemaps));
};
export const writeDiscovery = function writeDiscovery(
outDir: string,
discovery: Discovery,
stamped: StampedRoutes,
): readonly [string, string | null] {
for (const route of discovery.routes) {
writeText(outDir, jsonPath(route) + JSON_EXTENSION, renderRoutePayload(discovery, route));
writeText(outDir, markdownFileOf(route.path), renderMarkdownPage(discovery.site, route));
}
writeText(outDir, FULL_TEXT_ROUTE, renderFullText(discovery, fullTextRoutes(discovery)));
for (const part of fullTextParts(discovery)) {
writeText(outDir, part.file, renderFullText(discovery, part.routes));
}
writeText(outDir, PAGE_SITEMAP_ROUTE.slice(1), renderSitemap(discovery, stamped.stamps));
writeText(outDir, ROBOTS_FILE, renderRobots(discovery, [SITEMAP_ROUTE]));
const appendix = llmsAppendix(discovery, documentPages(discovery));
writeText(outDir, LLMS_FILE, renderLlms(discovery, appendix));
return [PAGE_SITEMAP_ROUTE, latestStamp(stamped.ledger)];
};