core/coordinators/link.coordinator.ts
core/coordinators/link.coordinator.ts is a file in Bane's Lab Build Scripts. 91 lines of code and 21 definitions.
import type { BakedTabs, LinkedPage, PageContent, VocabularyEntry } from "@banes-lab/web/types/vocabulary.types.js";
import { isAbsolute, relative } from "node:path";
import type { AnatomyIndex } from "@banes-lab/web/types/record.types.js";
import type { ModuleImporter } from "#types/loader.types";
import { RUNNER_MODULES } from "#configuration/constants/loader.constants";
import { absolutePath } from "@ssot/paths";
import { createServerModuleRunner } from "vite";
import { installDocument } from "#core/adapters/document.adapter";
import { renderAnatomyIndex } from "#core/formatters/anatomy.formatter";
import { renderBakedTabs } from "#core/formatters/tab.formatter";
import { withModuleServer } from "#core/factories/server.factory";
import { writeCanonicalText } from "@govlab/canonical-write";
const GENERATED_MARK = ".generated.";
const GENERATED_EXTENSION = ".generated.ts";
const INDEX_STEM = "anatomy.index";
const PARENT = "..";
interface SiteContent {
readonly ANATOMY_INDEX: AnatomyIndex;
readonly BAKED_TABS: readonly BakedTabs[];
readonly CHAPTER_PAGES: readonly PageContent[];
readonly LINKED_PAGES: readonly LinkedPage[];
}
interface Linking {
readonly linkPages: (
pages: readonly PageContent[],
linked: readonly LinkedPage[],
vocabulary: readonly VocabularyEntry[],
) => readonly LinkedPage[];
}
interface Baked {
readonly entries: readonly BakedTabs[];
readonly index: AnatomyIndex;
readonly pages: number;
}
export const bakedTabsOf = async function bakedTabsOf(runner: ModuleImporter): Promise<Baked> {
const [site, vocabulary, linking] = await Promise.all([
runner.import<SiteContent>(RUNNER_MODULES.site.path),
runner.import<{ readonly VOCABULARY: readonly VocabularyEntry[] }>(RUNNER_MODULES.vocabularyAsset.path),
runner.import<Linking>(RUNNER_MODULES.linking.path),
]);
const linked = linking.linkPages(site.CHAPTER_PAGES, site.LINKED_PAGES, vocabulary.VOCABULARY);
return { entries: [...linked, ...site.BAKED_TABS], index: site.ANATOMY_INDEX, pages: linked.length };
};
const byStem = function byStem(entries: readonly BakedTabs[]): ReadonlyMap<string, readonly BakedTabs[]> {
const grouped = new Map<string, BakedTabs[]>();
for (const entry of entries) {
grouped.set(entry.stem, [...(grouped.get(entry.stem) ?? []), entry]);
}
return grouped;
};
export const writeLinkedPages = async function writeLinkedPages(root: string): Promise<number> {
return withModuleServer(root, async (server) => {
const runner = createServerModuleRunner(server.environments.ssr);
const links = await runner.import<{ readonly SITE_URL: string }>(RUNNER_MODULES.links.path);
installDocument(links.SITE_URL);
const baked = await bakedTabsOf(runner);
await Promise.all([
...[...byStem(baked.entries)].map(async ([stem, entries]) =>
writeCanonicalText(absolutePath("app.pages", stem + GENERATED_EXTENSION), renderBakedTabs(entries)),
),
writeCanonicalText(
absolutePath("app.pages", INDEX_STEM + GENERATED_EXTENSION),
renderAnatomyIndex(baked.index),
),
]);
return baked.pages;
});
};
const isSourceOf = function isSourceOf(root: string, file: string): boolean {
const local = relative(root, file);
return local.length > 0 && !local.startsWith(PARENT) && !isAbsolute(local) && !local.includes(GENERATED_MARK);
};
export const createRelinker = function createRelinker(
root: string,
report: (error: unknown) => void,
): (file: string) => void {
let running: Promise<void> = Promise.resolve();
let queued = false;
return (file) => {
if (queued || !isSourceOf(root, file)) {
return;
}
queued = true;
running = running
.then(async () => {
queued = false;
await writeLinkedPages(root);
})
.catch(report);
};
};