#!/usr/bin/env node /** * checkSidebar.mjs — PLAN.md §12, added in phase 8. * * `src/config/sidebar.mjs` holds two trees: `docsSidebar`, which Starlight renders, and * `plannedSidebar`, the tree §10 planned. While pages were still being written the second * was a checklist. Now that every page exists it is a second copy of the first, maintained * by hand — and a hand-maintained copy with nothing reading it is exactly the shape of * thing §1 is about. * * It had already drifted, silently: phase 7 added the `Content` page under D37 and this * list was never updated. Nothing failed, because nothing read it. That is the whole * argument for this check. * * So the two must agree on groups, labels AND order. Order is checked because the order of * "Getting started" IS the installation path — §10 calls it the priority of the whole * project — and a reordering that nobody noticed would be a worse defect than a missing * page. * * node scripts/checkSidebar.mjs * * No token and no network: both trees are in this repository. */ import { docsSidebar, plannedSidebar } from '../src/config/sidebar.mjs'; const failures = []; const fail = (what, detail) => failures.push({ what, detail }); const live = new Map(docsSidebar.map((g) => [g.label, g.items.map((i) => i.label)])); const planned = new Map(Object.entries(plannedSidebar)); // ── Groups ────────────────────────────────────────────────────────────────── for (const label of live.keys()) { if (!planned.has(label)) fail(`group ${label}`, 'is in the live sidebar and not in plannedSidebar'); } for (const label of planned.keys()) { if (!live.has(label)) fail(`group ${label}`, 'is in plannedSidebar and not in the live sidebar'); } // ── Pages, in order ───────────────────────────────────────────────────────── for (const [label, liveItems] of live) { const plannedItems = planned.get(label); if (!plannedItems) continue; for (const page of liveItems) { if (!plannedItems.includes(page)) fail(`${label} → ${page}`, 'is live but not in plannedSidebar'); } for (const page of plannedItems) { if (!liveItems.includes(page)) fail(`${label} → ${page}`, 'is planned but has no live sidebar entry'); } // Only meaningful once membership matches; otherwise it just repeats the above. if (liveItems.length === plannedItems.length && liveItems.every((p) => plannedItems.includes(p))) { if (liveItems.join(' | ') !== plannedItems.join(' | ')) { fail( `${label} order`, `live "${liveItems.join(' → ')}" vs planned "${plannedItems.join(' → ')}"`, ); } } } // ── Report ────────────────────────────────────────────────────────────────── if (failures.length === 0) { const pages = [...live.values()].reduce((n, items) => n + items.length, 0); console.log(`checkSidebar: ${live.size} groups and ${pages} pages agree with plannedSidebar.`); } else { console.error(`\ncheckSidebar: ${failures.length} disagreement(s) between the two trees:\n`); for (const f of failures) console.error(` ✗ ${f.what}\n ${f.detail}`); console.error(` Both trees are in src/config/sidebar.mjs. Decide which one is right — if a page was deliberately added, renamed or reordered, plannedSidebar records that decision and should move with it. `); process.exit(1); }