/** * scripts/generate-docs/lib/sidebar-builder.ts * * Reads `docs-src/_data/sidebar.json`, validates that every referenced page * exists in `docs-src/*.md`, and returns a per-page enriched copy with the * `active` flag injected on the current page entry. */ import { readFileSync } from 'node:fs'; import { join } from 'node:path'; import type { LocalisedString } from './markdown-parser'; const SIDEBAR_PATH = join(__dirname, '..', '..', '..', 'docs-src', '_data', 'sidebar.json'); export interface SidebarItem { page: string; icon: string; label: LocalisedString; active?: boolean; /** Optional nested entries rendered indented below the parent. */ children?: SidebarItem[]; } export interface SidebarSection { title: LocalisedString; items: SidebarItem[]; } export interface Sidebar { sections: SidebarSection[]; } let cached: Sidebar | null = null; function loadSidebar(): Sidebar { if (cached) return cached; const raw = readFileSync(SIDEBAR_PATH, 'utf-8'); cached = JSON.parse(raw) as Sidebar; return cached; } /** * Throws if any sidebar entry points to a page slug that wasn't found among * the parsed pages — catches "added link to sidebar but never to docs-src". * Recurses into `children`. */ export function validateSidebar(knownSlugs: string[]): void { const sidebar = loadSidebar(); const known = new Set(knownSlugs); const missing: string[] = []; const visit = (item: SidebarItem): void => { if (!known.has(item.page)) missing.push(item.page); item.children?.forEach(visit); }; for (const section of sidebar.sections) section.items.forEach(visit); if (missing.length) { throw new Error( `sidebar.json references pages that have no docs-src/.md: ${missing.join(', ')}` ); } } /** * Returns a deep-cloned sidebar with `active: true` set on the item (or nested * child) whose `page` matches the current page slug. Cloning keeps each page * render independent. */ export function buildSidebarForPage(currentSlug: string): Sidebar { const sidebar = loadSidebar(); const cloneItem = (item: SidebarItem): SidebarItem => ({ ...item, active: item.page === currentSlug, children: item.children?.map(cloneItem), }); return { sections: sidebar.sections.map((section) => ({ title: section.title, items: section.items.map(cloneItem), })), }; }