nimbus-docs 0.1.7 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["PRIMARY_COLLECTION","githubSlug","PRIMARY_COLLECTION","getBreadcrumbs","getPrevNext","fs","splitTopLevelCommas","fs","fs","fs","PRIMARY_COLLECTION","PRIMARY_COLLECTION","resolveCollectionPrefix","resolveCollectionSlug","buildPrevNext","buildBreadcrumbs"],"sources":["../src/_internal/runtime-config.ts","../src/_internal/content.ts","../../../node_modules/.pnpm/github-slugger@2.0.0/node_modules/github-slugger/regex.js","../../../node_modules/.pnpm/github-slugger@2.0.0/node_modules/github-slugger/index.js","../src/_internal/astro-slug.ts","../src/_internal/url.ts","../src/_internal/sidebar.ts","../src/_internal/collection-mount.ts","../src/_internal/transform.ts","../src/_internal/navigation.ts","../src/_internal/toc.ts","../src/_internal/git-last-updated.ts","../src/_internal/parse-components-registry.ts","../src/lint/site-model.ts","../src/_internal/parse-content-collections.ts","../src/_internal/code-transformers.ts","../src/_internal/validate-mdx-content.ts","../src/_internal/validate.ts","../src/_internal/virtual-config.ts","../src/_internal/scan-version-frontmatter.ts","../src/_internal/version-alternates.ts","../src/integration.ts","../src/index.ts"],"sourcesContent":["/**\n * Runtime config bridge.\n *\n * Reads the user's validated NimbusConfig from `virtual:nimbus/config`,\n * which is provided by the Vite plugin our integration registers.\n *\n * **The import is dynamic on purpose.** Astro's config loader (in plain\n * Node) eagerly imports the whole framework bundle when it loads our\n * default export from `astro.config.ts`. A top-level static\n * `import \"virtual:...\"` would crash there because Vite hasn't booted\n * yet. Dynamic import keeps the load deferred until a page actually\n * calls a helper at request/render time.\n */\n\nimport type { NimbusConfig } from \"../types.js\";\nimport type { VersionAlternatesTable } from \"./version-alternates.js\";\n\n// `virtual:nimbus/config` declarations live at\n// `packages/nimbus-docs/src/types/virtual-modules.d.ts` so they're ambient\n// (resolvable from dynamic `await import()` calls below) rather than\n// scoped to this module.\n\nlet _cached: NimbusConfig | null = null;\nlet _cachedCollections: readonly string[] | null = null;\nlet _cachedAlternates: VersionAlternatesTable | null = null;\n\nexport async function loadNimbusConfig(): Promise<NimbusConfig> {\n if (_cached) return _cached;\n const mod = await import(\"virtual:nimbus/config\");\n // Intermediate const so the return path isn't typed `NimbusConfig | null`\n // (the module-scoped `_cached` keeps its union; the value being cached\n // and returned is the same object — observable behavior is identical).\n const value = mod.config;\n _cached = value;\n return value;\n}\n\n/**\n * Build-time-resolved list of collections the agent-facing routes\n * (llms.txt, per-page .md alternates) should iterate. Reserved names\n * (`partials`, `_*`) are already filtered. See `getIndexedEntries()`.\n */\nexport async function loadIndexedCollections(): Promise<readonly string[]> {\n if (_cachedCollections) return _cachedCollections;\n const mod = await import(\"virtual:nimbus/config\");\n const value = mod.indexedCollections;\n _cachedCollections = value;\n return value;\n}\n\n/**\n * Build-time-resolved alternates table for cross-version SEO links.\n * Returns the same object on every call (cached after first load).\n * Empty `{}` when the site is unversioned.\n */\nexport async function loadVersionAlternates(): Promise<VersionAlternatesTable> {\n if (_cachedAlternates) return _cachedAlternates;\n const mod = await import(\"virtual:nimbus/config\");\n // Fall back to an empty table when the virtual module doesn't define\n // `versionAlternates` (e.g. older integration build, or transient\n // dev-server cache state). Downstream lookups (`table[key] ?? null`)\n // then resolve cleanly instead of throwing on an undefined receiver.\n const value = mod.versionAlternates ?? {};\n _cachedAlternates = value;\n return value;\n}\n","/**\n * Content collection access for helpers.\n *\n * Dynamic import of `astro:content` for the same reason as\n * runtime-config: Astro's config loader runs in plain Node, where\n * `astro:content` doesn't exist. We defer to call time, which only\n * happens at page render.\n *\n * There is intentionally no global \"list of collections Nimbus knows\n * about\" — the framework doesn't try to mirror what\n * `content.config.ts` registers. Callers that need entries from\n * multiple collections pass them explicitly; the sidebar builder\n * derives its list from `sidebar.items` references.\n */\n\nimport type { CollectionEntry } from \"astro:content\";\n\n/** Primary collection name. Hard-coded — see also `getDocsStaticPaths`. */\nconst PRIMARY_COLLECTION = \"docs\";\n\n/**\n * Return visible entries from one or more collections. Drafts are\n * filtered out in production builds (matching the existing\n * single-collection behaviour).\n *\n * Defaults to `[\"docs\"]` — the framework's primary collection.\n * Cross-collection callers (llms.txt aggregators, custom indexes,\n * etc.) pass an explicit list.\n *\n * Returns a flat `CollectionEntry<string>[]` so cross-collection\n * traversal doesn't need to know the user's collection names at type\n * time. Callers that need per-collection type safety should call\n * `getCollection(\"api\")` directly.\n */\nexport async function getVisibleEntries(\n collections: string[] = [PRIMARY_COLLECTION],\n): Promise<CollectionEntry<string>[]> {\n const { getCollection } = await import(\"astro:content\");\n const lists = await Promise.all(\n collections.map((name) =>\n getCollection(name).catch(() => [] as CollectionEntry<string>[]),\n ),\n );\n const all = lists.flat();\n return import.meta.env.PROD\n ? all.filter((entry: CollectionEntry<string>) => !entry.data.draft)\n : all;\n}\n\n/**\n * Return visible entries grouped by collection. Used by the sidebar\n * builder so `collection:` autogenerate can look up entries by name\n * without re-fetching.\n */\nexport async function getVisibleEntriesByCollection(\n collections: string[],\n): Promise<Record<string, CollectionEntry<string>[]>> {\n const { getCollection } = await import(\"astro:content\");\n const out: Record<string, CollectionEntry<string>[]> = {};\n await Promise.all(\n collections.map(async (name) => {\n const all = await getCollection(name).catch(\n () => [] as CollectionEntry<string>[],\n );\n out[name] = import.meta.env.PROD\n ? all.filter((entry: CollectionEntry<string>) => !entry.data.draft)\n : all;\n }),\n );\n return out;\n}\n","// This module is generated by `script/`.\n/* eslint-disable no-control-regex, no-misleading-character-class, no-useless-escape */\nexport const regex = /[\\0-\\x1F!-,\\.\\/:-@\\[-\\^`\\{-\\xA9\\xAB-\\xB4\\xB6-\\xB9\\xBB-\\xBF\\xD7\\xF7\\u02C2-\\u02C5\\u02D2-\\u02DF\\u02E5-\\u02EB\\u02ED\\u02EF-\\u02FF\\u0375\\u0378\\u0379\\u037E\\u0380-\\u0385\\u0387\\u038B\\u038D\\u03A2\\u03F6\\u0482\\u0530\\u0557\\u0558\\u055A-\\u055F\\u0589-\\u0590\\u05BE\\u05C0\\u05C3\\u05C6\\u05C8-\\u05CF\\u05EB-\\u05EE\\u05F3-\\u060F\\u061B-\\u061F\\u066A-\\u066D\\u06D4\\u06DD\\u06DE\\u06E9\\u06FD\\u06FE\\u0700-\\u070F\\u074B\\u074C\\u07B2-\\u07BF\\u07F6-\\u07F9\\u07FB\\u07FC\\u07FE\\u07FF\\u082E-\\u083F\\u085C-\\u085F\\u086B-\\u089F\\u08B5\\u08C8-\\u08D2\\u08E2\\u0964\\u0965\\u0970\\u0984\\u098D\\u098E\\u0991\\u0992\\u09A9\\u09B1\\u09B3-\\u09B5\\u09BA\\u09BB\\u09C5\\u09C6\\u09C9\\u09CA\\u09CF-\\u09D6\\u09D8-\\u09DB\\u09DE\\u09E4\\u09E5\\u09F2-\\u09FB\\u09FD\\u09FF\\u0A00\\u0A04\\u0A0B-\\u0A0E\\u0A11\\u0A12\\u0A29\\u0A31\\u0A34\\u0A37\\u0A3A\\u0A3B\\u0A3D\\u0A43-\\u0A46\\u0A49\\u0A4A\\u0A4E-\\u0A50\\u0A52-\\u0A58\\u0A5D\\u0A5F-\\u0A65\\u0A76-\\u0A80\\u0A84\\u0A8E\\u0A92\\u0AA9\\u0AB1\\u0AB4\\u0ABA\\u0ABB\\u0AC6\\u0ACA\\u0ACE\\u0ACF\\u0AD1-\\u0ADF\\u0AE4\\u0AE5\\u0AF0-\\u0AF8\\u0B00\\u0B04\\u0B0D\\u0B0E\\u0B11\\u0B12\\u0B29\\u0B31\\u0B34\\u0B3A\\u0B3B\\u0B45\\u0B46\\u0B49\\u0B4A\\u0B4E-\\u0B54\\u0B58-\\u0B5B\\u0B5E\\u0B64\\u0B65\\u0B70\\u0B72-\\u0B81\\u0B84\\u0B8B-\\u0B8D\\u0B91\\u0B96-\\u0B98\\u0B9B\\u0B9D\\u0BA0-\\u0BA2\\u0BA5-\\u0BA7\\u0BAB-\\u0BAD\\u0BBA-\\u0BBD\\u0BC3-\\u0BC5\\u0BC9\\u0BCE\\u0BCF\\u0BD1-\\u0BD6\\u0BD8-\\u0BE5\\u0BF0-\\u0BFF\\u0C0D\\u0C11\\u0C29\\u0C3A-\\u0C3C\\u0C45\\u0C49\\u0C4E-\\u0C54\\u0C57\\u0C5B-\\u0C5F\\u0C64\\u0C65\\u0C70-\\u0C7F\\u0C84\\u0C8D\\u0C91\\u0CA9\\u0CB4\\u0CBA\\u0CBB\\u0CC5\\u0CC9\\u0CCE-\\u0CD4\\u0CD7-\\u0CDD\\u0CDF\\u0CE4\\u0CE5\\u0CF0\\u0CF3-\\u0CFF\\u0D0D\\u0D11\\u0D45\\u0D49\\u0D4F-\\u0D53\\u0D58-\\u0D5E\\u0D64\\u0D65\\u0D70-\\u0D79\\u0D80\\u0D84\\u0D97-\\u0D99\\u0DB2\\u0DBC\\u0DBE\\u0DBF\\u0DC7-\\u0DC9\\u0DCB-\\u0DCE\\u0DD5\\u0DD7\\u0DE0-\\u0DE5\\u0DF0\\u0DF1\\u0DF4-\\u0E00\\u0E3B-\\u0E3F\\u0E4F\\u0E5A-\\u0E80\\u0E83\\u0E85\\u0E8B\\u0EA4\\u0EA6\\u0EBE\\u0EBF\\u0EC5\\u0EC7\\u0ECE\\u0ECF\\u0EDA\\u0EDB\\u0EE0-\\u0EFF\\u0F01-\\u0F17\\u0F1A-\\u0F1F\\u0F2A-\\u0F34\\u0F36\\u0F38\\u0F3A-\\u0F3D\\u0F48\\u0F6D-\\u0F70\\u0F85\\u0F98\\u0FBD-\\u0FC5\\u0FC7-\\u0FFF\\u104A-\\u104F\\u109E\\u109F\\u10C6\\u10C8-\\u10CC\\u10CE\\u10CF\\u10FB\\u1249\\u124E\\u124F\\u1257\\u1259\\u125E\\u125F\\u1289\\u128E\\u128F\\u12B1\\u12B6\\u12B7\\u12BF\\u12C1\\u12C6\\u12C7\\u12D7\\u1311\\u1316\\u1317\\u135B\\u135C\\u1360-\\u137F\\u1390-\\u139F\\u13F6\\u13F7\\u13FE-\\u1400\\u166D\\u166E\\u1680\\u169B-\\u169F\\u16EB-\\u16ED\\u16F9-\\u16FF\\u170D\\u1715-\\u171F\\u1735-\\u173F\\u1754-\\u175F\\u176D\\u1771\\u1774-\\u177F\\u17D4-\\u17D6\\u17D8-\\u17DB\\u17DE\\u17DF\\u17EA-\\u180A\\u180E\\u180F\\u181A-\\u181F\\u1879-\\u187F\\u18AB-\\u18AF\\u18F6-\\u18FF\\u191F\\u192C-\\u192F\\u193C-\\u1945\\u196E\\u196F\\u1975-\\u197F\\u19AC-\\u19AF\\u19CA-\\u19CF\\u19DA-\\u19FF\\u1A1C-\\u1A1F\\u1A5F\\u1A7D\\u1A7E\\u1A8A-\\u1A8F\\u1A9A-\\u1AA6\\u1AA8-\\u1AAF\\u1AC1-\\u1AFF\\u1B4C-\\u1B4F\\u1B5A-\\u1B6A\\u1B74-\\u1B7F\\u1BF4-\\u1BFF\\u1C38-\\u1C3F\\u1C4A-\\u1C4C\\u1C7E\\u1C7F\\u1C89-\\u1C8F\\u1CBB\\u1CBC\\u1CC0-\\u1CCF\\u1CD3\\u1CFB-\\u1CFF\\u1DFA\\u1F16\\u1F17\\u1F1E\\u1F1F\\u1F46\\u1F47\\u1F4E\\u1F4F\\u1F58\\u1F5A\\u1F5C\\u1F5E\\u1F7E\\u1F7F\\u1FB5\\u1FBD\\u1FBF-\\u1FC1\\u1FC5\\u1FCD-\\u1FCF\\u1FD4\\u1FD5\\u1FDC-\\u1FDF\\u1FED-\\u1FF1\\u1FF5\\u1FFD-\\u203E\\u2041-\\u2053\\u2055-\\u2070\\u2072-\\u207E\\u2080-\\u208F\\u209D-\\u20CF\\u20F1-\\u2101\\u2103-\\u2106\\u2108\\u2109\\u2114\\u2116-\\u2118\\u211E-\\u2123\\u2125\\u2127\\u2129\\u212E\\u213A\\u213B\\u2140-\\u2144\\u214A-\\u214D\\u214F-\\u215F\\u2189-\\u24B5\\u24EA-\\u2BFF\\u2C2F\\u2C5F\\u2CE5-\\u2CEA\\u2CF4-\\u2CFF\\u2D26\\u2D28-\\u2D2C\\u2D2E\\u2D2F\\u2D68-\\u2D6E\\u2D70-\\u2D7E\\u2D97-\\u2D9F\\u2DA7\\u2DAF\\u2DB7\\u2DBF\\u2DC7\\u2DCF\\u2DD7\\u2DDF\\u2E00-\\u2E2E\\u2E30-\\u3004\\u3008-\\u3020\\u3030\\u3036\\u3037\\u303D-\\u3040\\u3097\\u3098\\u309B\\u309C\\u30A0\\u30FB\\u3100-\\u3104\\u3130\\u318F-\\u319F\\u31C0-\\u31EF\\u3200-\\u33FF\\u4DC0-\\u4DFF\\u9FFD-\\u9FFF\\uA48D-\\uA4CF\\uA4FE\\uA4FF\\uA60D-\\uA60F\\uA62C-\\uA63F\\uA673\\uA67E\\uA6F2-\\uA716\\uA720\\uA721\\uA789\\uA78A\\uA7C0\\uA7C1\\uA7CB-\\uA7F4\\uA828-\\uA82B\\uA82D-\\uA83F\\uA874-\\uA87F\\uA8C6-\\uA8CF\\uA8DA-\\uA8DF\\uA8F8-\\uA8FA\\uA8FC\\uA92E\\uA92F\\uA954-\\uA95F\\uA97D-\\uA97F\\uA9C1-\\uA9CE\\uA9DA-\\uA9DF\\uA9FF\\uAA37-\\uAA3F\\uAA4E\\uAA4F\\uAA5A-\\uAA5F\\uAA77-\\uAA79\\uAAC3-\\uAADA\\uAADE\\uAADF\\uAAF0\\uAAF1\\uAAF7-\\uAB00\\uAB07\\uAB08\\uAB0F\\uAB10\\uAB17-\\uAB1F\\uAB27\\uAB2F\\uAB5B\\uAB6A-\\uAB6F\\uABEB\\uABEE\\uABEF\\uABFA-\\uABFF\\uD7A4-\\uD7AF\\uD7C7-\\uD7CA\\uD7FC-\\uD7FF\\uE000-\\uF8FF\\uFA6E\\uFA6F\\uFADA-\\uFAFF\\uFB07-\\uFB12\\uFB18-\\uFB1C\\uFB29\\uFB37\\uFB3D\\uFB3F\\uFB42\\uFB45\\uFBB2-\\uFBD2\\uFD3E-\\uFD4F\\uFD90\\uFD91\\uFDC8-\\uFDEF\\uFDFC-\\uFDFF\\uFE10-\\uFE1F\\uFE30-\\uFE32\\uFE35-\\uFE4C\\uFE50-\\uFE6F\\uFE75\\uFEFD-\\uFF0F\\uFF1A-\\uFF20\\uFF3B-\\uFF3E\\uFF40\\uFF5B-\\uFF65\\uFFBF-\\uFFC1\\uFFC8\\uFFC9\\uFFD0\\uFFD1\\uFFD8\\uFFD9\\uFFDD-\\uFFFF]|\\uD800[\\uDC0C\\uDC27\\uDC3B\\uDC3E\\uDC4E\\uDC4F\\uDC5E-\\uDC7F\\uDCFB-\\uDD3F\\uDD75-\\uDDFC\\uDDFE-\\uDE7F\\uDE9D-\\uDE9F\\uDED1-\\uDEDF\\uDEE1-\\uDEFF\\uDF20-\\uDF2C\\uDF4B-\\uDF4F\\uDF7B-\\uDF7F\\uDF9E\\uDF9F\\uDFC4-\\uDFC7\\uDFD0\\uDFD6-\\uDFFF]|\\uD801[\\uDC9E\\uDC9F\\uDCAA-\\uDCAF\\uDCD4-\\uDCD7\\uDCFC-\\uDCFF\\uDD28-\\uDD2F\\uDD64-\\uDDFF\\uDF37-\\uDF3F\\uDF56-\\uDF5F\\uDF68-\\uDFFF]|\\uD802[\\uDC06\\uDC07\\uDC09\\uDC36\\uDC39-\\uDC3B\\uDC3D\\uDC3E\\uDC56-\\uDC5F\\uDC77-\\uDC7F\\uDC9F-\\uDCDF\\uDCF3\\uDCF6-\\uDCFF\\uDD16-\\uDD1F\\uDD3A-\\uDD7F\\uDDB8-\\uDDBD\\uDDC0-\\uDDFF\\uDE04\\uDE07-\\uDE0B\\uDE14\\uDE18\\uDE36\\uDE37\\uDE3B-\\uDE3E\\uDE40-\\uDE5F\\uDE7D-\\uDE7F\\uDE9D-\\uDEBF\\uDEC8\\uDEE7-\\uDEFF\\uDF36-\\uDF3F\\uDF56-\\uDF5F\\uDF73-\\uDF7F\\uDF92-\\uDFFF]|\\uD803[\\uDC49-\\uDC7F\\uDCB3-\\uDCBF\\uDCF3-\\uDCFF\\uDD28-\\uDD2F\\uDD3A-\\uDE7F\\uDEAA\\uDEAD-\\uDEAF\\uDEB2-\\uDEFF\\uDF1D-\\uDF26\\uDF28-\\uDF2F\\uDF51-\\uDFAF\\uDFC5-\\uDFDF\\uDFF7-\\uDFFF]|\\uD804[\\uDC47-\\uDC65\\uDC70-\\uDC7E\\uDCBB-\\uDCCF\\uDCE9-\\uDCEF\\uDCFA-\\uDCFF\\uDD35\\uDD40-\\uDD43\\uDD48-\\uDD4F\\uDD74\\uDD75\\uDD77-\\uDD7F\\uDDC5-\\uDDC8\\uDDCD\\uDDDB\\uDDDD-\\uDDFF\\uDE12\\uDE38-\\uDE3D\\uDE3F-\\uDE7F\\uDE87\\uDE89\\uDE8E\\uDE9E\\uDEA9-\\uDEAF\\uDEEB-\\uDEEF\\uDEFA-\\uDEFF\\uDF04\\uDF0D\\uDF0E\\uDF11\\uDF12\\uDF29\\uDF31\\uDF34\\uDF3A\\uDF45\\uDF46\\uDF49\\uDF4A\\uDF4E\\uDF4F\\uDF51-\\uDF56\\uDF58-\\uDF5C\\uDF64\\uDF65\\uDF6D-\\uDF6F\\uDF75-\\uDFFF]|\\uD805[\\uDC4B-\\uDC4F\\uDC5A-\\uDC5D\\uDC62-\\uDC7F\\uDCC6\\uDCC8-\\uDCCF\\uDCDA-\\uDD7F\\uDDB6\\uDDB7\\uDDC1-\\uDDD7\\uDDDE-\\uDDFF\\uDE41-\\uDE43\\uDE45-\\uDE4F\\uDE5A-\\uDE7F\\uDEB9-\\uDEBF\\uDECA-\\uDEFF\\uDF1B\\uDF1C\\uDF2C-\\uDF2F\\uDF3A-\\uDFFF]|\\uD806[\\uDC3B-\\uDC9F\\uDCEA-\\uDCFE\\uDD07\\uDD08\\uDD0A\\uDD0B\\uDD14\\uDD17\\uDD36\\uDD39\\uDD3A\\uDD44-\\uDD4F\\uDD5A-\\uDD9F\\uDDA8\\uDDA9\\uDDD8\\uDDD9\\uDDE2\\uDDE5-\\uDDFF\\uDE3F-\\uDE46\\uDE48-\\uDE4F\\uDE9A-\\uDE9C\\uDE9E-\\uDEBF\\uDEF9-\\uDFFF]|\\uD807[\\uDC09\\uDC37\\uDC41-\\uDC4F\\uDC5A-\\uDC71\\uDC90\\uDC91\\uDCA8\\uDCB7-\\uDCFF\\uDD07\\uDD0A\\uDD37-\\uDD39\\uDD3B\\uDD3E\\uDD48-\\uDD4F\\uDD5A-\\uDD5F\\uDD66\\uDD69\\uDD8F\\uDD92\\uDD99-\\uDD9F\\uDDAA-\\uDEDF\\uDEF7-\\uDFAF\\uDFB1-\\uDFFF]|\\uD808[\\uDF9A-\\uDFFF]|\\uD809[\\uDC6F-\\uDC7F\\uDD44-\\uDFFF]|[\\uD80A\\uD80B\\uD80E-\\uD810\\uD812-\\uD819\\uD824-\\uD82B\\uD82D\\uD82E\\uD830-\\uD833\\uD837\\uD839\\uD83D\\uD83F\\uD87B-\\uD87D\\uD87F\\uD885-\\uDB3F\\uDB41-\\uDBFF][\\uDC00-\\uDFFF]|\\uD80D[\\uDC2F-\\uDFFF]|\\uD811[\\uDE47-\\uDFFF]|\\uD81A[\\uDE39-\\uDE3F\\uDE5F\\uDE6A-\\uDECF\\uDEEE\\uDEEF\\uDEF5-\\uDEFF\\uDF37-\\uDF3F\\uDF44-\\uDF4F\\uDF5A-\\uDF62\\uDF78-\\uDF7C\\uDF90-\\uDFFF]|\\uD81B[\\uDC00-\\uDE3F\\uDE80-\\uDEFF\\uDF4B-\\uDF4E\\uDF88-\\uDF8E\\uDFA0-\\uDFDF\\uDFE2\\uDFE5-\\uDFEF\\uDFF2-\\uDFFF]|\\uD821[\\uDFF8-\\uDFFF]|\\uD823[\\uDCD6-\\uDCFF\\uDD09-\\uDFFF]|\\uD82C[\\uDD1F-\\uDD4F\\uDD53-\\uDD63\\uDD68-\\uDD6F\\uDEFC-\\uDFFF]|\\uD82F[\\uDC6B-\\uDC6F\\uDC7D-\\uDC7F\\uDC89-\\uDC8F\\uDC9A-\\uDC9C\\uDC9F-\\uDFFF]|\\uD834[\\uDC00-\\uDD64\\uDD6A-\\uDD6C\\uDD73-\\uDD7A\\uDD83\\uDD84\\uDD8C-\\uDDA9\\uDDAE-\\uDE41\\uDE45-\\uDFFF]|\\uD835[\\uDC55\\uDC9D\\uDCA0\\uDCA1\\uDCA3\\uDCA4\\uDCA7\\uDCA8\\uDCAD\\uDCBA\\uDCBC\\uDCC4\\uDD06\\uDD0B\\uDD0C\\uDD15\\uDD1D\\uDD3A\\uDD3F\\uDD45\\uDD47-\\uDD49\\uDD51\\uDEA6\\uDEA7\\uDEC1\\uDEDB\\uDEFB\\uDF15\\uDF35\\uDF4F\\uDF6F\\uDF89\\uDFA9\\uDFC3\\uDFCC\\uDFCD]|\\uD836[\\uDC00-\\uDDFF\\uDE37-\\uDE3A\\uDE6D-\\uDE74\\uDE76-\\uDE83\\uDE85-\\uDE9A\\uDEA0\\uDEB0-\\uDFFF]|\\uD838[\\uDC07\\uDC19\\uDC1A\\uDC22\\uDC25\\uDC2B-\\uDCFF\\uDD2D-\\uDD2F\\uDD3E\\uDD3F\\uDD4A-\\uDD4D\\uDD4F-\\uDEBF\\uDEFA-\\uDFFF]|\\uD83A[\\uDCC5-\\uDCCF\\uDCD7-\\uDCFF\\uDD4C-\\uDD4F\\uDD5A-\\uDFFF]|\\uD83B[\\uDC00-\\uDDFF\\uDE04\\uDE20\\uDE23\\uDE25\\uDE26\\uDE28\\uDE33\\uDE38\\uDE3A\\uDE3C-\\uDE41\\uDE43-\\uDE46\\uDE48\\uDE4A\\uDE4C\\uDE50\\uDE53\\uDE55\\uDE56\\uDE58\\uDE5A\\uDE5C\\uDE5E\\uDE60\\uDE63\\uDE65\\uDE66\\uDE6B\\uDE73\\uDE78\\uDE7D\\uDE7F\\uDE8A\\uDE9C-\\uDEA0\\uDEA4\\uDEAA\\uDEBC-\\uDFFF]|\\uD83C[\\uDC00-\\uDD2F\\uDD4A-\\uDD4F\\uDD6A-\\uDD6F\\uDD8A-\\uDFFF]|\\uD83E[\\uDC00-\\uDFEF\\uDFFA-\\uDFFF]|\\uD869[\\uDEDE-\\uDEFF]|\\uD86D[\\uDF35-\\uDF3F]|\\uD86E[\\uDC1E\\uDC1F]|\\uD873[\\uDEA2-\\uDEAF]|\\uD87A[\\uDFE1-\\uDFFF]|\\uD87E[\\uDE1E-\\uDFFF]|\\uD884[\\uDF4B-\\uDFFF]|\\uDB40[\\uDC00-\\uDCFF\\uDDF0-\\uDFFF]/g\n","import { regex } from './regex.js'\n\nconst own = Object.hasOwnProperty\n\n/**\n * Slugger.\n */\nexport default class BananaSlug {\n /**\n * Create a new slug class.\n */\n constructor () {\n /** @type {Record<string, number>} */\n // eslint-disable-next-line no-unused-expressions\n this.occurrences\n\n this.reset()\n }\n\n /**\n * Generate a unique slug.\n *\n * Tracks previously generated slugs: repeated calls with the same value\n * will result in different slugs.\n * Use the `slug` function to get same slugs.\n *\n * @param {string} value\n * String of text to slugify\n * @param {boolean} [maintainCase=false]\n * Keep the current case, otherwise make all lowercase\n * @return {string}\n * A unique slug string\n */\n slug (value, maintainCase) {\n const self = this\n let result = slug(value, maintainCase === true)\n const originalSlug = result\n\n while (own.call(self.occurrences, result)) {\n self.occurrences[originalSlug]++\n result = originalSlug + '-' + self.occurrences[originalSlug]\n }\n\n self.occurrences[result] = 0\n\n return result\n }\n\n /**\n * Reset - Forget all previous slugs\n *\n * @return void\n */\n reset () {\n this.occurrences = Object.create(null)\n }\n}\n\n/**\n * Generate a slug.\n *\n * Does not track previously generated slugs: repeated calls with the same value\n * will result in the exact same slug.\n * Use the `GithubSlugger` class to get unique slugs.\n *\n * @param {string} value\n * String of text to slugify\n * @param {boolean} [maintainCase=false]\n * Keep the current case, otherwise make all lowercase\n * @return {string}\n * A unique slug string\n */\nexport function slug (value, maintainCase) {\n if (typeof value !== 'string') return ''\n if (!maintainCase) value = value.toLowerCase()\n return value.replace(regex, '').replace(/ /g, '-')\n}\n","/**\n * Mirror of Astro's content-layer slug normalization, used by every\n * framework URL builder that derives a URL from an `entry.id`.\n *\n * Astro's `glob` content loader (used by Nimbus's `docsCollection` /\n * `partialsCollection` factories) runs each path segment through\n * `github-slugger.slug()` and strips a trailing `/index`. That output is\n * what `entry.id` becomes inside `getCollection`, and it's what `params.slug`\n * substitutes into `[...slug].astro` routes. Anything that constructs a\n * URL from the raw filesystem path *must* apply the same normalization or\n * the URL won't match what Astro actually serves.\n *\n * Why mirror instead of asking Astro: framework URL builders run at page\n * render time (sidebar hrefs) and during the integration's\n * `astro:config:setup` (sitemap, llms.txt). At neither point do we have a\n * clean way to read Astro's resolved routes. The honest architectural fix\n * is to refactor those builders to consume the route manifest at\n * `astro:routes:resolved` (sitemap/llms) or via a build-emitted lookup\n * table (sidebar). Until that work lands, this helper keeps the URLs\n * correct.\n *\n * Mirror caveat: this matches `github-slugger.slug()` and the trailing-\n * /index strip — the documented public-library behaviors Astro inherits\n * — not Astro's private routing internals. If a user supplies a custom\n * `generateId` to the content loader, or a `data.slug` override in\n * frontmatter, this helper doesn't see it. Both are uncommon.\n */\n\nimport { slug as githubSlug } from \"github-slugger\";\n\n/** Canonicalize one entry id the way Astro's content layer does. */\nexport function canonicalSlug(entryId: string): string {\n const slugged = entryId\n .split(\"/\")\n .map((segment) => githubSlug(segment))\n .join(\"/\");\n if (slugged === \"index\") return \"\";\n return slugged.endsWith(\"/index\")\n ? slugged.slice(0, -\"/index\".length)\n : slugged;\n}\n\n/**\n * Compose the URL Astro serves for an entry at a given collection prefix.\n *\n * canonicalEntryUrl(\"\", \"WIP/index\") → \"/wip\"\n * canonicalEntryUrl(\"/blog\", \"first\") → \"/blog/first\"\n * canonicalEntryUrl(\"/v1\", \"index\") → \"/v1\"\n *\n * `prefix` is the collection mount path (`\"\"` for the primary `docs`\n * collection, `/blog` for a `blog` collection, `/v1` for a version\n * collection). Pass exactly what your framework prefix resolver returns.\n */\nexport function canonicalEntryUrl(prefix: string, entryId: string): string {\n const slug = canonicalSlug(entryId);\n if (slug === \"\") return prefix === \"\" ? \"/\" : prefix;\n return `${prefix}/${slug}`;\n}\n","/**\n * Internal URL helpers — one shape for matching, one shape for rendering.\n *\n * Static hosts that serve `page/index.html` (Astro's default `build.format:\n * \"directory\"`) canonicalize to a trailing-slash URL. If framework helpers\n * emit slashless hrefs, every sidebar click costs a 307 redirect before\n * Astro's client router can pick up the page. The fix splits href shape\n * into two forms:\n *\n * - `toRouteKey(href)` — slashless canonical form. Used wherever the\n * framework compares paths for identity (active sidebar state,\n * prev/next lookup, validation against the indexed route set).\n *\n * - `toBrowserHref(href)` — what we emit into `<a href>` / `<link>` for\n * HTML document routes. Adds a trailing slash so the URL matches the\n * directory-index page the host serves directly.\n *\n * Asset URLs (`.md`, `.png`, `.txt`, …), external URLs, and anchor-only\n * hrefs are returned unchanged by `toBrowserHref` — they aren't HTML\n * document routes and adding a slash would break them.\n *\n * Keep these out of the public API: starter components consume hrefs the\n * framework already shaped. Authors don't (and shouldn't) call these\n * directly.\n */\n\n/**\n * True for hrefs that point off-site — anything with a URI scheme\n * (`https:`, `mailto:`, `data:`, …) or a protocol-relative `//cdn.…`\n * prefix. Bare relative paths like `\"cli\"` and `\"./foo\"` are NOT external\n * — they resolve against the current page and the framework shouldn't\n * second-guess them.\n */\nexport function isAbsoluteUrl(href: string): boolean {\n return /^([a-z][a-z0-9+\\-.]*:|\\/\\/)/i.test(href);\n}\n\n/**\n * Detect whether the final path segment looks like a file (has an\n * extension). HTML document routes don't carry an extension under\n * `build.format: \"directory\"`; assets like `/og/card.png`,\n * `/llms.txt`, and `/cli/index.md` do.\n *\n * Conservative: only treats short, ASCII-letter-only extensions as files,\n * so paths with dots inside a segment (`/v1.2/foo`, version slugs) still\n * count as document routes.\n */\nfunction hasFileExtension(pathname: string): boolean {\n const lastSegment = pathname.slice(pathname.lastIndexOf(\"/\") + 1);\n const dot = lastSegment.lastIndexOf(\".\");\n if (dot <= 0) return false;\n const ext = lastSegment.slice(dot + 1);\n return ext.length > 0 && ext.length <= 6 && /^[a-zA-Z0-9]+$/.test(ext);\n}\n\n/** Split an href into `[pathname, suffix]` where `suffix` is the `?…#…` tail. */\nfunction splitSuffix(href: string): [string, string] {\n const queryAt = href.indexOf(\"?\");\n const hashAt = href.indexOf(\"#\");\n const cutAt =\n queryAt === -1 ? hashAt : hashAt === -1 ? queryAt : Math.min(queryAt, hashAt);\n if (cutAt === -1) return [href, \"\"];\n return [href.slice(0, cutAt), href.slice(cutAt)];\n}\n\n/**\n * Slashless canonical form for path comparisons.\n *\n * /cli → /cli\n * /cli/ → /cli\n * /cli/?x=1#y → /cli\n * / → /\n * /guides/setup/ → /guides/setup\n *\n * Strips query and hash so callers can compare two hrefs that differ only\n * in their tail. Root stays `\"/\"` — that's identity, not a trailing-slash\n * artifact.\n */\nexport function toRouteKey(href: string): string {\n const [pathname] = splitSuffix(href);\n if (pathname.length <= 1) return pathname || \"/\";\n return pathname.endsWith(\"/\") ? pathname.slice(0, -1) : pathname;\n}\n\n/**\n * Trailing-slash form for browser-facing hrefs to HTML document routes.\n * Preserves query and hash; root, external URLs, anchor-only hrefs, and\n * asset URLs (paths with a file extension) are returned unchanged.\n *\n * /cli → /cli/\n * /cli/ → /cli/\n * /cli#install → /cli/#install\n * /cli?v=1 → /cli/?v=1\n * / → /\n * /og/card.png → /og/card.png (asset, unchanged)\n * /cli/index.md → /cli/index.md (asset, unchanged)\n * https://x.com/a → https://x.com/a (external, unchanged)\n * #anchor → #anchor (anchor-only, unchanged)\n */\nexport function toBrowserHref(href: string): string {\n // External URLs (anything with a scheme, including `//cdn.example.com`)\n // and protocol-relative URLs aren't ours to normalize.\n if (isAbsoluteUrl(href)) return href;\n // Anchor-only and query-only hrefs stay relative to the current page.\n if (href.startsWith(\"#\") || href.startsWith(\"?\")) return href;\n // Anything that isn't an absolute site path: don't touch it.\n if (!href.startsWith(\"/\")) return href;\n\n const [pathname, suffix] = splitSuffix(href);\n if (pathname === \"/\") return href;\n if (hasFileExtension(pathname)) return href;\n if (pathname.endsWith(\"/\")) return href;\n return `${pathname}/${suffix}`;\n}\n","// ---------------------------------------------------------------------------\n// sidebar.ts — Hybrid sidebar builder\n//\n// `buildSidebarTree` returns the un-scoped tree. The public `getSidebar`\n// helper applies `scopeToCurrentSection` on top so each page only renders\n// its own top-level section in the rail. Callers that need the full tree\n// (e.g. the section-tabs derivation in `deriveSidebarSections`) skip the\n// scoping step.\n//\n// Three sources for the tree:\n// 1. Config-defined: items array in docs.config.ts takes priority\n// 2. Auto-generated: `autogenerate: { directory }` scans filesystem\n// 3. Filesystem fallback: if no config items, build from all docs entries\n// ---------------------------------------------------------------------------\n\nimport type {\n SidebarBadge,\n SidebarConfig,\n SidebarConfigItem as ConfigItem,\n SidebarExternalLinkItem,\n SidebarGroupItem,\n SidebarItem,\n SidebarLinkItem,\n SidebarSection,\n} from \"../types.js\";\nimport { canonicalEntryUrl } from \"./astro-slug.js\";\nimport { isAbsoluteUrl, toBrowserHref, toRouteKey } from \"./url.js\";\n\n/** Minimal shape needed from content entries */\ninterface CollectionEntry {\n id: string;\n data: {\n title: string;\n draft?: boolean;\n sidebar?: {\n order?: number;\n label?: string;\n badge?: SidebarBadge;\n hidden?: boolean;\n hideChildren?: boolean;\n };\n };\n}\n\n// ---------------------------------------------------------------------------\n// Sort\n// ---------------------------------------------------------------------------\n\nconst sortKeyByItem = new WeakMap<SidebarItem, string>();\n\nfunction sortSidebarItems(a: SidebarItem, b: SidebarItem): number {\n const orderDiff = a.order - b.order;\n if (orderDiff !== 0) return orderDiff;\n\n const keyA = sortKeyByItem.get(a) ?? (\"href\" in a ? a.href : a.label);\n const keyB = sortKeyByItem.get(b) ?? (\"href\" in b ? b.href : b.label);\n const keyDiff = keyA.localeCompare(keyB);\n if (keyDiff !== 0) return keyDiff;\n\n return a.type.localeCompare(b.type);\n}\n\n// ---------------------------------------------------------------------------\n// Entry index — shared utilities for looking up content entries\n// ---------------------------------------------------------------------------\n\nfunction buildEntryIndex(entries: CollectionEntry[]) {\n const visible = entries.filter((e) => !e.data.sidebar?.hidden);\n const byId = new Map<string, CollectionEntry>();\n for (const entry of visible) {\n byId.set(entry.id, entry);\n }\n\n const hasChildren = new Set<string>();\n for (const entry of visible) {\n const parts = entry.id.split(\"/\");\n for (let i = 1; i < parts.length; i++) {\n hasChildren.add(parts.slice(0, i).join(\"/\"));\n }\n }\n\n return { visible, byId, hasChildren };\n}\n\n// ---------------------------------------------------------------------------\n// Link/group creation from content entries\n// ---------------------------------------------------------------------------\n\n/**\n * Compose a final href for an entry. `hrefPrefix` is the collection mount\n * path (e.g. `/api`). Runs through `canonicalEntryUrl` so the href\n * matches what Astro serves (lowercase + folder-index strip — see\n * `_internal/astro-slug.ts` for the why and known caveats), then through\n * `toBrowserHref` so the rendered link is the trailing-slash form static\n * hosts serve directly (no 307 round-trip on click).\n */\nfunction joinHref(hrefPrefix: string, entryId: string): string {\n // hrefPrefix is \"\" for root-mounted collections, \"/api\" for prefixed ones.\n // Drop a trailing slash on the prefix to avoid `/api//foo`.\n const prefix = hrefPrefix.replace(/\\/$/, \"\");\n return toBrowserHref(canonicalEntryUrl(prefix, entryId));\n}\n\nfunction createLink(\n entry: CollectionEntry,\n currentPath: string,\n hrefPrefix = \"\",\n): SidebarLinkItem {\n const href = joinHref(hrefPrefix, entry.id);\n const badge = entry.data.draft\n ? (entry.data.sidebar?.badge ?? { text: \"Draft\", variant: \"warning\" })\n : entry.data.sidebar?.badge;\n\n const link: SidebarLinkItem = {\n type: \"link\",\n label: entry.data.sidebar?.label ?? entry.data.title,\n href,\n isCurrent: toRouteKey(currentPath) === toRouteKey(href),\n badge,\n order: entry.data.sidebar?.order ?? Number.MAX_VALUE,\n };\n\n sortKeyByItem.set(link, entry.id);\n return link;\n}\n\n// ---------------------------------------------------------------------------\n// Filesystem tree builder (used for autogenerate + fallback)\n// ---------------------------------------------------------------------------\n\nfunction buildFilesystemTree(\n entries: CollectionEntry[],\n currentPath: string,\n directory?: string,\n hrefPrefix = \"\",\n): SidebarItem[] {\n const { visible, byId, hasChildren } = buildEntryIndex(entries);\n\n // Filter to entries under the target directory\n const scoped = directory ? visible.filter((e) => e.id === directory || e.id.startsWith(`${directory}/`)) : visible;\n\n function buildLevel(parentPath: string): SidebarItem[] {\n const result: SidebarItem[] = [];\n const groupsAtLevel = new Map<string, SidebarGroupItem>();\n\n for (const entry of scoped) {\n if (entry.id === \"index\") continue;\n\n const id = entry.id;\n const relativeTo = directory ?? \"\";\n const relativeId = relativeTo ? (id === relativeTo ? \"\" : id.slice(relativeTo.length + 1)) : id;\n\n // Skip if this entry doesn't belong at this level\n if (parentPath === \"\") {\n if (!relativeId || relativeId.includes(\"/\") === false) {\n // Top-level entry relative to scope\n if (!relativeId) continue; // directory index, handled as group\n\n if (hasChildren.has(id)) {\n if (!groupsAtLevel.has(id)) {\n const group = createGroupFromEntry(id, entry, currentPath, byId);\n groupsAtLevel.set(id, group);\n result.push(group);\n }\n } else {\n result.push(createLink(entry, currentPath, hrefPrefix));\n }\n } else {\n // Multi-segment — belongs under first segment group.\n // `[0]!`: `String.split` always returns ≥1 element.\n const firstSeg = relativeId.split(\"/\")[0]!;\n const topDir = directory ? `${directory}/${firstSeg}` : firstSeg;\n if (!groupsAtLevel.has(topDir)) {\n const indexEntry = byId.get(topDir);\n const group = createGroupFromEntry(topDir, indexEntry, currentPath, byId);\n groupsAtLevel.set(topDir, group);\n result.push(group);\n }\n }\n } else {\n if (!id.startsWith(`${parentPath}/`)) continue;\n const remainder = id.slice(parentPath.length + 1);\n const remainderParts = remainder.split(\"/\");\n\n if (remainderParts.length === 1) {\n if (hasChildren.has(id)) {\n if (!groupsAtLevel.has(id)) {\n const group = createGroupFromEntry(id, entry, currentPath, byId);\n groupsAtLevel.set(id, group);\n result.push(group);\n }\n } else {\n result.push(createLink(entry, currentPath, hrefPrefix));\n }\n } else {\n const nextDir = `${parentPath}/${remainderParts[0]}`;\n if (!groupsAtLevel.has(nextDir)) {\n const indexEntry = byId.get(nextDir);\n const group = createGroupFromEntry(nextDir, indexEntry, currentPath, byId);\n groupsAtLevel.set(nextDir, group);\n result.push(group);\n }\n }\n }\n }\n\n // Recursively build children for each group\n for (const [groupPath, group] of groupsAtLevel) {\n const nestedChildren = buildLevel(groupPath);\n group.children = [...group.children, ...nestedChildren].sort(sortSidebarItems);\n\n if (group.children.length > 0) {\n const minChildOrder = Math.min(...group.children.map((item) => item.order));\n group.order = Math.min(group.order, minChildOrder);\n }\n }\n\n return result.sort(sortSidebarItems);\n }\n\n function createGroupFromEntry(\n dirPath: string,\n indexEntry: CollectionEntry | undefined,\n currentPath: string,\n _byId: Map<string, CollectionEntry>,\n ): SidebarGroupItem {\n const dirSegment = dirPath.split(\"/\").pop()!;\n // Starlight parity: group label comes from the directory name, not the index page title.\n // Use sidebar.label on the index page to override, but never use title (that's for the link).\n const groupLabel = indexEntry?.data.sidebar?.label ?? formatLabel(dirSegment);\n const groupOrder = indexEntry?.data.sidebar?.order ?? Number.MAX_VALUE;\n const children: SidebarItem[] = [];\n\n // Index page as child link inside the group\n // (hideChildren is handled later by processHideChildren)\n if (indexEntry) {\n children.push(createLink(indexEntry, currentPath, hrefPrefix));\n }\n\n const group: SidebarGroupItem = {\n type: \"group\",\n label: groupLabel,\n order: groupOrder,\n badge: indexEntry?.data.sidebar?.badge,\n children,\n _indexId: indexEntry?.id,\n };\n\n sortKeyByItem.set(group, dirPath);\n return group;\n }\n\n // For directory-scoped autogenerate, just build the children level\n if (directory) {\n return buildLevel(directory);\n }\n\n return buildLevel(\"\");\n}\n\n// ---------------------------------------------------------------------------\n// Config-driven builder\n// ---------------------------------------------------------------------------\n\nfunction resolveConfigItems(\n configItems: ConfigItem[],\n entriesByCollection: Record<string, CollectionEntry[]>,\n primaryCollection: string,\n currentPath: string,\n orderStart: number = 0,\n primaryPrefix: string = \"\",\n): SidebarItem[] {\n const primaryEntries = entriesByCollection[primaryCollection] ?? [];\n const { byId } = buildEntryIndex(primaryEntries);\n const result: SidebarItem[] = [];\n\n for (let i = 0; i < configItems.length; i++) {\n const item = configItems[i];\n // Guard the `T | undefined` from indexed access. `for...of` would lose\n // `i` which we need for `order`; the guard is no-cost — sparse arrays\n // don't occur here at runtime.\n if (!item) continue;\n const order = orderStart + i;\n\n if (typeof item === \"string\") {\n // Bare slug references resolve against the primary collection only.\n // Cross-collection links use the `{ label, link }` form with an\n // explicit href.\n const entry = byId.get(item);\n if (entry) {\n const link = createLink(entry, currentPath, primaryPrefix);\n link.order = order;\n result.push(link);\n } else {\n // Warn but don't crash — might be a typo\n console.warn(\n `[sidebar] Page \"${item}\" referenced in config but not found in primary collection \"${primaryCollection}\"`,\n );\n }\n } else if (\"link\" in item) {\n // External iff the URL has a scheme (`https:`, `mailto:`, …) or is\n // protocol-relative. A bare relative path like `\"cli\"` isn't a\n // valid internal link either (the internal branch assumes an\n // absolute `/path` shape), so route those through external\n // rendering — the browser resolves them against the current page.\n const isExternal = isAbsoluteUrl(item.link) || !item.link.startsWith(\"/\");\n if (isExternal) {\n const extLink: SidebarExternalLinkItem = {\n type: \"external\",\n label: item.label,\n href: item.link,\n badge: item.badge,\n order,\n };\n result.push(extLink);\n } else {\n // Internal link with custom label. Emit the trailing-slash\n // browser-href form; use the slashless route key for matching\n // and primary-collection lookup.\n const href = toBrowserHref(item.link);\n const routeKey = toRouteKey(item.link);\n\n // Best-effort validation: warn only if the link looks like a\n // primary-collection slug and doesn't resolve. Cross-collection\n // links (e.g. `/api/users`) intentionally bypass this check.\n const lookup = routeKey.slice(1);\n const looksLikePrimaryRoot = lookup !== \"\" && !lookup.includes(\"/\");\n if (looksLikePrimaryRoot && !byId.has(lookup)) {\n console.warn(\n `[sidebar] Internal link \"${item.link}\" (label: \"${item.label}\") does not match any entry in primary collection \"${primaryCollection}\"`,\n );\n }\n\n const link: SidebarLinkItem = {\n type: \"link\",\n label: item.label,\n href,\n isCurrent: toRouteKey(currentPath) === routeKey,\n badge: item.badge,\n order,\n };\n result.push(link);\n }\n } else if (\"autogenerate\" in item) {\n // Two flavours: directory-within-primary, or named collection.\n let autoItems: SidebarItem[];\n // The URL prefix this autogenerated group \"lives at\". Only set\n // for **non-primary** collections — those are the ones mounted at\n // `/<name>` where sites typically build a hand-rolled landing\n // page. `deriveSidebarSections` uses this so the header section\n // tab links to that landing (`/components`) instead of the first\n // child entry (`/components/accordion`). The primary collection\n // (docs at root) and directory-scoped autogenerates keep the\n // first-link behavior because their first link IS the natural\n // entry point.\n let groupPrefix: string | undefined;\n if (\"collection\" in item.autogenerate) {\n const collectionName = item.autogenerate.collection;\n const collectionEntries = entriesByCollection[collectionName];\n if (!collectionEntries) {\n console.warn(\n `[sidebar] autogenerate references collection \"${collectionName}\" which is not registered in nimbus.config.collections; skipping`,\n );\n autoItems = [];\n } else {\n // Resolve the mount prefix. The primary collection uses the\n // caller-supplied `primaryPrefix` (`\"\"` for the default `docs`\n // collection; `/<v>` when versioning rewrites the primary to\n // a version collection). Other collections default to\n // `/<name>` unless explicitly overridden.\n const explicit = item.autogenerate.prefix;\n const isPrimary = collectionName === primaryCollection;\n const prefix = explicit ?? (isPrimary ? primaryPrefix : `/${collectionName}`);\n autoItems = buildFilesystemTree(\n collectionEntries,\n currentPath,\n undefined,\n prefix,\n );\n // Non-primary only — primary collection sections keep\n // first-link behavior (see comment on `groupPrefix` above).\n if (!isPrimary && prefix !== \"\") {\n groupPrefix = prefix;\n }\n }\n } else {\n // directory-scoped autogenerate operates on the primary collection\n autoItems = buildFilesystemTree(\n primaryEntries,\n currentPath,\n item.autogenerate.directory,\n primaryPrefix,\n );\n }\n\n // If the config item has a label, wrap in a group\n if (item.label) {\n const group: SidebarGroupItem = {\n type: \"group\",\n label: item.label,\n order,\n collapsed: item.collapsed,\n badge: item.badge,\n children: autoItems,\n _prefix: groupPrefix,\n };\n result.push(group);\n } else {\n // Inline autogenerate (inside a manual group's items)\n if (item.collapsed !== undefined) {\n for (const ai of autoItems) {\n if (ai.type === \"group\") {\n ai.collapsed = item.collapsed;\n }\n }\n }\n result.push(...autoItems);\n }\n } else if (\"items\" in item) {\n // Manual group\n const children = resolveConfigItems(\n item.items,\n entriesByCollection,\n primaryCollection,\n currentPath,\n 0,\n primaryPrefix,\n );\n const group: SidebarGroupItem = {\n type: \"group\",\n label: item.label,\n order,\n collapsed: item.collapsed,\n badge: item.badge,\n children,\n };\n result.push(group);\n }\n }\n\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Scoping — filter to current top-level section\n// ---------------------------------------------------------------------------\n\n/**\n * Return only the children of the top-level group containing the current\n * page. Falls back to the full tree if the current page isn't inside any\n * group (e.g. a top-level link, or a path that doesn't resolve).\n */\nexport function scopeToCurrentSection(items: SidebarItem[], currentPath: string): SidebarItem[] {\n const currentSegment = currentPath.split(\"/\").filter(Boolean)[0];\n if (!currentSegment) return items;\n\n for (const item of items) {\n if (item.type === \"group\") {\n if (hasActivePage(item, currentPath)) {\n return item.children;\n }\n }\n }\n\n return items;\n}\n\nfunction hasActivePage(item: SidebarItem, currentPath: string): boolean {\n if (item.type === \"link\") return item.isCurrent === true;\n if (item.type === \"external\") return false;\n return item.children.some((child) => hasActivePage(child, currentPath));\n}\n\n// ---------------------------------------------------------------------------\n// Section derivation — top-level groups as nav sections\n// ---------------------------------------------------------------------------\n\n/**\n * Derive one section per top-level group in the sidebar tree. Used by\n * `Header.astro` to render the section tab strip. Caller must pass the\n * *un-scoped* tree (the result of `buildSidebarTree`, not `getSidebar`);\n * otherwise only the current section's children are visible and the\n * derivation collapses to a single item.\n */\nexport function deriveSidebarSections(items: SidebarItem[]): SidebarSection[] {\n return items.flatMap((item) => {\n if (item.type !== \"group\") return [];\n const links = flattenLinks(item.children);\n if (links.length === 0) return [];\n return [\n {\n label: item.label,\n // Prefer the group's mount prefix (autogenerate-from-collection\n // groups set this) so the header tab links to the landing page\n // rather than the alphabetically-first child entry. Falls back\n // to the first link's href for hand-listed groups. Either way,\n // run through `toBrowserHref` so section tabs link directly to\n // the trailing-slash URL static hosts serve.\n // `links[0]!`: `??` short-circuits when `_prefix` is set; otherwise\n // the surrounding section-build invariant guarantees `links` is non-empty.\n href: toBrowserHref(item._prefix ?? links[0]!.href),\n isActive: links.some((link) => link.isCurrent === true),\n },\n ];\n });\n}\n\n/** Depth-first walk; collect every internal link descendant. */\nfunction flattenLinks(items: SidebarItem[]): SidebarLinkItem[] {\n const out: SidebarLinkItem[] = [];\n for (const item of items) {\n if (item.type === \"link\") out.push(item);\n else if (item.type === \"group\") out.push(...flattenLinks(item.children));\n }\n return out;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Build the un-scoped sidebar tree from config + content entries.\n *\n * `entriesByCollection` is a name → entries map covering every\n * collection the user listed in `NimbusConfig.collections`. The\n * `primaryCollection` (first entry of that list) is what\n * filesystem-fallback, `directory:` autogenerate, and bare-slug\n * references read from. Other collections only contribute when an\n * explicit `autogenerate: { collection: \"<name>\" }` references them.\n *\n * - If config has items: resolve them (config takes priority)\n * - If config has no items: auto-generate from primary collection\n *\n * Always returns the full top-level tree. Scoping (showing only the\n * current section's children in the rail) is applied by the public\n * `getSidebar` helper via `scopeToCurrentSection`.\n */\nexport function buildSidebarTree(\n entriesByCollection: Record<string, CollectionEntry[]>,\n primaryCollection: string,\n currentPath: string,\n config?: SidebarConfig,\n /**\n * URL prefix for entries in the primary collection. Default `\"\"`\n * (root) — matches the convention for the `docs` collection. When\n * building a version-aware sidebar (primary is `docs-<v>`), the\n * caller passes the version's URL prefix (e.g. `\"/v0\"`) so the\n * generated hrefs land at the right URLs. Without this, version\n * pages would link to root paths like `/getting-started` instead of\n * `/v0/getting-started`.\n */\n primaryPrefix = \"\",\n): SidebarItem[] {\n const primaryEntries = entriesByCollection[primaryCollection] ?? [];\n let items: SidebarItem[];\n\n if (config?.items && config.items.length > 0) {\n // Config-driven\n items = resolveConfigItems(\n config.items,\n entriesByCollection,\n primaryCollection,\n currentPath,\n 0,\n primaryPrefix,\n );\n } else {\n // Filesystem fallback — primary collection only. Cross-collection\n // sidebars require explicit config items.\n items = buildFilesystemTree(primaryEntries, currentPath, undefined, primaryPrefix);\n }\n\n // Apply hideChildren — pool entries across all collections so a\n // non-primary `autogenerate: { collection }` group's index lookup\n // resolves correctly.\n const pooledEntries = Object.values(entriesByCollection).flat();\n items = processHideChildren(items, pooledEntries);\n\n return items;\n}\n\n/**\n * Process hideChildren: replace groups whose index has hideChildren=true\n * with a single link to the index page.\n */\nfunction processHideChildren(items: SidebarItem[], entries: CollectionEntry[]): SidebarItem[] {\n const entryById = new Map<string, CollectionEntry>();\n for (const e of entries) entryById.set(e.id, e);\n\n function process(items: SidebarItem[]): SidebarItem[] {\n const result: SidebarItem[] = [];\n for (const item of items) {\n if (item.type !== \"group\") {\n result.push(item);\n continue;\n }\n\n // Check if this group's index entry has hideChildren\n if (item._indexId) {\n const entry = entryById.get(item._indexId);\n if (entry?.data.sidebar?.hideChildren) {\n // Find the index link in children. Child hrefs ran through\n // `canonicalEntryUrl` (lowercase + folder-index strip), so the\n // raw entry id needs the same normalization before comparison\n // — otherwise an uppercase id like `WIP/foo` won't match the\n // emitted `/wip/foo/`. Compare via route key so the trailing\n // slash added by `toBrowserHref` doesn't trip the equality.\n const indexKey = toRouteKey(canonicalEntryUrl(\"\", item._indexId));\n const indexLink = item.children.find(\n (c): c is SidebarLinkItem =>\n c.type === \"link\" && toRouteKey(c.href) === indexKey,\n );\n if (indexLink) {\n // Replace group with single link\n const link: SidebarLinkItem = {\n ...indexLink,\n label: item.label,\n };\n result.push(link);\n continue;\n }\n }\n }\n\n // Recurse into children\n item.children = process(item.children);\n result.push(item);\n }\n return result;\n }\n\n return process(items);\n}\n\n/**\n * Walk a sidebar config items array (recursively, through nested\n * `items:` groups) and collect every collection name referenced by an\n * `autogenerate: { collection: ... }` entry.\n *\n * The framework uses this to figure out which collections to load for\n * the sidebar — there's no separate `collections: string[]` config\n * field. The primary collection (`docs`) is always included by the\n * caller; this helper returns only the *extra* names referenced by\n * sidebar items.\n */\nexport function collectSidebarCollectionRefs(\n items: ConfigItem[] | undefined,\n): string[] {\n if (!items) return [];\n const found = new Set<string>();\n function walk(items: ConfigItem[]): void {\n for (const item of items) {\n if (typeof item === \"string\") continue;\n if (\"autogenerate\" in item && \"collection\" in item.autogenerate) {\n found.add(item.autogenerate.collection);\n } else if (\"items\" in item) {\n walk(item.items);\n }\n }\n }\n walk(items);\n return [...found];\n}\n\n/** Flatten sidebar tree into a list of links (for pagination) */\nexport function flattenSidebar(items: SidebarItem[]): SidebarLinkItem[] {\n const flat: SidebarLinkItem[] = [];\n for (const item of items) {\n if (item.type === \"link\") {\n flat.push(item);\n } else if (item.type === \"group\") {\n flat.push(...flattenSidebar(item.children));\n }\n }\n return flat;\n}\n\nfunction formatLabel(segment: string): string {\n return segment.replace(/-/g, \" \").replace(/\\b\\w/g, (char) => char.toUpperCase());\n}\n\n// ---------------------------------------------------------------------------\n// Sidebar hash — deterministic hash of sidebar structure for state invalidation.\n// Uses DJBX33A (same as Starlight). When the hash changes (pages added/removed,\n// labels renamed), persisted sidebar state is discarded.\n// ---------------------------------------------------------------------------\n\nfunction buildSidebarIdentity(items: SidebarItem[]): string {\n return items\n .flatMap((item) =>\n item.type === \"group\"\n ? item.label + buildSidebarIdentity(item.children)\n : item.label + (\"href\" in item ? item.href : \"\"),\n )\n .join(\"\");\n}\n\n/** Hash the sidebar structure into a short string for sessionStorage invalidation. */\nexport function sidebarHash(items: SidebarItem[]): string {\n const identity = buildSidebarIdentity(items);\n let hash = 0;\n for (let i = 0; i < identity.length; i++) {\n hash = (hash << 5) - hash + identity.charCodeAt(i);\n }\n return (hash >>> 0).toString(36).padStart(7, \"0\");\n}\n","/**\n * Collection-mount conventions — one source of truth for \"what URL prefix\n * does collection X serve at?\".\n *\n * Shared between `index.ts` (which uses it for `getIndexedEntries`,\n * `getDocsPageProps`, etc.) and `lint/site-model.ts` (where\n * `findDuplicateRoutes` needs it to detect cross-collection URL\n * collisions). Keeping the function here prevents the duplicate-slug\n * validator from drifting out of sync with the actual routing.\n */\n\n/** Primary collection name — mounted at the site root with no prefix. */\nexport const PRIMARY_COLLECTION = \"docs\";\n\nexport interface VersionInfo {\n /** Non-current version slugs (`docs-<slug>` collections mount at `/<slug>`). */\n others: readonly string[];\n}\n\n/**\n * Resolve the URL-prefix segment for a given collection name.\n *\n * 1. Primary `docs` collection mounts at root → returns `\"\"`.\n * 2. With `versions` configured, a `docs-<slug>` collection whose slug\n * appears in `versions.others` mounts under `/<slug>` (the version\n * label, not the collection id).\n * 3. Any other collection (`api`, `blog`, …) mounts at `/<collection>`.\n *\n * Returned shape: empty string OR `/<segment>` with leading slash, no\n * trailing slash. Callers append `/<entryId>` or `/index.md`.\n */\nexport function collectionMountPrefix(\n collection: string,\n versions?: VersionInfo | null,\n): string {\n if (collection === PRIMARY_COLLECTION) return \"\";\n if (versions && collection.startsWith(\"docs-\")) {\n const slug = collection.slice(\"docs-\".length);\n if (versions.others.includes(slug)) return `/${slug}`;\n }\n return `/${collection}`;\n}\n\n/**\n * Resolve the URL-safe label a collection is referenced by — the segment\n * that appears in URLs and section headers. For version collections this\n * is the manifest's short slug; for everything else it's the collection id.\n */\nexport function collectionLabel(\n collection: string,\n versions?: VersionInfo | null,\n): string {\n if (collection === PRIMARY_COLLECTION) return collection;\n if (versions && collection.startsWith(\"docs-\")) {\n const slug = collection.slice(\"docs-\".length);\n if (versions.others.includes(slug)) return slug;\n }\n return collection;\n}\n","/**\n * MDX → markdown transform for AI-readable static routes.\n *\n * This intentionally starts small and dependency-free: it operates on the\n * raw MDX body that Astro's content layer exposes and maps the starter's\n * default components to plain markdown equivalents. The route that calls this\n * lives in user code, so replacing or bypassing this transformer is a one-line\n * edit.\n */\n\nexport interface MarkdownComponentRenderContext {\n name: string;\n attrs: Record<string, string | boolean>;\n children: string;\n}\n\nexport type MarkdownComponentRenderer = (\n context: MarkdownComponentRenderContext,\n) => string;\n\nexport interface RenderEntryAsMarkdownOptions {\n /**\n * Override how specific MDX components are rendered. Keys are component\n * names (e.g. `Aside`, `Tabs`, `PackageManagers`).\n */\n componentMap?: Record<string, MarkdownComponentRenderer>;\n /** Strip YAML frontmatter if the raw body includes it. Default: true. */\n stripFrontmatter?: boolean;\n}\n\ninterface MarkdownEntry {\n body?: string;\n}\n\nfunction protectCode(markdown: string): { markdown: string; restore: (value: string) => string } {\n const protectedChunks: string[] = [];\n function store(chunk: string): string {\n const token = `@@NIMBUS_MD_CODE_${protectedChunks.length}@@`;\n protectedChunks.push(chunk.startsWith(\"```\") ? chunk.replace(/\\n[ \\t]{4}/g, \"\\n\") : chunk);\n return token;\n }\n\n // Fenced blocks first so inline-code protection doesn't touch backticks inside.\n let next = markdown.replace(/```[\\s\\S]*?```/g, store);\n next = next.replace(/`[^`\\n]+`/g, store);\n\n return {\n markdown: next,\n restore(value: string): string {\n return value.replace(/@@NIMBUS_MD_CODE_(\\d+)@@/g, (_match, index: string) =>\n protectedChunks[Number(index)] ?? \"\",\n );\n },\n };\n}\n\nfunction parseAttrs(raw = \"\"): Record<string, string | boolean> {\n const attrs: Record<string, string | boolean> = {};\n const re = /([A-Za-z_:][\\w:.-]*)(?:\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|\\{([^}]*)\\}|([^\\s>]+)))?/g;\n for (const match of raw.matchAll(re)) {\n const [, name, dq, sq, expr, bare] = match;\n if (!name) continue;\n attrs[name] = dq ?? sq ?? expr?.trim() ?? bare ?? true;\n }\n return attrs;\n}\n\nfunction cleanChildren(children: string): string {\n return children\n .replace(/^\\s+/g, \"\")\n .replace(/\\s+$/g, \"\")\n .replace(/\\n[ \\t]+/g, \"\\n\");\n}\n\nfunction blockquote(body: string): string {\n return body\n .split(\"\\n\")\n .map((line) => (line ? `> ${line}` : \">\"))\n .join(\"\\n\");\n}\n\nfunction asTitle(value: string | boolean | undefined, fallback: string): string {\n return typeof value === \"string\" && value.trim() ? value.trim() : fallback;\n}\n\nfunction renderPackageManagers(attrs: Record<string, string | boolean>): string {\n const pkg = typeof attrs.pkg === \"string\" ? attrs.pkg : undefined;\n const args = typeof attrs.args === \"string\" ? attrs.args : undefined;\n const type = typeof attrs.type === \"string\" ? attrs.type : \"install\";\n const dev = attrs.dev === true || attrs.dev === \"true\";\n\n let commands: string[];\n if (type === \"run\") {\n const command = args ?? \"dev\";\n commands = [\n `npm run ${command}`,\n `pnpm ${command}`,\n `yarn ${command}`,\n `bun run ${command}`,\n ];\n } else if (type === \"exec\") {\n const command = args ?? pkg ?? \"\";\n commands = [\n `npx ${command}`,\n `pnpm exec ${command}`,\n `yarn exec ${command}`,\n `bunx ${command}`,\n ];\n } else if (type === \"dlx\") {\n const command = args ?? pkg ?? \"\";\n commands = [\n `npx ${command}`,\n `pnpm dlx ${command}`,\n `yarn dlx ${command}`,\n `bunx ${command}`,\n ];\n } else if (pkg) {\n commands = [\n `npm install ${dev ? \"--save-dev \" : \"\"}${pkg}`,\n `pnpm add ${dev ? \"-D \" : \"\"}${pkg}`,\n `yarn add ${dev ? \"-D \" : \"\"}${pkg}`,\n `bun add ${dev ? \"-d \" : \"\"}${pkg}`,\n ];\n } else {\n return \"\";\n }\n\n return [\"```sh\", ...commands, \"```\"].join(\"\\n\");\n}\n\nfunction applyDefaultComponentTransforms(markdown: string): string {\n let out = markdown;\n\n out = out.replace(\n /<PackageManagers\\b([^>]*)\\/>/g,\n (_match, rawAttrs: string) => renderPackageManagers(parseAttrs(rawAttrs)),\n );\n\n out = out.replace(\n /<Aside\\b([^>]*)>([\\s\\S]*?)<\\/Aside>/g,\n (_match, rawAttrs: string, children: string) => {\n const attrs = parseAttrs(rawAttrs);\n const type = asTitle(attrs.type, \"note\").toUpperCase();\n const title = asTitle(attrs.title, type.charAt(0) + type.slice(1).toLowerCase());\n const body = cleanChildren(children);\n return blockquote(`**${title}**\\n\\n${body}`);\n },\n );\n\n out = out.replace(\n /<Card\\b([^>]*)>([\\s\\S]*?)<\\/Card>/g,\n (_match, rawAttrs: string, children: string) => {\n const attrs = parseAttrs(rawAttrs);\n const title = asTitle(attrs.title, \"Card\");\n const body = cleanChildren(children);\n return `- **${title}**${body ? ` — ${body}` : \"\"}`;\n },\n );\n out = out.replace(/<\\/?CardGrid\\b[^>]*>/g, \"\");\n\n out = out.replace(/<Steps\\b[^>]*>([\\s\\S]*?)<\\/Steps>/g, (_match, children: string) => {\n let index = 0;\n return children.replace(\n /<Step\\b([^>]*)>([\\s\\S]*?)<\\/Step>/g,\n (_stepMatch, rawAttrs: string, stepChildren: string) => {\n index += 1;\n const attrs = parseAttrs(rawAttrs);\n const title = asTitle(attrs.title, `Step ${index}`);\n const body = cleanChildren(stepChildren);\n return `${index}. **${title}**${body ? `\\n\\n ${body.replace(/\\n/g, \"\\n \")}` : \"\"}`;\n },\n );\n });\n\n out = out.replace(/<Tabs\\b[^>]*>([\\s\\S]*?)<\\/Tabs>/g, (_match, children: string) =>\n children.replace(\n /<TabItem\\b([^>]*)>([\\s\\S]*?)<\\/TabItem>/g,\n (_tabMatch, rawAttrs: string, tabChildren: string) => {\n const attrs = parseAttrs(rawAttrs);\n const label = asTitle(attrs.label, \"Option\");\n return `### ${label}\\n\\n${cleanChildren(tabChildren)}`;\n },\n ),\n );\n\n // If user content includes raw component wrappers we don't know about,\n // preserve their children rather than leaking JSX into the markdown.\n out = out.replace(/<([A-Z][A-Za-z0-9]*)\\b[^>]*>([\\s\\S]*?)<\\/\\1>/g, \"$2\");\n out = out.replace(/<([A-Z][A-Za-z0-9]*)\\b[^>]*\\/>/g, \"\");\n\n return out;\n}\n\nfunction applyCustomComponentTransforms(\n markdown: string,\n componentMap: Record<string, MarkdownComponentRenderer>,\n): string {\n let out = markdown;\n for (const [name, render] of Object.entries(componentMap)) {\n const paired = new RegExp(`<${name}\\\\b([^>]*)>([\\\\s\\\\S]*?)<\\\\/${name}>`, \"g\");\n out = out.replace(paired, (_match, rawAttrs: string, children: string) =>\n render({ name, attrs: parseAttrs(rawAttrs), children: cleanChildren(children) }),\n );\n\n const selfClosing = new RegExp(`<${name}\\\\b([^>]*)\\\\/>`, \"g\");\n out = out.replace(selfClosing, (_match, rawAttrs: string) =>\n render({ name, attrs: parseAttrs(rawAttrs), children: \"\" }),\n );\n }\n return out;\n}\n\n/**\n * Render an Astro content entry's raw MDX body as plain markdown.\n *\n * This handles the starter's default MDX components. Users can pass a\n * `componentMap` to override individual component renderers or replace this\n * function entirely from their user-owned `.md` route.\n */\nexport function renderEntryAsMarkdown(\n entry: MarkdownEntry,\n options: RenderEntryAsMarkdownOptions = {},\n): string {\n const stripFrontmatter = options.stripFrontmatter ?? true;\n let markdown = entry.body ?? \"\";\n\n if (stripFrontmatter) {\n markdown = markdown.replace(/^---\\n[\\s\\S]*?\\n---\\n?/, \"\");\n }\n\n const protectedCode = protectCode(markdown);\n markdown = protectedCode.markdown;\n\n if (options.componentMap) {\n markdown = applyCustomComponentTransforms(markdown, options.componentMap);\n }\n markdown = applyDefaultComponentTransforms(markdown);\n markdown = protectedCode.restore(markdown);\n\n return markdown\n .replace(/^[ \\t]+(- \\*\\*)/gm, \"$1\")\n .replace(/^[ \\t]+(\\d+\\. \\*\\*)/gm, \"$1\")\n .replace(/^[ \\t]+(### )/gm, \"$1\")\n .replace(/^[ \\t]+(```)/gm, \"$1\")\n .replace(/^[ \\t]+$/gm, \"\")\n .replace(/\\n{3,}/g, \"\\n\\n\")\n .trim();\n}\n","import type { Breadcrumb, PrevNext, PrevNextOverrides, SidebarItem } from \"../types.js\";\nimport { flattenSidebar } from \"./sidebar.js\";\nimport { toBrowserHref, toRouteKey } from \"./url.js\";\n\nexport type { Breadcrumb, PrevNext, PrevNextOverrides };\n\nexport function getBreadcrumbs(slug: string, homeLabel = \"Home\"): Breadcrumb[] {\n const parts = slug.split(\"/\").filter(Boolean);\n const crumbs: Breadcrumb[] = [{ label: homeLabel, href: \"/\" }];\n\n let path = \"\";\n for (const part of parts) {\n path += `/${part}`;\n crumbs.push({\n label: part.replace(/-/g, \" \").replace(/\\b\\w/g, (c) => c.toUpperCase()),\n href: toBrowserHref(path),\n });\n }\n\n return crumbs;\n}\n\ntype PrevNextOverride = { link?: string; label?: string };\n\nfunction resolveOverride(\n override: string | PrevNextOverride | false | undefined,\n fallback: { label: string; href: string } | undefined,\n validInternalLinks?: Set<string>,\n): { label: string; href: string } | undefined {\n if (override === false) return undefined;\n if (override === undefined) return fallback;\n if (typeof override === \"string\") {\n // String form: label-only override — keeps the sidebar neighbor's href, replaces the label\n if (!fallback) return undefined;\n return { label: override, href: fallback.href };\n }\n // Object form: merge with fallback — omitted fields inherit from sidebar neighbor\n if (override.link && !override.link.startsWith(\"/\") && !override.link.startsWith(\"http\")) {\n throw new Error(\n `prev/next override link \"${override.link}\" must be an absolute path (starting with /) or a full URL`,\n );\n }\n if (override.link?.startsWith(\"/\") && validInternalLinks) {\n // `validInternalLinks` holds slashless route keys (from indexed\n // entries). Normalize the override's link to the same shape so\n // `/cli`, `/cli/`, and `/cli/?ref=x` all resolve.\n const targetPath = toRouteKey(override.link);\n if (!validInternalLinks.has(targetPath)) {\n throw new Error(`prev/next override link \"${override.link}\" does not match any existing internal docs route`);\n }\n }\n const label = override.label ?? fallback?.label;\n // Override links go straight to `<a href>`, so render in the trailing-\n // slash form static hosts serve. External `http(s)://…` URLs and asset\n // paths fall through `toBrowserHref` unchanged.\n const href = override.link ? toBrowserHref(override.link) : fallback?.href;\n\n // Without a sidebar neighbor, object overrides must be complete.\n if (!fallback && (label === undefined || href === undefined)) {\n throw new Error(\"prev/next object override requires both `label` and `link` when no sidebar neighbor exists\");\n }\n\n if (!href) return undefined;\n return { label: label ?? \"\", href };\n}\n\nexport function getPrevNext(\n currentPath: string,\n sidebarTree: SidebarItem[],\n overrides?: PrevNextOverrides,\n validInternalLinks?: Set<string>,\n): PrevNext {\n const flat = flattenSidebar(sidebarTree);\n // Sidebar hrefs are trailing-slash browser-form; `currentPath` from\n // routes may be either form. Compare via route key so they line up.\n const currentKey = toRouteKey(currentPath);\n const index = flat.findIndex((item) => toRouteKey(item.href) === currentKey);\n\n const sidebarPrev = index > 0 ? { label: flat[index - 1]!.label, href: flat[index - 1]!.href } : undefined;\n const sidebarNext =\n index >= 0 && index < flat.length - 1 ? { label: flat[index + 1]!.label, href: flat[index + 1]!.href } : undefined;\n\n if (!overrides) {\n return { prev: sidebarPrev, next: sidebarNext };\n }\n\n return {\n prev: resolveOverride(overrides.prev, sidebarPrev, validInternalLinks),\n next: resolveOverride(overrides.next, sidebarNext, validInternalLinks),\n };\n}\n","import type { TOCItem } from \"../types.js\";\n\nexport interface TocConfig {\n minHeadingLevel?: number;\n maxHeadingLevel?: number;\n}\n\nexport function getHeadings(\n headings: { depth: number; text: string; slug: string }[],\n config?: TocConfig,\n): TOCItem[] {\n const min = config?.minHeadingLevel ?? 2;\n const max = config?.maxHeadingLevel ?? 3;\n return headings.filter((h) => h.depth >= min && h.depth <= max);\n}\n","/**\n * git-last-updated.ts — Derive a per-page `lastUpdated` from `git log`.\n *\n * Uses the **author date** (`%aI`) instead of the committer date (`%cI`)\n * so the value stays stable when a branch is rebased: rebases rewrite\n * commit dates but preserve author dates for unchanged content. Squash\n * merges produce a single new commit that touches the file, so the\n * date naturally reflects the squash moment — which is the right answer\n * for \"when did this content last change in the published history.\"\n *\n * Returns `undefined` on every failure mode so the caller can fall back\n * cleanly to frontmatter (or render nothing):\n *\n * - `git` not on PATH (CI image without git, container without it)\n * - File isn't tracked yet (new content in a draft branch, untracked)\n * - Repo is a shallow clone / partial clone and the file's history\n * isn't in the local pack (Vercel default `fetch-depth: 1`,\n * Cloudflare Pages similar). Users who want git-derived dates in\n * production should set `fetch-depth: 0` on `actions/checkout` or\n * equivalent.\n * - Process isn't inside a git working tree at all\n *\n * Results are cached per-process. A typical docs build calls this once\n * per entry; the cache prevents redundant subprocess spawns when the\n * same entry's filePath shows up across multiple pages (e.g. sidebar\n * preview, full render).\n */\n\nimport { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\n\nconst execFileAsync = promisify(execFile);\n\nconst cache = new Map<string, Date | undefined>();\n\n/**\n * Run `git log -1 --format=%aI -- <filePath>` and parse the result as a\n * `Date`. Returns `undefined` on any error or empty result.\n *\n * Pass either the entry's `filePath` (Astro provides this on every\n * content entry) or an explicit relative path. Relative paths resolve\n * against the current working directory (Astro builds run from the\n * project root, which is inside the git repo).\n */\nexport async function getLastUpdatedFromGit(\n filePath: string,\n): Promise<Date | undefined> {\n if (!filePath) return undefined;\n if (cache.has(filePath)) return cache.get(filePath);\n\n let result: Date | undefined;\n try {\n const { stdout } = await execFileAsync(\n \"git\",\n [\"log\", \"-1\", \"--format=%aI\", \"--\", filePath],\n // No `cwd` override — Astro runs from the project root which is\n // inside the repo. Git itself walks upward to find `.git`.\n { windowsHide: true },\n );\n const trimmed = stdout.trim();\n if (trimmed) {\n const parsed = new Date(trimmed);\n // Guard against `new Date(\"\")` → Invalid Date that still passes\n // `instanceof Date`. `getTime()` returns NaN for invalid dates.\n if (!Number.isNaN(parsed.getTime())) {\n result = parsed;\n }\n }\n } catch {\n // Swallow: caller falls back to frontmatter or renders no date.\n result = undefined;\n }\n\n cache.set(filePath, result);\n return result;\n}\n","/**\n * Extract registered MDX global names from the user's `src/components.ts`.\n *\n * The framework needs this list to validate PascalCase tags in MDX at\n * build time, but it must not execute user code at build time. Strategy:\n * read the file as text, locate the `export const components = { ... }`\n * declaration, and parse its top-level keys.\n *\n * Supported entry shapes inside the object literal:\n * - shorthand: `Foo,` → \"Foo\"\n * - aliased: `Foo: Other,` → \"Foo\" (the key)\n * - string key: `\"Foo\": Other,` → \"Foo\"\n *\n * Skipped (no false-positive failures):\n * - spread elements (`...other`)\n * - computed keys (`[expr]: value`)\n * - lowercase keys (not PascalCase, so not validator-relevant)\n *\n * Returns:\n * - `string[]` of registered names when the file exists and the pattern\n * matches.\n * - `null` when the file is missing OR present but doesn't expose a\n * parseable `export const components = { ... }`. The caller decides\n * whether to warn or skip validation.\n */\n\nimport fs from \"node:fs/promises\";\n\nconst EXPORT_PATTERN =\n /export\\s+const\\s+components\\s*(?::\\s*[^=]+)?=\\s*\\{([\\s\\S]*?)\\n\\s*\\}\\s*(?:as\\s+const)?\\s*;?/;\n\nexport async function parseComponentsRegistry(\n filePath: string,\n): Promise<string[] | null> {\n let source: string;\n try {\n source = await fs.readFile(filePath, \"utf8\");\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return null;\n throw err;\n }\n\n const match = source.match(EXPORT_PATTERN);\n if (!match) return null;\n\n // Strip line + block comments so commas inside `// foo, bar` don't split.\n // `match[1]!`: regex's first capture group is required, defined whenever match succeeded.\n const body = match[1]!\n .replace(/\\/\\/[^\\n]*/g, \"\")\n .replace(/\\/\\*[\\s\\S]*?\\*\\//g, \"\");\n\n const names: string[] = [];\n for (const raw of splitTopLevelCommas(body)) {\n const entry = raw.trim();\n if (!entry) continue;\n if (entry.startsWith(\"...\")) continue;\n if (entry.startsWith(\"[\")) continue;\n\n const colonIdx = entry.indexOf(\":\");\n const rawKey = colonIdx === -1 ? entry : entry.slice(0, colonIdx);\n const key = rawKey.trim().replace(/^['\"`]|['\"`]$/g, \"\");\n\n if (/^[A-Z][A-Za-z0-9_]*$/.test(key)) names.push(key);\n }\n\n return names;\n}\n\n/**\n * Split a string on commas that are at depth 0 (not inside `{}`, `[]`,\n * `()`, or string literals). Required because object entries can themselves\n * contain commas (e.g. `Foo: bar({ a: 1, b: 2 })`).\n */\nfunction splitTopLevelCommas(input: string): string[] {\n const result: string[] = [];\n let depth = 0;\n let start = 0;\n let inString: string | null = null;\n\n for (let i = 0; i < input.length; i++) {\n const ch = input[i];\n if (inString) {\n if (ch === \"\\\\\") {\n i++;\n continue;\n }\n if (ch === inString) inString = null;\n continue;\n }\n if (ch === '\"' || ch === \"'\" || ch === \"`\") inString = ch;\n else if (ch === \"{\" || ch === \"[\" || ch === \"(\") depth++;\n else if (ch === \"}\" || ch === \"]\" || ch === \")\") depth--;\n else if (ch === \",\" && depth === 0) {\n result.push(input.slice(start, i));\n start = i + 1;\n }\n }\n result.push(input.slice(start));\n return result;\n}\n","/**\n * Lint-side data shapes + the `nimbus/duplicate-slug` build validator.\n *\n * Route truth for `nimbus/internal-link` comes from Astro itself\n * (`astro:build:done` hands us the emitted `pages` array — the single\n * source of truth for served URLs). The integration writes that to\n * `.nimbus/routes.json`; the type lives here only so the rule and the\n * writer agree on the shape.\n *\n * The duplicate-slug validator runs *before* the build because Astro\n * silently dedupes colliding routes — by the time `astro:build:done`\n * fires, one entry has already shadowed the other. We catch collisions\n * pre-build by computing each entry's canonical slug with the same\n * library Astro's content layer uses (`github-slugger`), then grouping\n * entries by collection + slug. This is mirror-behavior, but mirroring\n * a documented public library rather than Astro's private internals —\n * a much smaller maintenance surface.\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport { canonicalEntryUrl, canonicalSlug } from \"../_internal/astro-slug.js\";\nimport {\n collectionMountPrefix,\n type VersionInfo,\n} from \"../_internal/collection-mount.js\";\n\n// Re-exported for tests; the helpers themselves live in `_internal/` because\n// framework URL builders (sidebar, sitemap, llms.txt) also use them.\nexport { canonicalSlug };\nexport type { VersionInfo };\n\nexport interface ContentEntry {\n /** Collection name — the first path segment under `src/content`. */\n collection: string;\n /** Entry id — the path under the collection, without the `.mdx` extension. */\n id: string;\n /** Path relative to the content root (`<collection>/<id>.mdx`), for display. */\n relPath: string;\n}\n\n/**\n * Walk a content root for `.mdx` files and return one entry per file.\n * Uses the filesystem segment as the `collection` — correct only when each\n * collection's `base` matches its registered key. Use\n * `enumerateEntriesByBase` when the caller has the real `(key, base)` map\n * (so `docsCollection({ base: \"documentation\" })` doesn't get mis-tagged).\n */\nexport function enumerateEntries(contentRoot: string): ContentEntry[] {\n const out: ContentEntry[] = [];\n walk(contentRoot, contentRoot, out, null);\n return out;\n}\n\n/**\n * Walk `src/content/` using a `(collection key → folder name)` map so\n * entries are tagged with the registered key, not the on-disk folder.\n * Skips folders that aren't in the map (they're loose content, not a\n * routed collection).\n */\nexport function enumerateEntriesByBase(\n contentRoot: string,\n bases: ReadonlyMap<string, string>,\n): ContentEntry[] {\n // Invert the map (folder → key). Two collections with the same base\n // would be a content.config.ts authoring error; we silently keep the\n // last one — Astro itself errors on duplicate-key collection registration.\n const folderToKey = new Map<string, string>();\n for (const [key, base] of bases) folderToKey.set(base, key);\n\n const out: ContentEntry[] = [];\n for (const [folder, key] of folderToKey) {\n const baseDir = path.join(contentRoot, folder);\n walkFolder(baseDir, baseDir, (relInBase) => {\n out.push({\n collection: key,\n id: relInBase.replace(/\\.mdx$/, \"\"),\n relPath: `${folder}/${relInBase}`,\n });\n });\n }\n return out;\n}\n\nfunction walkFolder(\n dir: string,\n root: string,\n visit: (relPath: string) => void,\n): void {\n let dirents: fs.Dirent[];\n try {\n dirents = fs.readdirSync(dir, { withFileTypes: true });\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return;\n throw err;\n }\n for (const entry of dirents) {\n const full = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n if (entry.name === \"node_modules\" || entry.name.startsWith(\".\")) continue;\n walkFolder(full, root, visit);\n } else if (entry.isFile() && entry.name.endsWith(\".mdx\")) {\n const rel = path.relative(root, full).replace(/\\\\/g, \"/\");\n visit(rel);\n }\n }\n}\n\nfunction walk(\n dir: string,\n root: string,\n out: ContentEntry[],\n _: null,\n): void {\n let dirents: fs.Dirent[];\n try {\n dirents = fs.readdirSync(dir, { withFileTypes: true });\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return;\n throw err;\n }\n for (const entry of dirents) {\n const full = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n if (entry.name === \"node_modules\" || entry.name.startsWith(\".\")) continue;\n walk(full, root, out, null);\n } else if (entry.isFile() && entry.name.endsWith(\".mdx\")) {\n const rel = path.relative(root, full).replace(/\\\\/g, \"/\");\n const slash = rel.indexOf(\"/\");\n if (slash === -1) continue;\n out.push({\n collection: rel.slice(0, slash),\n id: rel.slice(slash + 1).replace(/\\.mdx$/, \"\"),\n relPath: rel,\n });\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Duplicate-slug detection — runs pre-build because Astro silently dedupes.\n// ---------------------------------------------------------------------------\n\n/**\n * A single thing that owns a URL: either a content entry under\n * `src/content/<collection>/` or a static page file under `src/pages/`.\n * The dup-check only needs the URL and a display path; everything else is\n * caller-side bookkeeping.\n */\nexport interface RouteOwner {\n /** The mounted URL this source resolves to (canonicalized, no trailing slash). */\n url: string;\n /** Project-relative path, used in error messages. */\n source: string;\n}\n\nexport interface DuplicateGroup {\n /** The URL multiple sources resolve to. */\n url: string;\n /** All sources that claim that URL (project-relative paths). */\n sources: string[];\n}\n\n/**\n * Group route owners by URL. A group with more than one member is a real\n * collision Astro will silently shadow at build time — that's what this\n * helper exists to surface before the build wastes a cycle.\n *\n * Owners come from two sources, both fed in by the integration:\n *\n * - **Content entries**, with URLs computed via\n * `collectionMountPrefix(entry.collection, versions) +\n * canonicalEntryUrl(prefix, entry.id)`. Catches cross-collection\n * (`docs/blog/post.mdx` vs `blog/post.mdx`), version-collection\n * (`docs/v1/x.mdx` vs `docs-v1/x.mdx`), case-only, and\n * leaf-vs-folder-index collisions Astro's content layer dedupes.\n * - **Static `src/pages/**` files** (via `enumerateStaticPageRoutes`).\n * Catches the page-vs-content collision (`pages/search.astro`\n * shadowing `content/docs/search.mdx` at `/search`).\n *\n * Doesn't honor `data.slug` frontmatter overrides — entries that use those\n * may produce false negatives. Reading frontmatter from every entry\n * pre-build would add noticeable I/O for a v1 feature; tracked as a\n * follow-up.\n */\nexport function findDuplicateRoutes(\n owners: readonly RouteOwner[],\n): DuplicateGroup[] {\n const byUrl = new Map<string, string[]>();\n for (const { url, source } of owners) {\n const bucket = byUrl.get(url);\n if (bucket) bucket.push(source);\n else byUrl.set(url, [source]);\n }\n const dups: DuplicateGroup[] = [];\n for (const [url, sources] of byUrl) {\n if (sources.length > 1) dups.push({ url, sources });\n }\n return dups;\n}\n\n/** Format duplicate groups into a build-error message. */\nexport function formatDuplicateRoutes(dups: DuplicateGroup[]): string {\n const lines = dups.map((d) => ` ${d.url} ← ${d.sources.join(\", \")}`);\n const noun = dups.length === 1 ? \"route is\" : \"routes are\";\n return (\n `[nimbus-docs] Duplicate ${noun} claimed by more than one source (nimbus/duplicate-slug):\\n` +\n lines.join(\"\\n\") +\n `\\n\\nTwo or more sources resolve to the same URL — one would shadow the other on the deployed site ` +\n `(Astro silently dedupes colliding routes). Rename or move one source in each pair.`\n );\n}\n\n// ---------------------------------------------------------------------------\n// Static-page-route enumeration — the second source for findDuplicateRoutes.\n// ---------------------------------------------------------------------------\n\n/**\n * Walk `src/pages/**` for static page files and return their served URLs.\n *\n * Considered \"static\" iff the path has no dynamic segments (`[id]`,\n * `[...slug]`). Dynamic routes are skipped because their emitted URLs come\n * from `getStaticPaths` at build time — we can't know them statically, so\n * we can't detect collisions involving them pre-build. The opaque-namespace\n * pattern from earlier drafts (mark a whole namespace as un-checkable)\n * doesn't apply here: dup-detection only catches *exact* URL collisions,\n * and any catch-all owned by the framework's docs renderer collides with a\n * content entry the *content* enumeration also catches.\n *\n * URL normalization: lowercase each segment + strip a trailing `/index`,\n * matching Astro's `joinSegments` behavior for static routes. Underscore-\n * prefixed files are skipped (Astro's private-helper convention). Endpoint\n * files (`name.<ext>.<ts|js>`) map to `/name.<ext>` per Astro's convention.\n */\nexport interface StaticPageRoute {\n /** The URL this page file serves at, canonicalized (no trailing slash). */\n url: string;\n /** Project-relative path, e.g. `src/pages/search.astro`. */\n source: string;\n}\n\nconst PAGE_EXTS = new Set([\".astro\", \".ts\", \".js\", \".md\", \".mdx\"]);\n\nexport function enumerateStaticPageRoutes(\n pagesRoot: string,\n projectRoot: string,\n): StaticPageRoute[] {\n const out: StaticPageRoute[] = [];\n walkPages(pagesRoot, pagesRoot, (relPath) => {\n const ext = path.extname(relPath);\n if (!PAGE_EXTS.has(ext)) return;\n\n const base = path.basename(relPath);\n if (base.startsWith(\"_\")) return; // Astro's private-helper convention\n\n const parts = relPath.replace(/\\\\/g, \"/\").split(\"/\");\n // Pre-strip the leaf extension so the dynamic-segment test sees the\n // bare segment (`[id]` not `[id].astro`).\n const bareLeaf = parts[parts.length - 1]!.replace(/\\.[^.]+$/, \"\");\n if (parts.slice(0, -1).some(isDynamicSegment) || isDynamicSegment(bareLeaf)) {\n return;\n }\n\n const url = fileToRoute(parts, ext);\n const sourceAbs = path.join(pagesRoot, relPath);\n const source = path.relative(projectRoot, sourceAbs).replace(/\\\\/g, \"/\");\n out.push({ url, source });\n });\n return out;\n}\n\nfunction walkPages(\n dir: string,\n root: string,\n visit: (relPath: string) => void,\n): void {\n let dirents: fs.Dirent[];\n try {\n dirents = fs.readdirSync(dir, { withFileTypes: true });\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return;\n throw err;\n }\n for (const entry of dirents) {\n const full = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n if (entry.name.startsWith(\"_\") || entry.name === \"node_modules\") continue;\n walkPages(full, root, visit);\n } else if (entry.isFile()) {\n visit(path.relative(root, full));\n }\n }\n}\n\nfunction isDynamicSegment(seg: string): boolean {\n return seg.startsWith(\"[\") && seg.endsWith(\"]\") && seg.length >= 3;\n}\n\n/**\n * Translate a static page file path (no dynamic segments) to its URL.\n *\n * pages/index.astro → /\n * pages/search.astro → /search\n * pages/Search.astro → /search (Astro lowercases via joinSegments)\n * pages/llms.txt.ts → /llms.txt (endpoint: strip .ts, keep .txt)\n * pages/blog/index.astro → /blog\n * pages/blog/post.md → /blog/post\n */\nfunction fileToRoute(parts: string[], ext: string): string {\n const cloned = [...parts];\n const last = cloned[cloned.length - 1]!;\n\n if ((ext === \".ts\" || ext === \".js\") && /\\.[^.]+\\.[tj]s$/.test(last)) {\n // Endpoint: `name.<inner>.<ts|js>` → strip just the trailing `.ts`/`.js`.\n cloned[cloned.length - 1] = last.replace(/\\.[tj]s$/, \"\");\n } else {\n cloned[cloned.length - 1] = last.replace(/\\.[^.]+$/, \"\");\n }\n\n // Strip trailing `index` segment so `foo/index.astro` → `/foo`.\n if (cloned[cloned.length - 1] === \"index\") cloned.pop();\n\n // Lowercase each segment to match Astro's `joinSegments` behavior.\n const joined = cloned.map((s) => s.toLowerCase()).join(\"/\");\n return joined === \"\" ? \"/\" : `/${joined}`;\n}\n\n/**\n * Compute the mounted URL a content entry resolves to. Exposed so callers\n * (the integration's pre-build dup-check) can build `RouteOwner` records\n * with the same logic the framework uses everywhere else.\n */\nexport function contentEntryUrl(\n entry: ContentEntry,\n versions?: VersionInfo | null,\n): string {\n const prefix = collectionMountPrefix(entry.collection, versions);\n return canonicalEntryUrl(prefix, entry.id);\n}\n\n// ---------------------------------------------------------------------------\n// Route truth — shape only. The integration's `astro:build:done` hook\n// constructs and writes this; `internal-link.ts` reads it.\n// ---------------------------------------------------------------------------\n\nexport interface RouteTruth {\n /** Schema version. Bump if the shape changes. */\n version: 1;\n /** Astro `base` config (`\"/docs\"`, `\"\"`). Empty string when unset. */\n base: string;\n /**\n * Every URL Astro emitted during the last build, canonicalized to\n * `/foo` form (no trailing slash unless root). The lint rule resolves\n * internal links against this set.\n */\n knownRoutes: string[];\n /**\n * Reserved for future SSR-route handling — URL prefixes that can't be\n * statically enumerated. Empty in the current all-prerendered path.\n */\n opaqueNamespaces: string[];\n}\n","/**\n * Extract registered collection names from the user's `src/content.config.ts`.\n *\n * Used by `getIndexedEntries()` and the agent-facing routes (`llms.txt`,\n * per-page `.md` alternates) so they don't have to hardcode `\"docs\"`.\n * Adding a new collection to `content.config.ts` lights up every\n * indexing surface automatically.\n *\n * Strategy: read the file as text and locate the `export const collections =\n * { ... }` declaration, parse its top-level keys. Same approach used by\n * `parse-components-registry.ts` — we never execute user code at build\n * time.\n *\n * Supported entry shapes inside the object literal:\n * - shorthand: `docs,` → \"docs\"\n * - aliased: `docs: defineCollection(...),` → \"docs\" (the key)\n * - string key: `\"docs\": defineCollection(...)` → \"docs\"\n *\n * Skipped:\n * - spread elements (`...other`)\n * - computed keys (`[expr]: value`)\n *\n * The result is *not* filtered against reserved names here — that's\n * `getIndexedEntries()`'s job, so consumers that want the raw list (e.g.\n * tooling) can still see it.\n *\n * Returns:\n * - `string[]` of registered names when the file exists and the\n * pattern matches.\n * - `null` when the file is missing OR present but doesn't expose a\n * parseable `export const collections = { ... }`. Callers decide\n * whether to warn or fall back to `[\"docs\"]`.\n */\n\nimport fs from \"node:fs/promises\";\n\n// Locate the start of the `export const collections = {` declaration; the\n// matching close brace is found by walking braces (the original regex-only\n// match stopped at the first nested `\\n\\s*}` and missed entries declared\n// after any deeply nested object literal).\nconst EXPORT_PREFIX_PATTERN =\n /export\\s+const\\s+collections\\s*(?::\\s*[^=]+)?=\\s*\\{/;\n\nexport async function parseContentCollections(\n filePath: string,\n): Promise<string[] | null> {\n let source: string;\n try {\n source = await fs.readFile(filePath, \"utf8\");\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return null;\n throw err;\n }\n\n // Strip JS line + block comments up front. Critical for two reasons:\n // 1. Comments can contain apostrophes / quotes / braces that the\n // brace walker would mistake for real string delimiters (e.g.\n // `// the user's content` would put the walker into \"inside\n // single-quoted string\" mode for the rest of the file).\n // 2. Commas inside `// foo, bar` would split entries incorrectly\n // downstream in `splitTopLevelCommas`.\n // We zero-out comments to spaces (not delete) so character offsets\n // stay aligned with the original `source` — the regex match index\n // and the brace walker both rely on positional indices.\n const stripped = source.replace(/\\/\\/[^\\n]*|\\/\\*[\\s\\S]*?\\*\\//g, (m) =>\n m.replace(/[^\\n]/g, \" \"),\n );\n\n const prefixMatch = stripped.match(EXPORT_PREFIX_PATTERN);\n if (!prefixMatch || prefixMatch.index === undefined) return null;\n const objectStart = prefixMatch.index + prefixMatch[0].length;\n const objectEnd = findMatchingBrace(stripped, objectStart - 1);\n if (objectEnd === -1) return null;\n const body = stripped.slice(objectStart, objectEnd);\n\n const names: string[] = [];\n for (const raw of splitTopLevelCommas(body)) {\n const entry = raw.trim();\n if (!entry) continue;\n if (entry.startsWith(\"...\")) continue;\n if (entry.startsWith(\"[\")) continue;\n\n const colonIdx = entry.indexOf(\":\");\n const rawKey = colonIdx === -1 ? entry : entry.slice(0, colonIdx);\n const key = rawKey.trim().replace(/^['\"`]|['\"`]$/g, \"\");\n\n // Collection names are conventionally lowercase identifiers\n // (`docs`, `blog`, `api`), but Astro accepts any non-empty string as a\n // collection ID and the versioning convention (`docs-v1`, `docs-2025-q1`)\n // relies on hyphens. Accept letters/digits/underscores/hyphens after\n // a leading letter or underscore (the `_*` underscore convention for\n // hidden-from-indexing collections stays intact).\n if (/^[A-Za-z_][A-Za-z0-9_-]*$/.test(key)) names.push(key);\n }\n\n return names;\n}\n\n/**\n * Sibling of `parseContentCollections` that also extracts each entry's\n * `base` option — the folder name under `src/content/` the collection\n * loads from. Returns a `Map<key, folderName>` where the folder defaults\n * to the collection key when no `base:` override is present.\n *\n * Used by the `nimbus/duplicate-slug` validator and any other framework\n * code that needs to walk a collection's actual on-disk location rather\n * than assuming the folder name matches the collection key. Astro's\n * content layer respects the `base` option — `docsCollection({ base:\n * \"documentation\" })` loads from `src/content/documentation/` while still\n * registering as collection `docs`. Without this map, filesystem-walking\n * code would mis-tag those entries (`collection: \"documentation\"`) and\n * either flag bogus collisions or silently skip them via the indexable-\n * collections filter.\n *\n * Extraction is regex-based — for each entry's value text, looks for\n * `\\bbase:\\s*[\"']([^\"']+)[\"']`. That covers the documented Nimbus pattern\n * (`docsCollection({ base: \"...\" })`, `partialsCollection({ base: \"...\" })`,\n * `componentsCollection({ base: \"...\" })`). Limitations:\n *\n * - Computed/dynamic bases (`base: someVar`) fall back to the collection\n * key. A future regression would silently miscount; for now,\n * accepted as a known v1 limitation.\n * - Hand-rolled `defineCollection({ loader: glob({ base: \"./src/content/x\" }) })`\n * puts a *path* in `base`, not a folder name. The extracted value\n * starts with `./src/content/` and won't match a folder under\n * `src/content/` directly. Users who write the loader by hand can\n * keep folder names matching collection keys, or accept that\n * `duplicate-slug` won't see their non-conforming collection until\n * they migrate to the Nimbus helpers.\n *\n * Returns `null` when the file is missing or unparseable, matching\n * `parseContentCollections`'s contract.\n */\nexport async function parseCollectionBases(\n filePath: string,\n): Promise<Map<string, string> | null> {\n let source: string;\n try {\n source = await fs.readFile(filePath, \"utf8\");\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return null;\n throw err;\n }\n\n const stripped = source.replace(/\\/\\/[^\\n]*|\\/\\*[\\s\\S]*?\\*\\//g, (m) =>\n m.replace(/[^\\n]/g, \" \"),\n );\n\n const prefixMatch = stripped.match(EXPORT_PREFIX_PATTERN);\n if (!prefixMatch || prefixMatch.index === undefined) return null;\n const objectStart = prefixMatch.index + prefixMatch[0].length;\n const objectEnd = findMatchingBrace(stripped, objectStart - 1);\n if (objectEnd === -1) return null;\n const body = stripped.slice(objectStart, objectEnd);\n\n const out = new Map<string, string>();\n for (const raw of splitTopLevelCommas(body)) {\n const entry = raw.trim();\n if (!entry) continue;\n if (entry.startsWith(\"...\")) continue;\n if (entry.startsWith(\"[\")) continue;\n\n const colonIdx = entry.indexOf(\":\");\n const rawKey = colonIdx === -1 ? entry : entry.slice(0, colonIdx);\n const key = rawKey.trim().replace(/^['\"`]|['\"`]$/g, \"\");\n if (!/^[A-Za-z_][A-Za-z0-9_-]*$/.test(key)) continue;\n\n // Default the folder to the key name. Override if a literal `base:`\n // appears in the entry's value — or, for shorthand entries (`{ docs }`),\n // in the local `const|let|var docs = …` declaration the shorthand\n // refers to.\n const valueText =\n colonIdx === -1\n ? findLocalDeclarationValue(stripped, key)\n : entry.slice(colonIdx + 1);\n const baseMatch = valueText.match(/\\bbase\\s*:\\s*[\"']([^\"']+)[\"']/);\n out.set(key, baseMatch ? baseMatch[1]! : key);\n }\n\n return out;\n}\n\n/**\n * Locate `const|let|var <identifier> = <value>` at any depth in the source\n * and return the captured `<value>` text. Used to resolve shorthand\n * collection entries (`{ docs }` in the registration object refers to a\n * `const docs = defineCollection(...)` declared somewhere above).\n *\n * The walker is brace/bracket/paren-aware and string-literal-aware so the\n * captured value spans nested object/function-call expressions without\n * losing depth. It stops at the next top-level `;` or end-of-input.\n *\n * Limitations (false negatives — never false positives):\n * - Identifiers imported from other modules can't be resolved (we don't\n * follow imports).\n * - Type-only annotations using `=>` (`const docs: () => X = …`) would\n * fool the `=` detection. Realistic collection declarations don't use\n * this shape; if a user does, the check falls back to the key.\n */\nfunction findLocalDeclarationValue(\n source: string,\n identifier: string,\n): string {\n const safeId = identifier.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const declRe = new RegExp(`\\\\b(?:const|let|var)\\\\s+${safeId}\\\\b`, \"g\");\n\n let match: RegExpExecArray | null;\n while ((match = declRe.exec(source)) !== null) {\n const afterId = match.index + match[0].length;\n const eqIdx = findAssignmentEquals(source, afterId);\n if (eqIdx === -1) continue;\n const endIdx = findStatementEnd(source, eqIdx + 1);\n return source.slice(eqIdx + 1, endIdx);\n }\n return \"\";\n}\n\n/**\n * Find the first `=` after `from` that's an assignment operator —\n * skipping `==`, `===`, and `=>`. Used to locate where the assignment\n * value begins in `const id [: Type] = value;`.\n */\nfunction findAssignmentEquals(source: string, from: number): number {\n for (let i = from; i < source.length; i++) {\n if (source[i] !== \"=\") continue;\n if (source[i + 1] === \"=\") {\n i++; // skip `==` (and `===` since next iter handles the trailing `=`)\n continue;\n }\n if (source[i + 1] === \">\") {\n i++; // skip `=>`\n continue;\n }\n if (source[i - 1] === \"!\" || source[i - 1] === \"<\" || source[i - 1] === \">\") {\n continue; // `!=`, `<=`, `>=`\n }\n return i;\n }\n return -1;\n}\n\n/**\n * Walk forward from `from` tracking brace/bracket/paren depth and string\n * literals; return the index of the statement terminator.\n *\n * Terminates at the first of:\n * 1. A top-level `;`.\n * 2. A top-level newline that occurs *after* the value expression has\n * produced any non-whitespace content. This is the ASI rule the\n * walker has to honor for semicolonless code:\n *\n * ```\n * const docs = defineCollection(docsCollection()) // ← stop here\n * export const collections = { … } // ← not part of `docs`\n * ```\n *\n * Without the ASI rule the walker would swallow the next statement\n * and the `base:` regex could read an unrelated value.\n * 3. End-of-input.\n *\n * The \"after non-whitespace\" gate handles the leading-newline case:\n *\n * ```\n * const docs =\n * defineCollection(docsCollection({ base: \"x\" }))\n * ```\n *\n * Here the newline right after `=` doesn't terminate; the walker\n * keeps scanning until the value starts. Once content has been seen,\n * the next top-level newline ends the statement.\n *\n * Limitations: method-chain continuations like `defineCollection(…)\\n .extend(…)`\n * stop at the first `)`. That captures the inner call's options (where any\n * `base:` would live), so the result is still correct in practice.\n */\nfunction findStatementEnd(source: string, from: number): number {\n let depth = 0;\n let inString: string | null = null;\n let sawContent = false;\n for (let i = from; i < source.length; i++) {\n const ch = source[i];\n if (inString) {\n if (ch === \"\\\\\") {\n i++;\n continue;\n }\n if (ch === inString) inString = null;\n continue;\n }\n if (ch === '\"' || ch === \"'\" || ch === \"`\") {\n inString = ch;\n sawContent = true;\n } else if (ch === \"{\" || ch === \"[\" || ch === \"(\") {\n depth++;\n sawContent = true;\n } else if (ch === \"}\" || ch === \"]\" || ch === \")\") {\n depth--;\n } else if (ch === \";\" && depth === 0) {\n return i;\n } else if (ch === \"\\n\" && depth === 0 && sawContent) {\n return i;\n } else if (ch !== \" \" && ch !== \"\\t\" && ch !== \"\\r\" && ch !== \"\\n\") {\n sawContent = true;\n }\n }\n return source.length;\n}\n\n/**\n * Starting from an opening brace at `openIdx`, walk forward tracking brace\n * depth (and skipping string literals + nested brackets) and return the\n * index of the matching close brace. Returns `-1` if no match is found —\n * which only happens on a syntactically broken file.\n */\nfunction findMatchingBrace(input: string, openIdx: number): number {\n if (input[openIdx] !== \"{\") return -1;\n let depth = 0;\n let inString: string | null = null;\n for (let i = openIdx; i < input.length; i++) {\n const ch = input[i];\n if (inString) {\n if (ch === \"\\\\\") {\n i++;\n continue;\n }\n if (ch === inString) inString = null;\n continue;\n }\n if (ch === '\"' || ch === \"'\" || ch === \"`\") {\n inString = ch;\n } else if (ch === \"{\") {\n depth++;\n } else if (ch === \"}\") {\n depth--;\n if (depth === 0) return i;\n }\n }\n return -1;\n}\n\n/**\n * Split a string on commas that are at depth 0 (not inside `{}`, `[]`,\n * `()`, or string literals). Required because object entries can themselves\n * contain commas (e.g. `docs: defineCollection(docsCollection({ a: 1 }))`).\n */\nfunction splitTopLevelCommas(input: string): string[] {\n const result: string[] = [];\n let depth = 0;\n let start = 0;\n let inString: string | null = null;\n\n for (let i = 0; i < input.length; i++) {\n const ch = input[i];\n if (inString) {\n if (ch === \"\\\\\") {\n i++;\n continue;\n }\n if (ch === inString) inString = null;\n continue;\n }\n if (ch === '\"' || ch === \"'\" || ch === \"`\") inString = ch;\n else if (ch === \"{\" || ch === \"[\" || ch === \"(\") depth++;\n else if (ch === \"}\" || ch === \"]\" || ch === \")\") depth--;\n else if (ch === \",\" && depth === 0) {\n result.push(input.slice(start, i));\n start = i + 1;\n }\n }\n result.push(input.slice(start));\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Reserved-name filter\n// ---------------------------------------------------------------------------\n\n/**\n * Collection names that should never appear in the agent-facing index,\n * regardless of how they were registered. The rule pair is intentionally\n * minimal so the convention is easy to remember:\n *\n * - `partials` — Nimbus's built-in factory for `<Render slug=…/>`\n * snippets. They're component content, not pages.\n * - any name starting with `_` — author-chosen \"loaded but internal\"\n * marker (e.g. `_drafts`, `_archive`, `_legacy`).\n */\nconst RESERVED_LITERAL = new Set([\"partials\"]);\nconst RESERVED_PREFIX = \"_\";\n\nexport function filterIndexableCollections(names: string[]): string[] {\n return names.filter(\n (name) => !RESERVED_LITERAL.has(name) && !name.startsWith(RESERVED_PREFIX),\n );\n}\n","/**\n * code-transformers.ts — Shiki transformer chain used both by the\n * markdown pipeline (registered into `shikiConfig.transformers` from\n * the Astro integration so fenced MDX blocks pick them up) and by the\n * user's `<Code>` component (Astro's built-in `<Code>` accepts\n * `transformers` as a prop but does *not* auto-read `shikiConfig`).\n *\n * The single source of truth lives here so both paths get the same\n * polish — diff, highlight, focus, error-level, word-highlight, plus\n * meta line/word highlight from `@shikijs/transformers`, plus the\n * Nimbus-owned `titleAndLangTransformer` that:\n *\n * - Wraps the rendered `<pre>` in a `<figure class=\"nb-code-figure\">`\n * whenever the fenced block has `title=\"...\"` in its meta. The\n * figure carries a `<figcaption class=\"nb-code-title\">` with the\n * filename and a small language tag at the right end.\n * - Always tags the `<pre>` with `data-nb-lang=\"…\"` so the starter\n * CSS can render a top-right language badge on un-titled blocks.\n */\n\n// `@shikijs/types` is a dedicated types-only package — devDep here, used\n// internally by `shiki` and `@shikijs/transformers`. Avoids importing from\n// the `shiki` runtime package (which we don't ship as a direct dep).\nimport type { ShikiTransformer } from \"@shikijs/types\";\nimport {\n transformerNotationDiff,\n transformerNotationFocus,\n transformerNotationHighlight,\n transformerNotationErrorLevel,\n transformerNotationWordHighlight,\n transformerMetaHighlight,\n transformerMetaWordHighlight,\n} from \"@shikijs/transformers\";\n\n/**\n * Parse Shiki meta string (the bit after the language fence:\n * ```ts title=\"src/foo.ts\" {1,3}`) for the `title=\"...\"` key.\n * Returns `undefined` when the meta has no title.\n */\nfunction parseTitle(meta: string | undefined): string | undefined {\n if (!meta) return undefined;\n const match = meta.match(/\\btitle=\"([^\"]+)\"/) ?? meta.match(/\\btitle='([^']+)'/);\n return match?.[1];\n}\n\n/**\n * The canonical Shiki transformer chain for Nimbus. Returns a fresh\n * array each call so callers don't accidentally mutate a shared list.\n *\n * Used by:\n * - `integration.ts` → `shikiConfig.transformers` (fenced MDX blocks)\n * - `Code.astro` in the starter → `transformers` prop on Astro's\n * built-in `<Code>` component (and by extension, anything that\n * composes `<Code>` such as `<CodeGroup>`)\n */\nexport function defaultCodeTransformers(): ShikiTransformer[] {\n return [\n transformerNotationDiff(),\n transformerNotationHighlight(),\n transformerNotationFocus(),\n transformerNotationErrorLevel(),\n transformerNotationWordHighlight(),\n transformerMetaHighlight(),\n transformerMetaWordHighlight(),\n titleAndLangTransformer(),\n ];\n}\n\nexport function titleAndLangTransformer(): ShikiTransformer {\n return {\n name: \"nimbus:title-and-lang\",\n pre(preNode) {\n const lang = this.options.lang || \"text\";\n const meta: string | undefined = (this.options.meta as { __raw?: string } | undefined)?.__raw;\n const title = parseTitle(meta);\n\n // Always tag the pre with its language for CSS.\n preNode.properties = preNode.properties ?? {};\n preNode.properties[\"data-nb-lang\"] = lang;\n\n // Always wrap in a <figure>. With title → figcaption + pre. Without\n // title → just the pre, but the figure still provides a non-scrolling\n // positioning context so the language badge (rendered via CSS on the\n // figure) stays pinned at top-right even when the pre overflows\n // horizontally on mobile.\n const children: typeof preNode[] = [];\n if (title) {\n children.push({\n type: \"element\",\n tagName: \"figcaption\",\n properties: { class: \"nb-code-title\" },\n children: [\n {\n type: \"element\",\n tagName: \"span\",\n properties: { class: \"nb-code-title-name\" },\n children: [{ type: \"text\", value: title }],\n },\n {\n type: \"element\",\n tagName: \"span\",\n properties: { class: \"nb-code-title-lang\" },\n children: [{ type: \"text\", value: lang }],\n },\n ],\n });\n }\n children.push(preNode);\n\n return {\n type: \"element\",\n tagName: \"figure\",\n properties: {\n class: title ? \"nb-code-figure nb-code-figure-titled\" : \"nb-code-figure\",\n \"data-nb-lang\": lang,\n },\n children,\n };\n },\n };\n}\n","/**\n * MDX PascalCase tag validator — runs as a content pass, not a remark\n * plugin, so it works regardless of which markdown processor the user\n * has wired into `markdown.processor` (Sätteri replaces unified's\n * pipeline, which silently disables remark plugins attached via\n * `mdx({ remarkPlugins })`).\n *\n * Strategy:\n *\n * 1. Walk the configured content directories for `.mdx` files.\n * 2. For each file: split frontmatter, parse imports + JSX tags from\n * the body, validate every PascalCase tag against globals + per-file\n * imports.\n * 3. Collect every failure across every file (don't fail-fast), then\n * throw one error with all locations and \"did you mean\" hints.\n *\n * Parsing approach is intentionally regex-based and not a full MDX\n * parser. Tradeoffs:\n *\n * - Pro: zero MDX/remark deps, runs in milliseconds, no pipeline\n * coupling. Survives processor swaps (satteri / unified / future).\n * - Pro: tolerates malformed MDX — the validator's job is to find\n * unknown tags, not to be the parser of record.\n * - Con: a few edge cases (JSX inside string literals inside expression\n * children, deeply nested fenced code with `~~~`) can produce false\n * positives. Code blocks (``` and indented) are stripped before\n * scanning to keep the common case clean.\n *\n * Catches the silent-failure case where MDX renders unknown PascalCase\n * tags as literal text on the deployed page — the bug appears in\n * production, not in the build log.\n */\n\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { suggest } from \"./levenshtein.js\";\n\nexport interface ValidateMdxContentOptions {\n /** Names available globally (from `src/components.ts`). */\n globals: ReadonlyArray<string>;\n /**\n * Absolute paths to scan. Typically `[<projectRoot>/src/content]`.\n * Each path is walked recursively for `.mdx` files.\n */\n contentDirs: ReadonlyArray<string>;\n /**\n * Optional filter to skip files (e.g. vendored MDX). Receives the\n * absolute path; return `true` to skip validation.\n */\n skip?: (filePath: string) => boolean;\n /**\n * Project root, used to print file paths relative to it in error\n * messages. Falls back to the absolute path when not provided.\n */\n projectRoot?: string;\n}\n\nexport interface ValidationFailure {\n filePath: string;\n tag: string;\n line: number;\n column: number;\n hint: string | null;\n}\n\nexport async function validateMdxContent(\n options: ValidateMdxContentOptions,\n): Promise<ValidationFailure[]> {\n const globalsSet = new Set(options.globals);\n const failures: ValidationFailure[] = [];\n\n for (const dir of options.contentDirs) {\n const files = await walkMdx(dir);\n for (const file of files) {\n if (options.skip?.(file)) continue;\n const source = await fs.readFile(file, \"utf8\");\n const fileFailures = scanFile(source, globalsSet);\n for (const f of fileFailures) {\n const knownNames = [...globalsSet, ...f.imports];\n failures.push({\n filePath: options.projectRoot\n ? path.relative(options.projectRoot, file)\n : file,\n tag: f.tag,\n line: f.line,\n column: f.column,\n hint: suggest(f.tag, knownNames),\n });\n }\n }\n }\n\n return failures;\n}\n\n/**\n * Format a list of failures into a single multi-line error message\n * suitable for `throw new Error(...)`.\n */\nexport function formatFailures(\n failures: ReadonlyArray<ValidationFailure>,\n globalsCount: number,\n): string {\n const lines = failures.map((f) => {\n const fix = f.hint\n ? `Did you mean <${f.hint} />?`\n : globalsCount === 0\n ? `Register it in src/components.ts, or add an explicit \\`import\\` at the top of this file.`\n : `Register it in src/components.ts, or add an explicit \\`import\\` at the top of this file.`;\n return ` ${f.filePath}:${f.line}:${f.column} <${f.tag} /> → ${fix}`;\n });\n\n const noun = failures.length === 1 ? \"tag\" : \"tags\";\n return (\n `[nimbus-docs] Unknown MDX component ${noun}:\\n` +\n lines.join(\"\\n\") +\n `\\n\\nA PascalCase tag in MDX must either be registered in src/components.ts (the global registry) or imported at the top of the file. ` +\n `Without either, MDX renders the tag as literal text on the page — a silent failure this validator turns into a build error.`\n );\n}\n\n// ---------------------------------------------------------------------------\n// File walking\n// ---------------------------------------------------------------------------\n\nasync function walkMdx(dir: string): Promise<string[]> {\n const out: string[] = [];\n async function visit(current: string) {\n let entries: import(\"node:fs\").Dirent[];\n try {\n entries = await fs.readdir(current, { withFileTypes: true });\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return;\n throw err;\n }\n for (const entry of entries) {\n const full = path.join(current, entry.name);\n if (entry.isDirectory()) {\n if (entry.name === \"node_modules\" || entry.name.startsWith(\".\")) continue;\n await visit(full);\n } else if (entry.isFile() && entry.name.endsWith(\".mdx\")) {\n out.push(full);\n }\n }\n }\n await visit(dir);\n return out;\n}\n\n// ---------------------------------------------------------------------------\n// Per-file scanner\n// ---------------------------------------------------------------------------\n\ninterface RawFailure {\n tag: string;\n line: number;\n column: number;\n imports: Set<string>;\n}\n\nfunction scanFile(\n source: string,\n globalsSet: ReadonlySet<string>,\n): RawFailure[] {\n const { body, bodyOffset } = stripFrontmatter(source);\n const imports = parseImports(body);\n const stripped = stripCodeBlocks(body);\n const tags = findPascalCaseTags(stripped);\n\n const failures: RawFailure[] = [];\n for (const tag of tags) {\n if (globalsSet.has(tag.name) || imports.has(tag.name)) continue;\n const position = absolutePosition(source, bodyOffset + tag.offset);\n failures.push({\n tag: tag.name,\n line: position.line,\n column: position.column,\n imports,\n });\n }\n return failures;\n}\n\nfunction stripFrontmatter(source: string): { body: string; bodyOffset: number } {\n const match = source.match(/^---\\n[\\s\\S]*?\\n---\\n?/);\n if (!match) return { body: source, bodyOffset: 0 };\n return { body: source.slice(match[0].length), bodyOffset: match[0].length };\n}\n\n/**\n * Extract names introduced by top-level `import` statements. Handles\n * default, named (with optional aliases), and namespace imports.\n */\nfunction parseImports(body: string): Set<string> {\n const names = new Set<string>();\n // Match `import ... from \"...\"` and side-effect `import \"...\"` (no names).\n const importPattern = /^\\s*import\\s+([^\"';]+?)\\s+from\\s+[\"'][^\"']+[\"']\\s*;?/gm;\n // `!` on every regex capture-group access: the `import…from` pattern's\n // group is *required* (not optional), so it's defined whenever `match`\n // succeeded. Same logic applies to `namespaceMatch[1]`, `braceMatch[1]`,\n // `aliasMatch[1]` — all required groups. `.split(\"{\")[0]!` is safe\n // because `String.split` always returns ≥1 element.\n for (const match of body.matchAll(importPattern)) {\n const clause = match[1]!;\n // Namespace: `import * as Foo from \"...\"`\n const namespaceMatch = clause.match(/\\*\\s+as\\s+([A-Za-z_$][\\w$]*)/);\n if (namespaceMatch) {\n names.add(namespaceMatch[1]!);\n continue;\n }\n // Strip and split: `Default, { Named, Aliased as Local }`\n const beforeBrace = clause.split(\"{\")[0]!.trim().replace(/,\\s*$/, \"\");\n if (beforeBrace && /^[A-Za-z_$][\\w$]*$/.test(beforeBrace)) {\n names.add(beforeBrace);\n }\n const braceMatch = clause.match(/\\{([^}]*)\\}/);\n if (braceMatch) {\n for (const raw of braceMatch[1]!.split(\",\")) {\n const spec = raw.trim();\n if (!spec) continue;\n const aliasMatch = spec.match(/^[A-Za-z_$][\\w$]*\\s+as\\s+([A-Za-z_$][\\w$]*)$/);\n if (aliasMatch) {\n names.add(aliasMatch[1]!);\n } else if (/^[A-Za-z_$][\\w$]*$/.test(spec)) {\n names.add(spec);\n }\n }\n }\n }\n return names;\n}\n\n/**\n * Remove fenced code blocks and inline code spans so JSX-looking text\n * inside code samples doesn't trip the validator.\n */\nfunction stripCodeBlocks(body: string): string {\n return body\n .replace(/```[\\s\\S]*?```/g, (m) => \" \".repeat(m.length))\n .replace(/~~~[\\s\\S]*?~~~/g, (m) => \" \".repeat(m.length))\n .replace(/`[^`\\n]*`/g, (m) => \" \".repeat(m.length));\n}\n\ninterface FoundTag {\n name: string;\n offset: number;\n}\n\n/**\n * Find PascalCase JSX-like tags. Matches `<Capital...` at the start of\n * an element (opening or self-closing). Closing tags `</Capital>` and\n * JSX fragments `<>` are not counted (the opener already covers\n * registration; counting closers would double-report).\n */\nfunction findPascalCaseTags(body: string): FoundTag[] {\n const out: FoundTag[] = [];\n const pattern = /<([A-Z][A-Za-z0-9_]*)\\b/g;\n for (const match of body.matchAll(pattern)) {\n // `match[1]!`: required capture group, defined whenever match succeeded.\n out.push({ name: match[1]!, offset: match.index ?? 0 });\n }\n return out;\n}\n\n/**\n * Compute 1-based line + column for an absolute character offset in the\n * original source.\n */\nfunction absolutePosition(source: string, offset: number): { line: number; column: number } {\n let line = 1;\n let column = 1;\n const end = Math.min(offset, source.length);\n for (let i = 0; i < end; i++) {\n if (source[i] === \"\\n\") {\n line++;\n column = 1;\n } else {\n column++;\n }\n }\n return { line, column };\n}\n","/**\n * Config validation.\n *\n * Errors target content authors, not framework developers.\n * Astro 6 ships Zod v4 via `astro/zod` — single `error` field, not v3 patterns.\n */\n\nimport { z } from \"astro/zod\";\nimport type { NimbusConfig } from \"../types.js\";\nimport { withStrictKeys } from \"./strict-keys.js\";\n\nconst headElementSchema = z.object({\n tag: z.enum([\"meta\", \"link\", \"script\", \"style\"]),\n attrs: z.record(z.string(), z.string()).default({}),\n content: z.string().optional(),\n});\n\n/**\n * Removed/renamed keys in the `features` sub-object. Each maps to the\n * back-half of a sentence — the parent error message prepends\n * `features sub-key \"<name>\" ` automatically.\n */\nconst REMOVED_FEATURE_KEYS: Record<string, string> = {\n toc:\n 'was renamed to \"tableOfContents\". Replace `features: { toc: false }` with `features: { tableOfContents: false }`.',\n pagination:\n \"was removed. To hide pagination site-wide, remove `<Pagination />` from `src/layouts/DocsLayout.astro` (it is user-owned).\",\n editLinks:\n \"was removed. To hide edit links site-wide, omit `editPattern` from the config — the default is null, which produces no edit URLs. Setting `github` alone does not enable edit links.\",\n search:\n \"moved to the top-level `search` field on the config. Replace `features: { search: false }` with `search: false`.\",\n};\n\n// Narrow features schema: only kill switches for chrome that's hard to\n// hide via user-side edits alone (the sidebar threads through layout +\n// header + mobile dialog; the TOC has its own column the layout sets up).\n// Both default to `true`. Per-page frontmatter (sidebar/tableOfContents)\n// can override in the \"off\" direction via AND-merge in the route.\nconst featuresSchema = withStrictKeys(\n z.object({\n sidebar: z.boolean().default(true),\n tableOfContents: z.boolean().default(true),\n }),\n {\n removedKeys: REMOVED_FEATURE_KEYS,\n contextLabel: \"features sub-key\",\n },\n).default({ sidebar: true, tableOfContents: true });\n\nconst searchSchema = z\n .union([\n z.literal(false),\n z.object({\n provider: z.enum([\"pagefind\", \"custom\"]).default(\"pagefind\"),\n }),\n ])\n .optional();\n\n// Sidebar items are intentionally loose — the sidebar builder accepts the\n// shapes documented in types.ts; tightening here adds friction for users\n// without catching real errors that the builder doesn't already catch.\nconst sidebarSchema = z\n .object({\n items: z.array(z.unknown()).optional(),\n scope: z.enum([\"full\", \"section\"]).default(\"full\"),\n })\n .passthrough()\n .optional();\n\n// Versioning manifest. Shape validation only (P0). Cross-checking that each\n// `others[i]` actually corresponds to a registered `docs-<i>` collection\n// happens at integration setup time in `integration.ts` where the parsed\n// collections list is available.\n//\n// Rules enforced here (mirrors versioned-docs spec acceptance criteria):\n// - `current` is a non-empty string.\n// - `others` are non-empty strings, no duplicates.\n// - `deprecated` ⊆ `others`.\n// - `hidden` ⊆ `others`.\n// - `current` not present in `others` (a version is either current or older,\n// never both).\nconst versionSlugSchema = z\n .string({ error: '\"versions\" entries must be strings' })\n .min(1, { message: 'Empty string is not a valid version slug' });\n\nconst versionsSchema = z\n .object({\n current: versionSlugSchema,\n others: z.array(versionSlugSchema).default([]),\n deprecated: z.array(versionSlugSchema).default([]),\n hidden: z.array(versionSlugSchema).default([]),\n })\n .superRefine((v, ctx) => {\n const seen = new Set<string>();\n v.others.forEach((slug, i) => {\n if (seen.has(slug)) {\n ctx.addIssue({\n code: \"custom\",\n message: `Duplicate version slug \"${slug}\" in \"others\"`,\n path: [\"others\", i],\n });\n }\n seen.add(slug);\n });\n if (v.others.includes(v.current)) {\n ctx.addIssue({\n code: \"custom\",\n message:\n `\"current\" (${JSON.stringify(v.current)}) must not also appear in \"others\". ` +\n `The current version lives in the primary \\`docs\\` collection; ` +\n `entries in \"others\" describe older versions stored in \\`docs-<slug>\\` collections.`,\n path: [\"current\"],\n });\n }\n for (const [i, slug] of v.deprecated.entries()) {\n if (!v.others.includes(slug)) {\n ctx.addIssue({\n code: \"custom\",\n message:\n `\"deprecated\" entry ${JSON.stringify(slug)} is not in \"others\". ` +\n `Every deprecated version must also be listed in \"others\".`,\n path: [\"deprecated\", i],\n });\n }\n }\n for (const [i, slug] of v.hidden.entries()) {\n if (!v.others.includes(slug)) {\n ctx.addIssue({\n code: \"custom\",\n message:\n `\"hidden\" entry ${JSON.stringify(slug)} is not in \"others\". ` +\n `Every hidden version must also be listed in \"others\".`,\n path: [\"hidden\", i],\n });\n }\n }\n })\n .optional();\n\n/**\n * Removed top-level config keys. Hits emit a friendly migration message\n * instead of being silently dropped.\n */\nconst REMOVED_CONFIG_KEYS: Record<string, string> = {\n logo:\n 'was removed. The header now renders `config.title` as text. To use a logo image, edit `src/components/Header.astro` and drop in an <img> or <svg>.',\n footer:\n \"was removed. The starter no longer ships a default `Footer.astro`. To add one, create your own component and render it in `src/layouts/DocsLayout.astro`.\",\n};\n\nconst nimbusConfigSchema = withStrictKeys(\n z.object({\n site: z.string().url({ message: '\"site\" must be a valid URL' }),\n title: z.string(),\n description: z.string().optional(),\n locale: z.string().default(\"en\"),\n homeLabel: z.string().default(\"Home\"),\n github: z.string().url().nullable().default(null),\n // editPattern must contain the `{path}` placeholder. Without it,\n // `getEditUrl()` returns the pattern unchanged for every entry — a\n // silent footgun that ships broken edit links to production.\n editPattern: z\n .string()\n .nullable()\n .default(null)\n .refine((v) => v === null || v.includes(\"{path}\"), {\n message:\n '\"editPattern\" must contain the \"{path}\" placeholder, which is replaced with the entry source path. ' +\n 'Example: \"https://github.com/my-org/my-repo/edit/main/{path}\"',\n }),\n socialImage: z\n .string({ error: '\"socialImage\" must be a string (path or URL)' })\n .optional(),\n socialImageAlt: z\n .string({ error: '\"socialImageAlt\" must be a string' })\n .optional(),\n head: z.array(headElementSchema).default([]),\n sidebar: sidebarSchema,\n features: featuresSchema,\n search: searchSchema,\n versions: versionsSchema,\n }),\n {\n removedKeys: REMOVED_CONFIG_KEYS,\n contextLabel: \"Config field\",\n },\n);\n\nexport function validateNimbusConfig(input: unknown): NimbusConfig {\n const result = nimbusConfigSchema.safeParse(input);\n if (result.success) {\n // Zod safeParse upstream validated the shape against nimbusConfigSchema;\n // double-cast restores the type info tsc lost through the schema's\n // generic `Record<string, unknown>` representation.\n return result.data as unknown as NimbusConfig;\n }\n\n // Build a content-author-readable issue list. Each line carries:\n // - the dotted config path (so it's greppable in nimbus.config.ts)\n // - the validator message\n // - the offending value (truncated) when one was supplied\n const issues = result.error.issues\n .map((issue) => {\n // Zod v4 widens path entries to PropertyKey. Symbols never appear in\n // our schema (no symbol keys), so it's safe to coerce to string|number\n // for both display and value lookup.\n const issuePath = issue.path\n .filter((p): p is string | number => typeof p !== \"symbol\");\n const display = issuePath.length > 0 ? issuePath.join(\".\") : \"(root)\";\n const received = formatReceived(input, issuePath);\n const tail = received === null ? \"\" : `\\n received: ${received}`;\n return ` - ${display}: ${issue.message}${tail}`;\n })\n .join(\"\\n\");\n\n throw new Error(\n `Invalid nimbus.config — fix these issues:\\n${issues}\\n\\n` +\n `See https://nimbus-docs.dev/config for the full config schema.`,\n );\n}\n\n/**\n * Resolve the value at `path` inside the raw input and format it for an\n * error message. Returns null when the path is unreachable (e.g. a\n * required key is missing entirely — in that case the message itself\n * already says \"Required\", so we don't double up).\n */\nfunction formatReceived(input: unknown, path: ReadonlyArray<string | number>): string | null {\n let cursor: unknown = input;\n for (const key of path) {\n if (cursor === null || typeof cursor !== \"object\") return null;\n cursor = (cursor as Record<string | number, unknown>)[key];\n if (cursor === undefined) return null;\n }\n if (cursor === undefined) return null;\n try {\n const json = JSON.stringify(cursor);\n if (json === undefined) return String(cursor);\n return json.length > 120 ? `${json.slice(0, 117)}...` : json;\n } catch {\n return String(cursor);\n }\n}\n","/**\n * Vite plugin: exposes the validated NimbusConfig via `virtual:nimbus/config`.\n *\n * Consumers in user-land:\n *\n * import { config, indexedCollections, versionAlternates }\n * from \"virtual:nimbus/config\";\n *\n * Used by data helpers (getSidebar, getPrevNext, etc.) so they don't need\n * the config passed at every call site. The `indexedCollections` export\n * is the build-time-resolved list of collections that agent-facing routes\n * (llms.txt, per-page .md alternates) should iterate. See\n * `parse-content-collections.ts` and `getIndexedEntries()`.\n *\n * `versionAlternates` is the build-time alternates table for cross-version\n * SEO links (`<link rel=\"alternate\">`, `<link rel=\"canonical\">`). Empty\n * object when the site is unversioned. See `version-alternates.ts`.\n */\n\nimport type { NimbusConfig } from \"../types.js\";\nimport type { VersionAlternatesTable } from \"./version-alternates.js\";\n\nconst VIRTUAL_ID = \"virtual:nimbus/config\";\nconst RESOLVED_ID = `\\0${VIRTUAL_ID}`;\n\nexport interface VitePluginLike {\n name: string;\n resolveId(id: string): string | undefined;\n load(id: string): string | undefined;\n}\n\nexport interface VirtualConfigExtras {\n /**\n * Registered docs-shaped collection names, with reserved (`partials`,\n * `_*`) already filtered out. Empty array falls back to `[\"docs\"]` at\n * read time so a brand-new project without `content.config.ts` still\n * works.\n */\n indexedCollections: string[];\n /**\n * Build-time alternates table for cross-version SEO links. Empty `{}`\n * when the site is unversioned or has only the current version.\n */\n versionAlternates: VersionAlternatesTable;\n}\n\nexport function virtualConfigPlugin(\n config: NimbusConfig,\n extras: VirtualConfigExtras,\n): VitePluginLike {\n return {\n name: \"nimbus-docs:virtual-config\",\n resolveId(id: string) {\n if (id === VIRTUAL_ID) return RESOLVED_ID;\n return undefined;\n },\n load(id: string) {\n if (id === RESOLVED_ID) {\n return (\n `export const config = ${JSON.stringify(config)};\\n` +\n `export const indexedCollections = ${JSON.stringify(extras.indexedCollections)};\\n` +\n `export const versionAlternates = ${JSON.stringify(extras.versionAlternates)};\\n`\n );\n }\n return undefined;\n },\n };\n}\n","/**\n * Walk every version-collection directory and extract the frontmatter\n * fields the alternates table needs (`previousSlug`, `draft`).\n *\n * Runs at `astro:config:setup` — before Astro's content layer is\n * initialized, so we can't use `getCollection()`. Walks the filesystem\n * directly, slices the YAML frontmatter from each MDX/MD file, and\n * pulls the two fields we care about. Same \"never execute user code\"\n * posture as `parse-content-collections.ts` and `parse-components-registry.ts`.\n *\n * Returns one `VersionEntryInput` per visible entry across the\n * versioned-docs collections. Drafts (frontmatter `draft: true`) are\n * filtered. Consumers feed this into `buildVersionAlternates()`.\n */\n\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport type { ResolvedVersions } from \"../types.js\";\nimport type { VersionEntryInput } from \"./version-alternates.js\";\n\nconst PRIMARY_COLLECTION = \"docs\";\nconst EXTENSIONS = new Set([\".mdx\", \".md\"]);\n\nexport interface ScanOptions {\n /** Absolute path to the project root (`fileURLToPath(astroConfig.root)`). */\n projectRoot: string;\n /** Resolved versioning manifest. */\n versions: ResolvedVersions;\n}\n\nexport async function scanVersionFrontmatter(\n options: ScanOptions,\n): Promise<VersionEntryInput[]> {\n const { projectRoot, versions } = options;\n const out: VersionEntryInput[] = [];\n\n // Primary collection's directory: src/content/docs/.\n // Version collections: src/content/docs-<slug>/.\n const collectionsToScan: { collection: string; dir: string }[] = [\n { collection: PRIMARY_COLLECTION, dir: path.join(projectRoot, \"src/content/docs\") },\n ...versions.others.map((slug) => ({\n collection: `docs-${slug}`,\n dir: path.join(projectRoot, `src/content/docs-${slug}`),\n })),\n ];\n\n for (const { collection, dir } of collectionsToScan) {\n const files = await walk(dir);\n for (const file of files) {\n let source: string;\n try {\n source = await fs.readFile(file, \"utf8\");\n } catch {\n continue;\n }\n const front = extractFrontmatter(source);\n if (front === null) continue;\n if (parseBoolField(front, \"draft\") === true) continue;\n\n const previousSlug = parsePreviousSlugField(front);\n const id = idFromPath(dir, file);\n out.push({ collection, id, previousSlug });\n }\n }\n\n return out;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nasync function walk(dir: string): Promise<string[]> {\n const out: string[] = [];\n async function visit(current: string) {\n let entries: import(\"node:fs\").Dirent[];\n try {\n entries = await fs.readdir(current, { withFileTypes: true });\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return;\n throw err;\n }\n for (const entry of entries) {\n const full = path.join(current, entry.name);\n if (entry.isDirectory()) {\n if (entry.name === \"node_modules\" || entry.name.startsWith(\".\")) continue;\n await visit(full);\n } else if (entry.isFile()) {\n const ext = path.extname(entry.name);\n if (EXTENSIONS.has(ext)) out.push(full);\n }\n }\n }\n await visit(dir);\n return out;\n}\n\n/**\n * Slice the YAML frontmatter block from a source file. Returns the body\n * between the leading `---` and the closing `---` (without the markers),\n * or `null` if no frontmatter is present.\n *\n * Matches the same convention Astro's content layer enforces — leading\n * `---\\n`, closing `\\n---\\n` (or `\\n---` at EOF).\n */\nfunction extractFrontmatter(source: string): string | null {\n if (!source.startsWith(\"---\")) return null;\n // First line must be exactly `---` (or `---\\r`).\n const afterFirstMarker = source.indexOf(\"\\n\");\n if (afterFirstMarker === -1) return null;\n const firstLine = source.slice(0, afterFirstMarker).trim();\n if (firstLine !== \"---\") return null;\n\n const rest = source.slice(afterFirstMarker + 1);\n // Look for a line that's just `---` (start-of-line, optional trailing CR).\n const closingMatch = rest.match(/(^|\\n)---\\s*(\\n|$)/);\n if (!closingMatch || closingMatch.index === undefined) return null;\n // closingMatch.index is the start of the `\\n---` (or the leading\n // newline before it). Slice up to it.\n const endIndex = closingMatch[1] === \"\\n\" ? closingMatch.index : closingMatch.index;\n return rest.slice(0, endIndex);\n}\n\n/**\n * Find a top-level boolean field in YAML frontmatter. Returns the\n * boolean value or `undefined` if the field is absent / malformed.\n *\n * Handles only the shape Nimbus uses: `<field>: true` / `<field>: false`\n * on a single line, no indentation, no quotes.\n */\nfunction parseBoolField(yaml: string, field: string): boolean | undefined {\n const re = new RegExp(`^${escapeRe(field)}\\\\s*:\\\\s*(true|false)\\\\s*$`, \"m\");\n const m = yaml.match(re);\n if (!m) return undefined;\n return m[1] === \"true\";\n}\n\n/**\n * Find the top-level `previousSlug` field in YAML frontmatter. Accepts:\n * - scalar: `previousSlug: foo`\n * - inline array: `previousSlug: [foo, \"bar\", 'baz']`\n * - multiline block array:\n * ```yaml\n * previousSlug:\n * - foo\n * - bar\n * ```\n *\n * Returns:\n * - `string` for a scalar\n * - `string[]` for either array form\n * - `undefined` if absent\n *\n * The multiline form is the canonical YAML list syntax; the scanner\n * previously accepted only scalar and inline-array, which silently\n * dropped valid block lists at build time. The schema validates the\n * post-parse shape; the scanner has to match it.\n */\nfunction parsePreviousSlugField(yaml: string): string | string[] | undefined {\n // First locate the `previousSlug:` line. If the right-hand side is\n // empty (just whitespace), it's the lead-in to a block list — parse\n // the indented `- value` lines that follow.\n const blockHeader = yaml.match(/^previousSlug\\s*:\\s*$/m);\n if (blockHeader && blockHeader.index !== undefined) {\n const after = yaml.slice(blockHeader.index + blockHeader[0].length + 1);\n return parseBlockList(after);\n }\n\n // Inline array form: previousSlug: [foo, \"bar\", 'baz']\n const arr = yaml.match(/^previousSlug\\s*:\\s*\\[([^\\]]*)\\]\\s*$/m);\n if (arr) {\n const inner = arr[1]!;\n return inner\n .split(\",\")\n .map((s) => unquote(s.trim()))\n .filter((s) => s.length > 0);\n }\n\n // Scalar form: previousSlug: foo OR previousSlug: \"foo\"\n const scalar = yaml.match(/^previousSlug\\s*:\\s*(?!\\[)(.+?)\\s*$/m);\n if (scalar) {\n const raw = scalar[1]!.trim();\n if (raw.length === 0) return undefined;\n return unquote(raw);\n }\n\n return undefined;\n}\n\n/**\n * Parse a YAML block list (one `- value` per line, leading indent).\n * Stops at the first non-list, non-blank line (i.e. the next sibling\n * frontmatter field at the same indentation as the list header).\n *\n * previousSlug:\n * - foo\n * - \"bar\"\n * - 'baz'\n * title: Whatever ← stops here\n */\nfunction parseBlockList(source: string): string[] {\n const lines = source.split(\"\\n\");\n const out: string[] = [];\n for (const line of lines) {\n if (line.trim().length === 0) continue;\n const m = line.match(/^\\s+-\\s+(.+?)\\s*$/);\n if (!m) break;\n const value = unquote(m[1]!.trim());\n if (value.length > 0) out.push(value);\n }\n return out;\n}\n\nfunction unquote(s: string): string {\n if (\n (s.startsWith('\"') && s.endsWith('\"')) ||\n (s.startsWith(\"'\") && s.endsWith(\"'\"))\n ) {\n return s.slice(1, -1);\n }\n return s;\n}\n\nfunction escapeRe(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\n/**\n * Compute the Astro entry id (the slug) from a file's absolute path,\n * relative to the collection directory.\n *\n * Examples:\n * - <dir>/index.mdx → \"index\"\n * - <dir>/foo.mdx → \"foo\"\n * - <dir>/guides/setup.mdx → \"guides/setup\"\n */\nfunction idFromPath(collectionDir: string, filePath: string): string {\n const rel = path.relative(collectionDir, filePath);\n const noExt = rel.replace(/\\.(mdx|md)$/, \"\");\n // Normalise path separators for cross-platform stability.\n return noExt.split(path.sep).join(\"/\");\n}\n","/**\n * Cross-version alternates table.\n *\n * Builds the SEO-equivalence-class map at build time: which pages across\n * version collections refer to the same logical content. Consumers\n * (`getVersionAlternates`, `getCanonicalUrl`, the auto-redirect step)\n * read from the resolved structure rather than re-walking collections.\n *\n * Two ways pages get linked into the same class:\n * 1. Same `entry.id` across collections — `docs/foo.mdx` and\n * `docs-v1/foo.mdx` are obviously the same page.\n * 2. `previousSlug` frontmatter on the newer page declares its slug in\n * an older version — `docs/foo-renamed.mdx` with\n * `previousSlug: \"foo-old\"` connects to `docs-v1/foo-old.mdx`.\n *\n * Multiple equivalences chain — if v3.A renames to v2.B and v2.B\n * renames to v1.C, they're all the same logical page. Union-find\n * collapses the chains in one pass.\n *\n * The output is queried per-page from page routes (to inject\n * `<link rel=\"alternate\">` and `<link rel=\"canonical\">` into `<head>`)\n * and once per build to emit Astro redirects.\n */\n\nimport type { ResolvedVersions } from \"../types.js\";\nimport { canonicalEntryUrl } from \"./astro-slug.js\";\nimport { PRIMARY_COLLECTION } from \"./collection-mount.js\";\nimport { toBrowserHref } from \"./url.js\";\n\n/**\n * Minimum-viable entry shape the table needs. Matches what\n * `astro:content`'s `getCollection()` returns, but expressed structurally\n * so the integration can construct the same data without importing the\n * virtual `astro:content` module (which isn't available at integration\n * setup time).\n */\nexport interface VersionEntryInput {\n /** Astro collection ID (e.g. `\"docs\"`, `\"docs-v1\"`). */\n collection: string;\n /** Astro entry id (the slug — `\"foo\"`, `\"guides/setup\"`, …). */\n id: string;\n /** Frontmatter `previousSlug` (string, array of strings, or absent). */\n previousSlug?: string | string[];\n}\n\n/** A single page reference within the alternates graph. */\nexport interface VersionPageRef {\n /** Astro collection ID. */\n collection: string;\n /** Version slug — `current` for `docs`, or `v1`/`v2`/… for `docs-v*`. */\n version: string;\n /** Page slug (entry.id). */\n slug: string;\n /**\n * Resolved URL path, browser-href form: leading slash and a trailing\n * slash on HTML document routes. Rendered straight into\n * `<link rel=\"alternate\">` / `<link rel=\"canonical\">`.\n */\n url: string;\n}\n\nexport interface VersionAlternateRecord {\n /** The page this record describes. */\n self: VersionPageRef;\n /** All other pages in the same logical-page class, in version order. */\n alternates: VersionPageRef[];\n /**\n * The current-version page in this logical-page class, if one exists\n * and is not `self`. Drives `<link rel=\"canonical\">`. `null` when:\n * - `self` is already the current-version page, or\n * - no page in the current version exists for this logical page.\n */\n canonical: VersionPageRef | null;\n}\n\n/**\n * Resolved alternates table, indexed for O(1) lookup per page.\n *\n * The key is `${collection}:${entryId}`. Consumers compute that key from\n * the entry they're rendering and read out the alternates + canonical.\n */\nexport type VersionAlternatesTable = Record<string, VersionAlternateRecord>;\n\n/**\n * Build the alternates table for one site.\n *\n * Pass:\n * - `versions`: resolved manifest (or null when the site is unversioned).\n * - `entries`: every visible entry from every docs-shaped version\n * collection. Drafts already filtered.\n *\n * Returns an empty table when `versions` is null or only one version is\n * configured (no cross-linking work to do).\n */\nexport function buildVersionAlternates(\n versions: ResolvedVersions | null,\n entries: VersionEntryInput[],\n): VersionAlternatesTable {\n if (!versions || versions.all.length < 2) return {};\n\n // 1. Filter to entries we actually care about (in a version collection)\n // and compute each one's PageRef.\n const refs: VersionPageRef[] = [];\n for (const entry of entries) {\n const version = collectionToVersion(versions, entry.collection);\n if (version === null) continue;\n refs.push({\n collection: entry.collection,\n version,\n slug: entry.id,\n url: pageUrl(versions, version, entry.id),\n });\n }\n\n // 2. Union-find over the ref set. Two refs are unioned if either\n // they share a slug across versions OR one's previousSlug names\n // the other's slug in the same version chain.\n const indexByKey = new Map<string, number>();\n refs.forEach((ref, i) => indexByKey.set(refKey(ref), i));\n\n const parent = refs.map((_, i) => i);\n const find = (i: number): number => {\n let root = i;\n while (parent[root]! !== root) root = parent[root]!;\n let cursor = i;\n while (parent[cursor]! !== cursor) {\n const next = parent[cursor]!;\n parent[cursor] = root;\n cursor = next;\n }\n return root;\n };\n const union = (a: number, b: number) => {\n const ra = find(a);\n const rb = find(b);\n if (ra !== rb) parent[ra] = rb;\n };\n\n // 2a. Group by slug across versions.\n const bySlug = new Map<string, number[]>();\n for (let i = 0; i < refs.length; i++) {\n const slug = refs[i]!.slug;\n const bucket = bySlug.get(slug);\n if (bucket) bucket.push(i);\n else bySlug.set(slug, [i]);\n }\n for (const ids of bySlug.values()) {\n for (let i = 1; i < ids.length; i++) union(ids[0]!, ids[i]!);\n }\n\n // 2b. Walk previousSlug edges. Each entry's previousSlug names a slug\n // that existed in an older version. We search ALL older versions\n // for that slug and union — multiple matches are fine (a slug\n // that persisted through v1 → v2 → v3 with the same name).\n //\n // `versions.all = [current, ...others]` is the ordering. For an\n // entry in version V at index `i`, \"older\" means versions at\n // indices > `i`.\n const orderIndex = new Map<string, number>();\n versions.all.forEach((v, i) => orderIndex.set(v, i));\n\n for (let i = 0; i < entries.length; i++) {\n const entry = entries[i]!;\n if (!entry.previousSlug) continue;\n const ref = refs[refs.findIndex((r) => r.collection === entry.collection && r.slug === entry.id)];\n if (!ref) continue;\n const selfOrder = orderIndex.get(ref.version);\n if (selfOrder === undefined) continue;\n const previousSlugs = Array.isArray(entry.previousSlug)\n ? entry.previousSlug\n : [entry.previousSlug];\n\n for (const prevSlug of previousSlugs) {\n // Find every ref with slug == prevSlug in an older version\n for (let j = 0; j < refs.length; j++) {\n const other = refs[j]!;\n if (other.slug !== prevSlug) continue;\n const otherOrder = orderIndex.get(other.version);\n if (otherOrder === undefined) continue;\n if (otherOrder <= selfOrder) continue; // not older\n const selfIdx = refs.indexOf(ref);\n if (selfIdx >= 0) union(selfIdx, j);\n }\n }\n }\n\n // 3. Group refs by their root (each root = one logical page).\n const groups = new Map<number, number[]>();\n for (let i = 0; i < refs.length; i++) {\n const root = find(i);\n const g = groups.get(root);\n if (g) g.push(i);\n else groups.set(root, [i]);\n }\n\n // 4. Emit one record per ref.\n const table: VersionAlternatesTable = {};\n for (const memberIndices of groups.values()) {\n // Sort within group by manifest version order so output is deterministic.\n memberIndices.sort((a, b) => {\n const ai = orderIndex.get(refs[a]!.version) ?? Number.MAX_SAFE_INTEGER;\n const bi = orderIndex.get(refs[b]!.version) ?? Number.MAX_SAFE_INTEGER;\n return ai - bi;\n });\n const members = memberIndices.map((i) => refs[i]!);\n const currentRef = members.find((m) => m.version === versions.current) ?? null;\n\n // Hidden-version filtering. Hidden versions are off the radar from\n // every agent/SEO surface — so non-hidden pages must not advertise\n // an `<link rel=\"alternate\">` pointing at a hidden sibling. Hidden\n // pages themselves can still have a canonical pointing at current\n // (SEO authority consolidation on direct visits is desirable), but\n // their own alternates list is suppressed at the head emission\n // layer in NimbusHead.\n const hiddenVersions = new Set(versions.hidden);\n\n for (const self of members) {\n // Exclude hidden-version siblings from this page's alternates.\n // The page itself may be hidden (and NimbusHead will suppress the\n // emission anyway), but we keep the data consistent.\n const alternates = members.filter(\n (m) => m !== self && !hiddenVersions.has(m.version),\n );\n const canonical =\n currentRef && currentRef !== self ? currentRef : null;\n table[refKey(self)] = { self, alternates, canonical };\n }\n }\n\n return table;\n}\n\n/**\n * Compute the slugs that exist in `current` but are absent in a given\n * older version — the set that should auto-redirect from `/v/<slug>` to\n * `/<slug>` when a reader follows a stale link. Includes slugs reached\n * via `previousSlug` (so a renamed page's old slug in the old version\n * also redirects correctly when the user types the original new URL by\n * accident under the old prefix).\n *\n * Returns a list of `{ from, to }` redirect pairs ready for Astro's\n * `redirects` config. `from` is the URL the reader hit; `to` is the\n * current-version sibling. Both are absolute paths in the trailing-slash\n * browser-href form Astro serves under `build.format: \"directory\"`.\n * Astro's default `trailingSlash: \"ignore\"` matches incoming requests in\n * either form, so a reader landing on `/v1/foo` still resolves.\n */\nexport function computeMissingPageRedirects(\n versions: ResolvedVersions | null,\n table: VersionAlternatesTable,\n entries: VersionEntryInput[],\n): { from: string; to: string }[] {\n if (!versions || versions.all.length < 2) return [];\n\n // Build a set of (version, slug) pairs that actually exist as files.\n const existing = new Set<string>();\n for (const entry of entries) {\n const version = collectionToVersion(versions, entry.collection);\n if (version === null) continue;\n existing.add(`${version}:${entry.id}`);\n }\n\n const redirects: { from: string; to: string }[] = [];\n\n // For each current-version page, check each older version. If the old\n // version doesn't have a file with the same slug AND doesn't have one\n // linked via the alternates table → emit a redirect from the would-be\n // old URL to the current URL.\n for (const entry of entries) {\n const version = collectionToVersion(versions, entry.collection);\n if (version !== versions.current) continue;\n const currentUrl = pageUrl(versions, version, entry.id);\n\n for (const oldVersion of versions.others) {\n if (existing.has(`${oldVersion}:${entry.id}`)) continue;\n // Also skip when the alternates table already provides a\n // direct sibling in that old version (rename case).\n // `refKey` only consumes `collection` and `slug`; drop the extras.\n const record = table[refKey({ collection: entry.collection, slug: entry.id })];\n const altInOldVersion = record?.alternates.some((a) => a.version === oldVersion);\n if (altInOldVersion) continue;\n\n redirects.push({\n from: pageUrl(versions, oldVersion, entry.id),\n to: currentUrl,\n });\n }\n }\n\n return redirects;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction refKey(ref: { collection: string; slug: string }): string {\n return `${ref.collection}:${ref.slug}`;\n}\n\n/**\n * Resolve the version slug for a given Astro collection ID, or null if\n * the collection is not part of the versioning manifest.\n */\nfunction collectionToVersion(\n versions: ResolvedVersions,\n collection: string,\n): string | null {\n if (collection === PRIMARY_COLLECTION) return versions.current;\n if (!collection.startsWith(\"docs-\")) return null;\n const slug = collection.slice(\"docs-\".length);\n return versions.all.includes(slug) ? slug : null;\n}\n\n/**\n * Build the URL for a `(version, slug)` pair. Matches the convention in\n * `index.ts::resolveCollectionPrefix`:\n * - current version → root (`/foo`)\n * - others → `/<version>/<slug>`\n *\n * Runs through `canonicalEntryUrl` so the result matches what Astro\n * serves (lowercase + folder-index strip — see `_internal/astro-slug.ts`\n * for the why and known caveats). The `<link rel=\"alternate\">` tags and\n * auto-redirect machinery both consume these URLs.\n */\nfunction pageUrl(versions: ResolvedVersions, version: string, slug: string): string {\n const prefix = version === versions.current ? \"\" : `/${version}`;\n // Browser-facing form: `<link rel=\"alternate\">`, the version picker, and\n // Astro `redirects` all consume these. Trailing slash matches what the\n // static host actually serves under `build.format: \"directory\"`.\n return toBrowserHref(canonicalEntryUrl(prefix, slug));\n}\n","/**\n * The Nimbus Astro integration.\n *\n * Responsibilities:\n * - Validate the user-supplied config (throws on invalid input).\n * - Bridge `nimbusConfig.site` → Astro's top-level `site` so the\n * sitemap integration and `Astro.site` read from one source.\n * - Register `@astrojs/mdx` and `@astrojs/sitemap`.\n * - Install the Sätteri markdown processor — handles heading slugs +\n * ships with built-in Shiki dual-theme highlighting (configured via\n * Astro's `markdown.shikiConfig`).\n * - Build-time MDX PascalCase tag validation against the user's\n * `src/components.ts` registry plus per-file imports. Catches the\n * silent-failure case where MDX renders an unknown PascalCase tag\n * as literal text on the deployed site. Opt out via\n * `validateMdx: false`.\n * - Expose validated config via `virtual:nimbus/config`.\n * - Inject TypeScript types for the virtual module so consumers get\n * intellisense without manual ambient declarations.\n *\n * Not framework territory (the user's `content.config.ts` owns these):\n * - Content collection registration. The user imports\n * `docsCollection()` / `partialsCollection()` from\n * `nimbus-docs/content` and registers them themselves.\n * - MDX globals injection. The user passes `components={components}`\n * when rendering `<Content />`.\n *\n * Planned (not shipped):\n * - `/llms.txt` and `/robots.txt` route injection.\n */\n\nimport { execFile } from \"node:child_process\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport type { AstroIntegration } from \"astro\";\nimport mdx from \"@astrojs/mdx\";\nimport { satteri } from \"@astrojs/markdown-satteri\";\nimport sitemap from \"@astrojs/sitemap\";\nimport { parseComponentsRegistry } from \"./_internal/parse-components-registry.js\";\nimport {\n validateLintOptions,\n type CollectionsConfig,\n type RulesConfig,\n} from \"./lint/config.js\";\nimport { IMPLEMENTED_CODES } from \"./lint/rules/index.js\";\nimport {\n contentEntryUrl,\n enumerateEntriesByBase,\n enumerateStaticPageRoutes,\n findDuplicateRoutes,\n formatDuplicateRoutes,\n type RouteOwner,\n type RouteTruth,\n} from \"./lint/site-model.js\";\nimport {\n filterIndexableCollections,\n parseCollectionBases,\n parseContentCollections,\n} from \"./_internal/parse-content-collections.js\";\nimport { defaultCodeTransformers } from \"./_internal/code-transformers.js\";\nimport {\n formatFailures,\n validateMdxContent,\n} from \"./_internal/validate-mdx-content.js\";\nimport { validateNimbusConfig } from \"./_internal/validate.js\";\nimport { virtualConfigPlugin } from \"./_internal/virtual-config.js\";\nimport { scanVersionFrontmatter } from \"./_internal/scan-version-frontmatter.js\";\nimport {\n buildVersionAlternates,\n computeMissingPageRedirects,\n type VersionAlternatesTable,\n} from \"./_internal/version-alternates.js\";\nimport type { NimbusConfig } from \"./types.js\";\n\nexport interface NimbusIntegrationOptions {\n /** MDX options forwarded to `@astrojs/mdx`. */\n mdx?: Parameters<typeof mdx>[0];\n /** Skip sitemap integration. Default: enabled when `site.url` is set. */\n sitemap?: boolean;\n /**\n * Build-time MDX PascalCase tag validation.\n *\n * - `true` (default): parse `src/components.ts` for the globals\n * registry and fail the build on unknown PascalCase tags found\n * in `src/content/**\\/*.mdx`.\n * - `false`: skip validation entirely.\n * - `{ componentsPath }`: override the registry file location.\n * Relative paths resolve to the project root.\n * - `{ contentDirs }`: override the scanned directories. Relative\n * paths resolve to the project root. Default: `[\"src/content\"]`.\n * - `{ skip }`: filter out files (e.g. vendored or generated MDX).\n *\n * Runs as a pre-build content pass rather than as a remark plugin so\n * it works regardless of which markdown processor is wired into\n * `markdown.processor`. Sätteri (the default) replaces unified's\n * pipeline, which silently disables remark plugins attached via\n * `mdx({ remarkPlugins })`.\n */\n validateMdx?:\n | boolean\n | {\n componentsPath?: string;\n contentDirs?: string[];\n skip?: (filePath: string) => boolean;\n };\n /**\n * Authoring-lint severity overrides for `nimbus-docs lint`. Maps a rule\n * code to `\"error\" | \"warn\" | \"off\"` or a `[severity, options]` tuple.\n * Build validators are rejected here — they have no severity knob.\n * Omitted = every authoring rule on at `error`.\n *\n * These are materialized to `.nimbus/lint.json` at config setup so the\n * standalone `nimbus-docs lint` CLI can read them. The build itself is\n * never gated on authoring rules.\n */\n rules?: RulesConfig;\n /**\n * Per-collection overrides. Each entry's `rules` block shallow-merges\n * over the top-level `rules` for files in that collection — same shape,\n * same validation, same build-validator carve-out (build validators\n * stay global, they can't be configured per-collection).\n *\n * @example\n * collections: {\n * partials: { rules: { \"nimbus/single-h1\": \"off\", \"nimbus/heading-hierarchy\": \"off\" } },\n * }\n */\n collections?: CollectionsConfig;\n}\n\nexport function nimbus(\n rawConfig: NimbusConfig,\n options: NimbusIntegrationOptions = {},\n): AstroIntegration {\n const config = validateNimbusConfig(rawConfig);\n // Validate the lint half of the options up front (build validators can't\n // take a severity; `collections` is reserved). Throws on misconfig.\n const lintOptions = validateLintOptions(\n { rules: options.rules, collections: options.collections },\n IMPLEMENTED_CODES,\n );\n\n // Threaded from `astro:config:setup` to `astro:build:done` so the post-\n // build materialization knows where to write `.nimbus/routes.json` and\n // what `base` Astro is using.\n let projectRootForBuild = \"\";\n let astroBaseForBuild = \"\";\n\n return {\n name: \"nimbus-docs\",\n hooks: {\n \"astro:config:setup\": async (params) => {\n const { updateConfig, config: astroConfig, logger } = params;\n\n const integrationsToAdd: AstroIntegration[] = [];\n\n // Materialize the resolved lint config so the standalone\n // `nimbus-docs lint` CLI can read severities authored here. Guarded\n // — a write failure must never break the build.\n materializeLintConfig(\n fileURLToPath(astroConfig.root),\n lintOptions.rules,\n lintOptions.collections,\n config.site,\n );\n\n // Pre-build MDX validation. Runs as a content pass against\n // `src/content/**/*.mdx` rather than as a remark plugin —\n // Sätteri replaces unified's pipeline and silently disables\n // any remark plugins, so the per-file-during-compile path is\n // not reliable here.\n if (options.validateMdx !== false) {\n const validateOpts =\n typeof options.validateMdx === \"object\" ? options.validateMdx : {};\n const projectRoot = fileURLToPath(astroConfig.root);\n const componentsPath = path.isAbsolute(validateOpts.componentsPath ?? \"\")\n ? (validateOpts.componentsPath as string)\n : path.join(\n projectRoot,\n validateOpts.componentsPath ?? \"src/components.ts\",\n );\n\n const globals = await parseComponentsRegistry(componentsPath);\n if (globals === null) {\n logger.warn(\n `MDX validation disabled: \\`${path.relative(projectRoot, componentsPath)}\\` is missing or does not export a parseable \\`components\\` object. ` +\n `Create the file with \\`export const components = { /* ... */ };\\` or set \\`validateMdx: false\\` to silence this warning.`,\n );\n } else {\n const contentDirs = (validateOpts.contentDirs ?? [\"src/content\"]).map(\n (d) => (path.isAbsolute(d) ? d : path.join(projectRoot, d)),\n );\n const failures = await validateMdxContent({\n globals,\n contentDirs,\n skip: validateOpts.skip,\n projectRoot,\n });\n if (failures.length > 0) {\n throw new Error(formatFailures(failures, globals.length));\n }\n logger.info(\n `MDX validation passed — ${globals.length} global component${globals.length === 1 ? \"\" : \"s\"} registered, ${contentDirs.length} content dir${contentDirs.length === 1 ? \"\" : \"s\"} scanned.`,\n );\n }\n }\n\n // Parse user's content.config.ts to enumerate registered\n // collections. Powers `getIndexedEntries()` and the agent-facing\n // routes (llms.txt, per-page .md alternates) so they don't have\n // to hardcode `\"docs\"`. Adding a `blog` collection to\n // content.config.ts lights up every indexing surface\n // automatically — no second file to edit.\n const projectRoot = fileURLToPath(astroConfig.root);\n\n // Stash for the `astro:build:done` hook, which uses Astro's actual\n // emitted `pages` array as the route truth (single source of truth\n // — Astro itself tells us which URLs the site serves).\n projectRootForBuild = projectRoot;\n astroBaseForBuild = astroConfig.base ?? \"\";\n\n // Parse `content.config.ts` up front: we need\n // - the registered collection set (for `virtual:nimbus/config`'s\n // indexable list);\n // - the (key → base) map (for the duplicate-slug walk, so a\n // `docsCollection({ base: \"documentation\" })` collection gets\n // scanned at the right on-disk location rather than being\n // silently skipped).\n const contentConfigPath = path.join(projectRoot, \"src/content.config.ts\");\n const rawCollections = await parseContentCollections(contentConfigPath);\n const collectionBases = await parseCollectionBases(contentConfigPath);\n const indexedCollections =\n rawCollections === null\n ? [\"docs\"] // Fallback: brand-new project hasn't written content.config yet.\n : filterIndexableCollections(rawCollections);\n\n if (rawCollections === null) {\n logger.warn(\n `nimbus-docs: \\`src/content.config.ts\\` is missing or doesn't expose a parseable \\`export const collections = { ... }\\`. ` +\n `Falling back to indexing the \\`docs\\` collection only.`,\n );\n }\n\n // Build validator `nimbus/duplicate-slug`: two sources that resolve\n // to the same URL silently shadow each other during `astro build`.\n // Runs pre-build because Astro dedupes colliding routes before the\n // integration sees them — by the time `astro:build:done` fires,\n // one source has already won.\n //\n // Two URL sources feed the check:\n //\n // 1. Content entries from indexable collections, grouped by\n // *mounted URL* (collection prefix + canonical slug). Catches\n // cross-collection collisions (`docs/blog/post.mdx` vs\n // `blog/post.mdx`), version collisions (`docs/v1/x.mdx` vs\n // `docs-v1/x.mdx`), case-only, and folder-index-vs-leaf.\n // Non-routed collections like `partials` are excluded\n // (per `filterIndexableCollections`) since they aren't pages.\n //\n // 2. Static `src/pages/**` files (no dynamic segments). Catches\n // the page-vs-content collision — e.g. `pages/search.astro`\n // shadowing `content/docs/search.mdx` at `/search`. Dynamic\n // page routes are skipped: their emitted URLs come from\n // `getStaticPaths` at build time, so we can't know them\n // pre-build without invoking the same machinery Astro\n // silently dedupes through anyway.\n const indexedSet = new Set(indexedCollections);\n const versionInfo = config.versions\n ? { others: config.versions.others ?? [] }\n : null;\n\n // Restrict the walk to *indexable* collections, and use the parsed\n // `(key → base)` map so a custom `base: \"documentation\"` collection\n // is scanned at `src/content/documentation/` and tagged with key\n // `docs`. Falls back to `(key → key)` when content.config.ts wasn't\n // parseable — the brand-new-project case where we already warned.\n const indexedBases = new Map<string, string>();\n if (collectionBases !== null) {\n for (const [key, base] of collectionBases) {\n if (indexedSet.has(key)) indexedBases.set(key, base);\n }\n } else {\n for (const key of indexedCollections) indexedBases.set(key, key);\n }\n\n const contentOwners: RouteOwner[] = enumerateEntriesByBase(\n path.join(projectRoot, \"src/content\"),\n indexedBases,\n ).map((entry) => ({\n url: contentEntryUrl(entry, versionInfo),\n source: `src/content/${entry.relPath}`,\n }));\n\n const pageOwners: RouteOwner[] = enumerateStaticPageRoutes(\n path.join(projectRoot, \"src/pages\"),\n projectRoot,\n );\n\n const duplicateRoutes = findDuplicateRoutes([\n ...contentOwners,\n ...pageOwners,\n ]);\n if (duplicateRoutes.length > 0) {\n throw new Error(formatDuplicateRoutes(duplicateRoutes));\n }\n\n // Cross-check `versions.others` against registered collections.\n // Zod validated the shape; this pass enforces the invariant that\n // every non-current version slug `<v>` corresponds to a registered\n // collection named `docs-<v>`. We can only check this when we\n // actually parsed content.config.ts — if `rawCollections` is null\n // the user is on a brand-new project and we already warned.\n if (config.versions && rawCollections !== null) {\n const registered = new Set(rawCollections);\n const missing = config.versions.others.filter(\n (slug) => !registered.has(`docs-${slug}`),\n );\n if (missing.length > 0) {\n const lines = missing.map((slug) => {\n return (\n ` - \"${slug}\" → expected a collection named \"docs-${slug}\" ` +\n `in src/content.config.ts (e.g. \\`\"docs-${slug}\": docsCollection({ base: \"docs-${slug}\" })\\`)`\n );\n });\n throw new Error(\n `nimbus-docs: \\`versions.others\\` references slugs without matching collections:\\n${lines.join(\"\\n\")}\\n\\n` +\n `Every entry in \\`versions.others\\` must correspond to a registered Astro content ` +\n `collection. Register the collection(s) above in src/content.config.ts and try again.`,\n );\n }\n }\n\n // ----- Versioning P1: build the cross-version alternates table.\n //\n // Walks every version collection's content directory, extracts\n // `previousSlug` + `draft` from frontmatter, and builds the\n // alternates graph (slug-equality + previousSlug edges, union-find\n // for chains). The resolved table is JSON-serialised into\n // `virtual:nimbus/config` so route helpers can read it without\n // re-walking the filesystem. Also computes the redirect pairs\n // (old-version URLs whose slug no longer exists in that version)\n // and merges them into Astro's `redirects` config.\n let versionAlternates: VersionAlternatesTable = {};\n let versionRedirects: { from: string; to: string }[] = [];\n if (config.versions) {\n const resolved = {\n current: config.versions.current,\n others: config.versions.others ?? [],\n deprecated: config.versions.deprecated ?? [],\n hidden: config.versions.hidden ?? [],\n all: [config.versions.current, ...(config.versions.others ?? [])],\n };\n const versionEntries = await scanVersionFrontmatter({\n projectRoot,\n versions: resolved,\n });\n versionAlternates = buildVersionAlternates(resolved, versionEntries);\n versionRedirects = computeMissingPageRedirects(\n resolved,\n versionAlternates,\n versionEntries,\n );\n }\n\n // MDX is always added; sitemap only when `site` is configured.\n integrationsToAdd.push(mdx(options.mdx ?? {}));\n if (options.sitemap !== false && Boolean(config.site)) {\n integrationsToAdd.push(sitemap());\n }\n\n updateConfig({\n // Bridge `nimbusConfig.site` → Astro's top-level `site`. The\n // sitemap integration and `Astro.site` both read this; without\n // it, sitemap warns \"missing `site` astro.config option\" at\n // build time even though nimbus has a site URL right there.\n // Only set when configured (validate.ts already enforces it,\n // but stay defensive for future optionality).\n ...(config.site ? { site: config.site } : {}),\n // Astro deep-merges arrays in updateConfig, so user-declared\n // integrations are preserved.\n integrations: integrationsToAdd,\n // Sätteri markdown processor. Heading IDs, image collection,\n // and Shiki highlighting are all wired internally by Sätteri's\n // default plugin set — no manual registration needed. MDX\n // inherits via @astrojs/mdx's `extendMarkdownConfig: true`.\n markdown: {\n processor: satteri(),\n // Dual-theme Shiki output. `defaultColor: false` makes Shiki\n // emit BOTH themes as inline CSS variables (`--shiki-light`,\n // `--shiki-dark`, `--shiki-light-bg`, `--shiki-dark-bg`)\n // rather than baking one theme into the HTML. The starter's\n // globals.css then switches between them based on the\n // `<html data-mode=\"dark\">` attribute the theme toggle flips.\n //\n // `defaultCodeTransformers()` is the single source of truth\n // for the premium code-block features — diff/highlight/focus/\n // error/word notations, meta highlight, and the title-frame +\n // lang badge transformer. The same factory is exported as a\n // named entry from `nimbus-docs` so the starter's `Code.astro`\n // can wire them into Astro's built-in `<Code>` component\n // (Astro's `<Code>` doesn't auto-read `shikiConfig`).\n //\n // Users can override these defaults by passing their own\n // shikiConfig at the user-config level (Astro merges shallowly).\n shikiConfig: {\n themes: {\n light: \"github-light\",\n dark: \"github-dark\",\n },\n defaultColor: false,\n transformers: defaultCodeTransformers(),\n },\n },\n // Versioning P1: auto-redirects from old-version URLs whose\n // slug no longer exists in that version to the current-version\n // sibling. Astro merges `redirects` shallowly across calls; the\n // user's hand-written redirects (if any) win on conflict because\n // their config runs after this hook.\n ...(versionRedirects.length > 0\n ? {\n redirects: Object.fromEntries(\n versionRedirects.map(({ from, to }) => [from, to]),\n ),\n }\n : {}),\n // Vite plugin exposing the validated config to user-land via\n // the `virtual:nimbus/config` import. Also emits the\n // build-time-resolved `indexedCollections` list — see\n // `getIndexedEntries()` and the llms.txt routes — and the\n // versioning alternates table (P1).\n vite: {\n plugins: [\n virtualConfigPlugin(config, {\n indexedCollections,\n versionAlternates,\n }),\n ],\n },\n });\n },\n \"astro:config:done\": ({ injectTypes }) => {\n // TypeScript declaration for the virtual module. Written to\n // `.astro/integrations/nimbus-docs/virtual-config.d.ts` and\n // auto-referenced by the project tsconfig via Astro's generated\n // types.\n injectTypes({\n filename: \"virtual-config.d.ts\",\n content: [\n 'declare module \"virtual:nimbus/config\" {',\n ' import type { NimbusConfig, VersionAlternatesTable } from \"nimbus-docs/types\";',\n \" export const config: NimbusConfig;\",\n \" /** Build-time list of indexable collection names. See `getIndexedEntries()`. */\",\n \" export const indexedCollections: readonly string[];\",\n \" /** Build-time cross-version alternates table. See `getVersionAlternates()`. */\",\n \" export const versionAlternates: VersionAlternatesTable;\",\n \"}\",\n \"\",\n ].join(\"\\n\"),\n });\n },\n \"astro:build:done\": async ({ dir, pages, logger }) => {\n // Materialize the site's route truth from Astro's emitted `pages`\n // array — the single source of truth: every URL on this list is a\n // page Astro just wrote to disk. No reconstruction, no slug\n // mirroring, no Astro-internals coupling. The build/lint\n // contract is \"after `astro build`, `.nimbus/routes.json` reflects\n // exactly what the site serves.\" Lint that runs without a prior\n // build silently skips `internal-link`.\n //\n // Duplicate-slug detection happens in `astro:config:setup`, not\n // here: Astro silently dedupes colliding routes before this hook\n // fires, so the collisions are invisible post-build.\n materializeRouteTruthFromPages(\n projectRootForBuild,\n astroBaseForBuild,\n pages,\n logger,\n );\n\n if (config.search === false || config.search?.provider === \"custom\") {\n return;\n }\n\n await runPagefind(fileURLToPath(dir));\n },\n },\n };\n}\n\n/**\n * Write the resolved authoring-lint config to `<root>/.nimbus/lint.json`\n * for the standalone CLI. Best-effort: any filesystem error is swallowed\n * so it can't fail an `astro build`. `.nimbus/` is a gitignored scratch\n * dir (same home the Vale recipe uses).\n *\n * `site` is materialized alongside the rules so site-aware rules\n * (`no-self-host-url`) get the project's deploy host without making the\n * user duplicate it in their lint config.\n */\nfunction materializeLintConfig(\n projectRoot: string,\n rules: RulesConfig,\n collections: CollectionsConfig,\n site: string,\n): void {\n try {\n const dir = path.join(projectRoot, \".nimbus\");\n fs.mkdirSync(dir, { recursive: true });\n fs.writeFileSync(\n path.join(dir, \"lint.json\"),\n JSON.stringify({ version: 1, rules, collections, site }, null, 2) + \"\\n\",\n \"utf8\",\n );\n } catch {\n // Non-fatal — `nimbus-docs lint` falls back to all-rules-on defaults.\n }\n}\n\n/**\n * Write the site's route truth to `<root>/.nimbus/routes.json` from the\n * `pages` array Astro hands us at `astro:build:done`. Each entry in `pages`\n * is a real emitted URL — no reconstruction, no slug mirroring.\n *\n * Best-effort write, same as `materializeLintConfig`. When the file is\n * missing (e.g. lint ran before any `astro build`), `internal-link` skips\n * silently rather than false-positive.\n *\n * Duplicate-slug detection lives in `astro:config:setup` (above), not\n * here. Astro silently dedupes colliding routes before this hook fires,\n * so a post-build collision check on `pages` would never see the\n * collisions it claims to catch.\n */\nfunction materializeRouteTruthFromPages(\n projectRoot: string,\n base: string,\n pages: readonly { pathname: string }[],\n logger: { warn: (msg: string) => void; debug?: (msg: string) => void },\n): void {\n // Normalize and dedupe pathnames into the canonical `/foo` form used by\n // the lookup logic in `internal-link.ts`. The dedupe is defensive —\n // Astro already deduped before this hook, so `pages` shouldn't contain\n // collisions; we still tolerate it in case a route re-emits across\n // formats (e.g. `.html` + `.md` siblings).\n const canonical = new Set<string>();\n for (const { pathname } of pages) {\n canonical.add(canonicalizePathname(pathname));\n }\n\n const truth: RouteTruth = {\n version: 1,\n base,\n knownRoutes: [...canonical].sort(),\n // With `pages` as the truth, every emitted URL is in `knownRoutes` —\n // there are no opaque namespaces. The field stays in the schema for\n // forward-compat with future SSR-route handling.\n opaqueNamespaces: [],\n };\n\n try {\n const dir = path.join(projectRoot, \".nimbus\");\n fs.mkdirSync(dir, { recursive: true });\n fs.writeFileSync(\n path.join(dir, \"routes.json\"),\n JSON.stringify(truth, null, 2) + \"\\n\",\n \"utf8\",\n );\n } catch (err) {\n logger.debug?.(\n `failed to write .nimbus/routes.json — internal-link will skip: ${(err as Error).message}`,\n );\n }\n}\n\nfunction canonicalizePathname(pathname: string): string {\n // Astro's `pages.pathname` comes in two flavors:\n // - Root: literal `/`.\n // - Non-root: leading slash absent in some emissions (\"cli\"), present in\n // others (\"/cli\"). Trailing slash also varies by `trailingSlash` config.\n // Canonical form: leading `/`, no trailing `/` (except for root itself).\n let s = pathname;\n if (s === \"\") return \"/\";\n if (!s.startsWith(\"/\")) s = `/${s}`;\n if (s.length > 1 && s.endsWith(\"/\")) s = s.slice(0, -1);\n return s;\n}\n\nfunction runPagefind(siteDir: string): Promise<void> {\n const bin = process.platform === \"win32\" ? \"pagefind.cmd\" : \"pagefind\";\n return new Promise((resolve) => {\n execFile(bin, [\"--site\", siteDir], (error, stdout, stderr) => {\n if (stdout) process.stdout.write(stdout);\n if (stderr) process.stderr.write(stderr);\n if (error) {\n console.warn(\n `[nimbus-docs] Pagefind did not run. Install pagefind as a devDependency or set search: false in your Nimbus config.\\n${error.message}`,\n );\n }\n resolve();\n });\n });\n}\n","/**\n * Main entry for `nimbus-docs`.\n *\n * Exports the Astro integration (default), config helper, and the four\n * data helpers (sidebar, prev/next, breadcrumbs, TOC). Phase 6 will\n * add page composition helpers (`getDocsStaticPaths`, `getDocsPageProps`).\n *\n * Helpers read the user's config from `virtual:nimbus/config` (provided\n * by our Vite plugin) and content entries from `astro:content`. Both\n * are external in tsdown and resolved at the consumer's build time.\n */\n\nimport {\n loadIndexedCollections,\n loadNimbusConfig,\n loadVersionAlternates,\n} from \"./_internal/runtime-config.js\";\nimport {\n getVisibleEntries,\n getVisibleEntriesByCollection,\n} from \"./_internal/content.js\";\nimport {\n buildSidebarTree,\n collectSidebarCollectionRefs,\n deriveSidebarSections,\n scopeToCurrentSection,\n sidebarHash,\n} from \"./_internal/sidebar.js\";\nimport { canonicalEntryUrl } from \"./_internal/astro-slug.js\";\nimport { toBrowserHref, toRouteKey } from \"./_internal/url.js\";\nimport {\n PRIMARY_COLLECTION,\n collectionLabel as resolveCollectionSlug,\n collectionMountPrefix as resolveCollectionPrefix,\n} from \"./_internal/collection-mount.js\";\nimport { renderEntryAsMarkdown } from \"./_internal/transform.js\";\nimport {\n getBreadcrumbs as buildBreadcrumbs,\n getPrevNext as buildPrevNext,\n} from \"./_internal/navigation.js\";\nimport { getHeadings } from \"./_internal/toc.js\";\nimport { getLastUpdatedFromGit } from \"./_internal/git-last-updated.js\";\n\nimport type {\n Breadcrumb,\n NimbusConfig,\n PrevNext,\n PrevNextOverrides,\n ResolvedVersions,\n SidebarItem,\n SidebarSection,\n TOCItem,\n VersionAlternateRecord,\n VersionPageRef,\n VersionStatus,\n} from \"./types.js\";\n\nexport { nimbus as default } from \"./integration.js\";\nexport type { NimbusIntegrationOptions } from \"./integration.js\";\n\nexport type {\n BadgeVariant,\n Breadcrumb,\n NimbusConfig,\n PrevNext,\n PrevNextLink,\n PrevNextOverrides,\n ResolvedVersions,\n SearchProvider,\n SearchResult,\n SidebarBadge,\n SidebarConfig,\n SidebarConfigItem,\n SidebarExternalLinkItem,\n SidebarGroupItem,\n SidebarItem,\n SidebarLinkItem,\n SidebarSection,\n TOCItem,\n VersionAlternateRecord,\n VersionAlternatesTable,\n VersionPageRef,\n VersionStatus,\n VersionsConfig,\n} from \"./types.js\";\n\n/**\n * Define a typed Nimbus config. Returns the config unchanged but inferred.\n */\nexport function defineConfig<T extends NimbusConfig>(config: T): T {\n return config;\n}\n\n/** Deterministic short hash of the sidebar structure (for sessionStorage invalidation). */\nexport { sidebarHash };\n\n/** Render an Astro content entry's raw MDX body as clean markdown. */\nexport { renderEntryAsMarkdown };\n\n/**\n * The canonical Shiki transformer chain — diff / highlight / focus /\n * error-level / word notations, meta highlight, plus the title-frame +\n * language-badge transformer. Pre-wired into the markdown pipeline for\n * fenced MDX blocks; re-export it so `Code.astro` can pass the same\n * list to Astro's built-in `<Code>` component (which accepts\n * `transformers` as a prop but doesn't auto-read `shikiConfig`).\n */\nexport { defaultCodeTransformers } from \"./_internal/code-transformers.js\";\n\n/**\n * Return visible entries across the user's configured `collections`.\n * Drafts are filtered in production builds. Pass an explicit\n * `collections` argument to scope the query to a subset.\n *\n * Replaces the old `getVisibleDocs()` — same draft-filtering semantics,\n * but now collection-aware. Returns `CollectionEntry<string>[]` so\n * cross-collection traversal doesn't need per-name type narrowing.\n */\nexport { getVisibleEntries };\n\n// ---------------------------------------------------------------------------\n// Agent-facing indexing\n// ---------------------------------------------------------------------------\n\nexport interface IndexedEntry {\n /** The Astro CollectionEntry, untyped at the union level. */\n entry: import(\"astro:content\").CollectionEntry<string>;\n /** Collection this entry belongs to (e.g. `\"docs\"`, `\"blog\"`). */\n collection: string;\n /** Display title — schema field if present, otherwise the entry id. */\n title: string;\n /** Description — undefined when the schema doesn't expose one or it's empty. */\n description: string | undefined;\n /**\n * Page URL path under the site (no origin). Browser-facing form:\n * trailing slash on HTML document routes (`/getting-started/`) so\n * `<a href>` consumers don't trigger a redirect on static hosts that\n * canonicalize directory-index pages. The primary docs collection\n * mounts at the site root, every other collection mounts under its\n * name (`/api/payments/create/`, `/blog/my-first-post/`). Routes\n * building `.md` alternates should consume `markdownUrl` rather than\n * deriving from this field — the root-index case (`/`) needs a\n * different shape than the trailing-slash-strip recipe produces.\n */\n url: string;\n /**\n * Site-relative URL of the page's clean-markdown alternate. Mirrors\n * the path the per-page `.md` route emits: `/getting-started/index.md`\n * for an entry at `/getting-started/`, `/index.md` for the root-index\n * entry. Consumers (`llms.txt`, the `.md` route's `Source:` line)\n * should use this directly instead of synthesizing from `url`.\n */\n markdownUrl: string;\n}\n\nexport interface IndexedTopLevelGroup {\n /** Top-level slug — first URL segment under root. */\n slug: string;\n /** Display label (today: identical to `slug`; reserved for future sidebar-label integration). */\n label: string;\n /** Entries inside this group, sorted alphabetically by url. */\n members: IndexedEntry[];\n /**\n * What kind of group this is:\n * - `\"primary\"` — a folder inside the primary `docs` collection.\n * - `\"secondary\"` — a separate non-version, non-primary collection\n * (`blog`, `api`, `changelog`, …).\n * - `\"version\"` — an older docs version (`docs-v1`, `docs-v2`, …)\n * when versioning is configured. Routes that build the **root**\n * `/llms.txt` should typically filter these out (old versions\n * pollute the entry point); routes that emit per-section files\n * should include them so `/v1/llms.txt` still ships.\n */\n kind: \"primary\" | \"secondary\" | \"version\";\n /**\n * True when this group is a version collection listed in\n * `versions.hidden`. Hidden versions are URL-reachable but should\n * not surface on any indexing or discovery surface — neither root\n * `/llms.txt` nor a per-section `/<slug>/llms.txt`. Pagefind\n * exclusion happens per-page via `data-pagefind-ignore`. Routes that\n * emit per-section files should skip groups where `hidden === true`.\n */\n hidden: boolean;\n}\n\nexport interface IndexedTopLevel {\n /**\n * Single-entry top-level items — the root `/llms.txt` links directly\n * to each leaf's `.md` alternate. Sorted alphabetically by `url`.\n */\n leaves: IndexedEntry[];\n /**\n * Multi-entry top-level items — each becomes a section file at\n * `/<slug>/llms.txt`. Sorted alphabetically by `slug`.\n */\n groups: IndexedTopLevelGroup[];\n}\n\n/**\n * Cross-collection entry list for the agent-facing routes\n * (`llms.txt`, per-page `.md` alternates, future `llms-full.txt` and\n * `rag.jsonl`). Implements the indexing baseline of the two-layer\n * architecture documented at `/features/llms-txt`:\n *\n * - **Multi-collection by default, zero opt-in.** Iterates every\n * collection registered in `src/content.config.ts` except names\n * matching `partials` or starting with `_` (reserved).\n * - **Schema-tolerant.** Reads `title` and `description` if present;\n * falls back to the entry id for the title and omits the\n * description otherwise.\n * - **Per-page filters baked in.** Drops entries with `draft: true`;\n * absent fields read as the docs-schema default (`draft: false`).\n * All published pages are indexed — there is no per-page opt-out.\n * A page that genuinely shouldn't be agent-readable should be kept\n * out of the content collection entirely.\n *\n * The returned shape is identical regardless of which factory created\n * the collection: hand-rolled `defineCollection({ loader, schema })`\n * collections work without modification.\n */\nexport async function getIndexedEntries(): Promise<IndexedEntry[]> {\n const { getCollection } = await import(\"astro:content\");\n const collectionNames = await loadIndexedCollections();\n // Fall back to the primary collection name if the build-time parse\n // came up empty. Belt-and-braces: the integration also defaults to\n // [\"docs\"] when content.config.ts is missing.\n const names = collectionNames.length > 0 ? collectionNames : [PRIMARY_COLLECTION];\n const versions = await getVersions();\n\n const indexed: IndexedEntry[] = [];\n for (const name of names) {\n let entries: import(\"astro:content\").CollectionEntry<string>[];\n try {\n entries = await getCollection(name as any);\n } catch {\n // Astro throws when the collection name isn't actually registered.\n // Swallow and move on — the parser may have picked up a name that\n // was added to content.config.ts but isn't yet wired through\n // Astro's content layer.\n continue;\n }\n const prefix = resolveCollectionPrefix(name, versions);\n for (const entry of entries) {\n const data = (entry.data ?? {}) as Record<string, unknown>;\n if (data.draft === true) continue;\n\n const title =\n typeof data.title === \"string\" && data.title.length > 0\n ? data.title\n : entry.id;\n const rawDescription = data.description;\n const description =\n typeof rawDescription === \"string\" && rawDescription.length > 0\n ? rawDescription\n : undefined;\n\n // `canonicalEntryUrl` applies the same slug normalization Astro's\n // content layer does — lowercase + folder-index strip — so the URL\n // matches what the site actually serves. See `_internal/astro-slug.ts`\n // for the why and the known caveats. `toBrowserHref` adds the trailing\n // slash that the directory-index page is served at, so `url` consumers\n // can emit the value straight into `<a href>` without a redirect.\n const canonicalUrl = canonicalEntryUrl(prefix, entry.id);\n // The `.md` alternate lives at `<page>/index.md`. For the root index\n // of a collection (canonical URL is the bare prefix or `/`), append\n // directly rather than re-derive from the trailing-slash form — the\n // strip-trailing-slash recipe collapses `/` to `\"\"` and produces the\n // wrong path.\n const markdownUrl =\n canonicalUrl === \"/\" ? \"/index.md\" : `${canonicalUrl}/index.md`;\n indexed.push({\n entry,\n collection: name,\n title,\n description,\n url: toBrowserHref(canonicalUrl),\n markdownUrl,\n });\n }\n }\n return indexed;\n}\n\n/**\n * Partition the indexed entries into the shape the root `/llms.txt`\n * and `/[section]/llms.txt` routes need.\n *\n * Convention:\n * - Primary `\"docs\"` entries follow the leaf/group rule based on\n * their `entry.id` top segment (matches single-collection behavior).\n * - Every other collection becomes a single top-level group named\n * after the collection, regardless of how many entries it has.\n * This matches the URL convention (`/api/...`, `/blog/...`).\n */\nexport async function getIndexedTopLevel(): Promise<IndexedTopLevel> {\n const items = await getIndexedEntries();\n const versions = await getVersions();\n\n // Build two buckets keyed by their URL-facing slug:\n // - primary: top-level slug under the `docs` collection\n // - secondary: every other collection (versions tagged separately\n // below so consumers can filter the root listing)\n const primaryBuckets = new Map<string, IndexedEntry[]>();\n const secondaryBuckets = new Map<string, IndexedEntry[]>();\n const versionSlugs = new Set<string>(versions?.others ?? []);\n const hiddenSlugs = new Set<string>(versions?.hidden ?? []);\n\n for (const item of items) {\n if (item.collection === PRIMARY_COLLECTION) {\n const top = item.entry.id.split(\"/\")[0]!;\n const bucket = primaryBuckets.get(top);\n if (bucket) bucket.push(item);\n else primaryBuckets.set(top, [item]);\n } else {\n // Bucket secondary collections by their URL-facing slug (version\n // slug for `docs-<v>` collections, raw collection ID otherwise) so\n // the emitted group label and URL prefix match the route shape.\n const slug = resolveCollectionSlug(item.collection, versions);\n const bucket = secondaryBuckets.get(slug);\n if (bucket) bucket.push(item);\n else secondaryBuckets.set(slug, [item]);\n }\n }\n\n const leaves: IndexedEntry[] = [];\n const groups: IndexedTopLevelGroup[] = [];\n\n for (const [slug, members] of primaryBuckets) {\n const isLeaf = members.length === 1 && members[0]!.entry.id === slug;\n if (isLeaf) {\n leaves.push(members[0]!);\n } else {\n groups.push({ slug, label: slug, members, kind: \"primary\", hidden: false });\n }\n }\n for (const [slug, members] of secondaryBuckets) {\n const kind: \"version\" | \"secondary\" = versionSlugs.has(slug)\n ? \"version\"\n : \"secondary\";\n groups.push({ slug, label: slug, members, kind, hidden: hiddenSlugs.has(slug) });\n }\n\n leaves.sort((a, b) => a.url.localeCompare(b.url));\n groups.sort((a, b) => a.slug.localeCompare(b.slug));\n for (const g of groups) {\n g.members.sort((a, b) => a.url.localeCompare(b.url));\n }\n\n return { leaves, groups };\n}\n\n// ---------------------------------------------------------------------------\n// Data helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Build the sidebar tree for the given current path, scoped to the\n * top-level section containing that page.\n *\n * Reads `sidebar` from the user's nimbus.config. If `sidebar.items` is set,\n * resolves config-driven sidebar. Otherwise auto-generates from filesystem\n * (i.e. the `docs` collection's entry IDs).\n *\n * Returned shape depends on `sidebar.scope` in `nimbus.config.ts`:\n * - `\"full\"` (default) — every top-level item on every page.\n * - `\"section\"` — only the current top-level section's children. Use\n * the header section-tab strip (via `getSidebarSections`) for\n * cross-section nav when this mode is on.\n *\n * **Versioning awareness.** When the page is in a version collection\n * (`docs-<v>` where `<v>` is in `versions.others`), pass `collection` as\n * the second argument. The sidebar build will swap any\n * `{ autogenerate: { collection: \"docs\" } }` items to autogenerate from\n * that version's collection instead, and treat it as the primary for\n * the build. Without this, version pages render the current-version\n * sidebar and prev/next derives from the wrong tree.\n *\n * @param currentSlug - The current page's URL path (e.g. \"/getting-started\").\n * Used to set `isCurrent` on matching links and to pick\n * which top-level section to surface when scoping.\n * @param options.collection - The current page's Astro collection ID.\n * Pass `entry.collection` from your route.\n */\nexport async function getSidebar(\n currentSlug: string,\n options?: { collection?: string },\n): Promise<SidebarItem[]> {\n const config = await loadNimbusConfig();\n const tree = await buildFullSidebarTree(currentSlug, options?.collection);\n return config.sidebar?.scope === \"section\"\n ? scopeToCurrentSection(tree, currentSlug)\n : tree;\n}\n\n/**\n * Derive one section per top-level group in the sidebar — used by\n * `Header.astro` to render the section tab strip (and by any other\n * cross-section navigation).\n *\n * Reads the un-scoped tree so every section is visible, then collapses\n * each top-level group to `{ label, href, isActive }`.\n *\n * Accepts the same `collection` option as `getSidebar` so version pages\n * see version-scoped section tabs.\n */\nexport async function getSidebarSections(\n currentSlug: string,\n options?: { collection?: string },\n): Promise<SidebarSection[]> {\n const tree = await buildFullSidebarTree(currentSlug, options?.collection);\n return deriveSidebarSections(tree);\n}\n\n/**\n * Internal: build the un-scoped sidebar tree. Shared by `getSidebar` and\n * `getSidebarSections`.\n *\n * When `pageCollection` names a registered version collection\n * (`docs-<v>` with `<v>` in `versions.others`), the sidebar treats that\n * collection as the primary for this build: autogen items referencing\n * `docs` are rewritten to reference the version collection, and the\n * `primary` argument to `buildSidebarTree` is set accordingly. The net\n * effect: version pages get the right sidebar tree and the right\n * prev/next ordering.\n */\nasync function buildFullSidebarTree(\n currentSlug: string,\n pageCollection?: string,\n): Promise<SidebarItem[]> {\n const runtimeConfig = await loadNimbusConfig();\n const versions = await getVersions();\n\n // Resolve the effective \"primary\" collection for THIS sidebar build.\n // For pages in a non-current version collection, the primary IS that\n // collection (the sidebar should walk docs-v0, not docs).\n let effectivePrimary = PRIMARY_COLLECTION;\n let primaryPrefix = \"\";\n if (\n versions &&\n pageCollection &&\n pageCollection.startsWith(\"docs-\") &&\n versions.others.includes(pageCollection.slice(\"docs-\".length))\n ) {\n effectivePrimary = pageCollection;\n primaryPrefix = resolveCollectionPrefix(pageCollection, versions);\n }\n\n // Rewrite sidebar items so `{ autogenerate: { collection: \"docs\" } }`\n // becomes `{ autogenerate: { collection: \"docs-v0\" } }` on v0 pages.\n // Items that name a different collection (api, blog) are untouched —\n // they keep their global scope.\n // Cast at the boundary: `runtimeConfig.sidebar?.items` is `unknown[] | undefined`\n // because runtimeConfig is loaded through a virtual module whose data is\n // already Zod-validated at integration setup (`validateNimbusConfig`).\n // The cast restores the shape downstream functions expect.\n const rewrittenItems = (\n effectivePrimary !== PRIMARY_COLLECTION\n ? rewriteSidebarItemsForVersion(\n runtimeConfig.sidebar?.items,\n effectivePrimary,\n )\n : runtimeConfig.sidebar?.items\n ) as Parameters<typeof collectSidebarCollectionRefs>[0];\n\n const referenced = collectSidebarCollectionRefs(rewrittenItems);\n const collections = [\n effectivePrimary,\n ...referenced.filter((c) => c !== effectivePrimary),\n ];\n const entriesByCollection = await getVisibleEntriesByCollection(collections);\n return buildSidebarTree(\n // Cast: `astro:content` `CollectionEntry<string>` has `data: Record<string, unknown>`\n // in our stub; sidebar.ts's local `CollectionEntry` shapes `data` with `title`\n // required. Runtime entries always carry `title` (schema-enforced); the cast\n // documents that guarantee. `unknown` bridge is required because the two\n // CollectionEntry shapes don't structurally overlap on the `data` field.\n entriesByCollection as unknown as Parameters<typeof buildSidebarTree>[0],\n effectivePrimary,\n currentSlug,\n runtimeConfig.sidebar\n ? { ...runtimeConfig.sidebar, items: rewrittenItems }\n : undefined,\n primaryPrefix,\n );\n}\n\n/**\n * Substitute the primary collection (`docs`) for `effectivePrimary`\n * inside any sidebar item that autogenerates from a named collection.\n * Used by `buildFullSidebarTree` to make version pages render their\n * own collection's sidebar instead of the current version's.\n */\nfunction rewriteSidebarItemsForVersion(\n items: unknown[] | undefined,\n effectivePrimary: string,\n): unknown[] | undefined {\n if (!items) return items;\n return items.map((item) => {\n if (!item || typeof item !== \"object\") return item;\n const o = item as Record<string, unknown>;\n const autogen = o.autogenerate as { collection?: string; directory?: string } | undefined;\n if (autogen && autogen.collection === PRIMARY_COLLECTION) {\n return { ...o, autogenerate: { ...autogen, collection: effectivePrimary } };\n }\n // Nested groups recurse so per-group autogen items rewrite too.\n if (Array.isArray(o.items)) {\n return { ...o, items: rewriteSidebarItemsForVersion(o.items, effectivePrimary) };\n }\n return item;\n });\n}\n\n/**\n * Resolve prev/next links for the current page.\n *\n * Walks the flattened sidebar; returns the surrounding entries. Honors\n * `prev`/`next` frontmatter overrides if provided.\n *\n * When an override uses the object form with an internal `link`\n * (e.g. `prev: { link: \"/getting-started\" }`), the link is validated\n * against every visible content entry's URL at build time. A pointer\n * to a missing page fails the build with a clear error — the same\n * staleness-detection mechanism used for `previousSlug` in versioning.\n * The string form (`prev: \"Custom label\"`) is a label-only override\n * and doesn't go through link validation.\n */\nexport async function getPrevNext(\n currentSlug: string,\n options?: {\n overrides?: PrevNextOverrides;\n sidebarTree?: SidebarItem[];\n },\n): Promise<PrevNext> {\n const tree = options?.sidebarTree ?? (await getSidebar(currentSlug));\n // Build the set of valid internal route keys (slashless) from indexed\n // entries so object-form `prev: { link: \"/x\" }` overrides fail loudly\n // when the target doesn't exist. The set holds route keys, not browser\n // hrefs, so a `/cli`, `/cli/`, or `/cli/?ref=x` override all resolve\n // to the same canonical entry. Cheap: indexed entries are cached per\n // build.\n const indexed = await getIndexedEntries();\n const validInternalLinks = new Set(indexed.map((e) => toRouteKey(e.url)));\n return buildPrevNext(currentSlug, tree, options?.overrides, validInternalLinks);\n}\n\n/**\n * Build breadcrumb trail from \"/\" to the current page.\n *\n * Phase 5: simple URL-segment derivation. Later phases may enrich with\n * sidebar-aware labels.\n */\nexport async function getBreadcrumbs(\n currentSlug: string,\n options?: { homeLabel?: string },\n): Promise<Breadcrumb[]> {\n return buildBreadcrumbs(currentSlug, options?.homeLabel ?? \"Home\");\n}\n\n/**\n * Build an edit URL for a content entry using `config.editPattern`.\n *\n * `{path}` is replaced with the entry's source path when Astro provides it,\n * falling back to the default docs collection path convention.\n */\nexport async function getEditUrl(entry: {\n id: string;\n filePath?: string;\n}): Promise<string | undefined> {\n const runtimeConfig = await loadNimbusConfig();\n if (!runtimeConfig.editPattern) return undefined;\n\n const path = entry.filePath ?? `src/content/docs/${entry.id}.mdx`;\n return runtimeConfig.editPattern.replace(\"{path}\", path);\n}\n\n/**\n * Resolve a content entry's `lastUpdated` date from `git log`.\n *\n * Reads the author date (`%aI`) of the most recent commit that touched\n * the entry's source file. Author date is stable across rebases — the\n * value reflects when the content was actually changed, not when the\n * commit happened to land in this branch.\n *\n * Returns `undefined` when git can't answer (no `.git`, shallow clone,\n * file untracked, command not on PATH, etc.) so the caller can chain a\n * fallback:\n *\n * const lastUpdated = entry.data.lastUpdated ?? await getLastUpdated(entry);\n *\n * Frontmatter always wins. Per-process cached so repeated calls for\n * the same entry don't re-spawn `git`.\n *\n * Production note: most CI/CD systems do shallow clones by default\n * (Vercel, Cloudflare Pages, GitHub Actions checkout@v4) — set\n * `fetch-depth: 0` to make full history available, otherwise git\n * returns nothing and the helper falls back to frontmatter or nothing.\n */\nexport async function getLastUpdated(entry: {\n id: string;\n filePath?: string;\n}): Promise<Date | undefined> {\n const path = entry.filePath ?? `src/content/docs/${entry.id}.mdx`;\n return getLastUpdatedFromGit(path);\n}\n\n/**\n * Filter heading list to the configured min/max heading levels.\n *\n * @param headings - Raw `headings` from Astro's `render(entry)` return value.\n * @param options - Override min/max heading levels. Defaults: min=2, max=3.\n */\nexport function getTOC(\n headings: { depth: number; text: string; slug: string }[],\n options?: { minHeadingLevel?: number; maxHeadingLevel?: number },\n): TOCItem[] {\n return getHeadings(headings, options);\n}\n\n// ---------------------------------------------------------------------------\n// Page composition helpers\n// ---------------------------------------------------------------------------\n\nimport type { AstroGlobal, GetStaticPaths } from \"astro\";\n\n/**\n * `getStaticPaths` implementation for a docs catch-all route.\n *\n * Returns one path per visible entry in the `docs` collection. Drafts are\n * filtered in production. Each path passes `{ entry }` as props so the\n * page component can access it via `getDocsPageProps(Astro)`.\n *\n * Usage:\n *\n * // src/pages/[...slug].astro\n * export const prerender = true;\n * export const getStaticPaths = getDocsStaticPaths;\n *\n * The entry's `id` is used verbatim as the slug. So `docs/index.mdx` →\n * `/index`, `docs/guides/setup.mdx` → `/guides/setup`. If you want a docs\n * entry at the root URL, name it appropriately and decide whether to use\n * a static `pages/index.astro` or let the catch-all handle root.\n */\nexport const getDocsStaticPaths: GetStaticPaths = async () => {\n // Docs-specific helper: always reads the `docs` collection. Other\n // collections require their own `pages/<name>/[...slug].astro` with\n // a one-line `getCollection(\"<name>\")`-based getStaticPaths.\n const entries = await getVisibleEntries([\"docs\"]);\n return entries.map((entry) => ({\n params: { slug: entry.id },\n props: { entry },\n }));\n};\n\n/**\n * Read the current entry from `Astro.props`, render it, and return the\n * pieces a docs page needs: the typed entry, the renderable `<Content />`\n * component, and the headings list (for TOC generation).\n *\n * Pass the page's `Astro` global. Throws if `Astro.props.entry` is missing,\n * which indicates the page didn't wire `getDocsStaticPaths` (or a custom\n * equivalent) correctly.\n *\n * Usage:\n *\n * const { entry, Content, headings } = await getDocsPageProps(Astro);\n */\nexport async function getDocsPageProps(astro: AstroGlobal): Promise<{\n entry: import(\"astro:content\").CollectionEntry<\"docs\">;\n Content: unknown;\n headings: { depth: number; text: string; slug: string }[];\n}> {\n const entry = (astro.props as { entry?: import(\"astro:content\").CollectionEntry<\"docs\"> })\n .entry;\n if (!entry) {\n throw new Error(\n \"getDocsPageProps(): expected `entry` in Astro.props. \" +\n \"Ensure your route uses `getStaticPaths = getDocsStaticPaths` \" +\n \"(or passes an entry via custom getStaticPaths).\",\n );\n }\n const { render } = await import(\"astro:content\");\n const { Content, headings } = await render(entry);\n return { entry, Content, headings };\n}\n\n/**\n * `getStaticPaths` implementation for a catch-all route over a non-primary\n * collection (`api`, `blog`, …). Companion to `getDocsStaticPaths`.\n *\n * Returns one path per visible entry in the named collection. Drafts are\n * filtered in production (same rule as `getDocsStaticPaths`). Each path\n * passes `{ entry }` as props for `getCollectionPageProps()`.\n *\n * Usage:\n *\n * // src/pages/api/[...slug].astro\n * export const prerender = true;\n * export const getStaticPaths = getCollectionStaticPaths(\"api\");\n *\n * Why a sibling helper instead of an option on `getDocsStaticPaths`: the\n * `Docs` name carries the \"primary collection mounted at root\" semantic.\n * Non-primary collections mount under their own URL namespace\n * (`/<collection>/...`) by convention; the helper name reflects that.\n */\nexport function getCollectionStaticPaths(collection: string): GetStaticPaths {\n return async () => {\n const entries = await getVisibleEntries([collection]);\n return entries.map((entry) => ({\n params: { slug: entry.id },\n props: { entry },\n }));\n };\n}\n\n/**\n * Read the current entry from `Astro.props`, render it, and return the\n * pieces a docs-style page needs — typed for an arbitrary collection.\n *\n * Companion to `getCollectionStaticPaths`. Use this in routes mounted at\n * non-primary collections (`api`, `blog`, …) instead of `getDocsPageProps`,\n * which is typed to the `docs` collection.\n *\n * Pass the collection name as a type parameter for the entry's data\n * shape to narrow correctly:\n *\n * const { entry, Content, headings } = await getCollectionPageProps<\"api\">(Astro);\n */\nexport async function getCollectionPageProps<C extends string>(\n astro: AstroGlobal,\n): Promise<{\n entry: import(\"astro:content\").CollectionEntry<C>;\n Content: unknown;\n headings: { depth: number; text: string; slug: string }[];\n}> {\n const entry = (astro.props as { entry?: import(\"astro:content\").CollectionEntry<C> })\n .entry;\n if (!entry) {\n throw new Error(\n \"getCollectionPageProps(): expected `entry` in Astro.props. \" +\n \"Ensure your route uses `getStaticPaths = getCollectionStaticPaths(<collection>)`.\",\n );\n }\n const { render } = await import(\"astro:content\");\n const { Content, headings } = await render(entry);\n return { entry, Content, headings };\n}\n\n// ---------------------------------------------------------------------------\n// Versioning (P0 — data layer only)\n// ---------------------------------------------------------------------------\n\n/**\n * Return the resolved versioning manifest for the current site, or `null`\n * if the site is unversioned (`nimbus.config.ts` has no `versions` block).\n *\n * Optional fields are normalised to empty arrays (`deprecated`, `hidden`)\n * and `all` is `[current, ...others]` in manifest order — convenient for\n * picker enumeration or anywhere you need every known version slug.\n *\n * Usage:\n *\n * const versions = await getVersions();\n * if (versions) {\n * for (const slug of versions.all) {\n * // …enumerate\n * }\n * }\n *\n * Reads from `virtual:nimbus/config`, so the cost is one cached dynamic\n * import per build.\n */\nexport async function getVersions(): Promise<ResolvedVersions | null> {\n const config = await loadNimbusConfig();\n const v = config.versions;\n if (!v) return null;\n const others = v.others ?? [];\n return {\n current: v.current,\n others,\n deprecated: v.deprecated ?? [],\n hidden: v.hidden ?? [],\n all: [v.current, ...others],\n };\n}\n\n/**\n * Return the version slug a given Astro content collection ID belongs to,\n * or `null` if the collection is not a version of the primary docs.\n *\n * Rules:\n * - `\"docs\"` → `versions.current` (the current version's label).\n * - `\"docs-<slug>\"` where `<slug>` appears in `versions.current` or\n * `versions.others` → `<slug>`.\n * - Anything else (e.g. `\"blog\"`, `\"api\"`, `\"docs-archive\"` when\n * `archive` isn't in the manifest) → `null`.\n *\n * Returns `null` whenever the site has no `versions` config at all,\n * regardless of collection ID.\n *\n * Usage in a route:\n *\n * const { entry } = Astro.props;\n * const version = await getCurrentVersion(entry.collection);\n * // version === \"v3\" for entries in `docs`, \"v2\" for entries in `docs-v2`, …\n */\nexport async function getCurrentVersion(\n collectionId: string,\n): Promise<string | null> {\n const versions = await getVersions();\n if (!versions) return null;\n if (collectionId === PRIMARY_COLLECTION) return versions.current;\n if (!collectionId.startsWith(\"docs-\")) return null;\n const suffix = collectionId.slice(\"docs-\".length);\n return versions.all.includes(suffix) ? suffix : null;\n}\n\n/**\n * Look up the cross-version alternates for a given Astro entry.\n *\n * Returns `null` when the entry is not part of a versioning manifest\n * (unversioned site, non-`docs` collection like `blog`/`api`, or the\n * lookup misses for any other reason). Otherwise returns a record with:\n *\n * - `self`: the entry being looked up, expressed as a `VersionPageRef`.\n * - `alternates`: every other version's sibling page for the same\n * logical content (same slug or linked via `previousSlug`). Sorted\n * in manifest version order.\n * - `canonical`: the current-version sibling when one exists and\n * isn't `self`. `null` when `self` is already the current version\n * or no current-version sibling exists.\n *\n * Routes inject `<link rel=\"alternate\">` for every entry in\n * `alternates`, and `<link rel=\"canonical\">` pointing at `canonical.url`\n * when canonical is non-null.\n *\n * Usage in a route:\n *\n * const { entry } = Astro.props;\n * const alts = await getVersionAlternates(entry.collection, entry.id);\n *\n * {alts?.alternates.map((a) => (\n * <link rel=\"alternate\" data-version={a.version} href={a.url} />\n * ))}\n * {alts?.canonical && <link rel=\"canonical\" href={alts.canonical.url} />}\n */\nexport async function getVersionAlternates(\n collectionId: string,\n entryId: string,\n): Promise<VersionAlternateRecord | null> {\n const table = await loadVersionAlternates();\n const key = `${collectionId}:${entryId}`;\n return table[key] ?? null;\n}\n\n/**\n * Convenience wrapper: returns just the canonical URL for an entry, or\n * `null` when none applies. Equivalent to\n * `(await getVersionAlternates(c, e))?.canonical?.url ?? null` — handy\n * when a route only needs the canonical and not the full alternates list.\n */\nexport async function getCanonicalUrl(\n collectionId: string,\n entryId: string,\n): Promise<string | null> {\n const record = await getVersionAlternates(collectionId, entryId);\n return record?.canonical?.url ?? null;\n}\n\n/**\n * Return the agent index URL path (the `/llms.txt` route) that\n * corresponds to a given Astro collection. The path is mount-point\n * aware: pages in version collections point at the per-version index,\n * pages in non-primary collections point at their per-collection index,\n * and the primary `docs` collection points at the root.\n *\n * - `\"docs\"` → `\"/llms.txt\"`\n * - `\"docs-v1\"` → `\"/v1/llms.txt\"` (when `v1` is in `versions.others`)\n * - `\"blog\"` → `\"/blog/llms.txt\"`\n * - `\"api\"` → `\"/api/llms.txt\"`\n * - `\"docs-archive\"` (unrecognised version slug) → `\"/docs-archive/llms.txt\"`\n *\n * Returns a path with a leading slash and no trailing slash. Routes\n * resolve it against `Astro.site` to produce a full URL.\n *\n * Used by `BaseLayout` and `AgentDirective` to surface the correct\n * agent index hint on every page — readers landing on `/v1/foo` get\n * pointed at `/v1/llms.txt`, not `/llms.txt`, so agents don't crawl\n * the wrong section.\n */\nexport async function getCollectionLlmsUrl(\n collectionId: string,\n): Promise<string> {\n if (collectionId === PRIMARY_COLLECTION) return \"/llms.txt\";\n const versions = await getVersions();\n if (versions && collectionId.startsWith(\"docs-\")) {\n const slug = collectionId.slice(\"docs-\".length);\n if (versions.others.includes(slug)) {\n // Hidden versions do NOT emit a per-section /<v>/llms.txt — the\n // [section] route filters them out. Pointing readers at a 404\n // breaks the agent-discovery contract. Fall back to the root\n // index for hidden version pages instead.\n if (versions.hidden.includes(slug)) return \"/llms.txt\";\n return `/${slug}/llms.txt`;\n }\n }\n return `/${collectionId}/llms.txt`;\n}\n\n/**\n * Look up the versioning status for a page's collection — what the\n * layout needs to decide whether to render the deprecation banner,\n * apply the Pagefind facet filters, or exclude the page from search\n * entirely.\n *\n * Returns `null` when the site is unversioned or the page is not part\n * of a version collection (regular `docs`, `blog`, `api`, …). Layouts\n * treat that as \"no versioning UI to apply\" — render normally.\n *\n * Usage:\n *\n * const status = await getVersionStatus(entry.collection);\n * if (status?.isDeprecated) {\n * // render the deprecation banner\n * }\n */\n/**\n * Resolve a URL that's guaranteed to exist within a given version's\n * collection. Used by the picker (and any other \"jump to that version\"\n * surface) to avoid landing readers on a 404 when the current page has\n * no same-logical-page sibling in the target version.\n *\n * Resolution order:\n * 1. If `docs-<v>/index` exists, return its URL (the conventional\n * \"version landing page\").\n * 2. If `docs-<v>/overview` exists, return its URL (common alternate\n * name for a landing page).\n * 3. Otherwise return the first indexed entry's URL in that version,\n * sorted by URL — matches `getIndexedTopLevel()`'s sort so the\n * choice is deterministic across builds.\n * 4. If the version has no indexed entries at all, return `null`.\n * Callers should treat that as \"this version has nothing to link\n * to\" and either omit the picker entry or fall back to the\n * version's URL prefix root (which may still 404, but that's the\n * authoring problem to fix, not the picker's).\n *\n * `version` is the manifest slug (e.g. `\"v0\"`), NOT the collection ID\n * (`\"docs-v0\"`). For the current version, returns `\"/\"` when at least\n * one current-version entry exists, else `null`.\n *\n * Reads from `getIndexedEntries()`, so the cost is one cached lookup\n * per build (the indexed list is computed once per page render).\n */\nexport async function getVersionLandingUrl(\n version: string,\n): Promise<string | null> {\n const versions = await getVersions();\n if (!versions) return null;\n if (!versions.all.includes(version)) return null;\n\n const targetCollection =\n version === versions.current ? PRIMARY_COLLECTION : `docs-${version}`;\n const items = await getIndexedEntries();\n const inVersion = items.filter((i) => i.collection === targetCollection);\n if (inVersion.length === 0) return null;\n\n const byId = new Map(inVersion.map((i) => [i.entry.id, i]));\n // Prefer index / overview by convention.\n const preferred = byId.get(\"index\") ?? byId.get(\"overview\");\n // `IndexedEntry.url` is already the trailing-slash browser-href form\n // the version picker renders, so no extra normalization here.\n if (preferred) return preferred.url;\n // Else first by URL (sort is alphabetical → deterministic).\n inVersion.sort((a, b) => a.url.localeCompare(b.url));\n return inVersion[0]!.url;\n}\n\nexport async function getVersionStatus(\n collectionId: string,\n): Promise<VersionStatus | null> {\n const versions = await getVersions();\n if (!versions) return null;\n const version = await getCurrentVersion(collectionId);\n if (version === null) return null;\n return {\n version,\n isCurrent: version === versions.current,\n isDeprecated: versions.deprecated.includes(version),\n isHidden: versions.hidden.includes(version),\n };\n}\n"],"x_google_ignoreList":[2,3],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["PRIMARY_COLLECTION","githubSlug","PRIMARY_COLLECTION","getBreadcrumbs","getPrevNext","fs","splitTopLevelCommas","fs","walkMdx","fs","walk","fs","PRIMARY_COLLECTION","PRIMARY_COLLECTION","resolveCollectionPrefix","resolveCollectionSlug","buildPrevNext","buildBreadcrumbs"],"sources":["../src/_internal/runtime-config.ts","../src/_internal/content.ts","../../../node_modules/.pnpm/github-slugger@2.0.0/node_modules/github-slugger/regex.js","../../../node_modules/.pnpm/github-slugger@2.0.0/node_modules/github-slugger/index.js","../src/_internal/astro-slug.ts","../src/_internal/sidebar.ts","../src/_internal/collection-mount.ts","../src/_internal/transform.ts","../src/_internal/navigation.ts","../src/_internal/toc.ts","../src/_internal/git-last-updated.ts","../src/_internal/admonition-transform.ts","../src/_internal/admonition-vite-plugin.ts","../src/_internal/parse-components-registry.ts","../src/lint/site-model.ts","../src/_internal/parse-content-collections.ts","../src/_internal/code-transformers.ts","../src/_internal/validate-mdx-content.ts","../src/_internal/validate.ts","../src/_internal/virtual-config.ts","../src/_internal/scan-code-langs.ts","../src/_internal/incremental/cache.ts","../src/_internal/incremental/hash.ts","../src/_internal/incremental/namespace.ts","../src/_internal/incremental/partial-refs.ts","../src/_internal/incremental/index.ts","../src/_internal/incremental/mdx-skip-plugin.ts","../src/_internal/incremental/sitemap.ts","../src/_internal/scan-version-frontmatter.ts","../src/_internal/version-alternates.ts","../src/integration.ts","../src/index.ts"],"sourcesContent":["/**\n * Runtime config bridge.\n *\n * Reads the user's validated NimbusConfig from `virtual:nimbus/config`,\n * which is provided by the Vite plugin our integration registers.\n *\n * **The import is dynamic on purpose.** Astro's config loader (in plain\n * Node) eagerly imports the whole framework bundle when it loads our\n * default export from `astro.config.ts`. A top-level static\n * `import \"virtual:...\"` would crash there because Vite hasn't booted\n * yet. Dynamic import keeps the load deferred until a page actually\n * calls a helper at request/render time.\n */\n\nimport type { NimbusConfig } from \"../types.js\";\nimport type { VersionAlternatesTable } from \"./version-alternates.js\";\n\n// `virtual:nimbus/config` declarations live at\n// `packages/nimbus-docs/src/types/virtual-modules.d.ts` so they're ambient\n// (resolvable from dynamic `await import()` calls below) rather than\n// scoped to this module.\n\nlet _cached: NimbusConfig | null = null;\nlet _cachedCollections: readonly string[] | null = null;\nlet _cachedAlternates: VersionAlternatesTable | null = null;\n\nexport async function loadNimbusConfig(): Promise<NimbusConfig> {\n if (_cached) return _cached;\n const mod = await import(\"virtual:nimbus/config\");\n // Intermediate const so the return path isn't typed `NimbusConfig | null`\n // (the module-scoped `_cached` keeps its union; the value being cached\n // and returned is the same object — observable behavior is identical).\n const value = mod.config;\n _cached = value;\n return value;\n}\n\n/**\n * Build-time-resolved list of collections the agent-facing routes\n * (llms.txt, per-page .md alternates) should iterate. Reserved names\n * (`partials`, `_*`) are already filtered. See `getIndexedEntries()`.\n */\nexport async function loadIndexedCollections(): Promise<readonly string[]> {\n if (_cachedCollections) return _cachedCollections;\n const mod = await import(\"virtual:nimbus/config\");\n const value = mod.indexedCollections;\n _cachedCollections = value;\n return value;\n}\n\n/**\n * Build-time-resolved alternates table for cross-version SEO links.\n * Returns the same object on every call (cached after first load).\n * Empty `{}` when the site is unversioned.\n */\nexport async function loadVersionAlternates(): Promise<VersionAlternatesTable> {\n if (_cachedAlternates) return _cachedAlternates;\n const mod = await import(\"virtual:nimbus/config\");\n // Fall back to an empty table when the virtual module doesn't define\n // `versionAlternates` (e.g. older integration build, or transient\n // dev-server cache state). Downstream lookups (`table[key] ?? null`)\n // then resolve cleanly instead of throwing on an undefined receiver.\n const value = mod.versionAlternates ?? {};\n _cachedAlternates = value;\n return value;\n}\n","/**\n * Content collection access for helpers.\n *\n * Dynamic import of `astro:content` for the same reason as\n * runtime-config: Astro's config loader runs in plain Node, where\n * `astro:content` doesn't exist. We defer to call time, which only\n * happens at page render.\n *\n * There is intentionally no global \"list of collections Nimbus knows\n * about\" — the framework doesn't try to mirror what\n * `content.config.ts` registers. Callers that need entries from\n * multiple collections pass them explicitly; the sidebar builder\n * derives its list from `sidebar.items` references.\n */\n\nimport type { CollectionEntry } from \"astro:content\";\n\n/** Primary collection name. Hard-coded — see also `getDocsStaticPaths`. */\nconst PRIMARY_COLLECTION = \"docs\";\n\n/**\n * Return visible entries from one or more collections. Drafts are\n * filtered out in production builds (matching the existing\n * single-collection behaviour).\n *\n * Defaults to `[\"docs\"]` — the framework's primary collection.\n * Cross-collection callers (llms.txt aggregators, custom indexes,\n * etc.) pass an explicit list.\n *\n * Returns a flat `CollectionEntry<string>[]` so cross-collection\n * traversal doesn't need to know the user's collection names at type\n * time. Callers that need per-collection type safety should call\n * `getCollection(\"api\")` directly.\n */\nexport async function getVisibleEntries(\n collections: string[] = [PRIMARY_COLLECTION],\n): Promise<CollectionEntry<string>[]> {\n const { getCollection } = await import(\"astro:content\");\n const lists = await Promise.all(\n collections.map((name) =>\n getCollection(name).catch(() => [] as CollectionEntry<string>[]),\n ),\n );\n const all = lists.flat();\n return import.meta.env.PROD\n ? all.filter((entry: CollectionEntry<string>) => !entry.data.draft)\n : all;\n}\n\n/**\n * Return visible entries grouped by collection. Used by the sidebar\n * builder so `collection:` autogenerate can look up entries by name\n * without re-fetching.\n */\nexport async function getVisibleEntriesByCollection(\n collections: string[],\n): Promise<Record<string, CollectionEntry<string>[]>> {\n const { getCollection } = await import(\"astro:content\");\n const out: Record<string, CollectionEntry<string>[]> = {};\n await Promise.all(\n collections.map(async (name) => {\n const all = await getCollection(name).catch(\n () => [] as CollectionEntry<string>[],\n );\n out[name] = import.meta.env.PROD\n ? all.filter((entry: CollectionEntry<string>) => !entry.data.draft)\n : all;\n }),\n );\n return out;\n}\n","// This module is generated by `script/`.\n/* eslint-disable no-control-regex, no-misleading-character-class, no-useless-escape */\nexport const regex = /[\\0-\\x1F!-,\\.\\/:-@\\[-\\^`\\{-\\xA9\\xAB-\\xB4\\xB6-\\xB9\\xBB-\\xBF\\xD7\\xF7\\u02C2-\\u02C5\\u02D2-\\u02DF\\u02E5-\\u02EB\\u02ED\\u02EF-\\u02FF\\u0375\\u0378\\u0379\\u037E\\u0380-\\u0385\\u0387\\u038B\\u038D\\u03A2\\u03F6\\u0482\\u0530\\u0557\\u0558\\u055A-\\u055F\\u0589-\\u0590\\u05BE\\u05C0\\u05C3\\u05C6\\u05C8-\\u05CF\\u05EB-\\u05EE\\u05F3-\\u060F\\u061B-\\u061F\\u066A-\\u066D\\u06D4\\u06DD\\u06DE\\u06E9\\u06FD\\u06FE\\u0700-\\u070F\\u074B\\u074C\\u07B2-\\u07BF\\u07F6-\\u07F9\\u07FB\\u07FC\\u07FE\\u07FF\\u082E-\\u083F\\u085C-\\u085F\\u086B-\\u089F\\u08B5\\u08C8-\\u08D2\\u08E2\\u0964\\u0965\\u0970\\u0984\\u098D\\u098E\\u0991\\u0992\\u09A9\\u09B1\\u09B3-\\u09B5\\u09BA\\u09BB\\u09C5\\u09C6\\u09C9\\u09CA\\u09CF-\\u09D6\\u09D8-\\u09DB\\u09DE\\u09E4\\u09E5\\u09F2-\\u09FB\\u09FD\\u09FF\\u0A00\\u0A04\\u0A0B-\\u0A0E\\u0A11\\u0A12\\u0A29\\u0A31\\u0A34\\u0A37\\u0A3A\\u0A3B\\u0A3D\\u0A43-\\u0A46\\u0A49\\u0A4A\\u0A4E-\\u0A50\\u0A52-\\u0A58\\u0A5D\\u0A5F-\\u0A65\\u0A76-\\u0A80\\u0A84\\u0A8E\\u0A92\\u0AA9\\u0AB1\\u0AB4\\u0ABA\\u0ABB\\u0AC6\\u0ACA\\u0ACE\\u0ACF\\u0AD1-\\u0ADF\\u0AE4\\u0AE5\\u0AF0-\\u0AF8\\u0B00\\u0B04\\u0B0D\\u0B0E\\u0B11\\u0B12\\u0B29\\u0B31\\u0B34\\u0B3A\\u0B3B\\u0B45\\u0B46\\u0B49\\u0B4A\\u0B4E-\\u0B54\\u0B58-\\u0B5B\\u0B5E\\u0B64\\u0B65\\u0B70\\u0B72-\\u0B81\\u0B84\\u0B8B-\\u0B8D\\u0B91\\u0B96-\\u0B98\\u0B9B\\u0B9D\\u0BA0-\\u0BA2\\u0BA5-\\u0BA7\\u0BAB-\\u0BAD\\u0BBA-\\u0BBD\\u0BC3-\\u0BC5\\u0BC9\\u0BCE\\u0BCF\\u0BD1-\\u0BD6\\u0BD8-\\u0BE5\\u0BF0-\\u0BFF\\u0C0D\\u0C11\\u0C29\\u0C3A-\\u0C3C\\u0C45\\u0C49\\u0C4E-\\u0C54\\u0C57\\u0C5B-\\u0C5F\\u0C64\\u0C65\\u0C70-\\u0C7F\\u0C84\\u0C8D\\u0C91\\u0CA9\\u0CB4\\u0CBA\\u0CBB\\u0CC5\\u0CC9\\u0CCE-\\u0CD4\\u0CD7-\\u0CDD\\u0CDF\\u0CE4\\u0CE5\\u0CF0\\u0CF3-\\u0CFF\\u0D0D\\u0D11\\u0D45\\u0D49\\u0D4F-\\u0D53\\u0D58-\\u0D5E\\u0D64\\u0D65\\u0D70-\\u0D79\\u0D80\\u0D84\\u0D97-\\u0D99\\u0DB2\\u0DBC\\u0DBE\\u0DBF\\u0DC7-\\u0DC9\\u0DCB-\\u0DCE\\u0DD5\\u0DD7\\u0DE0-\\u0DE5\\u0DF0\\u0DF1\\u0DF4-\\u0E00\\u0E3B-\\u0E3F\\u0E4F\\u0E5A-\\u0E80\\u0E83\\u0E85\\u0E8B\\u0EA4\\u0EA6\\u0EBE\\u0EBF\\u0EC5\\u0EC7\\u0ECE\\u0ECF\\u0EDA\\u0EDB\\u0EE0-\\u0EFF\\u0F01-\\u0F17\\u0F1A-\\u0F1F\\u0F2A-\\u0F34\\u0F36\\u0F38\\u0F3A-\\u0F3D\\u0F48\\u0F6D-\\u0F70\\u0F85\\u0F98\\u0FBD-\\u0FC5\\u0FC7-\\u0FFF\\u104A-\\u104F\\u109E\\u109F\\u10C6\\u10C8-\\u10CC\\u10CE\\u10CF\\u10FB\\u1249\\u124E\\u124F\\u1257\\u1259\\u125E\\u125F\\u1289\\u128E\\u128F\\u12B1\\u12B6\\u12B7\\u12BF\\u12C1\\u12C6\\u12C7\\u12D7\\u1311\\u1316\\u1317\\u135B\\u135C\\u1360-\\u137F\\u1390-\\u139F\\u13F6\\u13F7\\u13FE-\\u1400\\u166D\\u166E\\u1680\\u169B-\\u169F\\u16EB-\\u16ED\\u16F9-\\u16FF\\u170D\\u1715-\\u171F\\u1735-\\u173F\\u1754-\\u175F\\u176D\\u1771\\u1774-\\u177F\\u17D4-\\u17D6\\u17D8-\\u17DB\\u17DE\\u17DF\\u17EA-\\u180A\\u180E\\u180F\\u181A-\\u181F\\u1879-\\u187F\\u18AB-\\u18AF\\u18F6-\\u18FF\\u191F\\u192C-\\u192F\\u193C-\\u1945\\u196E\\u196F\\u1975-\\u197F\\u19AC-\\u19AF\\u19CA-\\u19CF\\u19DA-\\u19FF\\u1A1C-\\u1A1F\\u1A5F\\u1A7D\\u1A7E\\u1A8A-\\u1A8F\\u1A9A-\\u1AA6\\u1AA8-\\u1AAF\\u1AC1-\\u1AFF\\u1B4C-\\u1B4F\\u1B5A-\\u1B6A\\u1B74-\\u1B7F\\u1BF4-\\u1BFF\\u1C38-\\u1C3F\\u1C4A-\\u1C4C\\u1C7E\\u1C7F\\u1C89-\\u1C8F\\u1CBB\\u1CBC\\u1CC0-\\u1CCF\\u1CD3\\u1CFB-\\u1CFF\\u1DFA\\u1F16\\u1F17\\u1F1E\\u1F1F\\u1F46\\u1F47\\u1F4E\\u1F4F\\u1F58\\u1F5A\\u1F5C\\u1F5E\\u1F7E\\u1F7F\\u1FB5\\u1FBD\\u1FBF-\\u1FC1\\u1FC5\\u1FCD-\\u1FCF\\u1FD4\\u1FD5\\u1FDC-\\u1FDF\\u1FED-\\u1FF1\\u1FF5\\u1FFD-\\u203E\\u2041-\\u2053\\u2055-\\u2070\\u2072-\\u207E\\u2080-\\u208F\\u209D-\\u20CF\\u20F1-\\u2101\\u2103-\\u2106\\u2108\\u2109\\u2114\\u2116-\\u2118\\u211E-\\u2123\\u2125\\u2127\\u2129\\u212E\\u213A\\u213B\\u2140-\\u2144\\u214A-\\u214D\\u214F-\\u215F\\u2189-\\u24B5\\u24EA-\\u2BFF\\u2C2F\\u2C5F\\u2CE5-\\u2CEA\\u2CF4-\\u2CFF\\u2D26\\u2D28-\\u2D2C\\u2D2E\\u2D2F\\u2D68-\\u2D6E\\u2D70-\\u2D7E\\u2D97-\\u2D9F\\u2DA7\\u2DAF\\u2DB7\\u2DBF\\u2DC7\\u2DCF\\u2DD7\\u2DDF\\u2E00-\\u2E2E\\u2E30-\\u3004\\u3008-\\u3020\\u3030\\u3036\\u3037\\u303D-\\u3040\\u3097\\u3098\\u309B\\u309C\\u30A0\\u30FB\\u3100-\\u3104\\u3130\\u318F-\\u319F\\u31C0-\\u31EF\\u3200-\\u33FF\\u4DC0-\\u4DFF\\u9FFD-\\u9FFF\\uA48D-\\uA4CF\\uA4FE\\uA4FF\\uA60D-\\uA60F\\uA62C-\\uA63F\\uA673\\uA67E\\uA6F2-\\uA716\\uA720\\uA721\\uA789\\uA78A\\uA7C0\\uA7C1\\uA7CB-\\uA7F4\\uA828-\\uA82B\\uA82D-\\uA83F\\uA874-\\uA87F\\uA8C6-\\uA8CF\\uA8DA-\\uA8DF\\uA8F8-\\uA8FA\\uA8FC\\uA92E\\uA92F\\uA954-\\uA95F\\uA97D-\\uA97F\\uA9C1-\\uA9CE\\uA9DA-\\uA9DF\\uA9FF\\uAA37-\\uAA3F\\uAA4E\\uAA4F\\uAA5A-\\uAA5F\\uAA77-\\uAA79\\uAAC3-\\uAADA\\uAADE\\uAADF\\uAAF0\\uAAF1\\uAAF7-\\uAB00\\uAB07\\uAB08\\uAB0F\\uAB10\\uAB17-\\uAB1F\\uAB27\\uAB2F\\uAB5B\\uAB6A-\\uAB6F\\uABEB\\uABEE\\uABEF\\uABFA-\\uABFF\\uD7A4-\\uD7AF\\uD7C7-\\uD7CA\\uD7FC-\\uD7FF\\uE000-\\uF8FF\\uFA6E\\uFA6F\\uFADA-\\uFAFF\\uFB07-\\uFB12\\uFB18-\\uFB1C\\uFB29\\uFB37\\uFB3D\\uFB3F\\uFB42\\uFB45\\uFBB2-\\uFBD2\\uFD3E-\\uFD4F\\uFD90\\uFD91\\uFDC8-\\uFDEF\\uFDFC-\\uFDFF\\uFE10-\\uFE1F\\uFE30-\\uFE32\\uFE35-\\uFE4C\\uFE50-\\uFE6F\\uFE75\\uFEFD-\\uFF0F\\uFF1A-\\uFF20\\uFF3B-\\uFF3E\\uFF40\\uFF5B-\\uFF65\\uFFBF-\\uFFC1\\uFFC8\\uFFC9\\uFFD0\\uFFD1\\uFFD8\\uFFD9\\uFFDD-\\uFFFF]|\\uD800[\\uDC0C\\uDC27\\uDC3B\\uDC3E\\uDC4E\\uDC4F\\uDC5E-\\uDC7F\\uDCFB-\\uDD3F\\uDD75-\\uDDFC\\uDDFE-\\uDE7F\\uDE9D-\\uDE9F\\uDED1-\\uDEDF\\uDEE1-\\uDEFF\\uDF20-\\uDF2C\\uDF4B-\\uDF4F\\uDF7B-\\uDF7F\\uDF9E\\uDF9F\\uDFC4-\\uDFC7\\uDFD0\\uDFD6-\\uDFFF]|\\uD801[\\uDC9E\\uDC9F\\uDCAA-\\uDCAF\\uDCD4-\\uDCD7\\uDCFC-\\uDCFF\\uDD28-\\uDD2F\\uDD64-\\uDDFF\\uDF37-\\uDF3F\\uDF56-\\uDF5F\\uDF68-\\uDFFF]|\\uD802[\\uDC06\\uDC07\\uDC09\\uDC36\\uDC39-\\uDC3B\\uDC3D\\uDC3E\\uDC56-\\uDC5F\\uDC77-\\uDC7F\\uDC9F-\\uDCDF\\uDCF3\\uDCF6-\\uDCFF\\uDD16-\\uDD1F\\uDD3A-\\uDD7F\\uDDB8-\\uDDBD\\uDDC0-\\uDDFF\\uDE04\\uDE07-\\uDE0B\\uDE14\\uDE18\\uDE36\\uDE37\\uDE3B-\\uDE3E\\uDE40-\\uDE5F\\uDE7D-\\uDE7F\\uDE9D-\\uDEBF\\uDEC8\\uDEE7-\\uDEFF\\uDF36-\\uDF3F\\uDF56-\\uDF5F\\uDF73-\\uDF7F\\uDF92-\\uDFFF]|\\uD803[\\uDC49-\\uDC7F\\uDCB3-\\uDCBF\\uDCF3-\\uDCFF\\uDD28-\\uDD2F\\uDD3A-\\uDE7F\\uDEAA\\uDEAD-\\uDEAF\\uDEB2-\\uDEFF\\uDF1D-\\uDF26\\uDF28-\\uDF2F\\uDF51-\\uDFAF\\uDFC5-\\uDFDF\\uDFF7-\\uDFFF]|\\uD804[\\uDC47-\\uDC65\\uDC70-\\uDC7E\\uDCBB-\\uDCCF\\uDCE9-\\uDCEF\\uDCFA-\\uDCFF\\uDD35\\uDD40-\\uDD43\\uDD48-\\uDD4F\\uDD74\\uDD75\\uDD77-\\uDD7F\\uDDC5-\\uDDC8\\uDDCD\\uDDDB\\uDDDD-\\uDDFF\\uDE12\\uDE38-\\uDE3D\\uDE3F-\\uDE7F\\uDE87\\uDE89\\uDE8E\\uDE9E\\uDEA9-\\uDEAF\\uDEEB-\\uDEEF\\uDEFA-\\uDEFF\\uDF04\\uDF0D\\uDF0E\\uDF11\\uDF12\\uDF29\\uDF31\\uDF34\\uDF3A\\uDF45\\uDF46\\uDF49\\uDF4A\\uDF4E\\uDF4F\\uDF51-\\uDF56\\uDF58-\\uDF5C\\uDF64\\uDF65\\uDF6D-\\uDF6F\\uDF75-\\uDFFF]|\\uD805[\\uDC4B-\\uDC4F\\uDC5A-\\uDC5D\\uDC62-\\uDC7F\\uDCC6\\uDCC8-\\uDCCF\\uDCDA-\\uDD7F\\uDDB6\\uDDB7\\uDDC1-\\uDDD7\\uDDDE-\\uDDFF\\uDE41-\\uDE43\\uDE45-\\uDE4F\\uDE5A-\\uDE7F\\uDEB9-\\uDEBF\\uDECA-\\uDEFF\\uDF1B\\uDF1C\\uDF2C-\\uDF2F\\uDF3A-\\uDFFF]|\\uD806[\\uDC3B-\\uDC9F\\uDCEA-\\uDCFE\\uDD07\\uDD08\\uDD0A\\uDD0B\\uDD14\\uDD17\\uDD36\\uDD39\\uDD3A\\uDD44-\\uDD4F\\uDD5A-\\uDD9F\\uDDA8\\uDDA9\\uDDD8\\uDDD9\\uDDE2\\uDDE5-\\uDDFF\\uDE3F-\\uDE46\\uDE48-\\uDE4F\\uDE9A-\\uDE9C\\uDE9E-\\uDEBF\\uDEF9-\\uDFFF]|\\uD807[\\uDC09\\uDC37\\uDC41-\\uDC4F\\uDC5A-\\uDC71\\uDC90\\uDC91\\uDCA8\\uDCB7-\\uDCFF\\uDD07\\uDD0A\\uDD37-\\uDD39\\uDD3B\\uDD3E\\uDD48-\\uDD4F\\uDD5A-\\uDD5F\\uDD66\\uDD69\\uDD8F\\uDD92\\uDD99-\\uDD9F\\uDDAA-\\uDEDF\\uDEF7-\\uDFAF\\uDFB1-\\uDFFF]|\\uD808[\\uDF9A-\\uDFFF]|\\uD809[\\uDC6F-\\uDC7F\\uDD44-\\uDFFF]|[\\uD80A\\uD80B\\uD80E-\\uD810\\uD812-\\uD819\\uD824-\\uD82B\\uD82D\\uD82E\\uD830-\\uD833\\uD837\\uD839\\uD83D\\uD83F\\uD87B-\\uD87D\\uD87F\\uD885-\\uDB3F\\uDB41-\\uDBFF][\\uDC00-\\uDFFF]|\\uD80D[\\uDC2F-\\uDFFF]|\\uD811[\\uDE47-\\uDFFF]|\\uD81A[\\uDE39-\\uDE3F\\uDE5F\\uDE6A-\\uDECF\\uDEEE\\uDEEF\\uDEF5-\\uDEFF\\uDF37-\\uDF3F\\uDF44-\\uDF4F\\uDF5A-\\uDF62\\uDF78-\\uDF7C\\uDF90-\\uDFFF]|\\uD81B[\\uDC00-\\uDE3F\\uDE80-\\uDEFF\\uDF4B-\\uDF4E\\uDF88-\\uDF8E\\uDFA0-\\uDFDF\\uDFE2\\uDFE5-\\uDFEF\\uDFF2-\\uDFFF]|\\uD821[\\uDFF8-\\uDFFF]|\\uD823[\\uDCD6-\\uDCFF\\uDD09-\\uDFFF]|\\uD82C[\\uDD1F-\\uDD4F\\uDD53-\\uDD63\\uDD68-\\uDD6F\\uDEFC-\\uDFFF]|\\uD82F[\\uDC6B-\\uDC6F\\uDC7D-\\uDC7F\\uDC89-\\uDC8F\\uDC9A-\\uDC9C\\uDC9F-\\uDFFF]|\\uD834[\\uDC00-\\uDD64\\uDD6A-\\uDD6C\\uDD73-\\uDD7A\\uDD83\\uDD84\\uDD8C-\\uDDA9\\uDDAE-\\uDE41\\uDE45-\\uDFFF]|\\uD835[\\uDC55\\uDC9D\\uDCA0\\uDCA1\\uDCA3\\uDCA4\\uDCA7\\uDCA8\\uDCAD\\uDCBA\\uDCBC\\uDCC4\\uDD06\\uDD0B\\uDD0C\\uDD15\\uDD1D\\uDD3A\\uDD3F\\uDD45\\uDD47-\\uDD49\\uDD51\\uDEA6\\uDEA7\\uDEC1\\uDEDB\\uDEFB\\uDF15\\uDF35\\uDF4F\\uDF6F\\uDF89\\uDFA9\\uDFC3\\uDFCC\\uDFCD]|\\uD836[\\uDC00-\\uDDFF\\uDE37-\\uDE3A\\uDE6D-\\uDE74\\uDE76-\\uDE83\\uDE85-\\uDE9A\\uDEA0\\uDEB0-\\uDFFF]|\\uD838[\\uDC07\\uDC19\\uDC1A\\uDC22\\uDC25\\uDC2B-\\uDCFF\\uDD2D-\\uDD2F\\uDD3E\\uDD3F\\uDD4A-\\uDD4D\\uDD4F-\\uDEBF\\uDEFA-\\uDFFF]|\\uD83A[\\uDCC5-\\uDCCF\\uDCD7-\\uDCFF\\uDD4C-\\uDD4F\\uDD5A-\\uDFFF]|\\uD83B[\\uDC00-\\uDDFF\\uDE04\\uDE20\\uDE23\\uDE25\\uDE26\\uDE28\\uDE33\\uDE38\\uDE3A\\uDE3C-\\uDE41\\uDE43-\\uDE46\\uDE48\\uDE4A\\uDE4C\\uDE50\\uDE53\\uDE55\\uDE56\\uDE58\\uDE5A\\uDE5C\\uDE5E\\uDE60\\uDE63\\uDE65\\uDE66\\uDE6B\\uDE73\\uDE78\\uDE7D\\uDE7F\\uDE8A\\uDE9C-\\uDEA0\\uDEA4\\uDEAA\\uDEBC-\\uDFFF]|\\uD83C[\\uDC00-\\uDD2F\\uDD4A-\\uDD4F\\uDD6A-\\uDD6F\\uDD8A-\\uDFFF]|\\uD83E[\\uDC00-\\uDFEF\\uDFFA-\\uDFFF]|\\uD869[\\uDEDE-\\uDEFF]|\\uD86D[\\uDF35-\\uDF3F]|\\uD86E[\\uDC1E\\uDC1F]|\\uD873[\\uDEA2-\\uDEAF]|\\uD87A[\\uDFE1-\\uDFFF]|\\uD87E[\\uDE1E-\\uDFFF]|\\uD884[\\uDF4B-\\uDFFF]|\\uDB40[\\uDC00-\\uDCFF\\uDDF0-\\uDFFF]/g\n","import { regex } from './regex.js'\n\nconst own = Object.hasOwnProperty\n\n/**\n * Slugger.\n */\nexport default class BananaSlug {\n /**\n * Create a new slug class.\n */\n constructor () {\n /** @type {Record<string, number>} */\n // eslint-disable-next-line no-unused-expressions\n this.occurrences\n\n this.reset()\n }\n\n /**\n * Generate a unique slug.\n *\n * Tracks previously generated slugs: repeated calls with the same value\n * will result in different slugs.\n * Use the `slug` function to get same slugs.\n *\n * @param {string} value\n * String of text to slugify\n * @param {boolean} [maintainCase=false]\n * Keep the current case, otherwise make all lowercase\n * @return {string}\n * A unique slug string\n */\n slug (value, maintainCase) {\n const self = this\n let result = slug(value, maintainCase === true)\n const originalSlug = result\n\n while (own.call(self.occurrences, result)) {\n self.occurrences[originalSlug]++\n result = originalSlug + '-' + self.occurrences[originalSlug]\n }\n\n self.occurrences[result] = 0\n\n return result\n }\n\n /**\n * Reset - Forget all previous slugs\n *\n * @return void\n */\n reset () {\n this.occurrences = Object.create(null)\n }\n}\n\n/**\n * Generate a slug.\n *\n * Does not track previously generated slugs: repeated calls with the same value\n * will result in the exact same slug.\n * Use the `GithubSlugger` class to get unique slugs.\n *\n * @param {string} value\n * String of text to slugify\n * @param {boolean} [maintainCase=false]\n * Keep the current case, otherwise make all lowercase\n * @return {string}\n * A unique slug string\n */\nexport function slug (value, maintainCase) {\n if (typeof value !== 'string') return ''\n if (!maintainCase) value = value.toLowerCase()\n return value.replace(regex, '').replace(/ /g, '-')\n}\n","/**\n * Mirror of Astro's content-layer slug normalization, used by every\n * framework URL builder that derives a URL from an `entry.id`.\n *\n * Astro's `glob` content loader (used by Nimbus's `docsCollection` /\n * `partialsCollection` factories) runs each path segment through\n * `github-slugger.slug()` and strips a trailing `/index`. That output is\n * what `entry.id` becomes inside `getCollection`, and it's what `params.slug`\n * substitutes into `[...slug].astro` routes. Anything that constructs a\n * URL from the raw filesystem path *must* apply the same normalization or\n * the URL won't match what Astro actually serves.\n *\n * Why mirror instead of asking Astro: framework URL builders run at page\n * render time (sidebar hrefs) and during the integration's\n * `astro:config:setup` (sitemap, llms.txt). At neither point do we have a\n * clean way to read Astro's resolved routes. The honest architectural fix\n * is to refactor those builders to consume the route manifest at\n * `astro:routes:resolved` (sitemap/llms) or via a build-emitted lookup\n * table (sidebar). Until that work lands, this helper keeps the URLs\n * correct.\n *\n * Mirror caveat: this matches `github-slugger.slug()` and the trailing-\n * /index strip — the documented public-library behaviors Astro inherits\n * — not Astro's private routing internals. If a user supplies a custom\n * `generateId` to the content loader, or a `data.slug` override in\n * frontmatter, this helper doesn't see it. Both are uncommon.\n */\n\nimport { slug as githubSlug } from \"github-slugger\";\n\n/** Canonicalize one entry id the way Astro's content layer does. */\nexport function canonicalSlug(entryId: string): string {\n const slugged = entryId\n .split(\"/\")\n .map((segment) => githubSlug(segment))\n .join(\"/\");\n if (slugged === \"index\") return \"\";\n return slugged.endsWith(\"/index\")\n ? slugged.slice(0, -\"/index\".length)\n : slugged;\n}\n\n/**\n * Compose the URL Astro serves for an entry at a given collection prefix.\n *\n * canonicalEntryUrl(\"\", \"WIP/index\") → \"/wip\"\n * canonicalEntryUrl(\"/blog\", \"first\") → \"/blog/first\"\n * canonicalEntryUrl(\"/v1\", \"index\") → \"/v1\"\n *\n * `prefix` is the collection mount path (`\"\"` for the primary `docs`\n * collection, `/blog` for a `blog` collection, `/v1` for a version\n * collection). Pass exactly what your framework prefix resolver returns.\n */\nexport function canonicalEntryUrl(prefix: string, entryId: string): string {\n const slug = canonicalSlug(entryId);\n if (slug === \"\") return prefix === \"\" ? \"/\" : prefix;\n return `${prefix}/${slug}`;\n}\n","// ---------------------------------------------------------------------------\n// sidebar.ts — Hybrid sidebar builder\n//\n// `buildSidebarTree` returns the un-scoped tree. The public `getSidebar`\n// helper applies `scopeToCurrentSection` on top so each page only renders\n// its own top-level section in the rail. Callers that need the full tree\n// (e.g. the section-tabs derivation in `deriveSidebarSections`) skip the\n// scoping step.\n//\n// Three sources for the tree:\n// 1. Config-defined: items array in docs.config.ts takes priority\n// 2. Auto-generated: `autogenerate: { directory }` scans filesystem\n// 3. Filesystem fallback: if no config items, build from all docs entries\n// ---------------------------------------------------------------------------\n\nimport type {\n SidebarBadge,\n SidebarConfig,\n SidebarConfigItem as ConfigItem,\n SidebarExternalLinkItem,\n SidebarGroupItem,\n SidebarItem,\n SidebarLinkItem,\n SidebarSection,\n} from \"../types.js\";\nimport { canonicalEntryUrl } from \"./astro-slug.js\";\nimport { isAbsoluteUrl, toBrowserHref, toRouteKey } from \"./url.js\";\n\n/** Minimal shape needed from content entries */\ninterface CollectionEntry {\n id: string;\n data: {\n title: string;\n draft?: boolean;\n /** Rewrites the sidebar link to point at this URL (external or\n * cross-section). Page still builds at its filesystem path. */\n external_link?: string;\n sidebar?: {\n order?: number;\n label?: string;\n badge?: SidebarBadge;\n hidden?: boolean;\n hideChildren?: boolean;\n /** Group-level overrides; consumed when this entry is the index\n * of a directory containing other entries. */\n group?: {\n label?: string;\n badge?: SidebarBadge;\n hideIndex?: boolean;\n };\n };\n };\n}\n\n// ---------------------------------------------------------------------------\n// Sort\n// ---------------------------------------------------------------------------\n\nconst sortKeyByItem = new WeakMap<SidebarItem, string>();\n\n// Tracks the leading link a `directory:` autogenerate emits for the\n// directory's own index page (the section \"landing\" slot). `overviewLabel`\n// relabels these; they aren't reachable via the group `_indexId` path\n// because the directory index renders as a plain leading link, not as a\n// group's child. Membership rides on the item object, so it survives the\n// post-build transforms (sort/scope) that reorder but don't recreate items.\nconst directoryIndexLinks = new WeakSet<SidebarLinkItem>();\n\nfunction sortSidebarItems(a: SidebarItem, b: SidebarItem): number {\n const orderDiff = a.order - b.order;\n if (orderDiff !== 0) return orderDiff;\n\n const keyA = sortKeyByItem.get(a) ?? (\"href\" in a ? a.href : a.label);\n const keyB = sortKeyByItem.get(b) ?? (\"href\" in b ? b.href : b.label);\n const keyDiff = keyA.localeCompare(keyB);\n if (keyDiff !== 0) return keyDiff;\n\n return a.type.localeCompare(b.type);\n}\n\n// ---------------------------------------------------------------------------\n// Entry index — shared utilities for looking up content entries\n// ---------------------------------------------------------------------------\n\nfunction buildEntryIndex(entries: CollectionEntry[]) {\n const visible = entries.filter((e) => !e.data.sidebar?.hidden);\n const byId = new Map<string, CollectionEntry>();\n for (const entry of visible) {\n byId.set(entry.id, entry);\n }\n\n const hasChildren = new Set<string>();\n for (const entry of visible) {\n const parts = entry.id.split(\"/\");\n for (let i = 1; i < parts.length; i++) {\n hasChildren.add(parts.slice(0, i).join(\"/\"));\n }\n }\n\n return { visible, byId, hasChildren };\n}\n\n// ---------------------------------------------------------------------------\n// Link/group creation from content entries\n// ---------------------------------------------------------------------------\n\n/**\n * Compose a final href for an entry. `hrefPrefix` is the collection mount\n * path (e.g. `/api`). Runs through `canonicalEntryUrl` so the href\n * matches what Astro serves (lowercase + folder-index strip — see\n * `_internal/astro-slug.ts` for the why and known caveats), then through\n * `toBrowserHref` so the rendered link is the trailing-slash form static\n * hosts serve directly (no 307 round-trip on click).\n */\nfunction joinHref(hrefPrefix: string, entryId: string): string {\n // hrefPrefix is \"\" for root-mounted collections, \"/api\" for prefixed ones.\n // Drop a trailing slash on the prefix to avoid `/api//foo`.\n const prefix = hrefPrefix.replace(/\\/$/, \"\");\n return toBrowserHref(canonicalEntryUrl(prefix, entryId));\n}\n\nfunction createLink(\n entry: CollectionEntry,\n currentPath: string,\n hrefPrefix = \"\",\n): SidebarLinkItem | SidebarExternalLinkItem {\n const internalHref = joinHref(hrefPrefix, entry.id);\n const badge = entry.data.draft\n ? (entry.data.sidebar?.badge ?? { text: \"Draft\", variant: \"warning\" })\n : entry.data.sidebar?.badge;\n const label = entry.data.sidebar?.label ?? entry.data.title;\n const order = entry.data.sidebar?.order ?? Number.MAX_VALUE;\n\n // `external_link` rewrites the sidebar destination. The page itself\n // still builds at `entry.id`; only the sidebar entry is redirected.\n // Two flavours: absolute URLs (`https://...`, protocol-relative) get\n // an external-link node; cross-section absolute paths (`/workers/...`)\n // get a link that points at the override but stays internal-link-shaped\n // for active-state matching.\n const externalLink = entry.data.external_link;\n if (externalLink) {\n if (isAbsoluteUrl(externalLink)) {\n const ext: SidebarExternalLinkItem = {\n type: \"external\",\n label,\n href: externalLink,\n badge,\n order,\n };\n sortKeyByItem.set(ext, entry.id);\n return ext;\n }\n // Internal cross-section redirect — keep link-shaped so the sidebar\n // doesn't render an external icon, but point at the override path.\n const link: SidebarLinkItem = {\n type: \"link\",\n label,\n href: toBrowserHref(externalLink),\n isCurrent: false, // cross-section: the override path isn't this page\n badge,\n order,\n };\n sortKeyByItem.set(link, entry.id);\n return link;\n }\n\n const link: SidebarLinkItem = {\n type: \"link\",\n label,\n href: internalHref,\n isCurrent: toRouteKey(currentPath) === toRouteKey(internalHref),\n badge,\n order,\n };\n\n sortKeyByItem.set(link, entry.id);\n return link;\n}\n\n// ---------------------------------------------------------------------------\n// Filesystem tree builder (used for autogenerate + fallback)\n// ---------------------------------------------------------------------------\n\nfunction buildFilesystemTree(\n entries: CollectionEntry[],\n currentPath: string,\n directory?: string,\n hrefPrefix = \"\",\n): SidebarItem[] {\n const { visible, byId, hasChildren } = buildEntryIndex(entries);\n\n // Filter to entries under the target directory\n const scoped = directory ? visible.filter((e) => e.id === directory || e.id.startsWith(`${directory}/`)) : visible;\n\n function buildLevel(parentPath: string): SidebarItem[] {\n const result: SidebarItem[] = [];\n const groupsAtLevel = new Map<string, SidebarGroupItem>();\n\n // Autogenerate-with-directory case: include the directory's own\n // index page as a child link of the autogen output. Without this,\n // `{ autogenerate: { directory: \"d1\" } }` silently drops `d1/index.mdx`\n // because the entry's id is `\"d1\"` (not `\"d1/...\"`) and the\n // per-entry path check below skips it. CF's \"Overview\" entry at the\n // top of the d1 sidebar comes from this — same shape Starlight produces.\n //\n // The leading link sorts by its own `sidebar.order` against siblings,\n // so authors can put \"Overview\" anywhere in the rail by setting an\n // order value on the directory's `index.mdx`.\n if (directory && parentPath === directory) {\n const dirIndex = byId.get(directory);\n if (dirIndex) {\n const indexLink = createLink(dirIndex, currentPath, hrefPrefix);\n // Mark as the directory's landing link so `overviewLabel` can\n // relabel it (see applyOverviewLabel). Skip external overrides, and\n // skip when the page sets an explicit `sidebar.label` — an author's\n // own label always wins over the convention.\n if (indexLink.type === \"link\" && !dirIndex.data.sidebar?.label) {\n directoryIndexLinks.add(indexLink);\n }\n result.push(indexLink);\n }\n }\n\n for (const entry of scoped) {\n if (entry.id === \"index\") continue;\n // Already pushed above as the leading directory-index link.\n if (entry.id === directory) continue;\n\n const id = entry.id;\n const relativeTo = directory ?? \"\";\n const relativeId = relativeTo ? (id === relativeTo ? \"\" : id.slice(relativeTo.length + 1)) : id;\n\n // Skip if this entry doesn't belong at this level\n if (parentPath === \"\") {\n if (!relativeId || relativeId.includes(\"/\") === false) {\n // Top-level entry relative to scope\n if (!relativeId) continue; // directory index, handled as group\n\n if (hasChildren.has(id)) {\n if (!groupsAtLevel.has(id)) {\n const group = createGroupFromEntry(id, entry, currentPath, byId);\n groupsAtLevel.set(id, group);\n result.push(group);\n }\n } else {\n result.push(createLink(entry, currentPath, hrefPrefix));\n }\n } else {\n // Multi-segment — belongs under first segment group.\n // `[0]!`: `String.split` always returns ≥1 element.\n const firstSeg = relativeId.split(\"/\")[0]!;\n const topDir = directory ? `${directory}/${firstSeg}` : firstSeg;\n if (!groupsAtLevel.has(topDir)) {\n const indexEntry = byId.get(topDir);\n const group = createGroupFromEntry(topDir, indexEntry, currentPath, byId);\n groupsAtLevel.set(topDir, group);\n result.push(group);\n }\n }\n } else {\n if (!id.startsWith(`${parentPath}/`)) continue;\n const remainder = id.slice(parentPath.length + 1);\n const remainderParts = remainder.split(\"/\");\n\n if (remainderParts.length === 1) {\n if (hasChildren.has(id)) {\n if (!groupsAtLevel.has(id)) {\n const group = createGroupFromEntry(id, entry, currentPath, byId);\n groupsAtLevel.set(id, group);\n result.push(group);\n }\n } else {\n result.push(createLink(entry, currentPath, hrefPrefix));\n }\n } else {\n const nextDir = `${parentPath}/${remainderParts[0]}`;\n if (!groupsAtLevel.has(nextDir)) {\n const indexEntry = byId.get(nextDir);\n const group = createGroupFromEntry(nextDir, indexEntry, currentPath, byId);\n groupsAtLevel.set(nextDir, group);\n result.push(group);\n }\n }\n }\n }\n\n // Recursively build children for each group\n for (const [groupPath, group] of groupsAtLevel) {\n const nestedChildren = buildLevel(groupPath);\n group.children = [...group.children, ...nestedChildren].sort(sortSidebarItems);\n\n // Inherit the lowest child's order only when the group has NO\n // explicit order from its own index. With an explicit\n // `sidebar.order` on the directory's `index.mdx`, the user is\n // declaring where the group ranks among its siblings — that\n // intent must not be overridden by an inner page that happens to\n // sort to 1 within the group. Without this guard, a section like\n // \"Configuration\" (`sidebar.order: 9` on its index) silently\n // collapses to whatever its first child happens to be.\n if (group.order === Number.MAX_VALUE && group.children.length > 0) {\n group.order = Math.min(...group.children.map((item) => item.order));\n }\n }\n\n return result.sort(sortSidebarItems);\n }\n\n function createGroupFromEntry(\n dirPath: string,\n indexEntry: CollectionEntry | undefined,\n currentPath: string,\n _byId: Map<string, CollectionEntry>,\n ): SidebarGroupItem {\n const dirSegment = dirPath.split(\"/\").pop()!;\n // Group-level overrides from `sidebar.group.{label,badge,hideIndex}`\n // take precedence over the simpler `sidebar.label` / `sidebar.badge`\n // forms — `sidebar.label` is the LINK label (used when the index\n // page is rendered as a child link), `sidebar.group.label` is the\n // GROUP label. The two can differ: a section's index might want to\n // appear as \"Overview\" in the sidebar (link label) while the group\n // itself displays as \"Configuration\" (group label).\n const groupConfig = indexEntry?.data.sidebar?.group;\n // Group label resolution order:\n // 1. `sidebar.group.label` — explicit group-level override.\n // 2. `sidebar.label` — page-level label override (also used as\n // the link label when the index is rendered as a child).\n // 3. The index page's `title` — gives \"Workers Binding API\"\n // instead of \"Worker Api\" (from the directory name), matching\n // how Starlight, Fumadocs, and Fern derive group labels.\n // 4. `formatLabel(dirSegment)` — fallback for directories without\n // an index page (the formatLabel-from-dirname rules apply).\n const groupLabel =\n groupConfig?.label ??\n indexEntry?.data.sidebar?.label ??\n indexEntry?.data.title ??\n formatLabel(dirSegment);\n const groupOrder = indexEntry?.data.sidebar?.order ?? Number.MAX_VALUE;\n const groupBadge = groupConfig?.badge ?? indexEntry?.data.sidebar?.badge;\n\n // Structural separation (matches Fumadocs / Fern / Docusaurus):\n // - If the directory has an `index.mdx`, the group label IS the\n // link to that page (`indexHref` below). The index is NEVER\n // added as a child of the group — there's only one slot for the\n // landing page, no duplication possible at the data layer.\n // - If the directory has no index, the group label is a\n // non-interactive header and only its children navigate.\n //\n // This replaces the older \"include the index as a child link\"\n // behavior, plus the `sidebar.group.hideIndex` opt-in and the\n // labels-match auto-suppression heuristic. None of those are needed:\n // the data model itself disallows the duplicate.\n let indexHref: string | undefined;\n let indexIsCurrent = false;\n let indexIsExternal = false;\n if (indexEntry) {\n const externalLink = indexEntry.data.external_link;\n if (externalLink !== undefined) {\n if (isAbsoluteUrl(externalLink)) {\n // Off-site override — group label becomes a `target=\"_blank\"`\n // link in the renderer. Active-state matching is suppressed\n // (an absolute URL can't be \"the current page\").\n indexHref = externalLink;\n indexIsExternal = true;\n } else {\n // Cross-section relative path — internal-link-shaped (no\n // `target=\"_blank\"`), but the override path isn't this\n // page either, so active-state stays false.\n indexHref = toBrowserHref(externalLink);\n }\n } else {\n indexHref = joinHref(hrefPrefix, indexEntry.id);\n indexIsCurrent = toRouteKey(currentPath) === toRouteKey(indexHref);\n }\n }\n\n const group: SidebarGroupItem = {\n type: \"group\",\n label: groupLabel,\n order: groupOrder,\n badge: groupBadge,\n children: [],\n _indexId: indexEntry?.id,\n indexHref,\n indexIsCurrent: indexIsCurrent || undefined,\n indexIsExternal: indexIsExternal || undefined,\n };\n\n sortKeyByItem.set(group, dirPath);\n return group;\n }\n\n // For directory-scoped autogenerate, just build the children level\n if (directory) {\n return buildLevel(directory);\n }\n\n return buildLevel(\"\");\n}\n\n// ---------------------------------------------------------------------------\n// Config-driven builder\n// ---------------------------------------------------------------------------\n\nfunction resolveConfigItems(\n configItems: ConfigItem[],\n entriesByCollection: Record<string, CollectionEntry[]>,\n primaryCollection: string,\n currentPath: string,\n orderStart: number = 0,\n primaryPrefix: string = \"\",\n): SidebarItem[] {\n const primaryEntries = entriesByCollection[primaryCollection] ?? [];\n const { byId } = buildEntryIndex(primaryEntries);\n const result: SidebarItem[] = [];\n\n for (let i = 0; i < configItems.length; i++) {\n const item = configItems[i];\n // Guard the `T | undefined` from indexed access. `for...of` would lose\n // `i` which we need for `order`; the guard is no-cost — sparse arrays\n // don't occur here at runtime.\n if (!item) continue;\n const order = orderStart + i;\n\n if (typeof item === \"string\") {\n // Bare slug references resolve against the primary collection only.\n // Cross-collection links use the `{ label, link }` form with an\n // explicit href.\n const entry = byId.get(item);\n if (entry) {\n const link = createLink(entry, currentPath, primaryPrefix);\n link.order = order;\n result.push(link);\n } else {\n // Warn but don't crash — might be a typo\n console.warn(\n `[sidebar] Page \"${item}\" referenced in config but not found in primary collection \"${primaryCollection}\"`,\n );\n }\n } else if (\"link\" in item) {\n // External iff the URL has a scheme (`https:`, `mailto:`, …) or is\n // protocol-relative. A bare relative path like `\"cli\"` isn't a\n // valid internal link either (the internal branch assumes an\n // absolute `/path` shape), so route those through external\n // rendering — the browser resolves them against the current page.\n const isExternal = isAbsoluteUrl(item.link) || !item.link.startsWith(\"/\");\n if (isExternal) {\n const extLink: SidebarExternalLinkItem = {\n type: \"external\",\n label: item.label,\n href: item.link,\n badge: item.badge,\n order,\n };\n result.push(extLink);\n } else {\n // Internal link with custom label. Emit the trailing-slash\n // browser-href form; use the slashless route key for matching\n // and primary-collection lookup.\n const href = toBrowserHref(item.link);\n const routeKey = toRouteKey(item.link);\n\n // Best-effort validation: warn only if the link looks like a\n // primary-collection slug and doesn't resolve. Cross-collection\n // links (e.g. `/api/users`) intentionally bypass this check.\n const lookup = routeKey.slice(1);\n const looksLikePrimaryRoot = lookup !== \"\" && !lookup.includes(\"/\");\n if (looksLikePrimaryRoot && !byId.has(lookup)) {\n console.warn(\n `[sidebar] Internal link \"${item.link}\" (label: \"${item.label}\") does not match any entry in primary collection \"${primaryCollection}\"`,\n );\n }\n\n const link: SidebarLinkItem = {\n type: \"link\",\n label: item.label,\n href,\n isCurrent: toRouteKey(currentPath) === routeKey,\n badge: item.badge,\n order,\n };\n result.push(link);\n }\n } else if (\"autogenerate\" in item) {\n // Two flavours: directory-within-primary, or named collection.\n let autoItems: SidebarItem[];\n // The URL prefix this autogenerated group \"lives at\". Only set\n // for **non-primary** collections — those are the ones mounted at\n // `/<name>` where sites typically build a hand-rolled landing\n // page. `deriveSidebarSections` uses this so the header section\n // tab links to that landing (`/components`) instead of the first\n // child entry (`/components/accordion`). The primary collection\n // (docs at root) and directory-scoped autogenerates keep the\n // first-link behavior because their first link IS the natural\n // entry point.\n let groupPrefix: string | undefined;\n if (\"collection\" in item.autogenerate) {\n const collectionName = item.autogenerate.collection;\n const collectionEntries = entriesByCollection[collectionName];\n if (!collectionEntries) {\n console.warn(\n `[sidebar] autogenerate references collection \"${collectionName}\" which is not registered in nimbus.config.collections; skipping`,\n );\n autoItems = [];\n } else {\n // Resolve the mount prefix. The primary collection uses the\n // caller-supplied `primaryPrefix` (`\"\"` for the default `docs`\n // collection; `/<v>` when versioning rewrites the primary to\n // a version collection). Other collections default to\n // `/<name>` unless explicitly overridden.\n const explicit = item.autogenerate.prefix;\n const isPrimary = collectionName === primaryCollection;\n const prefix = explicit ?? (isPrimary ? primaryPrefix : `/${collectionName}`);\n autoItems = buildFilesystemTree(\n collectionEntries,\n currentPath,\n undefined,\n prefix,\n );\n // Non-primary only — primary collection sections keep\n // first-link behavior (see comment on `groupPrefix` above).\n if (!isPrimary && prefix !== \"\") {\n groupPrefix = prefix;\n }\n }\n } else {\n // directory-scoped autogenerate operates on the primary collection\n autoItems = buildFilesystemTree(\n primaryEntries,\n currentPath,\n item.autogenerate.directory,\n primaryPrefix,\n );\n }\n\n // If the config item has a label, wrap in a group\n if (item.label) {\n const group: SidebarGroupItem = {\n type: \"group\",\n label: item.label,\n order,\n collapsed: item.collapsed,\n badge: item.badge,\n children: autoItems,\n _prefix: groupPrefix,\n };\n result.push(group);\n } else {\n // Inline autogenerate (inside a manual group's items)\n if (item.collapsed !== undefined) {\n for (const ai of autoItems) {\n if (ai.type === \"group\") {\n ai.collapsed = item.collapsed;\n }\n }\n }\n result.push(...autoItems);\n }\n } else if (\"items\" in item) {\n // Manual group\n const children = resolveConfigItems(\n item.items,\n entriesByCollection,\n primaryCollection,\n currentPath,\n 0,\n primaryPrefix,\n );\n const group: SidebarGroupItem = {\n type: \"group\",\n label: item.label,\n order,\n collapsed: item.collapsed,\n badge: item.badge,\n children,\n };\n result.push(group);\n }\n }\n\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Scoping — filter to current top-level section\n// ---------------------------------------------------------------------------\n\n/**\n * Return only the children of the top-level group containing the current\n * page. Falls back to the full tree if the current page isn't inside any\n * group (e.g. a top-level link, or a path that doesn't resolve).\n *\n * Under structural separation the group's landing page lives on\n * `indexHref` rather than in `children`. When the active group has a\n * landing, we prepend a synthetic link (or external item) for it so\n * the section landing remains reachable from the scoped rail — without\n * this, on `/api/` the rail shows `/api/users`, `/api/orders`, … but\n * the section's own overview page would be missing from the rail.\n */\nexport function scopeToCurrentSection(items: SidebarItem[], currentPath: string): SidebarItem[] {\n const currentSegment = currentPath.split(\"/\").filter(Boolean)[0];\n if (!currentSegment) return items;\n\n for (const item of items) {\n if (item.type === \"group\") {\n if (hasActivePage(item, currentPath)) {\n if (!item.indexHref) return item.children;\n // Synthesize a leading entry for the section landing. External\n // landings render as `external`; internal landings render as\n // `link` with the group's own active state. The synthetic entry\n // uses `order: -Infinity` so it sits ahead of every child\n // regardless of declared order — the section landing is\n // conventionally the \"Overview\" at the top of the rail.\n const lead: SidebarItem = item.indexIsExternal\n ? {\n type: \"external\",\n label: item.label,\n href: item.indexHref,\n badge: item.badge,\n order: Number.NEGATIVE_INFINITY,\n }\n : {\n type: \"link\",\n label: item.label,\n href: item.indexHref,\n isCurrent: item.indexIsCurrent === true,\n badge: item.badge,\n order: Number.NEGATIVE_INFINITY,\n };\n return [lead, ...item.children];\n }\n }\n }\n\n return items;\n}\n\nfunction hasActivePage(item: SidebarItem, currentPath: string): boolean {\n if (item.type === \"link\") return item.isCurrent === true;\n if (item.type === \"external\") return false;\n // Group landing pages (the directory's `index.mdx`) live on the group\n // itself via `indexIsCurrent`, not as a child link — so a group whose\n // own landing is the current route counts as \"active\" even when no\n // descendant is. Without this check, `scope: \"section\"` falls back to\n // the full tree on directory-index pages (no group claims them).\n if (item.indexIsCurrent === true) return true;\n return item.children.some((child) => hasActivePage(child, currentPath));\n}\n\n// ---------------------------------------------------------------------------\n// Section derivation — top-level groups as nav sections\n// ---------------------------------------------------------------------------\n\n/**\n * Derive one section per top-level group in the sidebar tree, scoped\n * to *cross-collection* navigation. Used by `Header.astro` to render\n * the section tab strip.\n *\n * Filter rule: only groups whose `_prefix` is set become sections. The\n * `_prefix` field is populated exclusively by `autogenerate: { collection: <non-primary> }`\n * config items (see `resolveConfigItems`). This is the structural\n * signal that the group represents a *separate collection mounted at a\n * URL prefix* — e.g. `Components` mounted at `/components/` —\n * rather than a sub-directory of the primary docs collection.\n *\n * Why this matters: under the previous unconditional behavior, every\n * top-level group in the sidebar (including `wip/`, `lab/`, and other\n * docs-collection subdirectories) was promoted to a header tab. The\n * header rail is meant for \"other collections\" navigation, not for\n * sub-sections of the default collection — those belong in the\n * sidebar's own collapsible tree.\n *\n * Caller must pass the *un-scoped* tree (the result of\n * `buildSidebarTree`, not `getSidebar`); otherwise only the current\n * section's children are visible and the derivation collapses to a\n * single item.\n */\nexport function deriveSidebarSections(items: SidebarItem[]): SidebarSection[] {\n return items.flatMap((item) => {\n if (item.type !== \"group\") return [];\n // Only cross-collection groups become header tabs. See the filter\n // rationale in the function header.\n if (!item._prefix) return [];\n // Reuse `flattenSidebar` so a directory-index page (group with\n // `indexHref` + `indexIsCurrent`) participates in active-state\n // detection. Without the synthetic-index handling, a section tab\n // wouldn't highlight on a page like `/components/accordion/`\n // (where \"accordion\" is a group landing) because no descendant\n // *link* is current — the current page lives on the group itself.\n const links = flattenSidebar(item.children);\n if (links.length === 0) return [];\n return [\n {\n label: item.label,\n // `_prefix` is the collection's mount path (e.g. `/components`).\n // Run through `toBrowserHref` so section tabs link directly to\n // the trailing-slash URL static hosts serve.\n href: toBrowserHref(item._prefix),\n isActive: links.some((link) => link.isCurrent === true),\n },\n ];\n });\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Build the un-scoped sidebar tree from config + content entries.\n *\n * `entriesByCollection` is a name → entries map covering every\n * collection the user listed in `NimbusConfig.collections`. The\n * `primaryCollection` (first entry of that list) is what\n * filesystem-fallback, `directory:` autogenerate, and bare-slug\n * references read from. Other collections only contribute when an\n * explicit `autogenerate: { collection: \"<name>\" }` references them.\n *\n * - If config has items: resolve them (config takes priority)\n * - If config has no items: auto-generate from primary collection\n *\n * Always returns the full top-level tree. Scoping (showing only the\n * current section's children in the rail) is applied by the public\n * `getSidebar` helper via `scopeToCurrentSection`.\n */\nexport function buildSidebarTree(\n entriesByCollection: Record<string, CollectionEntry[]>,\n primaryCollection: string,\n currentPath: string,\n config?: SidebarConfig,\n /**\n * URL prefix for entries in the primary collection. Default `\"\"`\n * (root) — matches the convention for the `docs` collection. When\n * building a version-aware sidebar (primary is `docs-<v>`), the\n * caller passes the version's URL prefix (e.g. `\"/v0\"`) so the\n * generated hrefs land at the right URLs. Without this, version\n * pages would link to root paths like `/getting-started` instead of\n * `/v0/getting-started`.\n */\n primaryPrefix = \"\",\n): SidebarItem[] {\n const primaryEntries = entriesByCollection[primaryCollection] ?? [];\n let items: SidebarItem[];\n\n if (config?.items && config.items.length > 0) {\n // Config-driven\n items = resolveConfigItems(\n config.items,\n entriesByCollection,\n primaryCollection,\n currentPath,\n 0,\n primaryPrefix,\n );\n } else {\n // Filesystem fallback — primary collection only. Cross-collection\n // sidebars require explicit config items.\n items = buildFilesystemTree(primaryEntries, currentPath, undefined, primaryPrefix);\n }\n\n // Apply hideChildren — pool entries across all collections so a\n // non-primary `autogenerate: { collection }` group's index lookup\n // resolves correctly.\n const pooledEntries = Object.values(entriesByCollection).flat();\n items = processHideChildren(items, pooledEntries);\n\n // Opt-in: relabel each group's first link to \"Overview\" (or a custom\n // string). Applied after hideChildren so the relabel sticks on the\n // remaining first child rather than on a link that's about to be\n // collapsed away. No-op when `overviewLabel` is unset/false.\n if (config?.overviewLabel) {\n const label =\n typeof config.overviewLabel === \"string\" ? config.overviewLabel : \"Overview\";\n items = applyOverviewLabel(items, label);\n }\n\n // Opt-in: default every group to collapsed. Walks the tree and sets\n // `collapsed: true` on any group that doesn't already declare its\n // own value (so per-item `collapsed: false` overrides survive). The\n // SidebarGroup renderer's `hasActive` check still opens the group\n // that contains the current page — collapsed defaults yield to\n // active state.\n if (config?.defaultCollapsed) {\n applyDefaultCollapsed(items);\n }\n\n return items;\n}\n\n/**\n * Walk every group and stamp `collapsed: true` where no explicit value\n * was set. Used by the `sidebar.defaultCollapsed` opt-in. Recurses into\n * nested children so a deeply-structured tree collapses at every level.\n */\nfunction applyDefaultCollapsed(items: SidebarItem[]): void {\n for (const item of items) {\n if (item.type === \"group\") {\n if (item.collapsed === undefined) {\n item.collapsed = true;\n }\n applyDefaultCollapsed(item.children);\n }\n }\n}\n\n/**\n * @deprecated Effective no-op under structural separation. Pre-2026\n * Nimbus rendered the group's index as the first child link and used\n * this to rename that link to \"Overview\". The index is now exposed via\n * `SidebarGroupItem.indexHref` (the group label IS the link), so there's\n * no first-child index to rename. The function is kept so older configs\n * that set `sidebar.overviewLabel` don't blow up; future major can drop it.\n *\n * Renamed only when the first link IS the group's index (matched via the\n * `sortKeyByItem` WeakMap) — under structural separation that condition\n * never holds, so this silently returns the input unchanged.\n */\nfunction applyOverviewLabel(items: SidebarItem[], label: string): SidebarItem[] {\n for (const item of items) {\n // A `directory:` autogenerate's landing link — rendered as a plain\n // leading link at any nesting level (incl. directly under a config\n // group), so relabel it wherever it surfaces.\n if (item.type === \"link\" && directoryIndexLinks.has(item)) {\n item.label = label;\n } else if (item.type === \"group\") {\n if (item._indexId) {\n const firstLink = item.children.find(\n (child): child is SidebarLinkItem => child.type === \"link\",\n );\n if (firstLink && sortKeyByItem.get(firstLink) === item._indexId) {\n firstLink.label = label;\n }\n }\n applyOverviewLabel(item.children, label);\n }\n }\n return items;\n}\n\n/**\n * Process `sidebar.hideChildren: true` on a group's index entry:\n * replace the entire group with a single flat link to the index page.\n *\n * Under structural separation the group already exposes its landing\n * page via `indexHref` and never adds the index as a child, so this\n * function reads `indexHref` directly when collapsing — no need to\n * search through `children` for an index link that isn't there.\n */\nfunction processHideChildren(items: SidebarItem[], entries: CollectionEntry[]): SidebarItem[] {\n const entryById = new Map<string, CollectionEntry>();\n for (const e of entries) entryById.set(e.id, e);\n\n function process(items: SidebarItem[]): SidebarItem[] {\n const result: SidebarItem[] = [];\n for (const item of items) {\n if (item.type !== \"group\") {\n result.push(item);\n continue;\n }\n\n // Collapse to a single link when the index page declares\n // `sidebar.hideChildren: true`. Requires the group to have a\n // landing page (`indexHref`) — otherwise there's nothing to\n // collapse to. External landings (`external_link` resolved to an\n // absolute URL) collapse to a `SidebarExternalLinkItem` so the\n // renderer keeps the off-site icon + `target=\"_blank\"` treatment;\n // collapsing them to an internal `link` would drop both.\n if (item._indexId && item.indexHref) {\n const entry = entryById.get(item._indexId);\n if (entry?.data.sidebar?.hideChildren) {\n const replacement: SidebarItem = item.indexIsExternal\n ? {\n type: \"external\",\n label: item.label,\n href: item.indexHref,\n badge: item.badge,\n order: item.order,\n }\n : {\n type: \"link\",\n label: item.label,\n href: item.indexHref,\n isCurrent: item.indexIsCurrent === true,\n badge: item.badge,\n order: item.order,\n };\n sortKeyByItem.set(replacement, item._indexId);\n result.push(replacement);\n continue;\n }\n }\n\n // Recurse into children\n item.children = process(item.children);\n result.push(item);\n }\n return result;\n }\n\n return process(items);\n}\n\n/**\n * Walk a sidebar config items array (recursively, through nested\n * `items:` groups) and collect every collection name referenced by an\n * `autogenerate: { collection: ... }` entry.\n *\n * The framework uses this to figure out which collections to load for\n * the sidebar — there's no separate `collections: string[]` config\n * field. The primary collection (`docs`) is always included by the\n * caller; this helper returns only the *extra* names referenced by\n * sidebar items.\n */\nexport function collectSidebarCollectionRefs(\n items: ConfigItem[] | undefined,\n): string[] {\n if (!items) return [];\n const found = new Set<string>();\n function walk(items: ConfigItem[]): void {\n for (const item of items) {\n if (typeof item === \"string\") continue;\n if (\"autogenerate\" in item && \"collection\" in item.autogenerate) {\n found.add(item.autogenerate.collection);\n } else if (\"items\" in item) {\n walk(item.items);\n }\n }\n }\n walk(items);\n return [...found];\n}\n\n/**\n * Flatten sidebar tree into a list of links (for pagination).\n *\n * Groups with a landing page (`indexHref` set; structural-separation\n * model) contribute a synthetic link at the group's position so that\n * `getPrevNext` includes the directory-index page in the prev/next\n * walk. Without this, navigating *to* or *from* a group's index page\n * skips it entirely (e.g. on `/api/`, \"prev\" jumps over the section\n * landing to the previous group's last child).\n *\n * External landing pages (`indexIsExternal`) are excluded — they're\n * off-site destinations, not part of the in-site pagination ring.\n */\nexport function flattenSidebar(items: SidebarItem[]): SidebarLinkItem[] {\n const flat: SidebarLinkItem[] = [];\n for (const item of items) {\n if (item.type === \"link\") {\n flat.push(item);\n } else if (item.type === \"group\") {\n if (item.indexHref && !item.indexIsExternal) {\n flat.push({\n type: \"link\",\n label: item.label,\n href: item.indexHref,\n isCurrent: item.indexIsCurrent === true,\n badge: item.badge,\n order: item.order,\n });\n }\n flat.push(...flattenSidebar(item.children));\n }\n }\n return flat;\n}\n\nfunction formatLabel(segment: string): string {\n return segment.replace(/-/g, \" \").replace(/\\b\\w/g, (char) => char.toUpperCase());\n}\n\n// ---------------------------------------------------------------------------\n// Sidebar hash — deterministic hash of sidebar structure for state invalidation.\n// Uses DJBX33A (same as Starlight). When the hash changes (pages added/removed,\n// labels renamed), persisted sidebar state is discarded.\n// ---------------------------------------------------------------------------\n\nfunction buildSidebarIdentity(items: SidebarItem[]): string {\n return items\n .flatMap((item) =>\n item.type === \"group\"\n ? item.label + buildSidebarIdentity(item.children)\n : item.label + (\"href\" in item ? item.href : \"\"),\n )\n .join(\"\");\n}\n\n/** Hash the sidebar structure into a short string for sessionStorage invalidation. */\nexport function sidebarHash(items: SidebarItem[]): string {\n const identity = buildSidebarIdentity(items);\n let hash = 0;\n for (let i = 0; i < identity.length; i++) {\n hash = (hash << 5) - hash + identity.charCodeAt(i);\n }\n return (hash >>> 0).toString(36).padStart(7, \"0\");\n}\n","/**\n * Collection-mount conventions — one source of truth for \"what URL prefix\n * does collection X serve at?\".\n *\n * Shared between `index.ts` (which uses it for `getIndexedEntries`,\n * `getDocsPageProps`, etc.) and `lint/site-model.ts` (where\n * `findDuplicateRoutes` needs it to detect cross-collection URL\n * collisions). Keeping the function here prevents the duplicate-slug\n * validator from drifting out of sync with the actual routing.\n */\n\n/** Primary collection name — mounted at the site root with no prefix. */\nexport const PRIMARY_COLLECTION = \"docs\";\n\nexport interface VersionInfo {\n /** Non-current version slugs (`docs-<slug>` collections mount at `/<slug>`). */\n others: readonly string[];\n}\n\n/**\n * Resolve the URL-prefix segment for a given collection name.\n *\n * 1. Primary `docs` collection mounts at root → returns `\"\"`.\n * 2. With `versions` configured, a `docs-<slug>` collection whose slug\n * appears in `versions.others` mounts under `/<slug>` (the version\n * label, not the collection id).\n * 3. Any other collection (`api`, `blog`, …) mounts at `/<collection>`.\n *\n * Returned shape: empty string OR `/<segment>` with leading slash, no\n * trailing slash. Callers append `/<entryId>` or `/index.md`.\n */\nexport function collectionMountPrefix(\n collection: string,\n versions?: VersionInfo | null,\n): string {\n if (collection === PRIMARY_COLLECTION) return \"\";\n if (versions && collection.startsWith(\"docs-\")) {\n const slug = collection.slice(\"docs-\".length);\n if (versions.others.includes(slug)) return `/${slug}`;\n }\n return `/${collection}`;\n}\n\n/**\n * Resolve the URL-safe label a collection is referenced by — the segment\n * that appears in URLs and section headers. For version collections this\n * is the manifest's short slug; for everything else it's the collection id.\n */\nexport function collectionLabel(\n collection: string,\n versions?: VersionInfo | null,\n): string {\n if (collection === PRIMARY_COLLECTION) return collection;\n if (versions && collection.startsWith(\"docs-\")) {\n const slug = collection.slice(\"docs-\".length);\n if (versions.others.includes(slug)) return slug;\n }\n return collection;\n}\n","/**\n * MDX → markdown transform for AI-readable static routes.\n *\n * This intentionally starts small and dependency-free: it operates on the\n * raw MDX body that Astro's content layer exposes and maps the starter's\n * default components to plain markdown equivalents. The route that calls this\n * lives in user code, so replacing or bypassing this transformer is a one-line\n * edit.\n */\n\nexport interface MarkdownComponentRenderContext {\n name: string;\n attrs: Record<string, string | boolean>;\n children: string;\n}\n\nexport type MarkdownComponentRenderer = (\n context: MarkdownComponentRenderContext,\n) => string;\n\nexport interface RenderEntryAsMarkdownOptions {\n /**\n * Override how specific MDX components are rendered. Keys are component\n * names (e.g. `Aside`, `Tabs`, `PackageManagers`).\n */\n componentMap?: Record<string, MarkdownComponentRenderer>;\n /** Strip YAML frontmatter if the raw body includes it. Default: true. */\n stripFrontmatter?: boolean;\n}\n\ninterface MarkdownEntry {\n body?: string;\n}\n\nfunction protectCode(markdown: string): { markdown: string; restore: (value: string) => string } {\n const protectedChunks: string[] = [];\n function store(chunk: string): string {\n const token = `@@NIMBUS_MD_CODE_${protectedChunks.length}@@`;\n protectedChunks.push(chunk.startsWith(\"```\") ? chunk.replace(/\\n[ \\t]{4}/g, \"\\n\") : chunk);\n return token;\n }\n\n // Fenced blocks first so inline-code protection doesn't touch backticks inside.\n let next = markdown.replace(/```[\\s\\S]*?```/g, store);\n next = next.replace(/`[^`\\n]+`/g, store);\n\n return {\n markdown: next,\n restore(value: string): string {\n return value.replace(/@@NIMBUS_MD_CODE_(\\d+)@@/g, (_match, index: string) =>\n protectedChunks[Number(index)] ?? \"\",\n );\n },\n };\n}\n\nfunction parseAttrs(raw = \"\"): Record<string, string | boolean> {\n const attrs: Record<string, string | boolean> = {};\n const re = /([A-Za-z_:][\\w:.-]*)(?:\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|\\{([^}]*)\\}|([^\\s>]+)))?/g;\n for (const match of raw.matchAll(re)) {\n const [, name, dq, sq, expr, bare] = match;\n if (!name) continue;\n attrs[name] = dq ?? sq ?? expr?.trim() ?? bare ?? true;\n }\n return attrs;\n}\n\nfunction cleanChildren(children: string): string {\n return children\n .replace(/^\\s+/g, \"\")\n .replace(/\\s+$/g, \"\")\n .replace(/\\n[ \\t]+/g, \"\\n\");\n}\n\nfunction blockquote(body: string): string {\n return body\n .split(\"\\n\")\n .map((line) => (line ? `> ${line}` : \">\"))\n .join(\"\\n\");\n}\n\nfunction asTitle(value: string | boolean | undefined, fallback: string): string {\n return typeof value === \"string\" && value.trim() ? value.trim() : fallback;\n}\n\nfunction renderPackageManagers(attrs: Record<string, string | boolean>): string {\n const pkg = typeof attrs.pkg === \"string\" ? attrs.pkg : undefined;\n const args = typeof attrs.args === \"string\" ? attrs.args : undefined;\n const type = typeof attrs.type === \"string\" ? attrs.type : \"install\";\n const dev = attrs.dev === true || attrs.dev === \"true\";\n\n let commands: string[];\n if (type === \"run\") {\n const command = args ?? \"dev\";\n commands = [\n `npm run ${command}`,\n `pnpm ${command}`,\n `yarn ${command}`,\n `bun run ${command}`,\n ];\n } else if (type === \"exec\") {\n const command = args ?? pkg ?? \"\";\n commands = [\n `npx ${command}`,\n `pnpm exec ${command}`,\n `yarn exec ${command}`,\n `bunx ${command}`,\n ];\n } else if (type === \"dlx\") {\n const command = args ?? pkg ?? \"\";\n commands = [\n `npx ${command}`,\n `pnpm dlx ${command}`,\n `yarn dlx ${command}`,\n `bunx ${command}`,\n ];\n } else if (pkg) {\n commands = [\n `npm install ${dev ? \"--save-dev \" : \"\"}${pkg}`,\n `pnpm add ${dev ? \"-D \" : \"\"}${pkg}`,\n `yarn add ${dev ? \"-D \" : \"\"}${pkg}`,\n `bun add ${dev ? \"-d \" : \"\"}${pkg}`,\n ];\n } else {\n return \"\";\n }\n\n return [\"```sh\", ...commands, \"```\"].join(\"\\n\");\n}\n\nfunction applyDefaultComponentTransforms(markdown: string): string {\n let out = markdown;\n\n out = out.replace(\n /<PackageManagers\\b([^>]*)\\/>/g,\n (_match, rawAttrs: string) => renderPackageManagers(parseAttrs(rawAttrs)),\n );\n\n out = out.replace(\n /<Aside\\b([^>]*)>([\\s\\S]*?)<\\/Aside>/g,\n (_match, rawAttrs: string, children: string) => {\n const attrs = parseAttrs(rawAttrs);\n const type = asTitle(attrs.type, \"note\").toUpperCase();\n const title = asTitle(attrs.title, type.charAt(0) + type.slice(1).toLowerCase());\n const body = cleanChildren(children);\n return blockquote(`**${title}**\\n\\n${body}`);\n },\n );\n\n out = out.replace(\n /<Card\\b([^>]*)>([\\s\\S]*?)<\\/Card>/g,\n (_match, rawAttrs: string, children: string) => {\n const attrs = parseAttrs(rawAttrs);\n const title = asTitle(attrs.title, \"Card\");\n const body = cleanChildren(children);\n return `- **${title}**${body ? ` — ${body}` : \"\"}`;\n },\n );\n out = out.replace(/<\\/?CardGrid\\b[^>]*>/g, \"\");\n\n out = out.replace(/<Steps\\b[^>]*>([\\s\\S]*?)<\\/Steps>/g, (_match, children: string) => {\n let index = 0;\n return children.replace(\n /<Step\\b([^>]*)>([\\s\\S]*?)<\\/Step>/g,\n (_stepMatch, rawAttrs: string, stepChildren: string) => {\n index += 1;\n const attrs = parseAttrs(rawAttrs);\n const title = asTitle(attrs.title, `Step ${index}`);\n const body = cleanChildren(stepChildren);\n return `${index}. **${title}**${body ? `\\n\\n ${body.replace(/\\n/g, \"\\n \")}` : \"\"}`;\n },\n );\n });\n\n out = out.replace(/<Tabs\\b[^>]*>([\\s\\S]*?)<\\/Tabs>/g, (_match, children: string) =>\n children.replace(\n /<TabItem\\b([^>]*)>([\\s\\S]*?)<\\/TabItem>/g,\n (_tabMatch, rawAttrs: string, tabChildren: string) => {\n const attrs = parseAttrs(rawAttrs);\n const label = asTitle(attrs.label, \"Option\");\n return `### ${label}\\n\\n${cleanChildren(tabChildren)}`;\n },\n ),\n );\n\n // If user content includes raw component wrappers we don't know about,\n // preserve their children rather than leaking JSX into the markdown.\n out = out.replace(/<([A-Z][A-Za-z0-9]*)\\b[^>]*>([\\s\\S]*?)<\\/\\1>/g, \"$2\");\n out = out.replace(/<([A-Z][A-Za-z0-9]*)\\b[^>]*\\/>/g, \"\");\n\n return out;\n}\n\nfunction applyCustomComponentTransforms(\n markdown: string,\n componentMap: Record<string, MarkdownComponentRenderer>,\n): string {\n let out = markdown;\n for (const [name, render] of Object.entries(componentMap)) {\n const paired = new RegExp(`<${name}\\\\b([^>]*)>([\\\\s\\\\S]*?)<\\\\/${name}>`, \"g\");\n out = out.replace(paired, (_match, rawAttrs: string, children: string) =>\n render({ name, attrs: parseAttrs(rawAttrs), children: cleanChildren(children) }),\n );\n\n const selfClosing = new RegExp(`<${name}\\\\b([^>]*)\\\\/>`, \"g\");\n out = out.replace(selfClosing, (_match, rawAttrs: string) =>\n render({ name, attrs: parseAttrs(rawAttrs), children: \"\" }),\n );\n }\n return out;\n}\n\n/**\n * Render an Astro content entry's raw MDX body as plain markdown.\n *\n * This handles the starter's default MDX components. Users can pass a\n * `componentMap` to override individual component renderers or replace this\n * function entirely from their user-owned `.md` route.\n */\nexport function renderEntryAsMarkdown(\n entry: MarkdownEntry,\n options: RenderEntryAsMarkdownOptions = {},\n): string {\n const stripFrontmatter = options.stripFrontmatter ?? true;\n let markdown = entry.body ?? \"\";\n\n if (stripFrontmatter) {\n markdown = markdown.replace(/^---\\n[\\s\\S]*?\\n---\\n?/, \"\");\n }\n\n const protectedCode = protectCode(markdown);\n markdown = protectedCode.markdown;\n\n if (options.componentMap) {\n markdown = applyCustomComponentTransforms(markdown, options.componentMap);\n }\n markdown = applyDefaultComponentTransforms(markdown);\n markdown = protectedCode.restore(markdown);\n\n return markdown\n .replace(/^[ \\t]+(- \\*\\*)/gm, \"$1\")\n .replace(/^[ \\t]+(\\d+\\. \\*\\*)/gm, \"$1\")\n .replace(/^[ \\t]+(### )/gm, \"$1\")\n .replace(/^[ \\t]+(```)/gm, \"$1\")\n .replace(/^[ \\t]+$/gm, \"\")\n .replace(/\\n{3,}/g, \"\\n\\n\")\n .trim();\n}\n","import type { Breadcrumb, PrevNext, PrevNextOverrides, SidebarItem } from \"../types.js\";\nimport { flattenSidebar } from \"./sidebar.js\";\nimport { toBrowserHref, toRouteKey } from \"./url.js\";\n\nexport type { Breadcrumb, PrevNext, PrevNextOverrides };\n\nexport function getBreadcrumbs(slug: string, homeLabel = \"Home\"): Breadcrumb[] {\n const parts = slug.split(\"/\").filter(Boolean);\n const crumbs: Breadcrumb[] = [{ label: homeLabel, href: \"/\" }];\n\n let path = \"\";\n for (const part of parts) {\n path += `/${part}`;\n crumbs.push({\n label: part.replace(/-/g, \" \").replace(/\\b\\w/g, (c) => c.toUpperCase()),\n href: toBrowserHref(path),\n });\n }\n\n return crumbs;\n}\n\ntype PrevNextOverride = { link?: string; label?: string };\n\nfunction resolveOverride(\n override: string | PrevNextOverride | false | undefined,\n fallback: { label: string; href: string } | undefined,\n validInternalLinks?: Set<string>,\n): { label: string; href: string } | undefined {\n if (override === false) return undefined;\n if (override === undefined) return fallback;\n if (typeof override === \"string\") {\n // String form: label-only override — keeps the sidebar neighbor's href, replaces the label\n if (!fallback) return undefined;\n return { label: override, href: fallback.href };\n }\n // Object form: merge with fallback — omitted fields inherit from sidebar neighbor\n if (override.link && !override.link.startsWith(\"/\") && !override.link.startsWith(\"http\")) {\n throw new Error(\n `prev/next override link \"${override.link}\" must be an absolute path (starting with /) or a full URL`,\n );\n }\n if (override.link?.startsWith(\"/\") && validInternalLinks) {\n // `validInternalLinks` holds slashless route keys (from indexed\n // entries). Normalize the override's link to the same shape so\n // `/cli`, `/cli/`, and `/cli/?ref=x` all resolve.\n const targetPath = toRouteKey(override.link);\n if (!validInternalLinks.has(targetPath)) {\n throw new Error(`prev/next override link \"${override.link}\" does not match any existing internal docs route`);\n }\n }\n const label = override.label ?? fallback?.label;\n // Override links go straight to `<a href>`, so render in the trailing-\n // slash form static hosts serve. External `http(s)://…` URLs and asset\n // paths fall through `toBrowserHref` unchanged.\n const href = override.link ? toBrowserHref(override.link) : fallback?.href;\n\n // Without a sidebar neighbor, object overrides must be complete.\n if (!fallback && (label === undefined || href === undefined)) {\n throw new Error(\"prev/next object override requires both `label` and `link` when no sidebar neighbor exists\");\n }\n\n if (!href) return undefined;\n return { label: label ?? \"\", href };\n}\n\nexport function getPrevNext(\n currentPath: string,\n sidebarTree: SidebarItem[],\n overrides?: PrevNextOverrides,\n validInternalLinks?: Set<string>,\n): PrevNext {\n const flat = flattenSidebar(sidebarTree);\n // Sidebar hrefs are trailing-slash browser-form; `currentPath` from\n // routes may be either form. Compare via route key so they line up.\n const currentKey = toRouteKey(currentPath);\n const index = flat.findIndex((item) => toRouteKey(item.href) === currentKey);\n\n const sidebarPrev = index > 0 ? { label: flat[index - 1]!.label, href: flat[index - 1]!.href } : undefined;\n const sidebarNext =\n index >= 0 && index < flat.length - 1 ? { label: flat[index + 1]!.label, href: flat[index + 1]!.href } : undefined;\n\n if (!overrides) {\n return { prev: sidebarPrev, next: sidebarNext };\n }\n\n return {\n prev: resolveOverride(overrides.prev, sidebarPrev, validInternalLinks),\n next: resolveOverride(overrides.next, sidebarNext, validInternalLinks),\n };\n}\n","import type { TOCItem } from \"../types.js\";\n\nexport interface TocConfig {\n minHeadingLevel?: number;\n maxHeadingLevel?: number;\n}\n\nexport function getHeadings(\n headings: { depth: number; text: string; slug: string }[],\n config?: TocConfig,\n): TOCItem[] {\n const min = config?.minHeadingLevel ?? 2;\n const max = config?.maxHeadingLevel ?? 3;\n return headings.filter((h) => h.depth >= min && h.depth <= max);\n}\n","/**\n * git-last-updated.ts — Derive a per-page `lastUpdated` from `git log`.\n *\n * Uses the **author date** (`%aI`) instead of the committer date (`%cI`)\n * so the value stays stable when a branch is rebased: rebases rewrite\n * commit dates but preserve author dates for unchanged content. Squash\n * merges produce a single new commit that touches the file, so the\n * date naturally reflects the squash moment — which is the right answer\n * for \"when did this content last change in the published history.\"\n *\n * Returns `undefined` on every failure mode so the caller can fall back\n * cleanly to frontmatter (or render nothing):\n *\n * - `git` not on PATH (CI image without git, container without it)\n * - File isn't tracked yet (new content in a draft branch, untracked)\n * - Repo is a shallow clone / partial clone and the file's history\n * isn't in the local pack (Vercel default `fetch-depth: 1`,\n * Cloudflare Pages similar). Users who want git-derived dates in\n * production should set `fetch-depth: 0` on `actions/checkout` or\n * equivalent.\n * - Process isn't inside a git working tree at all\n *\n * Results are cached per-process. A typical docs build calls this once\n * per entry; the cache prevents redundant subprocess spawns when the\n * same entry's filePath shows up across multiple pages (e.g. sidebar\n * preview, full render).\n */\n\nimport { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\n\nconst execFileAsync = promisify(execFile);\n\nconst cache = new Map<string, Date | undefined>();\n\n/**\n * Run `git log -1 --format=%aI -- <filePath>` and parse the result as a\n * `Date`. Returns `undefined` on any error or empty result.\n *\n * Pass either the entry's `filePath` (Astro provides this on every\n * content entry) or an explicit relative path. Relative paths resolve\n * against the current working directory (Astro builds run from the\n * project root, which is inside the git repo).\n */\nexport async function getLastUpdatedFromGit(\n filePath: string,\n): Promise<Date | undefined> {\n if (!filePath) return undefined;\n if (cache.has(filePath)) return cache.get(filePath);\n\n let result: Date | undefined;\n try {\n const { stdout } = await execFileAsync(\n \"git\",\n [\"log\", \"-1\", \"--format=%aI\", \"--\", filePath],\n // No `cwd` override — Astro runs from the project root which is\n // inside the repo. Git itself walks upward to find `.git`.\n { windowsHide: true },\n );\n const trimmed = stdout.trim();\n if (trimmed) {\n const parsed = new Date(trimmed);\n // Guard against `new Date(\"\")` → Invalid Date that still passes\n // `instanceof Date`. `getTime()` returns NaN for invalid dates.\n if (!Number.isNaN(parsed.getTime())) {\n result = parsed;\n }\n }\n } catch {\n // Swallow: caller falls back to frontmatter or renders no date.\n result = undefined;\n }\n\n cache.set(filePath, result);\n return result;\n}\n","/**\n * Admonition transform — rewrites `:::type[title]\\n...\\n:::` fenced\n * directives to `<Aside type=\"...\" title=\"...\">...</Aside>` in MDX source\n * before the markdown compiler sees the file.\n *\n * Runs as a Vite plugin transform (a content pass) rather than a remark\n * plugin so it survives the `markdown.processor` swap that disables\n * remark plugins under Sätteri. Same architectural reason as\n * `validate-mdx-content.ts` — see that file's header for the long form.\n *\n * Recognised syntaxes (CF / MyST / Docusaurus parity):\n *\n * Block, no title:\n * :::note\n * Body paragraph one.\n *\n * Body paragraph two.\n * :::\n *\n * Block with bracketed title:\n * :::warning[Be careful]\n * Body text.\n * :::\n *\n * Inline (single line, common in CF content):\n * :::note If you already have a Worker, skip to step 3. :::\n * :::note[Tip: …] To simulate latency, set your DB region. :::\n *\n * Type mapping (CF / MyST extra types fold into Nimbus's 4 Aside slots):\n * note, info → note\n * tip → tip\n * caution, warning, important → caution\n * danger → danger\n *\n * Aside availability: the user's `src/components.ts` must expose `Aside`\n * (the default starter does). If it doesn't, the post-transform MDX\n * compile will fail with \"unknown component Aside\" — which the existing\n * `validate-mdx-content.ts` validator already surfaces as a clean build\n * error pointing the user at the registry file.\n *\n * What this does NOT handle:\n * - Nested admonitions with extra colons (`::::note … :::sub … ::: ::::`).\n * Rare in practice; would need a counted-colon parser.\n * - Admonitions inside fenced code blocks. Code blocks are stashed\n * before the regex runs, so `\\`\\`\\` :::note … ::: \\`\\`\\`` is preserved\n * verbatim.\n */\n\nexport interface AdmonitionTransformOptions {\n /**\n * Extra type aliases on top of the built-in MyST set. Useful if the\n * upstream content uses product-specific synonyms — `{ heads: \"tip\" }`\n * would map `:::heads` → `<Aside type=\"tip\">`.\n */\n typeAliases?: Record<string, AsideType>;\n}\n\nexport type AsideType = \"note\" | \"tip\" | \"caution\" | \"danger\";\n\n/** Built-in MyST / Docusaurus / CF admonition types and their Aside mapping. */\nconst BUILTIN_TYPES: Record<string, AsideType> = {\n note: \"note\",\n info: \"note\",\n tip: \"tip\",\n caution: \"caution\",\n warning: \"caution\",\n important: \"caution\",\n danger: \"danger\",\n};\n\n/**\n * Transform a single MDX source string. Idempotent — running the\n * transform twice produces the same output as running it once.\n */\nexport function transformAdmonitions(\n source: string,\n options: AdmonitionTransformOptions = {},\n): string {\n const typeMap = { ...BUILTIN_TYPES, ...(options.typeAliases ?? {}) };\n\n // 1. Split frontmatter so we never rewrite YAML keys (e.g. `tip: …`).\n const { frontmatter, body, bodyOffset: _ } = splitFrontmatter(source);\n\n // 2. Stash fenced code blocks (``` and ~~~) so `:::` inside code samples\n // is preserved verbatim. Indented code blocks are rare in MDX; we\n // leave them alone (the `:::` token has to be flush-left to match\n // anyway, and indented code requires a 4-space prefix).\n const { stashed, restore } = stashCodeBlocks(body);\n\n // 3. Run the rewrite.\n const rewritten = stashed.replace(ADMONITION_PATTERN, (match, rawType, rawTitle, rawContent) => {\n const type = String(rawType).toLowerCase();\n const aside = typeMap[type];\n if (!aside) {\n // Unknown directive — leave it alone. Users may have other `:::foo`\n // patterns (custom containers, etc.); silently swallowing them\n // would be worse than the existing literal-text rendering.\n return match;\n }\n const title = typeof rawTitle === \"string\" ? rawTitle.trim() : \"\";\n const content = String(rawContent).trim();\n const titleAttr = title ? ` title=${JSON.stringify(title)}` : \"\";\n\n // MDX requires blank lines around block components so the markdown\n // parser doesn't pull surrounding paragraphs inside the JSX. The\n // `\\n\\n` padding is cosmetic in practice (the regex consumed the\n // surrounding newlines) but defensive against odd inputs.\n return `\\n\\n<Aside type=\"${aside}\"${titleAttr}>\\n\\n${content}\\n\\n</Aside>\\n\\n`;\n });\n\n // 4. Restore the stashed code blocks.\n const restored = restore(rewritten);\n\n return frontmatter + restored;\n}\n\n// ---------------------------------------------------------------------------\n// Regex\n// ---------------------------------------------------------------------------\n\n/**\n * Match `:::type[optional title] body :::` with non-greedy body.\n *\n * Components:\n * - `:::` literal opener\n * - `([a-zA-Z]+)` type token (captured, case-insensitive lookup at use site)\n * - `(?:\\[(...)\\])?` optional bracketed title; brackets stripped from capture\n * - `\\s+|\\n` at least one whitespace before content (avoids matching\n * `:::foo:::` directly)\n * - `([\\s\\S]*?)` non-greedy body, may span newlines\n * - `\\n?\\s*:::` closer, possibly with leading whitespace\n *\n * Non-greedy body + global flag means adjacent admonitions don't merge\n * (the engine finds the *nearest* `:::` closer for each opener).\n */\nconst ADMONITION_PATTERN = /:::([a-zA-Z]+)(?:\\[([^\\]]*)\\])?[ \\t]*(?:\\n|[ \\t]+)([\\s\\S]*?)\\n?[ \\t]*:::/g;\n\n// ---------------------------------------------------------------------------\n// Frontmatter\n// ---------------------------------------------------------------------------\n\ninterface FrontmatterSplit {\n frontmatter: string;\n body: string;\n bodyOffset: number;\n}\n\nfunction splitFrontmatter(source: string): FrontmatterSplit {\n const match = source.match(/^---\\n[\\s\\S]*?\\n---\\n?/);\n if (!match) return { frontmatter: \"\", body: source, bodyOffset: 0 };\n return {\n frontmatter: match[0],\n body: source.slice(match[0].length),\n bodyOffset: match[0].length,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Code-block stashing\n// ---------------------------------------------------------------------------\n\n/**\n * Replace fenced code blocks with opaque placeholders so the admonition\n * regex doesn't reach `:::` tokens inside code samples. The `restore()`\n * function reinstates the originals after the transform.\n *\n * Order matters: stash the longest fence flavors first (``` and ~~~)\n * so the placeholders themselves don't get re-stashed. Inline backtick\n * code spans are NOT stashed — a `:::` inside a single-line `code` span\n * is rare and would have to be on the same line as both fences anyway.\n */\nfunction stashCodeBlocks(body: string): { stashed: string; restore: (src: string) => string } {\n const blocks: string[] = [];\n const PLACEHOLDER = \"\\x00NIMBUS_CODEBLOCK_\";\n const PLACEHOLDER_END = \"\\x00\";\n\n // Match ``` and ~~~ fenced blocks with optional language tags.\n const stashed = body.replace(/```[\\s\\S]*?```|~~~[\\s\\S]*?~~~/g, (match) => {\n const index = blocks.length;\n blocks.push(match);\n return `${PLACEHOLDER}${index}${PLACEHOLDER_END}`;\n });\n\n function restore(src: string): string {\n return src.replace(\n new RegExp(`${PLACEHOLDER}(\\\\d+)${PLACEHOLDER_END}`, \"g\"),\n (_match, index) => blocks[Number(index)] ?? \"\",\n );\n }\n\n return { stashed, restore };\n}\n","/**\n * Vite plugin: intercept `.mdx` (and `.md`) loads under the project's\n * content directories, rewrite `:::admonition` directives to `<Aside>`\n * components, hand the transformed source to the next loader in the\n * chain. Sits in front of @astrojs/mdx and Sätteri.\n *\n * `enforce: \"pre\"` is load-bearing — the MDX integration's own transform\n * registers without an `enforce` and runs in the default mid-pipeline\n * slot. Pre-stage runs before that, so by the time MDX parses the file,\n * the directive syntax has already been rewritten to JSX.\n *\n * Scope is restricted to the project's content directories so we don't\n * touch unrelated `.md` files in `node_modules/` or vendored MDX.\n */\n\nimport path from \"node:path\";\nimport {\n transformAdmonitions,\n type AdmonitionTransformOptions,\n} from \"./admonition-transform.js\";\n\nexport interface AdmonitionPluginOptions extends AdmonitionTransformOptions {\n /**\n * Absolute paths the plugin will rewrite. Files outside these prefixes\n * pass through unchanged. Usually `[<projectRoot>/src/content]`.\n */\n contentDirs: ReadonlyArray<string>;\n /**\n * Optional per-file opt-out — receives an absolute path, returns true\n * to skip rewriting. Useful for vendored MDX or files that legitimately\n * use `:::` for something other than admonitions.\n */\n skip?: (filePath: string) => boolean;\n}\n\n// No explicit `Plugin` return annotation. Importing `Plugin` from \"vite\"\n// binds the returned type to a specific Vite type-instance, which then\n// fails to unify with Astro's `PluginOption` when the consumer's\n// `tsc` walks node-module resolution and finds a second Vite install in\n// an ancestor `node_modules/`. The hooks we use (`transform`, `enforce`,\n// `name`) are part of every Vite version's `Plugin` shape, so returning\n// a plain object literal stays structurally assignable everywhere.\nexport function admonitionPlugin(options: AdmonitionPluginOptions) {\n const normalizedDirs = options.contentDirs.map((d) => path.resolve(d));\n\n return {\n name: \"nimbus-docs:admonitions\",\n enforce: \"pre\" as const,\n transform(code: string, id: string) {\n // Vite passes ids with optional query strings (e.g. `?import`,\n // `?worker`); split before extension check so `foo.mdx?raw` still\n // matches.\n const [pathOnly] = id.split(\"?\", 1);\n if (!pathOnly) return null;\n if (!pathOnly.endsWith(\".mdx\") && !pathOnly.endsWith(\".md\")) return null;\n\n const absolute = path.resolve(pathOnly);\n const inScope = normalizedDirs.some(\n (dir) => absolute === dir || absolute.startsWith(dir + path.sep),\n );\n if (!inScope) return null;\n if (options.skip?.(absolute)) return null;\n\n // Cheap pre-filter: don't bother with the regex if there's no\n // `:::` token in the file at all. Saves time on the common case\n // (most pages don't use admonitions).\n if (!code.includes(\":::\")) return null;\n\n const transformed = transformAdmonitions(code, {\n typeAliases: options.typeAliases,\n });\n\n // No identity check — `transformAdmonitions` only changes content\n // when at least one admonition matched, and the `includes(\":::\")`\n // guard above already filtered the no-op case. Returning a string\n // (vs. null) tells Vite we did rewrite.\n return { code: transformed, map: null };\n },\n };\n}\n","/**\n * Extract registered MDX global names from the user's `src/components.ts`.\n *\n * The framework needs this list to validate PascalCase tags in MDX at\n * build time, but it must not execute user code at build time. Strategy:\n * read the file as text, locate the `export const components = { ... }`\n * declaration, and parse its top-level keys.\n *\n * Supported entry shapes inside the object literal:\n * - shorthand: `Foo,` → \"Foo\"\n * - aliased: `Foo: Other,` → \"Foo\" (the key)\n * - string key: `\"Foo\": Other,` → \"Foo\"\n *\n * Skipped (no false-positive failures):\n * - spread elements (`...other`)\n * - computed keys (`[expr]: value`)\n * - lowercase keys (not PascalCase, so not validator-relevant)\n *\n * Returns:\n * - `string[]` of registered names when the file exists and the pattern\n * matches.\n * - `null` when the file is missing OR present but doesn't expose a\n * parseable `export const components = { ... }`. The caller decides\n * whether to warn or skip validation.\n */\n\nimport fs from \"node:fs/promises\";\n\nconst EXPORT_PATTERN =\n /export\\s+const\\s+components\\s*(?::\\s*[^=]+)?=\\s*\\{([\\s\\S]*?)\\n\\s*\\}\\s*(?:as\\s+const)?\\s*;?/;\n\nexport async function parseComponentsRegistry(\n filePath: string,\n): Promise<string[] | null> {\n let source: string;\n try {\n source = await fs.readFile(filePath, \"utf8\");\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return null;\n throw err;\n }\n\n const match = source.match(EXPORT_PATTERN);\n if (!match) return null;\n\n // Strip line + block comments so commas inside `// foo, bar` don't split.\n // `match[1]!`: regex's first capture group is required, defined whenever match succeeded.\n const body = match[1]!\n .replace(/\\/\\/[^\\n]*/g, \"\")\n .replace(/\\/\\*[\\s\\S]*?\\*\\//g, \"\");\n\n const names: string[] = [];\n for (const raw of splitTopLevelCommas(body)) {\n const entry = raw.trim();\n if (!entry) continue;\n if (entry.startsWith(\"...\")) continue;\n if (entry.startsWith(\"[\")) continue;\n\n const colonIdx = entry.indexOf(\":\");\n const rawKey = colonIdx === -1 ? entry : entry.slice(0, colonIdx);\n const key = rawKey.trim().replace(/^['\"`]|['\"`]$/g, \"\");\n\n if (/^[A-Z][A-Za-z0-9_]*$/.test(key)) names.push(key);\n }\n\n return names;\n}\n\n/**\n * Split a string on commas that are at depth 0 (not inside `{}`, `[]`,\n * `()`, or string literals). Required because object entries can themselves\n * contain commas (e.g. `Foo: bar({ a: 1, b: 2 })`).\n */\nfunction splitTopLevelCommas(input: string): string[] {\n const result: string[] = [];\n let depth = 0;\n let start = 0;\n let inString: string | null = null;\n\n for (let i = 0; i < input.length; i++) {\n const ch = input[i];\n if (inString) {\n if (ch === \"\\\\\") {\n i++;\n continue;\n }\n if (ch === inString) inString = null;\n continue;\n }\n if (ch === '\"' || ch === \"'\" || ch === \"`\") inString = ch;\n else if (ch === \"{\" || ch === \"[\" || ch === \"(\") depth++;\n else if (ch === \"}\" || ch === \"]\" || ch === \")\") depth--;\n else if (ch === \",\" && depth === 0) {\n result.push(input.slice(start, i));\n start = i + 1;\n }\n }\n result.push(input.slice(start));\n return result;\n}\n","/**\n * Lint-side data shapes + the `nimbus/duplicate-slug` build validator.\n *\n * Route truth for `nimbus/internal-link` comes from Astro itself\n * (`astro:build:done` hands us the emitted `pages` array — the single\n * source of truth for served URLs). The integration writes that to\n * `.nimbus/routes.json`; the type lives here only so the rule and the\n * writer agree on the shape.\n *\n * The duplicate-slug validator runs *before* the build because Astro\n * silently dedupes colliding routes — by the time `astro:build:done`\n * fires, one entry has already shadowed the other. We catch collisions\n * pre-build by computing each entry's canonical slug with the same\n * library Astro's content layer uses (`github-slugger`), then grouping\n * entries by collection + slug. This is mirror-behavior, but mirroring\n * a documented public library rather than Astro's private internals —\n * a much smaller maintenance surface.\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport { canonicalEntryUrl, canonicalSlug } from \"../_internal/astro-slug.js\";\nimport {\n collectionMountPrefix,\n type VersionInfo,\n} from \"../_internal/collection-mount.js\";\n\n// Re-exported for tests; the helpers themselves live in `_internal/` because\n// framework URL builders (sidebar, sitemap, llms.txt) also use them.\nexport { canonicalSlug };\nexport type { VersionInfo };\n\nexport interface ContentEntry {\n /** Collection name — the first path segment under `src/content`. */\n collection: string;\n /** Entry id — the path under the collection, without the `.mdx` extension. */\n id: string;\n /** Path relative to the content root (`<collection>/<id>.mdx`), for display. */\n relPath: string;\n}\n\n/**\n * Walk a content root for `.mdx` files and return one entry per file.\n * Uses the filesystem segment as the `collection` — correct only when each\n * collection's `base` matches its registered key. Use\n * `enumerateEntriesByBase` when the caller has the real `(key, base)` map\n * (so `docsCollection({ base: \"documentation\" })` doesn't get mis-tagged).\n */\nexport function enumerateEntries(contentRoot: string): ContentEntry[] {\n const out: ContentEntry[] = [];\n walk(contentRoot, contentRoot, out, null);\n return out;\n}\n\n/**\n * Walk `src/content/` using a `(collection key → folder name)` map so\n * entries are tagged with the registered key, not the on-disk folder.\n * Skips folders that aren't in the map (they're loose content, not a\n * routed collection).\n */\nexport function enumerateEntriesByBase(\n contentRoot: string,\n bases: ReadonlyMap<string, string>,\n): ContentEntry[] {\n // Invert the map (folder → key). Two collections with the same base\n // would be a content.config.ts authoring error; we silently keep the\n // last one — Astro itself errors on duplicate-key collection registration.\n const folderToKey = new Map<string, string>();\n for (const [key, base] of bases) folderToKey.set(base, key);\n\n const out: ContentEntry[] = [];\n for (const [folder, key] of folderToKey) {\n const baseDir = path.join(contentRoot, folder);\n walkFolder(baseDir, baseDir, (relInBase) => {\n out.push({\n collection: key,\n id: relInBase.replace(/\\.mdx$/, \"\"),\n relPath: `${folder}/${relInBase}`,\n });\n });\n }\n return out;\n}\n\nfunction walkFolder(\n dir: string,\n root: string,\n visit: (relPath: string) => void,\n): void {\n let dirents: fs.Dirent[];\n try {\n dirents = fs.readdirSync(dir, { withFileTypes: true });\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return;\n throw err;\n }\n for (const entry of dirents) {\n const full = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n if (entry.name === \"node_modules\" || entry.name.startsWith(\".\")) continue;\n walkFolder(full, root, visit);\n } else if (entry.isFile() && entry.name.endsWith(\".mdx\")) {\n const rel = path.relative(root, full).replace(/\\\\/g, \"/\");\n visit(rel);\n }\n }\n}\n\nfunction walk(\n dir: string,\n root: string,\n out: ContentEntry[],\n _: null,\n): void {\n let dirents: fs.Dirent[];\n try {\n dirents = fs.readdirSync(dir, { withFileTypes: true });\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return;\n throw err;\n }\n for (const entry of dirents) {\n const full = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n if (entry.name === \"node_modules\" || entry.name.startsWith(\".\")) continue;\n walk(full, root, out, null);\n } else if (entry.isFile() && entry.name.endsWith(\".mdx\")) {\n const rel = path.relative(root, full).replace(/\\\\/g, \"/\");\n const slash = rel.indexOf(\"/\");\n if (slash === -1) continue;\n out.push({\n collection: rel.slice(0, slash),\n id: rel.slice(slash + 1).replace(/\\.mdx$/, \"\"),\n relPath: rel,\n });\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Duplicate-slug detection — runs pre-build because Astro silently dedupes.\n// ---------------------------------------------------------------------------\n\n/**\n * A single thing that owns a URL: either a content entry under\n * `src/content/<collection>/` or a static page file under `src/pages/`.\n * The dup-check only needs the URL and a display path; everything else is\n * caller-side bookkeeping.\n */\nexport interface RouteOwner {\n /** The mounted URL this source resolves to (canonicalized, no trailing slash). */\n url: string;\n /** Project-relative path, used in error messages. */\n source: string;\n}\n\nexport interface DuplicateGroup {\n /** The URL multiple sources resolve to. */\n url: string;\n /** All sources that claim that URL (project-relative paths). */\n sources: string[];\n}\n\n/**\n * Group route owners by URL. A group with more than one member is a real\n * collision Astro will silently shadow at build time — that's what this\n * helper exists to surface before the build wastes a cycle.\n *\n * Owners come from two sources, both fed in by the integration:\n *\n * - **Content entries**, with URLs computed via\n * `collectionMountPrefix(entry.collection, versions) +\n * canonicalEntryUrl(prefix, entry.id)`. Catches cross-collection\n * (`docs/blog/post.mdx` vs `blog/post.mdx`), version-collection\n * (`docs/v1/x.mdx` vs `docs-v1/x.mdx`), case-only, and\n * leaf-vs-folder-index collisions Astro's content layer dedupes.\n * - **Static `src/pages/**` files** (via `enumerateStaticPageRoutes`).\n * Catches the page-vs-content collision (`pages/search.astro`\n * shadowing `content/docs/search.mdx` at `/search`).\n *\n * Doesn't honor `data.slug` frontmatter overrides — entries that use those\n * may produce false negatives. Reading frontmatter from every entry\n * pre-build would add noticeable I/O for a v1 feature; tracked as a\n * follow-up.\n */\nexport function findDuplicateRoutes(\n owners: readonly RouteOwner[],\n): DuplicateGroup[] {\n const byUrl = new Map<string, string[]>();\n for (const { url, source } of owners) {\n const bucket = byUrl.get(url);\n if (bucket) bucket.push(source);\n else byUrl.set(url, [source]);\n }\n const dups: DuplicateGroup[] = [];\n for (const [url, sources] of byUrl) {\n if (sources.length > 1) dups.push({ url, sources });\n }\n return dups;\n}\n\n/** Format duplicate groups into a build-error message. */\nexport function formatDuplicateRoutes(dups: DuplicateGroup[]): string {\n const lines = dups.map((d) => ` ${d.url} ← ${d.sources.join(\", \")}`);\n const noun = dups.length === 1 ? \"route is\" : \"routes are\";\n return (\n `[nimbus-docs] Duplicate ${noun} claimed by more than one source (nimbus/duplicate-slug):\\n` +\n lines.join(\"\\n\") +\n `\\n\\nTwo or more sources resolve to the same URL — one would shadow the other on the deployed site ` +\n `(Astro silently dedupes colliding routes). Rename or move one source in each pair.`\n );\n}\n\n// ---------------------------------------------------------------------------\n// Static-page-route enumeration — the second source for findDuplicateRoutes.\n// ---------------------------------------------------------------------------\n\n/**\n * Walk `src/pages/**` for static page files and return their served URLs.\n *\n * Considered \"static\" iff the path has no dynamic segments (`[id]`,\n * `[...slug]`). Dynamic routes are skipped because their emitted URLs come\n * from `getStaticPaths` at build time — we can't know them statically, so\n * we can't detect collisions involving them pre-build. The opaque-namespace\n * pattern from earlier drafts (mark a whole namespace as un-checkable)\n * doesn't apply here: dup-detection only catches *exact* URL collisions,\n * and any catch-all owned by the framework's docs renderer collides with a\n * content entry the *content* enumeration also catches.\n *\n * URL normalization: lowercase each segment + strip a trailing `/index`,\n * matching Astro's `joinSegments` behavior for static routes. Underscore-\n * prefixed files are skipped (Astro's private-helper convention). Endpoint\n * files (`name.<ext>.<ts|js>`) map to `/name.<ext>` per Astro's convention.\n */\nexport interface StaticPageRoute {\n /** The URL this page file serves at, canonicalized (no trailing slash). */\n url: string;\n /** Project-relative path, e.g. `src/pages/search.astro`. */\n source: string;\n}\n\nconst PAGE_EXTS = new Set([\".astro\", \".ts\", \".js\", \".md\", \".mdx\"]);\n\nexport function enumerateStaticPageRoutes(\n pagesRoot: string,\n projectRoot: string,\n): StaticPageRoute[] {\n const out: StaticPageRoute[] = [];\n walkPages(pagesRoot, pagesRoot, (relPath) => {\n const ext = path.extname(relPath);\n if (!PAGE_EXTS.has(ext)) return;\n\n const base = path.basename(relPath);\n if (base.startsWith(\"_\")) return; // Astro's private-helper convention\n\n const parts = relPath.replace(/\\\\/g, \"/\").split(\"/\");\n // Pre-strip the leaf extension so the dynamic-segment test sees the\n // bare segment (`[id]` not `[id].astro`).\n const bareLeaf = parts[parts.length - 1]!.replace(/\\.[^.]+$/, \"\");\n if (parts.slice(0, -1).some(isDynamicSegment) || isDynamicSegment(bareLeaf)) {\n return;\n }\n\n const url = fileToRoute(parts, ext);\n const sourceAbs = path.join(pagesRoot, relPath);\n const source = path.relative(projectRoot, sourceAbs).replace(/\\\\/g, \"/\");\n out.push({ url, source });\n });\n return out;\n}\n\nfunction walkPages(\n dir: string,\n root: string,\n visit: (relPath: string) => void,\n): void {\n let dirents: fs.Dirent[];\n try {\n dirents = fs.readdirSync(dir, { withFileTypes: true });\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return;\n throw err;\n }\n for (const entry of dirents) {\n const full = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n if (entry.name.startsWith(\"_\") || entry.name === \"node_modules\") continue;\n walkPages(full, root, visit);\n } else if (entry.isFile()) {\n visit(path.relative(root, full));\n }\n }\n}\n\nfunction isDynamicSegment(seg: string): boolean {\n return seg.startsWith(\"[\") && seg.endsWith(\"]\") && seg.length >= 3;\n}\n\n/**\n * Translate a static page file path (no dynamic segments) to its URL.\n *\n * pages/index.astro → /\n * pages/search.astro → /search\n * pages/Search.astro → /search (Astro lowercases via joinSegments)\n * pages/llms.txt.ts → /llms.txt (endpoint: strip .ts, keep .txt)\n * pages/blog/index.astro → /blog\n * pages/blog/post.md → /blog/post\n */\nfunction fileToRoute(parts: string[], ext: string): string {\n const cloned = [...parts];\n const last = cloned[cloned.length - 1]!;\n\n if ((ext === \".ts\" || ext === \".js\") && /\\.[^.]+\\.[tj]s$/.test(last)) {\n // Endpoint: `name.<inner>.<ts|js>` → strip just the trailing `.ts`/`.js`.\n cloned[cloned.length - 1] = last.replace(/\\.[tj]s$/, \"\");\n } else {\n cloned[cloned.length - 1] = last.replace(/\\.[^.]+$/, \"\");\n }\n\n // Strip trailing `index` segment so `foo/index.astro` → `/foo`.\n if (cloned[cloned.length - 1] === \"index\") cloned.pop();\n\n // Lowercase each segment to match Astro's `joinSegments` behavior.\n const joined = cloned.map((s) => s.toLowerCase()).join(\"/\");\n return joined === \"\" ? \"/\" : `/${joined}`;\n}\n\n/**\n * Compute the mounted URL a content entry resolves to. Exposed so callers\n * (the integration's pre-build dup-check) can build `RouteOwner` records\n * with the same logic the framework uses everywhere else.\n */\nexport function contentEntryUrl(\n entry: ContentEntry,\n versions?: VersionInfo | null,\n): string {\n const prefix = collectionMountPrefix(entry.collection, versions);\n return canonicalEntryUrl(prefix, entry.id);\n}\n\n// ---------------------------------------------------------------------------\n// Route truth — shape only. The integration's `astro:build:done` hook\n// constructs and writes this; `internal-link.ts` reads it.\n// ---------------------------------------------------------------------------\n\nexport interface RouteTruth {\n /** Schema version. Bump if the shape changes. */\n version: 1;\n /** Astro `base` config (`\"/docs\"`, `\"\"`). Empty string when unset. */\n base: string;\n /**\n * Every URL Astro emitted during the last build, canonicalized to\n * `/foo` form (no trailing slash unless root). The lint rule resolves\n * internal links against this set.\n */\n knownRoutes: string[];\n /**\n * Reserved for future SSR-route handling — URL prefixes that can't be\n * statically enumerated. Empty in the current all-prerendered path.\n */\n opaqueNamespaces: string[];\n}\n","/**\n * Extract registered collection names from the user's `src/content.config.ts`.\n *\n * Used by `getIndexedEntries()` and the agent-facing routes (`llms.txt`,\n * per-page `.md` alternates) so they don't have to hardcode `\"docs\"`.\n * Adding a new collection to `content.config.ts` lights up every\n * indexing surface automatically.\n *\n * Strategy: read the file as text and locate the `export const collections =\n * { ... }` declaration, parse its top-level keys. Same approach used by\n * `parse-components-registry.ts` — we never execute user code at build\n * time.\n *\n * Supported entry shapes inside the object literal:\n * - shorthand: `docs,` → \"docs\"\n * - aliased: `docs: defineCollection(...),` → \"docs\" (the key)\n * - string key: `\"docs\": defineCollection(...)` → \"docs\"\n *\n * Skipped:\n * - spread elements (`...other`)\n * - computed keys (`[expr]: value`)\n *\n * The result is *not* filtered against reserved names here — that's\n * `getIndexedEntries()`'s job, so consumers that want the raw list (e.g.\n * tooling) can still see it.\n *\n * Returns:\n * - `string[]` of registered names when the file exists and the\n * pattern matches.\n * - `null` when the file is missing OR present but doesn't expose a\n * parseable `export const collections = { ... }`. Callers decide\n * whether to warn or fall back to `[\"docs\"]`.\n */\n\nimport fs from \"node:fs/promises\";\n\n// Locate the start of the `export const collections = {` declaration; the\n// matching close brace is found by walking braces (the original regex-only\n// match stopped at the first nested `\\n\\s*}` and missed entries declared\n// after any deeply nested object literal).\nconst EXPORT_PREFIX_PATTERN =\n /export\\s+const\\s+collections\\s*(?::\\s*[^=]+)?=\\s*\\{/;\n\nexport async function parseContentCollections(\n filePath: string,\n): Promise<string[] | null> {\n let source: string;\n try {\n source = await fs.readFile(filePath, \"utf8\");\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return null;\n throw err;\n }\n\n // Strip JS line + block comments up front. Critical for two reasons:\n // 1. Comments can contain apostrophes / quotes / braces that the\n // brace walker would mistake for real string delimiters (e.g.\n // `// the user's content` would put the walker into \"inside\n // single-quoted string\" mode for the rest of the file).\n // 2. Commas inside `// foo, bar` would split entries incorrectly\n // downstream in `splitTopLevelCommas`.\n // We zero-out comments to spaces (not delete) so character offsets\n // stay aligned with the original `source` — the regex match index\n // and the brace walker both rely on positional indices.\n const stripped = source.replace(/\\/\\/[^\\n]*|\\/\\*[\\s\\S]*?\\*\\//g, (m) =>\n m.replace(/[^\\n]/g, \" \"),\n );\n\n const prefixMatch = stripped.match(EXPORT_PREFIX_PATTERN);\n if (!prefixMatch || prefixMatch.index === undefined) return null;\n const objectStart = prefixMatch.index + prefixMatch[0].length;\n const objectEnd = findMatchingBrace(stripped, objectStart - 1);\n if (objectEnd === -1) return null;\n const body = stripped.slice(objectStart, objectEnd);\n\n const names: string[] = [];\n for (const raw of splitTopLevelCommas(body)) {\n const entry = raw.trim();\n if (!entry) continue;\n if (entry.startsWith(\"...\")) continue;\n if (entry.startsWith(\"[\")) continue;\n\n const colonIdx = entry.indexOf(\":\");\n const rawKey = colonIdx === -1 ? entry : entry.slice(0, colonIdx);\n const key = rawKey.trim().replace(/^['\"`]|['\"`]$/g, \"\");\n\n // Collection names are conventionally lowercase identifiers\n // (`docs`, `blog`, `api`), but Astro accepts any non-empty string as a\n // collection ID and the versioning convention (`docs-v1`, `docs-2025-q1`)\n // relies on hyphens. Accept letters/digits/underscores/hyphens after\n // a leading letter or underscore (the `_*` underscore convention for\n // hidden-from-indexing collections stays intact).\n if (/^[A-Za-z_][A-Za-z0-9_-]*$/.test(key)) names.push(key);\n }\n\n return names;\n}\n\n/**\n * Sibling of `parseContentCollections` that also extracts each entry's\n * `base` option — the folder name under `src/content/` the collection\n * loads from. Returns a `Map<key, folderName>` where the folder defaults\n * to the collection key when no `base:` override is present.\n *\n * Used by the `nimbus/duplicate-slug` validator and any other framework\n * code that needs to walk a collection's actual on-disk location rather\n * than assuming the folder name matches the collection key. Astro's\n * content layer respects the `base` option — `docsCollection({ base:\n * \"documentation\" })` loads from `src/content/documentation/` while still\n * registering as collection `docs`. Without this map, filesystem-walking\n * code would mis-tag those entries (`collection: \"documentation\"`) and\n * either flag bogus collisions or silently skip them via the indexable-\n * collections filter.\n *\n * Extraction is regex-based — for each entry's value text, looks for\n * `\\bbase:\\s*[\"']([^\"']+)[\"']`. That covers the documented Nimbus pattern\n * (`docsCollection({ base: \"...\" })`, `partialsCollection({ base: \"...\" })`,\n * `componentsCollection({ base: \"...\" })`). Limitations:\n *\n * - Computed/dynamic bases (`base: someVar`) fall back to the collection\n * key. A future regression would silently miscount; for now,\n * accepted as a known v1 limitation.\n * - Hand-rolled `defineCollection({ loader: glob({ base: \"./src/content/x\" }) })`\n * puts a *path* in `base`, not a folder name. The extracted value\n * starts with `./src/content/` and won't match a folder under\n * `src/content/` directly. Users who write the loader by hand can\n * keep folder names matching collection keys, or accept that\n * `duplicate-slug` won't see their non-conforming collection until\n * they migrate to the Nimbus helpers.\n *\n * Returns `null` when the file is missing or unparseable, matching\n * `parseContentCollections`'s contract.\n */\nexport async function parseCollectionBases(\n filePath: string,\n): Promise<Map<string, string> | null> {\n let source: string;\n try {\n source = await fs.readFile(filePath, \"utf8\");\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return null;\n throw err;\n }\n\n const stripped = source.replace(/\\/\\/[^\\n]*|\\/\\*[\\s\\S]*?\\*\\//g, (m) =>\n m.replace(/[^\\n]/g, \" \"),\n );\n\n const prefixMatch = stripped.match(EXPORT_PREFIX_PATTERN);\n if (!prefixMatch || prefixMatch.index === undefined) return null;\n const objectStart = prefixMatch.index + prefixMatch[0].length;\n const objectEnd = findMatchingBrace(stripped, objectStart - 1);\n if (objectEnd === -1) return null;\n const body = stripped.slice(objectStart, objectEnd);\n\n const out = new Map<string, string>();\n for (const raw of splitTopLevelCommas(body)) {\n const entry = raw.trim();\n if (!entry) continue;\n if (entry.startsWith(\"...\")) continue;\n if (entry.startsWith(\"[\")) continue;\n\n const colonIdx = entry.indexOf(\":\");\n const rawKey = colonIdx === -1 ? entry : entry.slice(0, colonIdx);\n const key = rawKey.trim().replace(/^['\"`]|['\"`]$/g, \"\");\n if (!/^[A-Za-z_][A-Za-z0-9_-]*$/.test(key)) continue;\n\n // Default the folder to the key name. Override if a literal `base:`\n // appears in the entry's value — or, for shorthand entries (`{ docs }`),\n // in the local `const|let|var docs = …` declaration the shorthand\n // refers to.\n const valueText =\n colonIdx === -1\n ? findLocalDeclarationValue(stripped, key)\n : entry.slice(colonIdx + 1);\n const baseMatch = valueText.match(/\\bbase\\s*:\\s*[\"']([^\"']+)[\"']/);\n out.set(key, baseMatch ? baseMatch[1]! : key);\n }\n\n return out;\n}\n\n/**\n * Locate `const|let|var <identifier> = <value>` at any depth in the source\n * and return the captured `<value>` text. Used to resolve shorthand\n * collection entries (`{ docs }` in the registration object refers to a\n * `const docs = defineCollection(...)` declared somewhere above).\n *\n * The walker is brace/bracket/paren-aware and string-literal-aware so the\n * captured value spans nested object/function-call expressions without\n * losing depth. It stops at the next top-level `;` or end-of-input.\n *\n * Limitations (false negatives — never false positives):\n * - Identifiers imported from other modules can't be resolved (we don't\n * follow imports).\n * - Type-only annotations using `=>` (`const docs: () => X = …`) would\n * fool the `=` detection. Realistic collection declarations don't use\n * this shape; if a user does, the check falls back to the key.\n */\nfunction findLocalDeclarationValue(\n source: string,\n identifier: string,\n): string {\n const safeId = identifier.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const declRe = new RegExp(`\\\\b(?:const|let|var)\\\\s+${safeId}\\\\b`, \"g\");\n\n let match: RegExpExecArray | null;\n while ((match = declRe.exec(source)) !== null) {\n const afterId = match.index + match[0].length;\n const eqIdx = findAssignmentEquals(source, afterId);\n if (eqIdx === -1) continue;\n const endIdx = findStatementEnd(source, eqIdx + 1);\n return source.slice(eqIdx + 1, endIdx);\n }\n return \"\";\n}\n\n/**\n * Find the first `=` after `from` that's an assignment operator —\n * skipping `==`, `===`, and `=>`. Used to locate where the assignment\n * value begins in `const id [: Type] = value;`.\n */\nfunction findAssignmentEquals(source: string, from: number): number {\n for (let i = from; i < source.length; i++) {\n if (source[i] !== \"=\") continue;\n if (source[i + 1] === \"=\") {\n i++; // skip `==` (and `===` since next iter handles the trailing `=`)\n continue;\n }\n if (source[i + 1] === \">\") {\n i++; // skip `=>`\n continue;\n }\n if (source[i - 1] === \"!\" || source[i - 1] === \"<\" || source[i - 1] === \">\") {\n continue; // `!=`, `<=`, `>=`\n }\n return i;\n }\n return -1;\n}\n\n/**\n * Walk forward from `from` tracking brace/bracket/paren depth and string\n * literals; return the index of the statement terminator.\n *\n * Terminates at the first of:\n * 1. A top-level `;`.\n * 2. A top-level newline that occurs *after* the value expression has\n * produced any non-whitespace content. This is the ASI rule the\n * walker has to honor for semicolonless code:\n *\n * ```\n * const docs = defineCollection(docsCollection()) // ← stop here\n * export const collections = { … } // ← not part of `docs`\n * ```\n *\n * Without the ASI rule the walker would swallow the next statement\n * and the `base:` regex could read an unrelated value.\n * 3. End-of-input.\n *\n * The \"after non-whitespace\" gate handles the leading-newline case:\n *\n * ```\n * const docs =\n * defineCollection(docsCollection({ base: \"x\" }))\n * ```\n *\n * Here the newline right after `=` doesn't terminate; the walker\n * keeps scanning until the value starts. Once content has been seen,\n * the next top-level newline ends the statement.\n *\n * Limitations: method-chain continuations like `defineCollection(…)\\n .extend(…)`\n * stop at the first `)`. That captures the inner call's options (where any\n * `base:` would live), so the result is still correct in practice.\n */\nfunction findStatementEnd(source: string, from: number): number {\n let depth = 0;\n let inString: string | null = null;\n let sawContent = false;\n for (let i = from; i < source.length; i++) {\n const ch = source[i];\n if (inString) {\n if (ch === \"\\\\\") {\n i++;\n continue;\n }\n if (ch === inString) inString = null;\n continue;\n }\n if (ch === '\"' || ch === \"'\" || ch === \"`\") {\n inString = ch;\n sawContent = true;\n } else if (ch === \"{\" || ch === \"[\" || ch === \"(\") {\n depth++;\n sawContent = true;\n } else if (ch === \"}\" || ch === \"]\" || ch === \")\") {\n depth--;\n } else if (ch === \";\" && depth === 0) {\n return i;\n } else if (ch === \"\\n\" && depth === 0 && sawContent) {\n return i;\n } else if (ch !== \" \" && ch !== \"\\t\" && ch !== \"\\r\" && ch !== \"\\n\") {\n sawContent = true;\n }\n }\n return source.length;\n}\n\n/**\n * Starting from an opening brace at `openIdx`, walk forward tracking brace\n * depth (and skipping string literals + nested brackets) and return the\n * index of the matching close brace. Returns `-1` if no match is found —\n * which only happens on a syntactically broken file.\n */\nfunction findMatchingBrace(input: string, openIdx: number): number {\n if (input[openIdx] !== \"{\") return -1;\n let depth = 0;\n let inString: string | null = null;\n for (let i = openIdx; i < input.length; i++) {\n const ch = input[i];\n if (inString) {\n if (ch === \"\\\\\") {\n i++;\n continue;\n }\n if (ch === inString) inString = null;\n continue;\n }\n if (ch === '\"' || ch === \"'\" || ch === \"`\") {\n inString = ch;\n } else if (ch === \"{\") {\n depth++;\n } else if (ch === \"}\") {\n depth--;\n if (depth === 0) return i;\n }\n }\n return -1;\n}\n\n/**\n * Split a string on commas that are at depth 0 (not inside `{}`, `[]`,\n * `()`, or string literals). Required because object entries can themselves\n * contain commas (e.g. `docs: defineCollection(docsCollection({ a: 1 }))`).\n */\nfunction splitTopLevelCommas(input: string): string[] {\n const result: string[] = [];\n let depth = 0;\n let start = 0;\n let inString: string | null = null;\n\n for (let i = 0; i < input.length; i++) {\n const ch = input[i];\n if (inString) {\n if (ch === \"\\\\\") {\n i++;\n continue;\n }\n if (ch === inString) inString = null;\n continue;\n }\n if (ch === '\"' || ch === \"'\" || ch === \"`\") inString = ch;\n else if (ch === \"{\" || ch === \"[\" || ch === \"(\") depth++;\n else if (ch === \"}\" || ch === \"]\" || ch === \")\") depth--;\n else if (ch === \",\" && depth === 0) {\n result.push(input.slice(start, i));\n start = i + 1;\n }\n }\n result.push(input.slice(start));\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Reserved-name filter\n// ---------------------------------------------------------------------------\n\n/**\n * Collection names that should never appear in the agent-facing index,\n * regardless of how they were registered. The rule pair is intentionally\n * minimal so the convention is easy to remember:\n *\n * - `partials` — Nimbus's built-in factory for `<Render slug=…/>`\n * snippets. They're component content, not pages.\n * - any name starting with `_` — author-chosen \"loaded but internal\"\n * marker (e.g. `_drafts`, `_archive`, `_legacy`).\n */\nconst RESERVED_LITERAL = new Set([\"partials\"]);\nconst RESERVED_PREFIX = \"_\";\n\nexport function filterIndexableCollections(names: string[]): string[] {\n return names.filter(\n (name) => !RESERVED_LITERAL.has(name) && !name.startsWith(RESERVED_PREFIX),\n );\n}\n","/**\n * code-transformers.ts — Shiki transformer chain used both by the\n * markdown pipeline (registered into `shikiConfig.transformers` from\n * the Astro integration so fenced MDX blocks pick them up) and by the\n * user's `<Code>` component (Astro's built-in `<Code>` accepts\n * `transformers` as a prop but does *not* auto-read `shikiConfig`).\n *\n * The single source of truth lives here so both paths get the same\n * polish — diff, highlight, focus, error-level, word-highlight, plus\n * meta line/word highlight from `@shikijs/transformers`, plus the\n * Nimbus-owned `titleAndLangTransformer` that:\n *\n * - Wraps the rendered `<pre>` in a `<figure class=\"nb-code-figure\">`\n * whenever the fenced block has `title=\"...\"` in its meta. The\n * figure carries a `<figcaption class=\"nb-code-title\">` with the\n * filename and a small language tag at the right end.\n * - Always tags the `<pre>` with `data-nb-lang=\"…\"` so the starter\n * CSS can render a top-right language badge on un-titled blocks.\n */\n\n// `@shikijs/types` is a dedicated types-only package — devDep here, used\n// internally by `shiki` and `@shikijs/transformers`. Avoids importing from\n// the `shiki` runtime package (which we don't ship as a direct dep).\nimport type { ShikiTransformer } from \"@shikijs/types\";\nimport {\n transformerNotationDiff,\n transformerNotationFocus,\n transformerNotationHighlight,\n transformerNotationErrorLevel,\n transformerNotationWordHighlight,\n transformerMetaHighlight,\n transformerMetaWordHighlight,\n} from \"@shikijs/transformers\";\n\n/**\n * Parse Shiki meta string (the bit after the language fence:\n * ```ts title=\"src/foo.ts\" {1,3}`) for the `title=\"...\"` key.\n * Returns `undefined` when the meta has no title.\n */\nfunction parseTitle(meta: string | undefined): string | undefined {\n if (!meta) return undefined;\n const match = meta.match(/\\btitle=\"([^\"]+)\"/) ?? meta.match(/\\btitle='([^']+)'/);\n return match?.[1];\n}\n\n/**\n * The canonical Shiki transformer chain for Nimbus. Returns a fresh\n * array each call so callers don't accidentally mutate a shared list.\n *\n * Used by:\n * - `integration.ts` → `shikiConfig.transformers` (fenced MDX blocks)\n * - `Code.astro` in the starter → `transformers` prop on Astro's\n * built-in `<Code>` component (and by extension, anything that\n * composes `<Code>` such as `<CodeGroup>`)\n */\nexport function defaultCodeTransformers(): ShikiTransformer[] {\n return [\n transformerNotationDiff(),\n transformerNotationHighlight(),\n transformerNotationFocus(),\n transformerNotationErrorLevel(),\n transformerNotationWordHighlight(),\n transformerMetaHighlight(),\n transformerMetaWordHighlight(),\n titleAndLangTransformer(),\n ];\n}\n\nexport function titleAndLangTransformer(): ShikiTransformer {\n return {\n name: \"nimbus:title-and-lang\",\n pre(preNode) {\n const lang = this.options.lang || \"text\";\n const meta: string | undefined = (this.options.meta as { __raw?: string } | undefined)?.__raw;\n const title = parseTitle(meta);\n\n // Always tag the pre with its language for CSS.\n preNode.properties = preNode.properties ?? {};\n preNode.properties[\"data-nb-lang\"] = lang;\n\n // Always wrap in a <figure>. With title → figcaption + pre. Without\n // title → just the pre, but the figure still provides a non-scrolling\n // positioning context so the language badge (rendered via CSS on the\n // figure) stays pinned at top-right even when the pre overflows\n // horizontally on mobile.\n const children: typeof preNode[] = [];\n if (title) {\n children.push({\n type: \"element\",\n tagName: \"figcaption\",\n properties: { class: \"nb-code-title\" },\n children: [\n {\n type: \"element\",\n tagName: \"span\",\n properties: { class: \"nb-code-title-name\" },\n children: [{ type: \"text\", value: title }],\n },\n {\n type: \"element\",\n tagName: \"span\",\n properties: { class: \"nb-code-title-lang\" },\n children: [{ type: \"text\", value: lang }],\n },\n ],\n });\n }\n children.push(preNode);\n\n return {\n type: \"element\",\n tagName: \"figure\",\n properties: {\n class: title ? \"nb-code-figure nb-code-figure-titled\" : \"nb-code-figure\",\n \"data-nb-lang\": lang,\n },\n children,\n };\n },\n };\n}\n","/**\n * MDX PascalCase tag validator — runs as a content pass, not a remark\n * plugin, so it works regardless of which markdown processor the user\n * has wired into `markdown.processor` (Sätteri replaces unified's\n * pipeline, which silently disables remark plugins attached via\n * `mdx({ remarkPlugins })`).\n *\n * Strategy:\n *\n * 1. Walk the configured content directories for `.mdx` files.\n * 2. For each file: split frontmatter, parse imports + JSX tags from\n * the body, validate every PascalCase tag against globals + per-file\n * imports.\n * 3. Collect every failure across every file (don't fail-fast), then\n * throw one error with all locations and \"did you mean\" hints.\n *\n * Parsing approach is intentionally regex-based and not a full MDX\n * parser. Tradeoffs:\n *\n * - Pro: zero MDX/remark deps, runs in milliseconds, no pipeline\n * coupling. Survives processor swaps (satteri / unified / future).\n * - Pro: tolerates malformed MDX — the validator's job is to find\n * unknown tags, not to be the parser of record.\n * - Con: a few edge cases (JSX inside string literals inside expression\n * children, deeply nested fenced code with `~~~`) can produce false\n * positives. Code blocks (``` and indented) are stripped before\n * scanning to keep the common case clean.\n *\n * Catches the silent-failure case where MDX renders unknown PascalCase\n * tags as literal text on the deployed page — the bug appears in\n * production, not in the build log.\n */\n\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { suggest } from \"./levenshtein.js\";\n\nexport interface ValidateMdxContentOptions {\n /** Names available globally (from `src/components.ts`). */\n globals: ReadonlyArray<string>;\n /**\n * Absolute paths to scan. Typically `[<projectRoot>/src/content]`.\n * Each path is walked recursively for `.mdx` files.\n */\n contentDirs: ReadonlyArray<string>;\n /**\n * Optional filter to skip files (e.g. vendored MDX). Receives the\n * absolute path; return `true` to skip validation.\n */\n skip?: (filePath: string) => boolean;\n /**\n * Project root, used to print file paths relative to it in error\n * messages. Falls back to the absolute path when not provided.\n */\n projectRoot?: string;\n}\n\nexport interface ValidationFailure {\n filePath: string;\n tag: string;\n line: number;\n column: number;\n hint: string | null;\n}\n\nexport async function validateMdxContent(\n options: ValidateMdxContentOptions,\n): Promise<ValidationFailure[]> {\n const globalsSet = new Set(options.globals);\n const failures: ValidationFailure[] = [];\n\n for (const dir of options.contentDirs) {\n const files = await walkMdx(dir);\n for (const file of files) {\n if (options.skip?.(file)) continue;\n const source = await fs.readFile(file, \"utf8\");\n const fileFailures = scanFile(source, globalsSet);\n for (const f of fileFailures) {\n const knownNames = [...globalsSet, ...f.imports];\n failures.push({\n filePath: options.projectRoot\n ? path.relative(options.projectRoot, file)\n : file,\n tag: f.tag,\n line: f.line,\n column: f.column,\n hint: suggest(f.tag, knownNames),\n });\n }\n }\n }\n\n return failures;\n}\n\n/**\n * Format a list of failures into a single multi-line error message\n * suitable for `throw new Error(...)`.\n */\nexport function formatFailures(\n failures: ReadonlyArray<ValidationFailure>,\n globalsCount: number,\n): string {\n const lines = failures.map((f) => {\n const fix = f.hint\n ? `Did you mean <${f.hint} />?`\n : globalsCount === 0\n ? `Register it in src/components.ts, or add an explicit \\`import\\` at the top of this file.`\n : `Register it in src/components.ts, or add an explicit \\`import\\` at the top of this file.`;\n return ` ${f.filePath}:${f.line}:${f.column} <${f.tag} /> → ${fix}`;\n });\n\n const noun = failures.length === 1 ? \"tag\" : \"tags\";\n return (\n `[nimbus-docs] Unknown MDX component ${noun}:\\n` +\n lines.join(\"\\n\") +\n `\\n\\nA PascalCase tag in MDX must either be registered in src/components.ts (the global registry) or imported at the top of the file. ` +\n `Without either, MDX renders the tag as literal text on the page — a silent failure this validator turns into a build error.`\n );\n}\n\n// ---------------------------------------------------------------------------\n// File walking\n// ---------------------------------------------------------------------------\n\nasync function walkMdx(dir: string): Promise<string[]> {\n const out: string[] = [];\n async function visit(current: string) {\n let entries: import(\"node:fs\").Dirent[];\n try {\n entries = await fs.readdir(current, { withFileTypes: true });\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return;\n throw err;\n }\n for (const entry of entries) {\n const full = path.join(current, entry.name);\n if (entry.isDirectory()) {\n if (entry.name === \"node_modules\" || entry.name.startsWith(\".\")) continue;\n await visit(full);\n } else if (entry.isFile() && entry.name.endsWith(\".mdx\")) {\n out.push(full);\n }\n }\n }\n await visit(dir);\n return out;\n}\n\n// ---------------------------------------------------------------------------\n// Per-file scanner\n// ---------------------------------------------------------------------------\n\ninterface RawFailure {\n tag: string;\n line: number;\n column: number;\n imports: Set<string>;\n}\n\nfunction scanFile(\n source: string,\n globalsSet: ReadonlySet<string>,\n): RawFailure[] {\n const { body, bodyOffset } = stripFrontmatter(source);\n const imports = parseImports(body);\n const stripped = stripCodeBlocks(body);\n const tags = findPascalCaseTags(stripped);\n\n const failures: RawFailure[] = [];\n for (const tag of tags) {\n if (globalsSet.has(tag.name) || imports.has(tag.name)) continue;\n const position = absolutePosition(source, bodyOffset + tag.offset);\n failures.push({\n tag: tag.name,\n line: position.line,\n column: position.column,\n imports,\n });\n }\n return failures;\n}\n\nfunction stripFrontmatter(source: string): { body: string; bodyOffset: number } {\n const match = source.match(/^---\\n[\\s\\S]*?\\n---\\n?/);\n if (!match) return { body: source, bodyOffset: 0 };\n return { body: source.slice(match[0].length), bodyOffset: match[0].length };\n}\n\n/**\n * Extract names introduced by top-level `import` statements. Handles\n * default, named (with optional aliases), and namespace imports.\n */\nfunction parseImports(body: string): Set<string> {\n const names = new Set<string>();\n // Match `import ... from \"...\"` and side-effect `import \"...\"` (no names).\n const importPattern = /^\\s*import\\s+([^\"';]+?)\\s+from\\s+[\"'][^\"']+[\"']\\s*;?/gm;\n // `!` on every regex capture-group access: the `import…from` pattern's\n // group is *required* (not optional), so it's defined whenever `match`\n // succeeded. Same logic applies to `namespaceMatch[1]`, `braceMatch[1]`,\n // `aliasMatch[1]` — all required groups. `.split(\"{\")[0]!` is safe\n // because `String.split` always returns ≥1 element.\n for (const match of body.matchAll(importPattern)) {\n const clause = match[1]!;\n // Namespace: `import * as Foo from \"...\"`\n const namespaceMatch = clause.match(/\\*\\s+as\\s+([A-Za-z_$][\\w$]*)/);\n if (namespaceMatch) {\n names.add(namespaceMatch[1]!);\n continue;\n }\n // Strip and split: `Default, { Named, Aliased as Local }`\n const beforeBrace = clause.split(\"{\")[0]!.trim().replace(/,\\s*$/, \"\");\n if (beforeBrace && /^[A-Za-z_$][\\w$]*$/.test(beforeBrace)) {\n names.add(beforeBrace);\n }\n const braceMatch = clause.match(/\\{([^}]*)\\}/);\n if (braceMatch) {\n for (const raw of braceMatch[1]!.split(\",\")) {\n const spec = raw.trim();\n if (!spec) continue;\n const aliasMatch = spec.match(/^[A-Za-z_$][\\w$]*\\s+as\\s+([A-Za-z_$][\\w$]*)$/);\n if (aliasMatch) {\n names.add(aliasMatch[1]!);\n } else if (/^[A-Za-z_$][\\w$]*$/.test(spec)) {\n names.add(spec);\n }\n }\n }\n }\n return names;\n}\n\n/**\n * Remove fenced code blocks and inline code spans so JSX-looking text\n * inside code samples doesn't trip the validator.\n */\nfunction stripCodeBlocks(body: string): string {\n return body\n .replace(/```[\\s\\S]*?```/g, (m) => \" \".repeat(m.length))\n .replace(/~~~[\\s\\S]*?~~~/g, (m) => \" \".repeat(m.length))\n // Inline code spans. Kept line-bounded on purpose: matching across\n // newlines makes backtick pairing global, so a single stray/odd\n // backtick anywhere cascades and exposes later inline code. Generics\n // that wrap across lines (`Promise<Map<…>>`) are instead handled in\n // findPascalCaseTags, which ignores `<Identifier<` (a TS generic, not\n // a JSX tag).\n .replace(/`[^`\\n]*`/g, (m) => \" \".repeat(m.length))\n // Attribute-shaped string values (`text=\"Promise<QueueSendResult>\"`).\n // JSX inside a quoted attribute is plain text to MDX, not a tag —\n // without this strip the scanner false-positives on generics like\n // `<Type text=\"Promise<Foo>\" />`. Length-preserving for offsets.\n .replace(/=\\s*\"[^\"\\n]*\"/g, (m) => \"=\" + \" \".repeat(m.length - 1))\n .replace(/=\\s*'[^'\\n]*'/g, (m) => \"=\" + \" \".repeat(m.length - 1))\n // String literals inside JSX expressions (`json={{ id: \"<IDP_UUID>\" }}`).\n // These aren't attribute-shaped, so the rules above miss them; a quoted\n // placeholder like \"<IDP_UUID>\" is a string value, never a component.\n // Real `<Component>` usage is never written inside a quoted string, so\n // stripping all single-line quoted spans can't hide a genuine tag.\n .replace(/\"[^\"\\n]*\"/g, (m) => \" \".repeat(m.length))\n .replace(/'[^'\\n]*'/g, (m) => \" \".repeat(m.length));\n}\n\ninterface FoundTag {\n name: string;\n offset: number;\n}\n\n/**\n * Find PascalCase JSX-like tags. Matches `<Capital...` at the start of\n * an element (opening or self-closing). Closing tags `</Capital>` and\n * JSX fragments `<>` are not counted (the opener already covers\n * registration; counting closers would double-report).\n */\nfunction findPascalCaseTags(body: string): FoundTag[] {\n const out: FoundTag[] = [];\n const pattern = /<([A-Z][A-Za-z0-9_]*)/g;\n for (const match of body.matchAll(pattern)) {\n const offset = match.index ?? 0;\n // A `<` immediately after the identifier is a TypeScript generic\n // (`Promise<Map<string, …>>`), not a JSX element. These show up in\n // inline code / type signatures and must not be flagged as tags.\n if (body[offset + match[0].length] === \"<\") continue;\n // `match[1]!`: required capture group, defined whenever match succeeded.\n out.push({ name: match[1]!, offset });\n }\n return out;\n}\n\n/**\n * Compute 1-based line + column for an absolute character offset in the\n * original source.\n */\nfunction absolutePosition(source: string, offset: number): { line: number; column: number } {\n let line = 1;\n let column = 1;\n const end = Math.min(offset, source.length);\n for (let i = 0; i < end; i++) {\n if (source[i] === \"\\n\") {\n line++;\n column = 1;\n } else {\n column++;\n }\n }\n return { line, column };\n}\n","/**\n * Config validation.\n *\n * Errors target content authors, not framework developers.\n * Astro 6 ships Zod v4 via `astro/zod` — single `error` field, not v3 patterns.\n */\n\nimport { z } from \"astro/zod\";\nimport type { NimbusConfig } from \"../types.js\";\nimport { withStrictKeys } from \"./strict-keys.js\";\n\n// Head elements: full set valid as direct children of `<head>`. Mirrors the\n// frontmatter `head` schema in `schemas.ts` and the `HeadElement` type\n// surface — the three sources need to agree so a tag accepted in\n// frontmatter doesn't trip config validation (or vice versa).\nconst headElementSchema = z.object({\n tag: z.enum([\"meta\", \"link\", \"script\", \"style\", \"title\", \"noscript\", \"base\"], {\n error:\n 'head element \"tag\" must be one of: meta, link, script, style, title, noscript, base',\n }),\n attrs: z.record(z.string(), z.string()).default({}),\n content: z.string().optional(),\n});\n\n/**\n * Removed/renamed keys in the `features` sub-object. Each maps to the\n * back-half of a sentence — the parent error message prepends\n * `features sub-key \"<name>\" ` automatically.\n */\nconst REMOVED_FEATURE_KEYS: Record<string, string> = {\n toc:\n 'was renamed to \"tableOfContents\". Replace `features: { toc: false }` with `features: { tableOfContents: false }`.',\n pagination:\n \"was removed. To hide pagination site-wide, remove `<Pagination />` from `src/layouts/DocsLayout.astro` (it is user-owned).\",\n editLinks:\n \"was removed. To hide edit links site-wide, omit `editPattern` from the config — the default is null, which produces no edit URLs. Setting `github` alone does not enable edit links.\",\n search:\n \"moved to the top-level `search` field on the config. Replace `features: { search: false }` with `search: false`.\",\n};\n\n// Narrow features schema: only kill switches for chrome that's hard to\n// hide via user-side edits alone (the sidebar threads through layout +\n// header + mobile dialog; the TOC has its own column the layout sets up).\n// Both default to `true`. Per-page frontmatter (sidebar/tableOfContents)\n// can override in the \"off\" direction via AND-merge in the route.\nconst featuresSchema = withStrictKeys(\n z.object({\n sidebar: z.boolean().default(true),\n tableOfContents: z.boolean().default(true),\n }),\n {\n removedKeys: REMOVED_FEATURE_KEYS,\n contextLabel: \"features sub-key\",\n },\n).default({ sidebar: true, tableOfContents: true });\n\nconst searchSchema = z\n .union([\n z.literal(false),\n z.object({\n provider: z.enum([\"pagefind\", \"custom\"]).default(\"pagefind\"),\n }),\n ])\n .optional();\n\n// Sidebar items are intentionally loose — the sidebar builder accepts the\n// shapes documented in types.ts; tightening here adds friction for users\n// without catching real errors that the builder doesn't already catch.\nconst sidebarSchema = z\n .object({\n items: z.array(z.unknown()).optional(),\n scope: z.enum([\"full\", \"section\"]).default(\"full\"),\n })\n .passthrough()\n .optional();\n\n// Versioning manifest. Shape validation only (P0). Cross-checking that each\n// `others[i]` actually corresponds to a registered `docs-<i>` collection\n// happens at integration setup time in `integration.ts` where the parsed\n// collections list is available.\n//\n// Rules enforced here (mirrors versioned-docs spec acceptance criteria):\n// - `current` is a non-empty string.\n// - `others` are non-empty strings, no duplicates.\n// - `deprecated` ⊆ `others`.\n// - `hidden` ⊆ `others`.\n// - `current` not present in `others` (a version is either current or older,\n// never both).\nconst versionSlugSchema = z\n .string({ error: '\"versions\" entries must be strings' })\n .min(1, { message: 'Empty string is not a valid version slug' });\n\nconst versionsSchema = z\n .object({\n current: versionSlugSchema,\n others: z.array(versionSlugSchema).default([]),\n deprecated: z.array(versionSlugSchema).default([]),\n hidden: z.array(versionSlugSchema).default([]),\n })\n .superRefine((v, ctx) => {\n const seen = new Set<string>();\n v.others.forEach((slug, i) => {\n if (seen.has(slug)) {\n ctx.addIssue({\n code: \"custom\",\n message: `Duplicate version slug \"${slug}\" in \"others\"`,\n path: [\"others\", i],\n });\n }\n seen.add(slug);\n });\n if (v.others.includes(v.current)) {\n ctx.addIssue({\n code: \"custom\",\n message:\n `\"current\" (${JSON.stringify(v.current)}) must not also appear in \"others\". ` +\n `The current version lives in the primary \\`docs\\` collection; ` +\n `entries in \"others\" describe older versions stored in \\`docs-<slug>\\` collections.`,\n path: [\"current\"],\n });\n }\n for (const [i, slug] of v.deprecated.entries()) {\n if (!v.others.includes(slug)) {\n ctx.addIssue({\n code: \"custom\",\n message:\n `\"deprecated\" entry ${JSON.stringify(slug)} is not in \"others\". ` +\n `Every deprecated version must also be listed in \"others\".`,\n path: [\"deprecated\", i],\n });\n }\n }\n for (const [i, slug] of v.hidden.entries()) {\n if (!v.others.includes(slug)) {\n ctx.addIssue({\n code: \"custom\",\n message:\n `\"hidden\" entry ${JSON.stringify(slug)} is not in \"others\". ` +\n `Every hidden version must also be listed in \"others\".`,\n path: [\"hidden\", i],\n });\n }\n }\n })\n .optional();\n\n/**\n * Removed top-level config keys. Hits emit a friendly migration message\n * instead of being silently dropped.\n */\nconst REMOVED_CONFIG_KEYS: Record<string, string> = {\n logo:\n 'was removed. The header now renders `config.title` as text. To use a logo image, edit `src/components/Header.astro` and drop in an <img> or <svg>.',\n footer:\n \"was removed. The starter no longer ships a default `Footer.astro`. To add one, create your own component and render it in `src/layouts/DocsLayout.astro`.\",\n};\n\nconst nimbusConfigSchema = withStrictKeys(\n z.object({\n site: z.string().url({ message: '\"site\" must be a valid URL' }),\n title: z.string(),\n description: z.string().optional(),\n locale: z.string().default(\"en\"),\n homeLabel: z.string().default(\"Home\"),\n github: z.string().url().nullable().default(null),\n // editPattern must contain the `{path}` placeholder. Without it,\n // `getEditUrl()` returns the pattern unchanged for every entry — a\n // silent footgun that ships broken edit links to production.\n editPattern: z\n .string()\n .nullable()\n .default(null)\n .refine((v) => v === null || v.includes(\"{path}\"), {\n message:\n '\"editPattern\" must contain the \"{path}\" placeholder, which is replaced with the entry source path. ' +\n 'Example: \"https://github.com/my-org/my-repo/edit/main/{path}\"',\n }),\n socialImage: z\n .string({ error: '\"socialImage\" must be a string (path or URL)' })\n .optional(),\n socialImageAlt: z\n .string({ error: '\"socialImageAlt\" must be a string' })\n .optional(),\n head: z.array(headElementSchema).default([]),\n sidebar: sidebarSchema,\n features: featuresSchema,\n search: searchSchema,\n versions: versionsSchema,\n }),\n {\n removedKeys: REMOVED_CONFIG_KEYS,\n contextLabel: \"Config field\",\n },\n);\n\nexport function validateNimbusConfig(input: unknown): NimbusConfig {\n const result = nimbusConfigSchema.safeParse(input);\n if (result.success) {\n // Zod safeParse upstream validated the shape against nimbusConfigSchema;\n // double-cast restores the type info tsc lost through the schema's\n // generic `Record<string, unknown>` representation.\n return result.data as unknown as NimbusConfig;\n }\n\n // Build a content-author-readable issue list. Each line carries:\n // - the dotted config path (so it's greppable in nimbus.config.ts)\n // - the validator message\n // - the offending value (truncated) when one was supplied\n const issues = result.error.issues\n .map((issue) => {\n // Zod v4 widens path entries to PropertyKey. Symbols never appear in\n // our schema (no symbol keys), so it's safe to coerce to string|number\n // for both display and value lookup.\n const issuePath = issue.path\n .filter((p): p is string | number => typeof p !== \"symbol\");\n const display = issuePath.length > 0 ? issuePath.join(\".\") : \"(root)\";\n const received = formatReceived(input, issuePath);\n const tail = received === null ? \"\" : `\\n received: ${received}`;\n return ` - ${display}: ${issue.message}${tail}`;\n })\n .join(\"\\n\");\n\n throw new Error(\n `Invalid nimbus.config — fix these issues:\\n${issues}\\n\\n` +\n `See https://nimbus-docs.dev/config for the full config schema.`,\n );\n}\n\n/**\n * Resolve the value at `path` inside the raw input and format it for an\n * error message. Returns null when the path is unreachable (e.g. a\n * required key is missing entirely — in that case the message itself\n * already says \"Required\", so we don't double up).\n */\nfunction formatReceived(input: unknown, path: ReadonlyArray<string | number>): string | null {\n let cursor: unknown = input;\n for (const key of path) {\n if (cursor === null || typeof cursor !== \"object\") return null;\n cursor = (cursor as Record<string | number, unknown>)[key];\n if (cursor === undefined) return null;\n }\n if (cursor === undefined) return null;\n try {\n const json = JSON.stringify(cursor);\n if (json === undefined) return String(cursor);\n return json.length > 120 ? `${json.slice(0, 117)}...` : json;\n } catch {\n return String(cursor);\n }\n}\n","/**\n * Vite plugin: exposes the validated NimbusConfig via `virtual:nimbus/config`.\n *\n * Consumers in user-land:\n *\n * import { config, indexedCollections, versionAlternates }\n * from \"virtual:nimbus/config\";\n *\n * Used by data helpers (getSidebar, getPrevNext, etc.) so they don't need\n * the config passed at every call site. The `indexedCollections` export\n * is the build-time-resolved list of collections that agent-facing routes\n * (llms.txt, per-page .md alternates) should iterate. See\n * `parse-content-collections.ts` and `getIndexedEntries()`.\n *\n * `versionAlternates` is the build-time alternates table for cross-version\n * SEO links (`<link rel=\"alternate\">`, `<link rel=\"canonical\">`). Empty\n * object when the site is unversioned. See `version-alternates.ts`.\n */\n\nimport type { NimbusConfig } from \"../types.js\";\nimport type { VersionAlternatesTable } from \"./version-alternates.js\";\n\nconst VIRTUAL_ID = \"virtual:nimbus/config\";\nconst RESOLVED_ID = `\\0${VIRTUAL_ID}`;\n\nexport interface VitePluginLike {\n name: string;\n resolveId(id: string): string | undefined;\n load(id: string): string | undefined;\n}\n\nexport interface VirtualConfigExtras {\n /**\n * Registered docs-shaped collection names, with reserved (`partials`,\n * `_*`) already filtered out. Empty array falls back to `[\"docs\"]` at\n * read time so a brand-new project without `content.config.ts` still\n * works.\n */\n indexedCollections: string[];\n /**\n * Build-time alternates table for cross-version SEO links. Empty `{}`\n * when the site is unversioned or has only the current version.\n */\n versionAlternates: VersionAlternatesTable;\n}\n\nexport function virtualConfigPlugin(\n config: NimbusConfig,\n extras: VirtualConfigExtras,\n): VitePluginLike {\n return {\n name: \"nimbus-docs:virtual-config\",\n resolveId(id: string) {\n if (id === VIRTUAL_ID) return RESOLVED_ID;\n return undefined;\n },\n load(id: string) {\n if (id === RESOLVED_ID) {\n return (\n `export const config = ${JSON.stringify(config)};\\n` +\n `export const indexedCollections = ${JSON.stringify(extras.indexedCollections)};\\n` +\n `export const versionAlternates = ${JSON.stringify(extras.versionAlternates)};\\n`\n );\n }\n return undefined;\n },\n };\n}\n","/**\n * Walk `src/content/` and collect every language used in fenced code blocks\n * inside `.md` / `.mdx` files. Output feeds `shikiConfig.langs` so Shiki\n * eager-loads every grammar at startup instead of lazy-loading on first use.\n *\n * Why this matters: Shiki's lazy load assumes every MDX file gets processed\n * during a build. Layer 2 (incremental builds' Vite MDX-skip plugin) breaks\n * that assumption — cached MDX files never enter the markdown pipeline, so\n * languages that only appear in cached files would never trigger a grammar\n * load, and any non-cached file using those languages would silently render\n * without highlighting.\n *\n * Eager loading also gives non-incremental users a small predictability win:\n * the highlighter behaves the same regardless of which file is processed\n * first.\n *\n * Cost: ~1s on a 7k-file bench. Acceptable.\n */\nimport { readdir, readFile } from \"node:fs/promises\";\nimport { extname, resolve } from \"node:path\";\n\nconst FENCE_RE = /^[ \\t]*```([a-zA-Z][a-zA-Z0-9_+\\-]*)/gm;\n\nasync function* walkMdx(dir: string): AsyncIterable<string> {\n let entries;\n try {\n entries = await readdir(dir, { withFileTypes: true });\n } catch {\n return;\n }\n for (const entry of entries) {\n if (entry.name.startsWith(\".\")) continue;\n const full = resolve(dir, entry.name);\n if (entry.isDirectory()) {\n yield* walkMdx(full);\n } else if (entry.isFile() && [\".mdx\", \".md\"].includes(extname(entry.name))) {\n yield full;\n }\n }\n}\n\n/**\n * Scan a project's content directories for code-fence languages.\n *\n * `langAlias` maps shorthand fence names (e.g. `curl`, `console`) to the\n * underlying highlighter Shiki actually knows. The mapping is applied\n * before deduping so the returned set is what Shiki should load.\n */\nexport async function scanCodeBlockLanguages(\n projectRoot: string,\n langAlias: Record<string, string> = {},\n): Promise<string[]> {\n const langs = new Set<string>();\n const contentRoot = resolve(projectRoot, \"src/content\");\n\n for await (const file of walkMdx(contentRoot)) {\n let content: string;\n try {\n content = await readFile(file, \"utf8\");\n } catch {\n continue;\n }\n // Reset stateful regex iterator across files.\n FENCE_RE.lastIndex = 0;\n for (const m of content.matchAll(FENCE_RE)) {\n const raw = m[1]!.toLowerCase();\n const mapped = langAlias[raw] ?? raw;\n langs.add(mapped);\n }\n }\n\n return Array.from(langs).sort();\n}\n","/**\n * Filesystem cache layer.\n *\n * Layout under `.nimbus/cache/`:\n *\n * manifest.json — see Manifest type\n * pages/<aa>/<full-hash>.html — cached HTML body for a page, sharded\n * by the first 2 hex chars of the hash\n *\n * Phase 2 MVP — atomic per-file writes. v2 adds a manifest-level\n * `namespace` field for PR-vs-main isolation; resolution lives in\n * `namespace.ts`. Framework/Node version is folded into `globalHash` via\n * `computeGlobalHash` already, so it doesn't need a separate field.\n */\nimport { cp, mkdir, readFile, readdir, rename, rm, stat, writeFile } from \"node:fs/promises\";\nimport { dirname, relative, resolve, sep } from \"node:path\";\n\nconst SCHEMA_VERSION = 2;\n\nexport interface Manifest {\n schemaVersion: number;\n /** Provenance tag — distinguishes one cache lineage from another (e.g.\n * PR branch vs. main branch). A mismatch on warm build is treated like\n * a globalHash mismatch: full cold rebuild. */\n namespace: string;\n globalHash: string;\n pages: Record<string, string>; // pathname → pageHash\n recordedAt: string;\n}\n\nexport class Cache {\n readonly root: string;\n\n constructor(projectRoot: string) {\n this.root = resolve(projectRoot, \".nimbus/cache\");\n }\n\n private pagePath(hash: string): string {\n return resolve(this.root, \"pages\", hash.slice(0, 2), `${hash}.html`);\n }\n\n private manifestPath(): string {\n return resolve(this.root, \"manifest.json\");\n }\n\n async readManifest(): Promise<Manifest | null> {\n try {\n const raw = await readFile(this.manifestPath(), \"utf8\");\n const m = JSON.parse(raw) as Manifest;\n if (m.schemaVersion !== SCHEMA_VERSION) return null;\n return m;\n } catch {\n return null;\n }\n }\n\n async writeManifest(manifest: Omit<Manifest, \"schemaVersion\" | \"recordedAt\">): Promise<void> {\n const full: Manifest = {\n schemaVersion: SCHEMA_VERSION,\n recordedAt: new Date().toISOString(),\n ...manifest,\n };\n await mkdir(this.root, { recursive: true });\n await writeAtomic(this.manifestPath(), JSON.stringify(full, null, 2) + \"\\n\");\n }\n\n async readPage(hash: string): Promise<string | null> {\n try {\n return await readFile(this.pagePath(hash), \"utf8\");\n } catch {\n return null;\n }\n }\n\n async hasPage(hash: string): Promise<boolean> {\n try {\n await readFile(this.pagePath(hash));\n return true;\n } catch {\n return false;\n }\n }\n\n async writePage(hash: string, html: string): Promise<void> {\n const path = this.pagePath(hash);\n await mkdir(dirname(path), { recursive: true });\n await writeAtomic(path, html);\n }\n\n async clear(): Promise<void> {\n await rm(this.root, { recursive: true, force: true });\n }\n\n /**\n * Snapshot a *bounded subset* of `dist/_astro/` into the cache.\n *\n * Naive snapshot was unbounded: every warm build accumulated new\n * bundle hashes (vite produces different hashes when the module graph\n * differs between builds) and we kept everything forever. Caller passes\n * the set of asset rel-paths that some cached HTML actually references —\n * anything outside that set gets dropped.\n *\n * `referencedRelPaths` should be the union of every `/_astro/...` URL\n * extracted from cached HTML — see `parseReferencedAssets` in index.ts.\n */\n async snapshotAssets(\n distAstroDir: string,\n referencedRelPaths: Set<string>,\n ): Promise<number> {\n const target = resolve(this.root, \"assets\");\n await rm(target, { recursive: true, force: true });\n try {\n await stat(distAstroDir);\n } catch {\n return 0;\n }\n if (referencedRelPaths.size === 0) {\n // No cached HTML references any asset — nothing to retain.\n return 0;\n }\n await mkdir(target, { recursive: true });\n let count = 0;\n for (const relPath of referencedRelPaths) {\n const src = resolve(distAstroDir, relPath);\n const dst = resolve(target, relPath);\n try {\n await stat(src);\n } catch {\n continue; // referenced asset isn't in dist; skip\n }\n try {\n await mkdir(dirname(dst), { recursive: true });\n await cp(src, dst);\n count++;\n } catch {\n // best-effort; a single bad copy doesn't abort the snapshot\n }\n }\n return count;\n }\n\n /**\n * Restore cached assets into the build's `_astro/` directory. Only writes\n * files that don't already exist — fresh assets from the current warm\n * build win when there's a collision.\n *\n * Per-file try/catch: a failed copy logs and continues. Aborting the\n * whole restore on a single bad file would prevent `astro:build:done`\n * from reaching the manifest write — that's a worse failure mode than\n * a few missing assets.\n */\n async restoreAssets(\n distAstroDir: string,\n onError?: (path: string, err: Error) => void,\n ): Promise<number> {\n const source = resolve(this.root, \"assets\");\n try {\n await stat(source);\n } catch {\n return 0;\n }\n let restored = 0;\n await mkdir(distAstroDir, { recursive: true });\n for await (const relPath of walkRelative(source)) {\n const src = resolve(source, relPath);\n const dst = resolve(distAstroDir, relPath);\n try {\n await stat(dst);\n continue; // already in fresh dist\n } catch {\n // fall through to copy\n }\n try {\n await mkdir(dirname(dst), { recursive: true });\n await cp(src, dst);\n restored++;\n } catch (err) {\n onError?.(relPath, err as Error);\n }\n }\n return restored;\n }\n\n /**\n * Snapshot `dist/pagefind/` into the cache. Called after a Pagefind run\n * completes so a subsequent zero-miss warm build can restore the prior\n * index without rerunning Pagefind (which sets a ~10s floor at 7k pages\n * by reindexing the entire site).\n *\n * Idempotent: replaces any prior snapshot. Returns the number of files\n * copied; 0 if `pagefind/` doesn't exist (e.g. user disabled search).\n */\n async snapshotPagefind(distPagefindDir: string): Promise<number> {\n const target = resolve(this.root, \"pagefind\");\n await rm(target, { recursive: true, force: true });\n try {\n await stat(distPagefindDir);\n } catch {\n return 0;\n }\n await mkdir(target, { recursive: true });\n let count = 0;\n for await (const relPath of walkRelative(distPagefindDir)) {\n const src = resolve(distPagefindDir, relPath);\n const dst = resolve(target, relPath);\n try {\n await mkdir(dirname(dst), { recursive: true });\n await cp(src, dst);\n count++;\n } catch {\n // best-effort\n }\n }\n return count;\n }\n\n /**\n * Restore the cached `pagefind/` into `dist/`. Used on zero-miss warm\n * builds in place of rerunning Pagefind. Per-file try/catch — a single\n * bad copy doesn't abort the restore.\n */\n async restorePagefind(distPagefindDir: string): Promise<number> {\n const source = resolve(this.root, \"pagefind\");\n try {\n await stat(source);\n } catch {\n return 0;\n }\n let restored = 0;\n await mkdir(distPagefindDir, { recursive: true });\n for await (const relPath of walkRelative(source)) {\n const src = resolve(source, relPath);\n const dst = resolve(distPagefindDir, relPath);\n try {\n await mkdir(dirname(dst), { recursive: true });\n await cp(src, dst);\n restored++;\n } catch {\n // best-effort\n }\n }\n return restored;\n }\n\n /** Whether a Pagefind snapshot is present on disk. */\n async hasPagefindSnapshot(): Promise<boolean> {\n try {\n await stat(resolve(this.root, \"pagefind\"));\n return true;\n } catch {\n return false;\n }\n }\n}\n\nasync function* walkRelative(root: string): AsyncIterable<string> {\n async function* walk(dir: string): AsyncIterable<string> {\n const entries = await readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n const full = resolve(dir, entry.name);\n if (entry.isDirectory()) {\n yield* walk(full);\n } else if (entry.isFile()) {\n yield relative(root, full).split(sep).join(\"/\");\n }\n }\n }\n yield* walk(root);\n}\n\nasync function countFiles(dir: string): Promise<number> {\n let n = 0;\n for await (const _ of walkRelative(dir)) n++;\n return n;\n}\n\n/**\n * Write `data` to `path` atomically — write to a sibling temp file, then\n * rename into place. Prevents half-written files when a build is interrupted.\n */\nasync function writeAtomic(path: string, data: string): Promise<void> {\n const tmp = `${path}.tmp-${process.pid}-${Date.now()}`;\n await writeFile(tmp, data, \"utf8\");\n await rename(tmp, path);\n}\n","/**\n * Hash primitives for the incremental builds cache.\n *\n * Two hash kinds:\n * - globalHash: fingerprint of anything outside src/content/ that could\n * change rendered output (config, components, layouts,\n * lockfile). Any change here invalidates every page.\n * - pageHash: sha256(page bytes + globalHash). Determines whether a\n * given page's cached HTML is still valid.\n *\n * Phase 2 MVP — deliberately no partial tracking, no data-collection tracking,\n * no component-graph tracking. Phase 3 wires the partial registry into the\n * page hash; that work depends on `validate-mdx-content.ts` being extended\n * to capture `<Render file=\"…\">` references.\n */\nimport { createHash } from \"node:crypto\";\nimport { readdir, readFile, stat } from \"node:fs/promises\";\nimport { relative, resolve, sep } from \"node:path\";\nimport { createRequire } from \"node:module\";\n\nconst TRACKED_DIRS = [\"src\", \"public\"];\nconst TRACKED_FILES = [\n \"astro.config.ts\",\n \"astro.config.mts\",\n \"astro.config.mjs\",\n \"astro.config.cts\",\n \"astro.config.js\",\n \"package.json\",\n \"pnpm-lock.yaml\",\n \"package-lock.json\",\n \"yarn.lock\",\n \"bun.lockb\",\n \"tsconfig.json\",\n];\n// Anything inside src/ that's content. Content files are hashed\n// individually as per-page inputs; folding them into the global hash\n// would invalidate every page when any single page changes.\nconst CONTENT_EXCLUDES = [\"src/content\"];\n\nexport function sha256Hex(input: string | Buffer): string {\n return createHash(\"sha256\").update(input).digest(\"hex\");\n}\n\nexport function shortHash(hex: string, len = 16): string {\n return hex.slice(0, len);\n}\n\n/**\n * Walk `dir` recursively, returning relative paths of every file.\n * Skips node_modules, dist, .astro, .nimbus, and hidden dirs.\n */\nasync function walk(dir: string, root: string): Promise<string[]> {\n let entries;\n try {\n entries = await readdir(dir, { withFileTypes: true });\n } catch {\n return [];\n }\n const out: string[] = [];\n for (const entry of entries) {\n if (entry.name.startsWith(\".\")) continue;\n if (entry.name === \"node_modules\") continue;\n if (entry.name === \"dist\") continue;\n const full = resolve(dir, entry.name);\n const rel = relative(root, full).split(sep).join(\"/\");\n if (CONTENT_EXCLUDES.some((ex) => rel === ex || rel.startsWith(ex + \"/\"))) continue;\n if (entry.isDirectory()) {\n out.push(...(await walk(full, root)));\n } else if (entry.isFile()) {\n out.push(rel);\n }\n }\n return out;\n}\n\n/**\n * Compute the global hash for the project at `projectRoot`.\n *\n * The hash is sha256 over a canonical line-per-file listing followed by\n * provenance lines for framework + runtime versions:\n *\n * FILE\\t<rel-path>\\t<sha256(file-bytes)>\\n\n * PROVENANCE\\t<key>=<value>\\n\n *\n * Sorted by line so the hash is deterministic across filesystems\n * (readdir order is not guaranteed).\n *\n * Provenance covers:\n * - Cache layout schemaVersion (bumped when the cache format changes)\n * - Nimbus framework version\n * - Astro version (resolved from the project's installed copy)\n * - Node major version (minor diffs occasionally affect bundling)\n * - Platform + arch (some asset emission is platform-sensitive)\n *\n * Including provenance closes BUG-002 / BUG-003: a framework upgrade\n * (or Node bump, or OS change) silently changed rendered output but the\n * old global hash matched, so warm builds served stale entries from a\n * different version of the world.\n */\nexport async function computeGlobalHash(projectRoot: string): Promise<string> {\n const files: string[] = [];\n for (const dir of TRACKED_DIRS) {\n const abs = resolve(projectRoot, dir);\n files.push(...(await walk(abs, projectRoot)));\n }\n for (const file of TRACKED_FILES) {\n const abs = resolve(projectRoot, file);\n try {\n const s = await stat(abs);\n if (s.isFile()) files.push(file);\n } catch {\n // missing top-level file is fine; just don't include in hash\n }\n }\n files.sort();\n\n const lines: string[] = [];\n for (const rel of files) {\n const abs = resolve(projectRoot, rel);\n const bytes = await readFile(abs);\n lines.push(`FILE\\t${rel}\\t${sha256Hex(bytes)}`);\n }\n\n const provenance = await readProvenance(projectRoot);\n for (const [key, value] of Object.entries(provenance).sort(([a], [b]) =>\n a.localeCompare(b),\n )) {\n lines.push(`PROVENANCE\\t${key}=${value}`);\n }\n\n return sha256Hex(lines.join(\"\\n\"));\n}\n\n/** Cache layout version. Bump when the on-disk cache format changes\n * incompatibly so old entries never get reused under new framework code. */\nconst CACHE_SCHEMA_VERSION = \"2\";\n\n/**\n * Read versions from the project's installed deps + the runtime. All\n * lookups are best-effort: a missing package.json just gets recorded as\n * \"unknown\" so the hash still composes, and is still stable across\n * runs on the same machine.\n */\nasync function readProvenance(projectRoot: string): Promise<Record<string, string>> {\n const out: Record<string, string> = {\n schemaVersion: CACHE_SCHEMA_VERSION,\n nodeMajor: process.versions.node.split(\".\")[0] ?? \"unknown\",\n platform: process.platform,\n arch: process.arch,\n };\n out.nimbusVersion = await readDepVersion(projectRoot, \"nimbus-docs\");\n out.astroVersion = await readDepVersion(projectRoot, \"astro\");\n // Env var allowlist. Each one materially affects rendered output:\n // - NODE_ENV / MODE → dev vs production output paths\n // - BASE_URL / SITE → injected into HTML head + asset URLs\n // - any PUBLIC_* / VITE_PUBLIC_* / ASTRO_* → user-defined; bundled by Vite\n //\n // Without this, a build under NODE_ENV=production then a rebuild under\n // NODE_ENV=staging produces an identical global hash → warm cache serves\n // production HTML in staging. (BUG-117.)\n for (const key of TRACKED_ENV_KEYS) {\n out[`env.${key}`] = process.env[key] ?? \"\";\n }\n for (const key of Object.keys(process.env).sort()) {\n if (TRACKED_ENV_PREFIXES.some((p) => key.startsWith(p))) {\n out[`env.${key}`] = process.env[key] ?? \"\";\n }\n }\n return out;\n}\n\nconst TRACKED_ENV_KEYS = [\"NODE_ENV\", \"MODE\", \"BASE_URL\", \"SITE\"];\nconst TRACKED_ENV_PREFIXES = [\"PUBLIC_\", \"VITE_PUBLIC_\", \"ASTRO_\"];\n\nasync function readDepVersion(projectRoot: string, dep: string): Promise<string> {\n try {\n const req = createRequire(resolve(projectRoot, \"package.json\"));\n const pkgJson = req.resolve(`${dep}/package.json`);\n const bytes = await readFile(pkgJson, \"utf8\");\n const parsed = JSON.parse(bytes) as { version?: string };\n return parsed.version ?? \"unknown\";\n } catch {\n return \"unknown\";\n }\n}\n\n/**\n * Compute a per-page hash from the page's source bytes and the global hash.\n *\n * Phase 2 MVP keeps this minimal: any change to the source file or to any\n * tracked global input invalidates the entry. Frontmatter is included\n * because it's inside the file bytes.\n */\nexport function computePageHash(pageBytes: Buffer | string, globalHash: string): string {\n return sha256Hex(\n typeof pageBytes === \"string\"\n ? `${globalHash}\\n${pageBytes}`\n : Buffer.concat([Buffer.from(globalHash + \"\\n\"), pageBytes]),\n );\n}\n\n/**\n * Phase 3 — per-page hash with transitive partial dependencies folded in.\n *\n * Same shape as `computePageHash` but additionally absorbs the bytes of\n * every partial the page transitively embeds. Sorted by path so two\n * builds with the same dependency set produce the same hash regardless\n * of discovery order.\n *\n * Paths are made *relative to projectRoot* before hashing — without this,\n * absolute paths like `/runner/work/run-N/...` change between CI runs\n * (ephemeral checkout dirs) and every page hash misses, neutralising the\n * cache. The path-in-hash detects rename-within-the-project; absolute\n * prefix differences across machines don't.\n */\nexport function computePageHashWithPartials(\n pageBytes: Buffer,\n globalHash: string,\n partialPaths: string[],\n partialBytesByPath: Map<string, Buffer>,\n projectRoot: string,\n): string {\n const h = createHash(\"sha256\");\n h.update(globalHash);\n h.update(\"\\n\");\n h.update(pageBytes);\n for (const absPath of partialPaths) {\n const relPath = relative(projectRoot, absPath).split(sep).join(\"/\");\n const bytes = partialBytesByPath.get(absPath);\n h.update(\"\\0\");\n h.update(relPath);\n h.update(\"\\0\");\n if (bytes) h.update(bytes);\n }\n return h.digest(\"hex\");\n}\n","/**\n * Cache namespace resolution.\n *\n * Why: PR builds and main builds sharing a cache directory cross-contaminate\n * — a PR build can reuse stale entries written by main, and vice versa.\n * Without an explicit namespace, the only mitigation is `nimbus-docs clean`\n * between branches, which authors forget and CI doesn't enforce.\n *\n * Resolution order (first match wins):\n *\n * 1. `NIMBUS_CACHE_NAMESPACE` env var — explicit override for users who\n * need a custom scheme (e.g. preview-vs-prod, or sharing one cache\n * across multiple branches deliberately).\n * 2. `GITHUB_REF` — GitHub Actions sets this on every workflow run.\n * `refs/heads/main`, `refs/pull/123/merge`, etc. Distinguishes PRs\n * from main without any per-repo setup.\n * 3. Local git branch via `git rev-parse --abbrev-ref HEAD`.\n * 4. `\"default\"` — fallback for detached HEAD, non-git checkouts, or\n * anything else the prior steps couldn't resolve.\n *\n * The resolved namespace lands in the manifest and is compared on warm\n * build. A mismatch is treated like a global-hash mismatch: full cold\n * rebuild, no per-page hit attempts.\n *\n * On-disk layout stays single-namespace (`.nimbus/cache/`). Switching\n * branches loses the prior namespace's cache; users running multi-branch\n * workflows can preserve per-branch cache via standard CI cache-key\n * conventions (`actions/cache` keyed on branch name).\n */\nimport { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\n\nconst execFileP = promisify(execFile);\n\nexport async function resolveCacheNamespace(\n projectRoot: string,\n): Promise<string> {\n const env = process.env.NIMBUS_CACHE_NAMESPACE?.trim();\n if (env) return env;\n\n const ghRef = process.env.GITHUB_REF?.trim();\n if (ghRef) return ghRef;\n\n try {\n const { stdout } = await execFileP(\n \"git\",\n [\"rev-parse\", \"--abbrev-ref\", \"HEAD\"],\n { cwd: projectRoot, timeout: 2000 },\n );\n const branch = stdout.trim();\n // `HEAD` means detached — no usable branch name, fall through.\n if (branch && branch !== \"HEAD\") return branch;\n } catch {\n // Not a git checkout, git not installed, or process spawning is\n // disallowed in the build environment. Fall through to default.\n }\n\n return \"default\";\n}\n","/**\n * Phase 3 — partial dependency tracking.\n *\n * Walks MDX content to find `<Render file=\"…\" />` and `<Render slug=\"…\" />`\n * references, then builds a per-page transitive closure: \"pathname X\n * embeds partials A, B, C — where A in turn embeds D, and B in turn\n * embeds E and F.\" Folding all of those partials' bytes into the page's\n * hash gives us the property the spec promises: edit one partial, exactly\n * the pages that transitively embed it re-render.\n *\n * Scope (v1):\n * - Only string-literal `file` / `slug` props get captured. Dynamic\n * `file={var}` references aren't extractable from regex; partials\n * reached that way will silently miss invalidation. Documented as a\n * v1 limitation; the `partialResolver` hook (deferred) gives sites\n * an escape valve.\n * - Default resolver: `<Render file=\"topic/slug\" />` resolves to\n * `src/content/partials/topic/slug.mdx`. Matches the bench, apps/www,\n * and mvvmm's PR shape for cloudflare-docs (their resolver also\n * prepends a `product` prop — that needs a custom resolver).\n * - Cycles in the partial graph are handled (visited set).\n */\nimport { readdir, readFile, stat } from \"node:fs/promises\";\nimport { extname, resolve, sep } from \"node:path\";\n\n/**\n * Check `candidate` is a normalised path under `rootWithSep`. Cheap\n * defense against `../` traversal escaping the partials root. We use a\n * trailing-sep marker on root to avoid false-matching `partialsRoot` with\n * `partialsRoot-evil/` style siblings.\n */\nfunction isInside(candidate: string, rootWithSep: string): boolean {\n return candidate.startsWith(rootWithSep) || candidate === rootWithSep.slice(0, -1);\n}\n\n// PascalCase open tags. The non-greedy props blob `[^>]*?` stops at `>`\n// only — `[^/>]` would reject `/` inside quoted prop values (e.g.\n// `<Render file=\"topic/slug\" />`), which is the common case. False\n// positive risk if a `>` appears inside a quoted value is acceptable for\n// content MDX.\nconst COMPONENT_OPEN_RE = /<([A-Z][A-Za-z0-9_]*)\\s+([^>]*?)\\/?\\s*>/g;\nconst ATTR_RE = /([a-zA-Z][a-zA-Z0-9_]*)\\s*=\\s*[\"']([^\"']*)[\"']/g;\n\n/**\n * Partial resolver hook. Called for every component opening tag scanner\n * encounters in MDX content. Returns the absolute file path of the partial\n * the component embeds, or null if the component isn't a partial-embedder\n * (Tabs, Aside, etc.) or the props don't match a known pattern.\n *\n * Pattern borrowed from mvvmm's cloudflare-docs PR — supports the\n * multi-prop case (`<Render file=\"setup\" product=\"workers\" />` →\n * `partials/workers/setup.mdx`) that single-prop string regex can't\n * capture.\n */\nexport type PartialResolverHook = (\n componentName: string,\n props: Record<string, string>,\n) => string | null;\n\n/**\n * Default partial resolver: `<Render file=\"topic/slug\" />` (or `slug=`)\n * → `<projectRoot>/<partialsBase>/topic/slug.{mdx,md}`. Sites using a\n * different convention (multi-prop, parent product, etc.) pass their own\n * resolver via `nimbus(config, { partialResolver: ... })`.\n *\n * Extension handling:\n * - The incoming `file`/`slug` value gets its `.mdx` or `.md` extension\n * stripped so authors can write `<Render file=\"x.mdx\" />` without\n * producing `x.mdx.mdx`.\n * - The resolver returns `.mdx` by default. The registry builder calls\n * `resolvePartialPath` below to try `.mdx` first and fall back to\n * `.md` if the `.mdx` file doesn't exist — handles sites that mix\n * extensions or use plain Markdown for partials.\n *\n * `partialsBase` lets callers point the resolver at a non-default partials\n * collection base (closes BUG-040). Default: `src/content/partials`.\n */\nexport function makeDefaultPartialResolver(\n projectRoot: string,\n partialsBase = \"src/content/partials\",\n): PartialResolverHook {\n const partialsRoot = resolve(projectRoot, partialsBase);\n return (name, props) => {\n if (name !== \"Render\") return null;\n const id = props.file ?? props.slug;\n if (!id) return null;\n const cleaned = id.replace(/^\\/+/, \"\").replace(/\\.(mdx|md)$/, \"\");\n return resolve(partialsRoot, `${cleaned}.mdx`);\n };\n}\n\n/**\n * Try a resolved partial path as `.mdx`, then fall back to `.md`. Returns\n * the path that actually exists on disk, or null. Used by the registry\n * builder so `.md` partials work even though the default resolver returns\n * `.mdx` for ergonomics.\n */\nexport async function resolvePartialPath(candidatePath: string): Promise<string | null> {\n try {\n await stat(candidatePath);\n return candidatePath;\n } catch {\n // try `.md` fallback\n if (candidatePath.endsWith(\".mdx\")) {\n const mdPath = candidatePath.slice(0, -4) + \".md\";\n try {\n await stat(mdPath);\n return mdPath;\n } catch {\n return null;\n }\n }\n return null;\n }\n}\n\nexport interface ComponentRef {\n name: string;\n props: Record<string, string>;\n}\n\n/**\n * Extract every PascalCase component opening tag from MDX content along\n * with its string-literal props. Dynamic-value attributes (`file={var}`)\n * aren't extracted by design — they can't be statically analysed without\n * a full MDX AST pass.\n */\nexport function extractComponentRefs(mdxContent: string): ComponentRef[] {\n const refs: ComponentRef[] = [];\n for (const m of mdxContent.matchAll(COMPONENT_OPEN_RE)) {\n const name = m[1]!;\n const propsBlob = m[2] ?? \"\";\n const props: Record<string, string> = {};\n for (const am of propsBlob.matchAll(ATTR_RE)) {\n props[am[1]!] = am[2]!;\n }\n refs.push({ name, props });\n }\n return refs;\n}\n\nasync function* walkPartials(partialsRoot: string): AsyncIterable<string> {\n let entries;\n try {\n entries = await readdir(partialsRoot, { withFileTypes: true });\n } catch {\n return;\n }\n for (const entry of entries) {\n if (entry.name.startsWith(\".\")) continue;\n const full = resolve(partialsRoot, entry.name);\n if (entry.isDirectory()) {\n yield* walkPartials(full);\n } else if (entry.isFile() && [\".mdx\", \".md\"].includes(extname(entry.name))) {\n yield full;\n }\n }\n}\n\nexport interface PartialRegistry {\n /**\n * Pathname → list of absolute paths of partials it transitively embeds,\n * sorted for deterministic hashing.\n */\n transitiveByPathname: Map<string, string[]>;\n /** Absolute path → file bytes for every partial that exists on disk. */\n partialBytes: Map<string, Buffer>;\n /** Stats for the build report. */\n stats: {\n partialCount: number;\n pagesWithPartials: number;\n totalTransitiveRefs: number;\n };\n}\n\n/**\n * Build the per-page transitive partial registry.\n *\n * Algorithm:\n * 1. Walk `src/content/partials/`, hash each file's bytes, record direct\n * partial → partial references from its content.\n * 2. Topologically expand the partial → partial graph into a per-partial\n * transitive-set map (with cycle protection).\n * 3. For each page, extract its direct partial refs, then union their\n * transitive sets into the page's full transitive partial set.\n */\nexport async function buildPartialRegistry(\n projectRoot: string,\n pageBytesByPathname: Map<string, Buffer>,\n resolver: PartialResolverHook,\n partialsBase = \"src/content/partials\",\n): Promise<PartialRegistry> {\n const partialsRoot = resolve(projectRoot, partialsBase);\n const partialsRootWithSep = partialsRoot + sep;\n const partialBytes = new Map<string, Buffer>();\n const partialDirectRefs = new Map<string, string[]>();\n\n // Resolver returns `.mdx` by default; partials may be `.md`. Wrap with\n // fallback so refs land on the correct existing file rather than a\n // path-only dependency that never sees the actual bytes.\n //\n // Also constrain results to be under `partialsRoot` (BUG-112/121): a\n // resolver result outside the partials directory would mean\n // `partialBytes` never sees the file (walkPartials is bounded to\n // partialsRoot), so the page hash only includes the *path* of the\n // dependency, not its bytes — edits to such a file silently don't\n // invalidate dependents. Reject results outside `partialsRoot` so the\n // limit is loud rather than silent.\n async function resolveWithFallback(name: string, props: Record<string, string>): Promise<string | null> {\n const candidate = resolver(name, props);\n if (!candidate) return null;\n if (!isInside(candidate, partialsRootWithSep)) return null;\n return resolvePartialPath(candidate);\n }\n\n // Pass 1: read every partial file, extract its direct partial refs.\n for await (const filePath of walkPartials(partialsRoot)) {\n const bytes = await readFile(filePath);\n partialBytes.set(filePath, bytes);\n const refs = extractComponentRefs(bytes.toString(\"utf8\"));\n const resolved: string[] = [];\n for (const ref of refs) {\n const r = await resolveWithFallback(ref.name, ref.props);\n if (r) resolved.push(r);\n }\n partialDirectRefs.set(filePath, resolved);\n }\n\n // Pass 2: compute transitive set per partial with cycle protection.\n const transitiveForPartial = new Map<string, Set<string>>();\n function computeTransitive(start: string): Set<string> {\n const cached = transitiveForPartial.get(start);\n if (cached) return cached;\n const visited = new Set<string>();\n const stack = [start];\n while (stack.length > 0) {\n const node = stack.pop()!;\n if (visited.has(node)) continue;\n visited.add(node);\n const direct = partialDirectRefs.get(node) ?? [];\n for (const d of direct) {\n if (!visited.has(d)) stack.push(d);\n }\n }\n transitiveForPartial.set(start, visited);\n return visited;\n }\n for (const p of partialBytes.keys()) computeTransitive(p);\n\n // Pass 3: for each page, expand its direct refs to a transitive set.\n const transitiveByPathname = new Map<string, string[]>();\n let pagesWithPartials = 0;\n let totalTransitiveRefs = 0;\n for (const [pathname, bytes] of pageBytesByPathname) {\n const directRefs = extractComponentRefs(bytes.toString(\"utf8\"));\n if (directRefs.length === 0) {\n transitiveByPathname.set(pathname, []);\n continue;\n }\n const allTransitive = new Set<string>();\n for (const ref of directRefs) {\n // Try to resolve with .mdx then .md fallback; if neither exists\n // record the .mdx candidate as a path-only dependency so a future\n // addition still invalidates the page.\n const candidate = resolver(ref.name, ref.props);\n if (!candidate) continue;\n // Same constraint as the partial→partial pass: reject results\n // outside partialsRoot so we never silently miss invalidation\n // for files we can't see the bytes of.\n if (!isInside(candidate, partialsRootWithSep)) continue;\n const resolved = (await resolvePartialPath(candidate)) ?? candidate;\n const trans = transitiveForPartial.get(resolved);\n if (trans) {\n for (const t of trans) allTransitive.add(t);\n } else {\n allTransitive.add(resolved);\n }\n }\n const sorted = Array.from(allTransitive).sort();\n transitiveByPathname.set(pathname, sorted);\n if (sorted.length > 0) {\n pagesWithPartials++;\n totalTransitiveRefs += sorted.length;\n }\n }\n\n return {\n transitiveByPathname,\n partialBytes,\n stats: {\n partialCount: partialBytes.size,\n pagesWithPartials,\n totalTransitiveRefs,\n },\n };\n}\n\n/**\n * Best-effort: just confirms the partials directory is present. Used for\n * skipping the registry build when a site has no partials at all.\n */\nexport async function partialsDirExists(\n projectRoot: string,\n partialsBase = \"src/content/partials\",\n): Promise<boolean> {\n try {\n const s = await stat(resolve(projectRoot, partialsBase));\n return s.isDirectory();\n } catch {\n return false;\n }\n}\n","/**\n * Incremental builds — Phase 2 MVP.\n *\n * Wires the cache layer into Astro's prerenderer. On warm build, pages whose\n * source bytes (and the global hash) haven't changed since the last build\n * return cached HTML directly from `prerenderer.render`; pages that did\n * change render normally and persist their output to the cache.\n *\n * Astro sees every route in `getStaticPaths` either way — cache hits flow\n * through `astro:build:generated`, adapter writers, route-headers accounting\n * exactly like fresh renders. This is the spec's design, *not* mvvmm's\n * `getStaticPaths`-filtering approach, because the latter hides cached\n * routes from downstream hooks.\n *\n * Anti-goals for this MVP (deferred to Phase 3+):\n * - Partial-dependency tracking. Edit a partial → still full rebuild today.\n * - Data-collection scoping.\n * - Component-graph tracking. Any tracked-file change → full rebuild.\n * - Provenance / namespacing / trust boundary. Hardening track.\n * - `nimbus build --explain` and structured build reports. Console log only.\n */\nimport { mkdir, readdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname, extname, relative, resolve, sep } from \"node:path\";\nimport type { AstroIntegrationLogger } from \"astro\";\nimport { canonicalEntryUrl } from \"../astro-slug.js\";\nimport { parseCollectionBases } from \"../parse-content-collections.js\";\nimport { Cache } from \"./cache.js\";\nimport { computeGlobalHash, computePageHashWithPartials } from \"./hash.js\";\nimport { resolveCacheNamespace } from \"./namespace.js\";\nimport {\n buildPartialRegistry,\n makeDefaultPartialResolver,\n partialsDirExists,\n type PartialResolverHook,\n} from \"./partial-refs.js\";\n\nexport interface IncrementalContext {\n /** Provenance tag (e.g. branch name) — distinguishes one cache lineage\n * from another. Persisted to the manifest; checked on warm build. */\n namespace: string;\n /** Global hash of tracked source files at build start. */\n globalHash: string;\n /** Set by setup(); read inside the wrapped prerenderer. */\n pageHashByPathname: Map<string, string>;\n /** Pathnames whose hash matches the cached manifest entry and whose\n * cached HTML file exists. Render-time cache hit. Pruned to the\n * confirmed-restored subset by `restoreCachedPagesToDist`. */\n cacheableHits: Set<string>;\n /** Cache instance. */\n cache: Cache;\n /** Hashes that successfully wrote to disk this build. Used at build:done\n * to compute the manifest write — only confirmed-on-disk hashes go in,\n * so a partial-write failure doesn't leave the manifest claiming a\n * hash that isn't actually cached. */\n persistedHashes: Set<string>;\n /** Per-build counters. */\n stats: { hits: number; misses: number; persisted: number };\n /** Logger from the integration. */\n logger: AstroIntegrationLogger;\n /** Absolute file path per pathname — used by the MDX-skip Vite plugin\n * to identify which `.mdx` files belong to cached routes. */\n filePathByPathname: Map<string, string>;\n}\n\n/**\n * Normalise a request URL to its canonical pathname (no trailing slash,\n * except \"/\"). Astro builds use trailing-slash format by default; cache\n * keys are stripped so both shapes match.\n */\nfunction canonicalisePathname(input: string): string {\n let p = input;\n // Strip query/hash if present.\n const q = p.indexOf(\"?\");\n if (q >= 0) p = p.slice(0, q);\n const h = p.indexOf(\"#\");\n if (h >= 0) p = p.slice(0, h);\n // Strip trailing slash except for \"/\".\n if (p.length > 1 && p.endsWith(\"/\")) p = p.slice(0, -1);\n if (p.length === 0) p = \"/\";\n return p;\n}\n\n/**\n * Build a map from pathname → MDX file bytes by walking the docs collection\n * directory. Phase 2 MVP only handles the primary `docs` collection.\n *\n * Pathname derivation: `src/content/docs/<entry.id>.mdx` → `/<entry.id>`,\n * mirroring `getDocsStaticPaths` which uses `entry.id` verbatim as slug.\n */\n/**\n * Normalise whatever `parseCollectionBases` returned for a collection\n * into a projectRoot-relative folder spec. Handles three shapes the\n * user might write:\n *\n * base: \"shared\" → src/content/shared\n * base: \"./src/content/shared\" → src/content/shared\n * base: \"/abs/path/to/partials\" → preserved (absolute)\n *\n * Falls back to `src/content/<defaultFolder>` if the input is empty.\n */\nfunction resolveCollectionBase(projectRoot: string, raw: string, defaultFolder: string): string {\n if (!raw) return `src/content/${defaultFolder}`;\n // Absolute path — pass through; resolve() will use it directly.\n if (raw.startsWith(\"/\")) return raw;\n // Path-shaped value (contains `/`) — already a relative path from\n // projectRoot, just strip any leading `./`.\n if (raw.includes(\"/\")) return raw.replace(/^\\.\\/+/, \"\");\n // Bare folder name — assume conventional `src/content/<name>`.\n return `src/content/${raw}`;\n}\n\n/**\n * Pick between the derived collection base and a safe fallback by\n * checking which actually has matching `.mdx` / `.md` files on disk.\n * Protects against `parseCollectionBases` mis-attributing across\n * collections when users hand-roll `defineCollection({ loader:\n * glob({ base: \"…\" }) })` — the regex parser can't always tell which\n * `base:` belongs to which entry.\n *\n * Preference order:\n * 1. If derived === fallback, return either.\n * 2. If only one of (derived, fallback) has content, return that.\n * 3. If both have content, prefer fallback — the conventional path is\n * more trustworthy than a regex-derived guess that could be wrong.\n * Sites with intentionally non-default bases get matched in (2)\n * because their default path doesn't exist.\n * 4. Neither has content: return derived (caller treats as absent).\n */\nasync function pickCollectionBase(\n projectRoot: string,\n derived: string,\n fallback: string,\n): Promise<string> {\n if (derived === fallback) return derived;\n const derivedAbs = resolve(projectRoot, derived);\n const fallbackAbs = resolve(projectRoot, fallback);\n const derivedHas = await hasContent(derivedAbs);\n const fallbackHas = await hasContent(fallbackAbs);\n if (derivedHas && !fallbackHas) return derived;\n if (!derivedHas && fallbackHas) return fallback;\n if (derivedHas && fallbackHas) return fallback; // ambiguous → prefer fallback\n return derived;\n}\n\nasync function hasContent(dir: string): Promise<boolean> {\n try {\n const entries = await readdir(dir, { withFileTypes: true });\n for (const e of entries) {\n if (e.isFile() && [\".mdx\", \".md\"].includes(extname(e.name))) return true;\n if (e.isDirectory()) {\n if (await hasContent(resolve(dir, e.name))) return true;\n }\n }\n } catch {\n return false;\n }\n return false;\n}\n\ninterface DocsPagesScan {\n bytesByPathname: Map<string, Buffer>;\n /** Absolute file paths keyed by canonical pathname — Layer 2's Vite plugin\n * needs this mapping to short-circuit MDX module loads for cached entries. */\n filePathByPathname: Map<string, string>;\n}\n\nasync function collectDocsPages(\n projectRoot: string,\n docsBase = \"src/content/docs\",\n): Promise<DocsPagesScan> {\n const docsRoot = resolve(projectRoot, docsBase);\n const bytesByPathname = new Map<string, Buffer>();\n const filePathByPathname = new Map<string, string>();\n\n async function walk(dir: string): Promise<void> {\n let entries;\n try {\n entries = await readdir(dir, { withFileTypes: true });\n } catch {\n return;\n }\n for (const entry of entries) {\n const full = resolve(dir, entry.name);\n if (entry.isDirectory()) {\n await walk(full);\n } else if (entry.isFile() && [\".mdx\", \".md\"].includes(extname(entry.name))) {\n const bytes = await readFile(full);\n const rel = relative(docsRoot, full).split(sep).join(\"/\");\n const entryId = rel.replace(/\\.(mdx|md)$/, \"\");\n // Use canonicalEntryUrl to mirror Astro's slug normalisation and\n // trailing-`/index` strip — without it, `docs/foo/index.mdx` becomes\n // pathname `/foo/index` but Astro serves it at `/foo/`. Mismatch →\n // cache never hits the route. Reused so we don't reimplement Astro's\n // routing rules.\n //\n // Carve-out for a top-level standalone `index.mdx`:\n // `canonicalSlug(\"index\") → \"\"` (collapses to root), but the\n // framework's `getDocsStaticPaths` passes `entry.id` verbatim as\n // `params.slug`, so Astro actually serves the file at `/index/`,\n // not `/`. Using canonicalEntryUrl here would produce a cache key\n // of `/` while the rendered route is `/index/` — a persistent\n // 1-miss-every-warm-build that defeats Pagefind-skip etc. We\n // bypass the helper only for this exact case so the cache scan\n // matches `getDocsStaticPaths`'s route shape without altering\n // canonicalSlug's contract (which sidebar, lint, version-alternates\n // all rely on).\n const pathname =\n entryId === \"index\"\n ? \"/index\"\n : canonicalisePathname(canonicalEntryUrl(\"\", entryId));\n bytesByPathname.set(pathname, bytes);\n filePathByPathname.set(pathname, full);\n }\n }\n }\n\n await walk(docsRoot);\n return { bytesByPathname, filePathByPathname };\n}\n\n/**\n * Set up the cache context for this build. Called at astro:build:start.\n * Computes per-page hashes, reads prior manifest, determines which pages\n * are cache-hits.\n *\n * Phase 3 — the page hash includes the bytes of every partial the page\n * transitively embeds, so editing a partial invalidates exactly the pages\n * that reference it (directly or transitively) and nothing else.\n */\nexport async function setupIncrementalContext(\n projectRoot: string,\n logger: AstroIntegrationLogger,\n partialResolver?: PartialResolverHook,\n): Promise<IncrementalContext> {\n const cache = new Cache(projectRoot);\n const globalHash = await computeGlobalHash(projectRoot);\n const namespace = await resolveCacheNamespace(projectRoot);\n const priorManifest = await cache.readManifest();\n\n // Parse the user's content config once. We need both the docs and\n // partials collection bases (BUG-104 + BUG-040) so we can find the\n // actual on-disk locations rather than guessing `src/content/docs`\n // and `src/content/partials`.\n //\n // parseCollectionBases is regex-based and was designed for the\n // documented Nimbus pattern (`docsCollection({ base: \"...\" })`); it\n // can mis-attribute when users hand-roll `defineCollection({ loader:\n // glob({ base: \"...\" }) })`. Falls back to default paths whenever the\n // derived path has no matching content.\n const bases = await parseCollectionBases(resolve(projectRoot, \"src/content.config.ts\"));\n const docsBase = await pickCollectionBase(\n projectRoot,\n resolveCollectionBase(projectRoot, bases?.get(\"docs\") ?? \"docs\", \"docs\"),\n \"src/content/docs\",\n );\n\n const { bytesByPathname, filePathByPathname } = await collectDocsPages(\n projectRoot,\n docsBase,\n );\n\n // Phase 3 — build the partial registry. If the project has no partials\n // directory, skip; pages without partials still hash via the empty list\n // path in computePageHashWithPartials.\n const partialsBase = await pickCollectionBase(\n projectRoot,\n resolveCollectionBase(projectRoot, bases?.get(\"partials\") ?? \"partials\", \"partials\"),\n \"src/content/partials\",\n );\n const resolver =\n partialResolver ?? makeDefaultPartialResolver(projectRoot, partialsBase);\n const hasPartialsDir = await partialsDirExists(projectRoot, partialsBase);\n const registry = hasPartialsDir\n ? await buildPartialRegistry(projectRoot, bytesByPathname, resolver, partialsBase)\n : {\n transitiveByPathname: new Map<string, string[]>(),\n partialBytes: new Map<string, Buffer>(),\n stats: { partialCount: 0, pagesWithPartials: 0, totalTransitiveRefs: 0 },\n };\n if (registry.stats.partialCount > 0) {\n logger.info(\n `[incremental] partial registry: ${registry.stats.partialCount} partials, ${registry.stats.pagesWithPartials} pages reference at least one`,\n );\n }\n\n const pageHashByPathname = new Map<string, string>();\n for (const [pathname, bytes] of bytesByPathname) {\n const transitive = registry.transitiveByPathname.get(pathname) ?? [];\n pageHashByPathname.set(\n pathname,\n computePageHashWithPartials(\n bytes,\n globalHash,\n transitive,\n registry.partialBytes,\n projectRoot,\n ),\n );\n }\n\n // Determine which pathnames have a valid cache hit.\n //\n // Namespace check sits alongside globalHash: prior manifest's namespace\n // must match the current build's. PR branches and main don't share entries\n // — even if every page hash and the global hash happen to align, the\n // provenance gate refuses the cache. Prevents the silent-stale-content\n // failure mode where a CI runner restores main's cache into a PR's build.\n const cacheableHits = new Set<string>();\n const namespaceChanged =\n priorManifest != null && priorManifest.namespace !== namespace;\n const globalChanged =\n !priorManifest || priorManifest.globalHash !== globalHash;\n const useCache = !globalChanged && !namespaceChanged && priorManifest != null;\n if (useCache) {\n for (const [pathname, hash] of pageHashByPathname) {\n const priorHash = priorManifest.pages[pathname];\n if (priorHash === hash && (await cache.hasPage(hash))) {\n cacheableHits.add(pathname);\n }\n }\n }\n\n logger.info(`[incremental] cache namespace: ${namespace}`);\n if (namespaceChanged) {\n logger.info(\n `[incremental] namespace changed (${priorManifest!.namespace} → ${namespace}) — full rebuild`,\n );\n } else if (globalChanged) {\n logger.info(\n priorManifest\n ? \"[incremental] global hash changed — full rebuild\"\n : \"[incremental] no prior cache — full cold build\",\n );\n } else {\n logger.info(\n `[incremental] ${cacheableHits.size} cache hits / ${pageHashByPathname.size} pages`,\n );\n }\n\n // Seed persistedHashes with the cacheable-hit set: those hashes are\n // confirmed on disk (we checked via cache.hasPage above). New\n // successful writes in this build will be added by the render wrap.\n const persistedHashes = new Set<string>();\n for (const pathname of cacheableHits) {\n const h = pageHashByPathname.get(pathname);\n if (h) persistedHashes.add(h);\n }\n\n return {\n namespace,\n globalHash,\n pageHashByPathname,\n cacheableHits,\n cache,\n persistedHashes,\n stats: { hits: 0, misses: 0, persisted: 0 },\n logger,\n filePathByPathname,\n };\n}\n\n/**\n * Wrap an Astro prerenderer with the cache.\n *\n * Strategy (mvvmm-style — chosen empirically over the \"wrap Response\" approach\n * because Astro's per-route work outside `render` is the actual dominant cost,\n * not MDX→HTML conversion):\n *\n * - `getStaticPaths` is filtered to dirty routes (cache misses) only.\n * Cached routes never enter Astro's render pipeline — Astro skips their\n * Vite bundling, their per-route emission overhead, everything.\n * - `render` only sees dirty routes. It renders normally and persists the\n * output to cache.\n * - After the build, `restoreCachedPagesToDist` copies cached HTML into\n * `dist/<pathname>/index.html` for the filtered cached routes — Astro\n * never wrote them, so we do.\n *\n * Trade-off vs. the spec's \"wrap Response in render\" design: downstream\n * Astro hooks (`astro:build:generated`, adapter writers, route accounting)\n * don't see cached routes. For Cloudflare adapter sites or anything that\n * depends on every route being visible to those hooks, this matters.\n * For static SSG sites where the rendered HTML *is* the output, it's fine.\n * Documented as a limitation in Phase 5 user-facing notes.\n */\nexport function wrapPrerenderer<P extends {\n getStaticPaths: () => Promise<Array<{ pathname: string; [k: string]: unknown }>>;\n render: (request: Request, opts: unknown) => Promise<Response>;\n name: string;\n [k: string]: unknown;\n}>(defaultPrerenderer: P, ctx: IncrementalContext): P {\n const wrapped = {\n ...defaultPrerenderer,\n name: `${defaultPrerenderer.name}+nimbus-incremental`,\n async getStaticPaths() {\n const all = await defaultPrerenderer.getStaticPaths();\n const dirty = all.filter(\n (p) => !ctx.cacheableHits.has(canonicalisePathname(p.pathname)),\n );\n ctx.logger.info(\n `[incremental] filtered ${all.length - dirty.length} cached routes from render; ${dirty.length} to build`,\n );\n return dirty;\n },\n async render(request: Request, opts: unknown) {\n const pathname = canonicalisePathname(new URL(request.url).pathname);\n const hash = ctx.pageHashByPathname.get(pathname);\n ctx.stats.misses++;\n const response = await defaultPrerenderer.render(request, opts);\n // Only persist successful renders. 4xx/5xx responses (e.g. a 404\n // page, or a render that threw and got recovered into an error\n // response) would otherwise pollute the cache and serve the error\n // back on every warm build.\n if (hash && response.ok) {\n try {\n const text = await response.clone().text();\n await ctx.cache.writePage(hash, text);\n ctx.persistedHashes.add(hash);\n ctx.stats.persisted++;\n } catch (err) {\n ctx.logger.warn(\n `[incremental] failed to persist ${pathname}: ${(err as Error).message}`,\n );\n }\n }\n return response;\n },\n };\n return wrapped as unknown as P;\n}\n\n/**\n * Copy cached HTML for filtered routes into `dist/`. Run at astro:build:done.\n *\n * Pathname → file mapping assumes `directory` build format (Astro default):\n * `/foo/bar` → `dist/foo/bar/index.html`\n * `/` → `dist/index.html`\n */\nexport async function restoreCachedPagesToDist(\n ctx: IncrementalContext,\n outDir: string,\n): Promise<void> {\n // Restore the cached `_astro/` snapshot first so the bundles cached HTML\n // references are present in dist. Skips files already in fresh dist.\n // Failed copies log and continue — losing the manifest write because of\n // one bad asset would be worse than a few missing files.\n const astroDir = resolve(outDir, \"_astro\");\n const restoredAssets = await ctx.cache.restoreAssets(astroDir, (path, err) => {\n ctx.logger.warn(`[incremental] failed to restore asset ${path}: ${err.message}`);\n });\n if (restoredAssets > 0) {\n ctx.logger.info(`[incremental] restored ${restoredAssets} cached asset files`);\n }\n\n // Track which pathnames we successfully restored vs. ones whose cached\n // HTML went missing between setup() and restore. Pruning cacheableHits\n // to the confirmed set means downstream consumers (the sitemap emitter,\n // the manifest write, the lint route truth) only see pathnames that\n // actually have output on disk — no advertising 404s.\n const failedRestores = new Set<string>();\n for (const pathname of ctx.cacheableHits) {\n const hash = ctx.pageHashByPathname.get(pathname);\n if (!hash) {\n failedRestores.add(pathname);\n continue;\n }\n const html = await ctx.cache.readPage(hash);\n if (html === null) {\n ctx.logger.warn(`[incremental] cached file missing for ${pathname} (hash ${hash.slice(0, 8)}) — dropping from output`);\n failedRestores.add(pathname);\n // Also drop the hash from persistedHashes so the manifest doesn't\n // re-advertise it.\n ctx.persistedHashes.delete(hash);\n continue;\n }\n const subPath = pathname === \"/\" ? \"index.html\" : `${pathname.slice(1)}/index.html`;\n const target = resolve(outDir, subPath);\n try {\n await mkdir(dirname(target), { recursive: true });\n await writeFile(target, html, \"utf8\");\n ctx.stats.hits++;\n } catch (err) {\n ctx.logger.warn(\n `[incremental] failed to restore ${pathname}: ${(err as Error).message}`,\n );\n failedRestores.add(pathname);\n ctx.persistedHashes.delete(hash);\n }\n }\n for (const failed of failedRestores) {\n ctx.cacheableHits.delete(failed);\n }\n}\n\n/**\n * Snapshot the just-built `dist/_astro/` into the cache so future warm\n * builds can restore asset bundles that this build's HTML references.\n *\n * Called at astro:build:done, AFTER any restored bundles have been placed\n * (so the snapshot is the union of fresh + previously-cached assets the\n * cached HTML still references).\n *\n * BUG-007 fix: bounded to assets actually referenced by cached HTML. We\n * walk every cached page's bytes, regex-extract `/_astro/...` URLs,\n * dedupe — and only persist those. Without this the snapshot grew\n * unboundedly because vite produces new bundle hashes on every warm\n * build (different module graph → different chunks).\n */\nexport async function snapshotAssetsToCache(\n ctx: IncrementalContext,\n outDir: string,\n): Promise<void> {\n const astroDir = resolve(outDir, \"_astro\");\n const referencedRelPaths = await collectReferencedAssets(ctx, outDir);\n const n = await ctx.cache.snapshotAssets(astroDir, referencedRelPaths);\n if (n > 0) {\n ctx.logger.info(\n `[incremental] snapshotted ${n} referenced asset files to cache`,\n );\n }\n}\n\n// Match `/_astro/<path>` URLs inside quoted attribute values. Stops at\n// `\"`, `'`, `)`, whitespace, `>`. Captured group 1 is the path starting\n// after `/_astro/`; query strings + hash fragments are stripped by\n// `normaliseAssetRef`.\nconst ASSET_REF_RE = /\\/_astro\\/([^\"')\\s>]+)/g;\n\n/**\n * Strip query string and hash from an extracted asset path. Without\n * this, `/_astro/foo.js?v=1` would record `foo.js?v=1` as the file\n * name — the snapshot would skip it because no such file exists in\n * `_astro/`, leaving the warm build with a broken reference (BUG-108).\n */\nfunction normaliseAssetRef(raw: string): string | null {\n if (!raw) return null;\n const q = raw.indexOf(\"?\");\n const h = raw.indexOf(\"#\");\n let end = raw.length;\n if (q >= 0 && q < end) end = q;\n if (h >= 0 && h < end) end = h;\n const path = raw.slice(0, end);\n return path.length > 0 ? path : null;\n}\n\n/**\n * Scan every cached HTML page on disk for `/_astro/...` references.\n * Returns the set of rel-paths (e.g. `BaseLayout.C1SNDqdc.css`) every\n * cache hit will need restored on future warm builds. Used to bound the\n * asset snapshot.\n *\n * The single regex matches `/_astro/...` anywhere in the HTML —\n * straightforward for `href=\"...\"`, `src=\"...\"`, `url(...)` in inline\n * styles, and individual `srcset` URLs alike. (BUG-107 was about the\n * earlier regex anchoring on a quote/paren prefix and missing the\n * second+nth URL inside a `srcset` value; the unanchored form here\n * catches them all.)\n *\n * We scan the dist output rather than the in-memory cache because dist\n * is the source of truth for what's currently referenced — after the\n * cached pages have been restored and fresh pages emitted, dist's HTML\n * collectively references every asset any warm build will need.\n */\nasync function collectReferencedAssets(\n ctx: IncrementalContext,\n outDir: string,\n): Promise<Set<string>> {\n const refs = new Set<string>();\n for (const [pathname, hash] of ctx.pageHashByPathname) {\n if (!ctx.persistedHashes.has(hash)) continue;\n const subPath = pathname === \"/\" ? \"index.html\" : `${pathname.slice(1)}/index.html`;\n const target = resolve(outDir, subPath);\n let content: string;\n try {\n content = await readFile(target, \"utf8\");\n } catch {\n continue;\n }\n for (const m of content.matchAll(ASSET_REF_RE)) {\n const path = normaliseAssetRef(m[1] ?? \"\");\n if (path) refs.add(path);\n }\n }\n return refs;\n}\n\n/**\n * Write the updated manifest. Called at astro:build:done.\n */\nexport async function finaliseIncrementalContext(\n ctx: IncrementalContext,\n): Promise<void> {\n // Build the manifest from the *confirmed* persisted hashes only.\n // A naive \"all intended hashes\" manifest would claim cache entries for\n // routes whose writePage threw or whose restoreCachedPagesToDist\n // failed — and the next warm build would treat those phantom entries\n // as hits, only to discover via `cache.hasPage` they don't exist.\n // Mitigated by hasPage but cleaner to never lie in the manifest.\n const pages: Record<string, string> = {};\n for (const [pathname, hash] of ctx.pageHashByPathname) {\n if (ctx.persistedHashes.has(hash)) {\n pages[pathname] = hash;\n }\n }\n await ctx.cache.writeManifest({\n namespace: ctx.namespace,\n globalHash: ctx.globalHash,\n pages,\n });\n ctx.logger.info(\n `[incremental] ${ctx.stats.hits} hits, ${ctx.stats.misses} misses, ${ctx.stats.persisted} persisted`,\n );\n}\n","/**\n * Vite plugin — Layer 2 of the incremental-builds amendment.\n *\n * Short-circuits MDX module loads for entries whose route is a cache hit.\n * Saves the dominant cold-build cost: parsing + Sätteri + Shiki for every\n * MDX file, even when the route's HTML is going to come from cache.\n *\n * How it works:\n * - `resolveId` intercepts imports of `.mdx` files. If the resolved path\n * is a cached entry, returns a virtual id `\\0nimbus-stub:<original>`.\n * The virtual id does NOT match `\\.mdx$`, so Astro's `@mdx-js/rollup`\n * transform skips it.\n * - `load` returns a minimal JS stub for virtual ids. The stub exports\n * `Content = () => null`, `frontmatter = {}`, `headings = []`, etc.\n * Astro's content collection reads frontmatter via its own scanner\n * (filesystem-direct, not through the bundler), so the stub doesn't\n * have to be accurate — it just has to be non-throwing if accessed.\n * - Layer 3 (prerenderer.getStaticPaths filter) ensures `Content` is\n * never actually called for cached routes; the stub is dead code at\n * runtime.\n *\n * The cached-paths set is *mutable* — the plugin reads from it at every\n * `resolveId`. This lets the integration populate the set at\n * `astro:build:start` after the plugin has already been registered.\n */\nimport type { Plugin } from \"vite\";\n\nconst VIRTUAL_PREFIX = \"\\0nimbus-stub:\";\n\nconst STUB_MODULE = `// nimbus-incremental: cached entry stub. Layer 3 ensures this is dead code.\nexport const frontmatter = {};\nexport const headings = [];\nexport const file = \"\";\nexport const url = undefined;\nexport const rawContent = () => \"\";\nexport const compiledContent = () => \"\";\nexport function Content() { return null; }\nexport default Content;\n`;\n\nexport interface MdxSkipPluginContext {\n cachedAbsolutePaths: Set<string>;\n enabled: boolean;\n}\n\nexport function createMdxSkipContext(): MdxSkipPluginContext {\n return { cachedAbsolutePaths: new Set(), enabled: false };\n}\n\nexport function mdxSkipPlugin(ctx: MdxSkipPluginContext): Plugin {\n return {\n name: \"nimbus-incremental-mdx-skip\",\n enforce: \"pre\",\n async resolveId(source, importer, options) {\n if (!ctx.enabled) return null;\n // We only care about MDX imports, and we let Vite/Astro resolve them\n // normally first so we can check the absolute path against the set.\n if (!source.endsWith(\".mdx\")) return null;\n const resolved = await this.resolve(source, importer, { ...options, skipSelf: true });\n if (!resolved || resolved.external) return resolved;\n const absPath = resolved.id.split(\"?\")[0];\n if (ctx.cachedAbsolutePaths.has(absPath)) {\n // Suffix must not end in `.mdx`, otherwise @mdx-js/rollup's\n // `id: /\\.mdx$/` filter still matches and runs the MDX transform\n // on our JS stub. `.cached.js` keeps the path readable in stack\n // traces while routing through Vite's JS pipeline instead.\n return `${VIRTUAL_PREFIX}${absPath.replace(/\\.mdx$/, \".cached.js\")}`;\n }\n return resolved;\n },\n load(id) {\n if (id.startsWith(VIRTUAL_PREFIX)) {\n return STUB_MODULE;\n }\n return null;\n },\n };\n}\n","/**\n * Layer 4 — sitemap emission.\n *\n * When incremental builds are on, the cache layer filters cached routes\n * from Astro's render pipeline. Downstream integrations that hook\n * `astro:build:done` (including `@astrojs/sitemap`) only see the dirty\n * subset in their `pages` argument. The sitemap they emit is missing all\n * cached routes — broken on every warm build.\n *\n * Fix: don't register `@astrojs/sitemap` at all when incremental is on.\n * Instead, this module emits the sitemap directly from the union of\n * (Astro's `pages` arg) and (incrementalCtx's cached pathnames).\n *\n * Output is **structurally compatible** with `@astrojs/sitemap`'s default\n * output — same xmlns set, same element shape, same sorted-URL invariant\n * — but isn't bit-identical to the upstream emitter. Specifically:\n *\n * - XML entity escaping uses `&apos;` for single quotes where upstream\n * uses `&#39;`. Functionally identical; lexically different.\n * - `@astrojs/sitemap` adds an XML declaration newline upstream's\n * serializer happens to insert; we don't.\n *\n * The shape that DOES hold across cold and warm builds of *this*\n * emitter is byte-identical (same URL set, same sort, same escape\n * table). Cold-vs-warm parity is the property the cache layer needs;\n * upstream-byte-parity is only relevant for sites comparing against\n * a non-incremental build of `@astrojs/sitemap`.\n *\n * Format details:\n * - One line, no whitespace between elements\n * - URLs sorted alphabetically by absolute URL\n * - Directory-format trailing slash (`/foo/` not `/foo`)\n * - xmlns declarations matching @astrojs/sitemap's set\n * - sitemap-0.xml carries all entries (we don't split until >45k urls)\n * - sitemap-index.xml lists sitemap-0.xml only\n *\n * v1 scope — no custom `serialize` yet (deferred; cloudflare-docs case),\n * no `lastmod`, `changefreq`, `priority`, no image/video sitemaps. Matches\n * `@astrojs/sitemap` *default* output for sites that don't override.\n */\nimport { mkdir, writeFile } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\n\n/** Mirror of `@astrojs/sitemap`'s `SitemapItem` shape. */\nexport interface SitemapItem {\n url: string;\n lastmod?: string;\n changefreq?:\n | \"always\"\n | \"hourly\"\n | \"daily\"\n | \"weekly\"\n | \"monthly\"\n | \"yearly\"\n | \"never\";\n priority?: number;\n links?: { lang: string; url: string }[];\n}\n\nexport type SitemapSerialize = (\n item: SitemapItem,\n) => SitemapItem | undefined | null | Promise<SitemapItem | undefined | null>;\n\nexport interface IncrementalSitemapOptions {\n /** Absolute site URL, no trailing slash. e.g. `https://example.com`. */\n siteUrl: string;\n /** Astro's `astro:build:done` `pages` argument — routes Astro just built. */\n builtPages: Array<{ pathname: string }>;\n /** Cached pathnames from the incremental context (no trailing slash). */\n cachedPathnames: Iterable<string>;\n /** Dist directory absolute path. */\n distDir: string;\n /** Optional `base` prefix from Astro config. */\n base?: string;\n /**\n * User-supplied serialize function. Called for every URL — cached and\n * dirty alike — so warm-build sitemap matches cold-build sitemap when the\n * site uses a serializer (cloudflare-docs's git-sourced `lastmod` pattern\n * is the motivating case). Returning `null`/`undefined` drops the entry.\n * If absent, the entry is emitted as `<url><loc>...</loc></url>` only.\n */\n serialize?: SitemapSerialize;\n /**\n * Extra URLs that aren't routes — e.g. external pages the site links to.\n * Mirrors `@astrojs/sitemap`'s `customPages`.\n */\n customPages?: string[];\n}\n\nconst URLSET_XMLNS =\n 'xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\"' +\n ' xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\"' +\n ' xmlns:xhtml=\"http://www.w3.org/1999/xhtml\"' +\n ' xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\"' +\n ' xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\"';\n\nfunction trimTrailingSlash(s: string): string {\n return s.endsWith(\"/\") && s.length > 1 ? s.slice(0, -1) : s;\n}\n\nfunction ensureTrailingSlash(s: string): string {\n return s.endsWith(\"/\") ? s : s + \"/\";\n}\n\nfunction xmlEscape(s: string): string {\n return s\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&apos;\");\n}\n\n/**\n * Build the canonical URL set. Astro's `pages` arg gives us pathnames as\n * `foo/bar/` style (already trailing-slash for directory format). The\n * cached pathnames from the incremental context are canonical-form\n * (no trailing slash). Normalise both, dedupe, sort.\n */\nfunction buildUrlSet(opts: IncrementalSitemapOptions): string[] {\n const siteRoot = trimTrailingSlash(opts.siteUrl);\n const base = opts.base ? trimTrailingSlash(opts.base) : \"\";\n const pathnames = new Set<string>();\n\n for (const page of opts.builtPages) {\n // Astro provides pathname without leading slash, with trailing slash.\n // Normalise to absolute URL.\n const path = ensureTrailingSlash(\"/\" + page.pathname.replace(/^\\/+/, \"\"));\n pathnames.add(`${siteRoot}${base}${path}`);\n }\n for (const cached of opts.cachedPathnames) {\n // Canonical form: \"/foo/bar\" or \"/\". We want trailing slash for\n // directory format.\n const withSlash = cached === \"/\" ? \"/\" : ensureTrailingSlash(cached);\n pathnames.add(`${siteRoot}${base}${withSlash}`);\n }\n\n return Array.from(pathnames).sort();\n}\n\nfunction renderItem(item: SitemapItem): string {\n let inner = `<loc>${xmlEscape(item.url)}</loc>`;\n if (item.lastmod !== undefined) inner += `<lastmod>${xmlEscape(item.lastmod)}</lastmod>`;\n if (item.changefreq !== undefined) inner += `<changefreq>${xmlEscape(item.changefreq)}</changefreq>`;\n if (item.priority !== undefined) inner += `<priority>${item.priority}</priority>`;\n if (item.links && item.links.length > 0) {\n for (const link of item.links) {\n inner += `<xhtml:link rel=\"alternate\" hreflang=\"${xmlEscape(link.lang)}\" href=\"${xmlEscape(link.url)}\"/>`;\n }\n }\n return `<url>${inner}</url>`;\n}\n\nexport async function emitIncrementalSitemap(\n opts: IncrementalSitemapOptions,\n): Promise<{ urlCount: number }> {\n const urls = buildUrlSet(opts);\n if (opts.customPages) {\n for (const extra of opts.customPages) urls.push(extra);\n }\n if (urls.length === 0) {\n return { urlCount: 0 };\n }\n urls.sort();\n\n // Apply the user serializer to every URL. CF's pattern: attach\n // git-sourced lastmod. Returning null/undefined drops the entry.\n const items: SitemapItem[] = [];\n for (const url of urls) {\n let item: SitemapItem | null | undefined = { url };\n if (opts.serialize) {\n item = await opts.serialize(item);\n }\n if (item) items.push(item);\n }\n\n const urlEntries = items.map(renderItem).join(\"\");\n const sitemap0 =\n `<?xml version=\"1.0\" encoding=\"UTF-8\"?>` +\n `<urlset ${URLSET_XMLNS}>${urlEntries}</urlset>`;\n\n const siteRoot = trimTrailingSlash(opts.siteUrl);\n const base = opts.base ? trimTrailingSlash(opts.base) : \"\";\n const sitemap0Loc = `${siteRoot}${base}/sitemap-0.xml`;\n const sitemapIndex =\n `<?xml version=\"1.0\" encoding=\"UTF-8\"?>` +\n `<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">` +\n `<sitemap><loc>${xmlEscape(sitemap0Loc)}</loc></sitemap>` +\n `</sitemapindex>`;\n\n await mkdir(opts.distDir, { recursive: true });\n await writeFile(resolve(opts.distDir, \"sitemap-0.xml\"), sitemap0, \"utf8\");\n await writeFile(resolve(opts.distDir, \"sitemap-index.xml\"), sitemapIndex, \"utf8\");\n\n return { urlCount: items.length };\n}\n","/**\n * Walk every version-collection directory and extract the frontmatter\n * fields the alternates table needs (`previousSlug`, `draft`).\n *\n * Runs at `astro:config:setup` — before Astro's content layer is\n * initialized, so we can't use `getCollection()`. Walks the filesystem\n * directly, slices the YAML frontmatter from each MDX/MD file, and\n * pulls the two fields we care about. Same \"never execute user code\"\n * posture as `parse-content-collections.ts` and `parse-components-registry.ts`.\n *\n * Returns one `VersionEntryInput` per visible entry across the\n * versioned-docs collections. Drafts (frontmatter `draft: true`) are\n * filtered. Consumers feed this into `buildVersionAlternates()`.\n */\n\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport type { ResolvedVersions } from \"../types.js\";\nimport type { VersionEntryInput } from \"./version-alternates.js\";\n\nconst PRIMARY_COLLECTION = \"docs\";\nconst EXTENSIONS = new Set([\".mdx\", \".md\"]);\n\nexport interface ScanOptions {\n /** Absolute path to the project root (`fileURLToPath(astroConfig.root)`). */\n projectRoot: string;\n /** Resolved versioning manifest. */\n versions: ResolvedVersions;\n}\n\nexport async function scanVersionFrontmatter(\n options: ScanOptions,\n): Promise<VersionEntryInput[]> {\n const { projectRoot, versions } = options;\n const out: VersionEntryInput[] = [];\n\n // Primary collection's directory: src/content/docs/.\n // Version collections: src/content/docs-<slug>/.\n const collectionsToScan: { collection: string; dir: string }[] = [\n { collection: PRIMARY_COLLECTION, dir: path.join(projectRoot, \"src/content/docs\") },\n ...versions.others.map((slug) => ({\n collection: `docs-${slug}`,\n dir: path.join(projectRoot, `src/content/docs-${slug}`),\n })),\n ];\n\n for (const { collection, dir } of collectionsToScan) {\n const files = await walk(dir);\n for (const file of files) {\n let source: string;\n try {\n source = await fs.readFile(file, \"utf8\");\n } catch {\n continue;\n }\n const front = extractFrontmatter(source);\n if (front === null) continue;\n if (parseBoolField(front, \"draft\") === true) continue;\n\n const previousSlug = parsePreviousSlugField(front);\n const id = idFromPath(dir, file);\n out.push({ collection, id, previousSlug });\n }\n }\n\n return out;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nasync function walk(dir: string): Promise<string[]> {\n const out: string[] = [];\n async function visit(current: string) {\n let entries: import(\"node:fs\").Dirent[];\n try {\n entries = await fs.readdir(current, { withFileTypes: true });\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return;\n throw err;\n }\n for (const entry of entries) {\n const full = path.join(current, entry.name);\n if (entry.isDirectory()) {\n if (entry.name === \"node_modules\" || entry.name.startsWith(\".\")) continue;\n await visit(full);\n } else if (entry.isFile()) {\n const ext = path.extname(entry.name);\n if (EXTENSIONS.has(ext)) out.push(full);\n }\n }\n }\n await visit(dir);\n return out;\n}\n\n/**\n * Slice the YAML frontmatter block from a source file. Returns the body\n * between the leading `---` and the closing `---` (without the markers),\n * or `null` if no frontmatter is present.\n *\n * Matches the same convention Astro's content layer enforces — leading\n * `---\\n`, closing `\\n---\\n` (or `\\n---` at EOF).\n */\nfunction extractFrontmatter(source: string): string | null {\n if (!source.startsWith(\"---\")) return null;\n // First line must be exactly `---` (or `---\\r`).\n const afterFirstMarker = source.indexOf(\"\\n\");\n if (afterFirstMarker === -1) return null;\n const firstLine = source.slice(0, afterFirstMarker).trim();\n if (firstLine !== \"---\") return null;\n\n const rest = source.slice(afterFirstMarker + 1);\n // Look for a line that's just `---` (start-of-line, optional trailing CR).\n const closingMatch = rest.match(/(^|\\n)---\\s*(\\n|$)/);\n if (!closingMatch || closingMatch.index === undefined) return null;\n // closingMatch.index is the start of the `\\n---` (or the leading\n // newline before it). Slice up to it.\n const endIndex = closingMatch[1] === \"\\n\" ? closingMatch.index : closingMatch.index;\n return rest.slice(0, endIndex);\n}\n\n/**\n * Find a top-level boolean field in YAML frontmatter. Returns the\n * boolean value or `undefined` if the field is absent / malformed.\n *\n * Handles only the shape Nimbus uses: `<field>: true` / `<field>: false`\n * on a single line, no indentation, no quotes.\n */\nfunction parseBoolField(yaml: string, field: string): boolean | undefined {\n const re = new RegExp(`^${escapeRe(field)}\\\\s*:\\\\s*(true|false)\\\\s*$`, \"m\");\n const m = yaml.match(re);\n if (!m) return undefined;\n return m[1] === \"true\";\n}\n\n/**\n * Find the top-level `previousSlug` field in YAML frontmatter. Accepts:\n * - scalar: `previousSlug: foo`\n * - inline array: `previousSlug: [foo, \"bar\", 'baz']`\n * - multiline block array:\n * ```yaml\n * previousSlug:\n * - foo\n * - bar\n * ```\n *\n * Returns:\n * - `string` for a scalar\n * - `string[]` for either array form\n * - `undefined` if absent\n *\n * The multiline form is the canonical YAML list syntax; the scanner\n * previously accepted only scalar and inline-array, which silently\n * dropped valid block lists at build time. The schema validates the\n * post-parse shape; the scanner has to match it.\n */\nfunction parsePreviousSlugField(yaml: string): string | string[] | undefined {\n // First locate the `previousSlug:` line. If the right-hand side is\n // empty (just whitespace), it's the lead-in to a block list — parse\n // the indented `- value` lines that follow.\n const blockHeader = yaml.match(/^previousSlug\\s*:\\s*$/m);\n if (blockHeader && blockHeader.index !== undefined) {\n const after = yaml.slice(blockHeader.index + blockHeader[0].length + 1);\n return parseBlockList(after);\n }\n\n // Inline array form: previousSlug: [foo, \"bar\", 'baz']\n const arr = yaml.match(/^previousSlug\\s*:\\s*\\[([^\\]]*)\\]\\s*$/m);\n if (arr) {\n const inner = arr[1]!;\n return inner\n .split(\",\")\n .map((s) => unquote(s.trim()))\n .filter((s) => s.length > 0);\n }\n\n // Scalar form: previousSlug: foo OR previousSlug: \"foo\"\n const scalar = yaml.match(/^previousSlug\\s*:\\s*(?!\\[)(.+?)\\s*$/m);\n if (scalar) {\n const raw = scalar[1]!.trim();\n if (raw.length === 0) return undefined;\n return unquote(raw);\n }\n\n return undefined;\n}\n\n/**\n * Parse a YAML block list (one `- value` per line, leading indent).\n * Stops at the first non-list, non-blank line (i.e. the next sibling\n * frontmatter field at the same indentation as the list header).\n *\n * previousSlug:\n * - foo\n * - \"bar\"\n * - 'baz'\n * title: Whatever ← stops here\n */\nfunction parseBlockList(source: string): string[] {\n const lines = source.split(\"\\n\");\n const out: string[] = [];\n for (const line of lines) {\n if (line.trim().length === 0) continue;\n const m = line.match(/^\\s+-\\s+(.+?)\\s*$/);\n if (!m) break;\n const value = unquote(m[1]!.trim());\n if (value.length > 0) out.push(value);\n }\n return out;\n}\n\nfunction unquote(s: string): string {\n if (\n (s.startsWith('\"') && s.endsWith('\"')) ||\n (s.startsWith(\"'\") && s.endsWith(\"'\"))\n ) {\n return s.slice(1, -1);\n }\n return s;\n}\n\nfunction escapeRe(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\n/**\n * Compute the Astro entry id (the slug) from a file's absolute path,\n * relative to the collection directory.\n *\n * Examples:\n * - <dir>/index.mdx → \"index\"\n * - <dir>/foo.mdx → \"foo\"\n * - <dir>/guides/setup.mdx → \"guides/setup\"\n */\nfunction idFromPath(collectionDir: string, filePath: string): string {\n const rel = path.relative(collectionDir, filePath);\n const noExt = rel.replace(/\\.(mdx|md)$/, \"\");\n // Normalise path separators for cross-platform stability.\n return noExt.split(path.sep).join(\"/\");\n}\n","/**\n * Cross-version alternates table.\n *\n * Builds the SEO-equivalence-class map at build time: which pages across\n * version collections refer to the same logical content. Consumers\n * (`getVersionAlternates`, `getCanonicalUrl`, the auto-redirect step)\n * read from the resolved structure rather than re-walking collections.\n *\n * Two ways pages get linked into the same class:\n * 1. Same `entry.id` across collections — `docs/foo.mdx` and\n * `docs-v1/foo.mdx` are obviously the same page.\n * 2. `previousSlug` frontmatter on the newer page declares its slug in\n * an older version — `docs/foo-renamed.mdx` with\n * `previousSlug: \"foo-old\"` connects to `docs-v1/foo-old.mdx`.\n *\n * Multiple equivalences chain — if v3.A renames to v2.B and v2.B\n * renames to v1.C, they're all the same logical page. Union-find\n * collapses the chains in one pass.\n *\n * The output is queried per-page from page routes (to inject\n * `<link rel=\"alternate\">` and `<link rel=\"canonical\">` into `<head>`)\n * and once per build to emit Astro redirects.\n */\n\nimport type { ResolvedVersions } from \"../types.js\";\nimport { canonicalEntryUrl } from \"./astro-slug.js\";\nimport { PRIMARY_COLLECTION } from \"./collection-mount.js\";\nimport { toBrowserHref } from \"./url.js\";\n\n/**\n * Minimum-viable entry shape the table needs. Matches what\n * `astro:content`'s `getCollection()` returns, but expressed structurally\n * so the integration can construct the same data without importing the\n * virtual `astro:content` module (which isn't available at integration\n * setup time).\n */\nexport interface VersionEntryInput {\n /** Astro collection ID (e.g. `\"docs\"`, `\"docs-v1\"`). */\n collection: string;\n /** Astro entry id (the slug — `\"foo\"`, `\"guides/setup\"`, …). */\n id: string;\n /** Frontmatter `previousSlug` (string, array of strings, or absent). */\n previousSlug?: string | string[];\n}\n\n/** A single page reference within the alternates graph. */\nexport interface VersionPageRef {\n /** Astro collection ID. */\n collection: string;\n /** Version slug — `current` for `docs`, or `v1`/`v2`/… for `docs-v*`. */\n version: string;\n /** Page slug (entry.id). */\n slug: string;\n /**\n * Resolved URL path, browser-href form: leading slash and a trailing\n * slash on HTML document routes. Rendered straight into\n * `<link rel=\"alternate\">` / `<link rel=\"canonical\">`.\n */\n url: string;\n}\n\nexport interface VersionAlternateRecord {\n /** The page this record describes. */\n self: VersionPageRef;\n /** All other pages in the same logical-page class, in version order. */\n alternates: VersionPageRef[];\n /**\n * The current-version page in this logical-page class, if one exists\n * and is not `self`. Drives `<link rel=\"canonical\">`. `null` when:\n * - `self` is already the current-version page, or\n * - no page in the current version exists for this logical page.\n */\n canonical: VersionPageRef | null;\n}\n\n/**\n * Resolved alternates table, indexed for O(1) lookup per page.\n *\n * The key is `${collection}:${entryId}`. Consumers compute that key from\n * the entry they're rendering and read out the alternates + canonical.\n */\nexport type VersionAlternatesTable = Record<string, VersionAlternateRecord>;\n\n/**\n * Build the alternates table for one site.\n *\n * Pass:\n * - `versions`: resolved manifest (or null when the site is unversioned).\n * - `entries`: every visible entry from every docs-shaped version\n * collection. Drafts already filtered.\n *\n * Returns an empty table when `versions` is null or only one version is\n * configured (no cross-linking work to do).\n */\nexport function buildVersionAlternates(\n versions: ResolvedVersions | null,\n entries: VersionEntryInput[],\n): VersionAlternatesTable {\n if (!versions || versions.all.length < 2) return {};\n\n // 1. Filter to entries we actually care about (in a version collection)\n // and compute each one's PageRef.\n const refs: VersionPageRef[] = [];\n for (const entry of entries) {\n const version = collectionToVersion(versions, entry.collection);\n if (version === null) continue;\n refs.push({\n collection: entry.collection,\n version,\n slug: entry.id,\n url: pageUrl(versions, version, entry.id),\n });\n }\n\n // 2. Union-find over the ref set. Two refs are unioned if either\n // they share a slug across versions OR one's previousSlug names\n // the other's slug in the same version chain.\n const indexByKey = new Map<string, number>();\n refs.forEach((ref, i) => indexByKey.set(refKey(ref), i));\n\n const parent = refs.map((_, i) => i);\n const find = (i: number): number => {\n let root = i;\n while (parent[root]! !== root) root = parent[root]!;\n let cursor = i;\n while (parent[cursor]! !== cursor) {\n const next = parent[cursor]!;\n parent[cursor] = root;\n cursor = next;\n }\n return root;\n };\n const union = (a: number, b: number) => {\n const ra = find(a);\n const rb = find(b);\n if (ra !== rb) parent[ra] = rb;\n };\n\n // 2a. Group by slug across versions.\n const bySlug = new Map<string, number[]>();\n for (let i = 0; i < refs.length; i++) {\n const slug = refs[i]!.slug;\n const bucket = bySlug.get(slug);\n if (bucket) bucket.push(i);\n else bySlug.set(slug, [i]);\n }\n for (const ids of bySlug.values()) {\n for (let i = 1; i < ids.length; i++) union(ids[0]!, ids[i]!);\n }\n\n // 2b. Walk previousSlug edges. Each entry's previousSlug names a slug\n // that existed in an older version. We search ALL older versions\n // for that slug and union — multiple matches are fine (a slug\n // that persisted through v1 → v2 → v3 with the same name).\n //\n // `versions.all = [current, ...others]` is the ordering. For an\n // entry in version V at index `i`, \"older\" means versions at\n // indices > `i`.\n const orderIndex = new Map<string, number>();\n versions.all.forEach((v, i) => orderIndex.set(v, i));\n\n for (let i = 0; i < entries.length; i++) {\n const entry = entries[i]!;\n if (!entry.previousSlug) continue;\n const ref = refs[refs.findIndex((r) => r.collection === entry.collection && r.slug === entry.id)];\n if (!ref) continue;\n const selfOrder = orderIndex.get(ref.version);\n if (selfOrder === undefined) continue;\n const previousSlugs = Array.isArray(entry.previousSlug)\n ? entry.previousSlug\n : [entry.previousSlug];\n\n for (const prevSlug of previousSlugs) {\n // Find every ref with slug == prevSlug in an older version\n for (let j = 0; j < refs.length; j++) {\n const other = refs[j]!;\n if (other.slug !== prevSlug) continue;\n const otherOrder = orderIndex.get(other.version);\n if (otherOrder === undefined) continue;\n if (otherOrder <= selfOrder) continue; // not older\n const selfIdx = refs.indexOf(ref);\n if (selfIdx >= 0) union(selfIdx, j);\n }\n }\n }\n\n // 3. Group refs by their root (each root = one logical page).\n const groups = new Map<number, number[]>();\n for (let i = 0; i < refs.length; i++) {\n const root = find(i);\n const g = groups.get(root);\n if (g) g.push(i);\n else groups.set(root, [i]);\n }\n\n // 4. Emit one record per ref.\n const table: VersionAlternatesTable = {};\n for (const memberIndices of groups.values()) {\n // Sort within group by manifest version order so output is deterministic.\n memberIndices.sort((a, b) => {\n const ai = orderIndex.get(refs[a]!.version) ?? Number.MAX_SAFE_INTEGER;\n const bi = orderIndex.get(refs[b]!.version) ?? Number.MAX_SAFE_INTEGER;\n return ai - bi;\n });\n const members = memberIndices.map((i) => refs[i]!);\n const currentRef = members.find((m) => m.version === versions.current) ?? null;\n\n // Hidden-version filtering. Hidden versions are off the radar from\n // every agent/SEO surface — so non-hidden pages must not advertise\n // an `<link rel=\"alternate\">` pointing at a hidden sibling. Hidden\n // pages themselves can still have a canonical pointing at current\n // (SEO authority consolidation on direct visits is desirable), but\n // their own alternates list is suppressed at the head emission\n // layer in NimbusHead.\n const hiddenVersions = new Set(versions.hidden);\n\n for (const self of members) {\n // Exclude hidden-version siblings from this page's alternates.\n // The page itself may be hidden (and NimbusHead will suppress the\n // emission anyway), but we keep the data consistent.\n const alternates = members.filter(\n (m) => m !== self && !hiddenVersions.has(m.version),\n );\n const canonical =\n currentRef && currentRef !== self ? currentRef : null;\n table[refKey(self)] = { self, alternates, canonical };\n }\n }\n\n return table;\n}\n\n/**\n * Compute the slugs that exist in `current` but are absent in a given\n * older version — the set that should auto-redirect from `/v/<slug>` to\n * `/<slug>` when a reader follows a stale link. Includes slugs reached\n * via `previousSlug` (so a renamed page's old slug in the old version\n * also redirects correctly when the user types the original new URL by\n * accident under the old prefix).\n *\n * Returns a list of `{ from, to }` redirect pairs ready for Astro's\n * `redirects` config. `from` is the URL the reader hit; `to` is the\n * current-version sibling. Both are absolute paths in the trailing-slash\n * browser-href form Astro serves under `build.format: \"directory\"`.\n * Astro's default `trailingSlash: \"ignore\"` matches incoming requests in\n * either form, so a reader landing on `/v1/foo` still resolves.\n */\nexport function computeMissingPageRedirects(\n versions: ResolvedVersions | null,\n table: VersionAlternatesTable,\n entries: VersionEntryInput[],\n): { from: string; to: string }[] {\n if (!versions || versions.all.length < 2) return [];\n\n // Build a set of (version, slug) pairs that actually exist as files.\n const existing = new Set<string>();\n for (const entry of entries) {\n const version = collectionToVersion(versions, entry.collection);\n if (version === null) continue;\n existing.add(`${version}:${entry.id}`);\n }\n\n const redirects: { from: string; to: string }[] = [];\n\n // For each current-version page, check each older version. If the old\n // version doesn't have a file with the same slug AND doesn't have one\n // linked via the alternates table → emit a redirect from the would-be\n // old URL to the current URL.\n for (const entry of entries) {\n const version = collectionToVersion(versions, entry.collection);\n if (version !== versions.current) continue;\n const currentUrl = pageUrl(versions, version, entry.id);\n\n for (const oldVersion of versions.others) {\n if (existing.has(`${oldVersion}:${entry.id}`)) continue;\n // Also skip when the alternates table already provides a\n // direct sibling in that old version (rename case).\n // `refKey` only consumes `collection` and `slug`; drop the extras.\n const record = table[refKey({ collection: entry.collection, slug: entry.id })];\n const altInOldVersion = record?.alternates.some((a) => a.version === oldVersion);\n if (altInOldVersion) continue;\n\n redirects.push({\n from: pageUrl(versions, oldVersion, entry.id),\n to: currentUrl,\n });\n }\n }\n\n return redirects;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction refKey(ref: { collection: string; slug: string }): string {\n return `${ref.collection}:${ref.slug}`;\n}\n\n/**\n * Resolve the version slug for a given Astro collection ID, or null if\n * the collection is not part of the versioning manifest.\n */\nfunction collectionToVersion(\n versions: ResolvedVersions,\n collection: string,\n): string | null {\n if (collection === PRIMARY_COLLECTION) return versions.current;\n if (!collection.startsWith(\"docs-\")) return null;\n const slug = collection.slice(\"docs-\".length);\n return versions.all.includes(slug) ? slug : null;\n}\n\n/**\n * Build the URL for a `(version, slug)` pair. Matches the convention in\n * `index.ts::resolveCollectionPrefix`:\n * - current version → root (`/foo`)\n * - others → `/<version>/<slug>`\n *\n * Runs through `canonicalEntryUrl` so the result matches what Astro\n * serves (lowercase + folder-index strip — see `_internal/astro-slug.ts`\n * for the why and known caveats). The `<link rel=\"alternate\">` tags and\n * auto-redirect machinery both consume these URLs.\n */\nfunction pageUrl(versions: ResolvedVersions, version: string, slug: string): string {\n const prefix = version === versions.current ? \"\" : `/${version}`;\n // Browser-facing form: `<link rel=\"alternate\">`, the version picker, and\n // Astro `redirects` all consume these. Trailing slash matches what the\n // static host actually serves under `build.format: \"directory\"`.\n return toBrowserHref(canonicalEntryUrl(prefix, slug));\n}\n","/**\n * The Nimbus Astro integration.\n *\n * Responsibilities:\n * - Validate the user-supplied config (throws on invalid input).\n * - Bridge `nimbusConfig.site` → Astro's top-level `site` so the\n * sitemap integration and `Astro.site` read from one source.\n * - Register `@astrojs/mdx` and `@astrojs/sitemap`.\n * - Install the Sätteri markdown processor — handles heading slugs +\n * ships with built-in Shiki dual-theme highlighting (configured via\n * Astro's `markdown.shikiConfig`).\n * - Build-time MDX PascalCase tag validation against the user's\n * `src/components.ts` registry plus per-file imports. Catches the\n * silent-failure case where MDX renders an unknown PascalCase tag\n * as literal text on the deployed site. Opt out via\n * `validateMdx: false`.\n * - Expose validated config via `virtual:nimbus/config`.\n * - Inject TypeScript types for the virtual module so consumers get\n * intellisense without manual ambient declarations.\n *\n * Not framework territory (the user's `content.config.ts` owns these):\n * - Content collection registration. The user imports\n * `docsCollection()` / `partialsCollection()` from\n * `nimbus-docs/content` and registers them themselves.\n * - MDX globals injection. The user passes `components={components}`\n * when rendering `<Content />`.\n *\n * Planned (not shipped):\n * - `/llms.txt` and `/robots.txt` route injection.\n */\n\nimport { execFile } from \"node:child_process\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport type { AstroIntegration } from \"astro\";\nimport mdx from \"@astrojs/mdx\";\nimport { satteri } from \"@astrojs/markdown-satteri\";\nimport sitemap from \"@astrojs/sitemap\";\nimport { admonitionPlugin } from \"./_internal/admonition-vite-plugin.js\";\nimport { parseComponentsRegistry } from \"./_internal/parse-components-registry.js\";\nimport {\n validateLintOptions,\n type CollectionsConfig,\n type RulesConfig,\n} from \"./lint/config.js\";\nimport { IMPLEMENTED_CODES } from \"./lint/rules/index.js\";\nimport {\n contentEntryUrl,\n enumerateEntriesByBase,\n enumerateStaticPageRoutes,\n findDuplicateRoutes,\n formatDuplicateRoutes,\n type RouteOwner,\n type RouteTruth,\n} from \"./lint/site-model.js\";\nimport {\n filterIndexableCollections,\n parseCollectionBases,\n parseContentCollections,\n} from \"./_internal/parse-content-collections.js\";\nimport { defaultCodeTransformers } from \"./_internal/code-transformers.js\";\nimport {\n formatFailures,\n validateMdxContent,\n} from \"./_internal/validate-mdx-content.js\";\nimport { validateNimbusConfig } from \"./_internal/validate.js\";\nimport { virtualConfigPlugin } from \"./_internal/virtual-config.js\";\nimport { scanCodeBlockLanguages } from \"./_internal/scan-code-langs.js\";\nimport {\n finaliseIncrementalContext,\n restoreCachedPagesToDist,\n setupIncrementalContext,\n snapshotAssetsToCache,\n wrapPrerenderer,\n} from \"./_internal/incremental/index.js\";\nimport type { PartialResolverHook } from \"./_internal/incremental/partial-refs.js\";\nimport {\n createMdxSkipContext,\n mdxSkipPlugin,\n} from \"./_internal/incremental/mdx-skip-plugin.js\";\nimport {\n emitIncrementalSitemap,\n type SitemapSerialize,\n} from \"./_internal/incremental/sitemap.js\";\nimport { scanVersionFrontmatter } from \"./_internal/scan-version-frontmatter.js\";\nimport {\n buildVersionAlternates,\n computeMissingPageRedirects,\n type VersionAlternatesTable,\n} from \"./_internal/version-alternates.js\";\nimport type { NimbusConfig } from \"./types.js\";\n\n/**\n * Common shorthand fences that Shiki doesn't recognise out of the box.\n * Hoisted to module scope so the code-block-language scanner can apply\n * the same mapping before passing the result to `shikiConfig.langs`.\n * Users can extend via Astro's shallow merge of `markdown.shikiConfig`.\n */\nconst SHIKI_LANG_ALIAS: Record<string, string> = {\n curl: \"bash\",\n console: \"bash\",\n shellsession: \"shellscript\",\n};\n\nexport interface SitemapOptions {\n serialize?: SitemapSerialize;\n customPages?: string[];\n}\n\nexport interface NimbusIntegrationOptions {\n /** MDX options forwarded to `@astrojs/mdx`. */\n mdx?: Parameters<typeof mdx>[0];\n /**\n * Sitemap behavior. Defaults: enabled when `site.url` is set, default\n * `@astrojs/sitemap` output. `false` disables it. Pass an object to\n * customise — currently `serialize` and `customPages` are supported, and\n * they apply both when incremental builds are on (we emit the sitemap\n * ourselves so cached routes appear) and when incremental is off (we\n * forward them to `@astrojs/sitemap`).\n *\n * The `serialize` callback runs once per URL and may return modified\n * fields (e.g. `lastmod` from git) or `null`/`undefined` to drop the\n * URL. Cloudflare-docs's pattern of git-sourced `lastmod` is the\n * motivating case.\n */\n sitemap?: boolean | SitemapOptions;\n /**\n * Override the markdown processor Nimbus wires into Astro's\n * `markdown.processor`. Default is Sätteri (Rust-based, fast).\n *\n * Pass a different processor when you need remark/rehype plugin\n * extensibility — Sätteri disables `mdx({ remarkPlugins })` because it\n * replaces unified's pipeline. The escape hatch:\n *\n * ```ts\n * import { unified } from \"@astrojs/markdown-remark\";\n * import remarkToc from \"remark-toc\";\n *\n * nimbus(config, {\n * markdown: {\n * processor: unified({ remarkPlugins: [remarkToc] }),\n * },\n * });\n * ```\n *\n * Trade-off: the Sätteri performance win goes away. Worth it for sites\n * that depend on unified-ecosystem plugins (CF docs uses seven).\n *\n * @default `satteri()`\n */\n markdown?: {\n /** Custom Astro `markdown.processor`. Imported from `@astrojs/markdown-remark` (unified), `@astrojs/markdown-satteri` (default), or any compatible processor. */\n // Typed loosely (`unknown`) to avoid pulling the Astro internal helper\n // types into the public surface. Astro validates the shape at use time.\n processor?: unknown;\n };\n /**\n * Build-time MDX PascalCase tag validation.\n *\n * - `true` (default): parse `src/components.ts` for the globals\n * registry and fail the build on unknown PascalCase tags found\n * in `src/content/**\\/*.mdx`.\n * - `false`: skip validation entirely.\n * - `{ componentsPath }`: override the registry file location.\n * Relative paths resolve to the project root.\n * - `{ contentDirs }`: override the scanned directories. Relative\n * paths resolve to the project root. Default: `[\"src/content\"]`.\n * - `{ skip }`: filter out files (e.g. vendored or generated MDX).\n *\n * Runs as a pre-build content pass rather than as a remark plugin so\n * it works regardless of which markdown processor is wired into\n * `markdown.processor`. Sätteri (the default) replaces unified's\n * pipeline, which silently disables remark plugins attached via\n * `mdx({ remarkPlugins })`.\n */\n validateMdx?:\n | boolean\n | {\n componentsPath?: string;\n contentDirs?: string[];\n skip?: (filePath: string) => boolean;\n };\n /**\n * Rewrite `:::type[title]` fenced directives to `<Aside>` components\n * in MDX/MD source before the markdown compiler sees them. Built-in\n * types: `note`, `info`, `tip`, `caution`, `warning`, `important`,\n * `danger` (mapped to Nimbus's 4 Aside slots).\n *\n * - `true` (default): rewrite against `src/content/**\\/*.{md,mdx}`.\n * - `false`: skip the transform; `:::` syntax renders as literal text.\n * - `{ typeAliases }`: extra type → Aside mappings for product\n * synonyms (`{ heads: \"tip\" }`).\n * - `{ contentDirs }`: override the scanned directories.\n * - `{ skip }`: per-file opt-out.\n *\n * Runs as a Vite plugin (content pass) so it survives the\n * `markdown.processor` swap that disables remark plugins under\n * Sätteri. Aside must be in the user's `src/components.ts` globals\n * registry — the default starter exports it; if your registry doesn't,\n * the MDX validator surfaces a clean build error.\n */\n admonitions?:\n | boolean\n | {\n typeAliases?: Record<string, \"note\" | \"tip\" | \"caution\" | \"danger\">;\n contentDirs?: string[];\n skip?: (filePath: string) => boolean;\n };\n /**\n * Authoring-lint severity overrides for `nimbus-docs lint`. Maps a rule\n * code to `\"error\" | \"warn\" | \"off\"` or a `[severity, options]` tuple.\n * Build validators are rejected here — they have no severity knob.\n * Omitted = every authoring rule on at `error`.\n *\n * These are materialized to `.nimbus/lint.json` at config setup so the\n * standalone `nimbus-docs lint` CLI can read them. The build itself is\n * never gated on authoring rules.\n */\n rules?: RulesConfig;\n /**\n * Per-collection overrides. Each entry's `rules` block shallow-merges\n * over the top-level `rules` for files in that collection — same shape,\n * same validation, same build-validator carve-out (build validators\n * stay global, they can't be configured per-collection).\n *\n * @example\n * collections: {\n * partials: { rules: { \"nimbus/single-h1\": \"off\", \"nimbus/heading-hierarchy\": \"off\" } },\n * }\n */\n collections?: CollectionsConfig;\n /**\n * Opt into per-page build caching. When `true`, Nimbus wraps Astro's\n * prerenderer and short-circuits cache hits with previously-rendered HTML.\n *\n * Phase 2 MVP — preview-quality:\n * - Per-page cache keyed on file bytes + a global hash of tracked\n * sources (config, components, layouts, lockfile).\n * - No partial-dependency tracking yet — editing a partial triggers a\n * full rebuild (because it doesn't change the page's bytes but it\n * does need to invalidate dependents). Phase 3 closes this gap.\n * - No cache provenance / namespace isolation / trust boundary.\n * Manual `rm -rf .nimbus/cache` between framework upgrades.\n *\n * Default: `false`. The bench (`apps/incremental-bench`) is the only\n * site that should set this today.\n */\n incrementalBuilds?: boolean;\n /**\n * Custom partial resolver for incremental builds. Called for every\n * PascalCase component opening tag found in MDX content with string-\n * literal props. Return the absolute file path of the partial the\n * component embeds, or `null` to indicate this component isn't a\n * partial-embedder.\n *\n * The default resolver covers the standard `<Render file=\"topic/slug\" />`\n * pattern shipping with Nimbus's starter. Sites with multi-prop\n * conventions need their own — cloudflare-docs is the motivating case:\n *\n * @example\n * partialResolver: (name, props) => {\n * if (name !== \"Render\" || !props.file) return null;\n * if (props.product) {\n * return resolve(projectRoot, `src/content/partials/${props.product}/${props.file}.mdx`);\n * }\n * return resolve(projectRoot, `src/content/partials/${props.file}.mdx`);\n * }\n *\n * Required only when `incrementalBuilds: true`. Ignored otherwise.\n */\n partialResolver?: PartialResolverHook;\n}\n\nexport function nimbus(\n rawConfig: NimbusConfig,\n options: NimbusIntegrationOptions = {},\n): AstroIntegration {\n const config = validateNimbusConfig(rawConfig);\n // Validate the lint half of the options up front (build validators can't\n // take a severity; `collections` is reserved). Throws on misconfig.\n const lintOptions = validateLintOptions(\n { rules: options.rules, collections: options.collections },\n IMPLEMENTED_CODES,\n );\n\n // Threaded from `astro:config:setup` to `astro:build:done` so the post-\n // build materialization knows where to write `.nimbus/routes.json` and\n // what `base` Astro is using.\n let projectRootForBuild = \"\";\n let astroBaseForBuild = \"\";\n // Phase 2 incremental builds context — populated at astro:build:start when\n // `options.incrementalBuilds` is true, read in :build:done.\n let incrementalCtx: import(\"./_internal/incremental/index.js\").IncrementalContext | null = null;\n // Layer 2 MDX-skip plugin context — registered at astro:config:setup,\n // populated at astro:build:start once the cache map is known.\n const mdxSkipCtx = createMdxSkipContext();\n\n return {\n name: \"nimbus-docs\",\n hooks: {\n \"astro:config:setup\": async (params) => {\n const { updateConfig, config: astroConfig, logger } = params;\n\n const integrationsToAdd: AstroIntegration[] = [];\n\n // Materialize the resolved lint config so the standalone\n // `nimbus-docs lint` CLI can read severities authored here. Guarded\n // — a write failure must never break the build.\n materializeLintConfig(\n fileURLToPath(astroConfig.root),\n lintOptions.rules,\n lintOptions.collections,\n config.site,\n );\n\n // Pre-build MDX validation. Runs as a content pass against\n // `src/content/**/*.mdx` rather than as a remark plugin —\n // Sätteri replaces unified's pipeline and silently disables\n // any remark plugins, so the per-file-during-compile path is\n // not reliable here.\n if (options.validateMdx !== false) {\n const validateOpts =\n typeof options.validateMdx === \"object\" ? options.validateMdx : {};\n const projectRoot = fileURLToPath(astroConfig.root);\n const componentsPath = path.isAbsolute(validateOpts.componentsPath ?? \"\")\n ? (validateOpts.componentsPath as string)\n : path.join(\n projectRoot,\n validateOpts.componentsPath ?? \"src/components.ts\",\n );\n\n const globals = await parseComponentsRegistry(componentsPath);\n if (globals === null) {\n logger.warn(\n `MDX validation disabled: \\`${path.relative(projectRoot, componentsPath)}\\` is missing or does not export a parseable \\`components\\` object. ` +\n `Create the file with \\`export const components = { /* ... */ };\\` or set \\`validateMdx: false\\` to silence this warning.`,\n );\n } else {\n const contentDirs = (validateOpts.contentDirs ?? [\"src/content\"]).map(\n (d) => (path.isAbsolute(d) ? d : path.join(projectRoot, d)),\n );\n const failures = await validateMdxContent({\n globals,\n contentDirs,\n skip: validateOpts.skip,\n projectRoot,\n });\n if (failures.length > 0) {\n throw new Error(formatFailures(failures, globals.length));\n }\n logger.info(\n `MDX validation passed — ${globals.length} global component${globals.length === 1 ? \"\" : \"s\"} registered, ${contentDirs.length} content dir${contentDirs.length === 1 ? \"\" : \"s\"} scanned.`,\n );\n }\n }\n\n // Parse user's content.config.ts to enumerate registered\n // collections. Powers `getIndexedEntries()` and the agent-facing\n // routes (llms.txt, per-page .md alternates) so they don't have\n // to hardcode `\"docs\"`. Adding a `blog` collection to\n // content.config.ts lights up every indexing surface\n // automatically — no second file to edit.\n const projectRoot = fileURLToPath(astroConfig.root);\n\n // Stash for the `astro:build:done` hook, which uses Astro's actual\n // emitted `pages` array as the route truth (single source of truth\n // — Astro itself tells us which URLs the site serves).\n projectRootForBuild = projectRoot;\n astroBaseForBuild = astroConfig.base ?? \"\";\n\n // Scan every code-fence language used in `src/content/**/*.{mdx,md}`\n // so Shiki eager-loads grammars at startup. Required for incremental\n // builds (Layer 2 stubs cached MDX → languages that only live there\n // never trigger Shiki's lazy load). Cheap enough to run for everyone.\n const codeBlockLangs = await scanCodeBlockLanguages(\n projectRoot,\n SHIKI_LANG_ALIAS,\n );\n\n // Parse `content.config.ts` up front: we need\n // - the registered collection set (for `virtual:nimbus/config`'s\n // indexable list);\n // - the (key → base) map (for the duplicate-slug walk, so a\n // `docsCollection({ base: \"documentation\" })` collection gets\n // scanned at the right on-disk location rather than being\n // silently skipped).\n const contentConfigPath = path.join(projectRoot, \"src/content.config.ts\");\n const rawCollections = await parseContentCollections(contentConfigPath);\n const collectionBases = await parseCollectionBases(contentConfigPath);\n const indexedCollections =\n rawCollections === null\n ? [\"docs\"] // Fallback: brand-new project hasn't written content.config yet.\n : filterIndexableCollections(rawCollections);\n\n if (rawCollections === null) {\n logger.warn(\n `nimbus-docs: \\`src/content.config.ts\\` is missing or doesn't expose a parseable \\`export const collections = { ... }\\`. ` +\n `Falling back to indexing the \\`docs\\` collection only.`,\n );\n }\n\n // Build validator `nimbus/duplicate-slug`: two sources that resolve\n // to the same URL silently shadow each other during `astro build`.\n // Runs pre-build because Astro dedupes colliding routes before the\n // integration sees them — by the time `astro:build:done` fires,\n // one source has already won.\n //\n // Two URL sources feed the check:\n //\n // 1. Content entries from indexable collections, grouped by\n // *mounted URL* (collection prefix + canonical slug). Catches\n // cross-collection collisions (`docs/blog/post.mdx` vs\n // `blog/post.mdx`), version collisions (`docs/v1/x.mdx` vs\n // `docs-v1/x.mdx`), case-only, and folder-index-vs-leaf.\n // Non-routed collections like `partials` are excluded\n // (per `filterIndexableCollections`) since they aren't pages.\n //\n // 2. Static `src/pages/**` files (no dynamic segments). Catches\n // the page-vs-content collision — e.g. `pages/search.astro`\n // shadowing `content/docs/search.mdx` at `/search`. Dynamic\n // page routes are skipped: their emitted URLs come from\n // `getStaticPaths` at build time, so we can't know them\n // pre-build without invoking the same machinery Astro\n // silently dedupes through anyway.\n const indexedSet = new Set(indexedCollections);\n const versionInfo = config.versions\n ? { others: config.versions.others ?? [] }\n : null;\n\n // Restrict the walk to *indexable* collections, and use the parsed\n // `(key → base)` map so a custom `base: \"documentation\"` collection\n // is scanned at `src/content/documentation/` and tagged with key\n // `docs`. Falls back to `(key → key)` when content.config.ts wasn't\n // parseable — the brand-new-project case where we already warned.\n const indexedBases = new Map<string, string>();\n if (collectionBases !== null) {\n for (const [key, base] of collectionBases) {\n if (indexedSet.has(key)) indexedBases.set(key, base);\n }\n } else {\n for (const key of indexedCollections) indexedBases.set(key, key);\n }\n\n const contentOwners: RouteOwner[] = enumerateEntriesByBase(\n path.join(projectRoot, \"src/content\"),\n indexedBases,\n ).map((entry) => ({\n url: contentEntryUrl(entry, versionInfo),\n source: `src/content/${entry.relPath}`,\n }));\n\n const pageOwners: RouteOwner[] = enumerateStaticPageRoutes(\n path.join(projectRoot, \"src/pages\"),\n projectRoot,\n );\n\n const duplicateRoutes = findDuplicateRoutes([\n ...contentOwners,\n ...pageOwners,\n ]);\n if (duplicateRoutes.length > 0) {\n throw new Error(formatDuplicateRoutes(duplicateRoutes));\n }\n\n // Cross-check `versions.others` against registered collections.\n // Zod validated the shape; this pass enforces the invariant that\n // every non-current version slug `<v>` corresponds to a registered\n // collection named `docs-<v>`. We can only check this when we\n // actually parsed content.config.ts — if `rawCollections` is null\n // the user is on a brand-new project and we already warned.\n if (config.versions && rawCollections !== null) {\n const registered = new Set(rawCollections);\n const missing = config.versions.others.filter(\n (slug) => !registered.has(`docs-${slug}`),\n );\n if (missing.length > 0) {\n const lines = missing.map((slug) => {\n return (\n ` - \"${slug}\" → expected a collection named \"docs-${slug}\" ` +\n `in src/content.config.ts (e.g. \\`\"docs-${slug}\": docsCollection({ base: \"docs-${slug}\" })\\`)`\n );\n });\n throw new Error(\n `nimbus-docs: \\`versions.others\\` references slugs without matching collections:\\n${lines.join(\"\\n\")}\\n\\n` +\n `Every entry in \\`versions.others\\` must correspond to a registered Astro content ` +\n `collection. Register the collection(s) above in src/content.config.ts and try again.`,\n );\n }\n }\n\n // ----- Versioning P1: build the cross-version alternates table.\n //\n // Walks every version collection's content directory, extracts\n // `previousSlug` + `draft` from frontmatter, and builds the\n // alternates graph (slug-equality + previousSlug edges, union-find\n // for chains). The resolved table is JSON-serialised into\n // `virtual:nimbus/config` so route helpers can read it without\n // re-walking the filesystem. Also computes the redirect pairs\n // (old-version URLs whose slug no longer exists in that version)\n // and merges them into Astro's `redirects` config.\n let versionAlternates: VersionAlternatesTable = {};\n let versionRedirects: { from: string; to: string }[] = [];\n if (config.versions) {\n const resolved = {\n current: config.versions.current,\n others: config.versions.others ?? [],\n deprecated: config.versions.deprecated ?? [],\n hidden: config.versions.hidden ?? [],\n all: [config.versions.current, ...(config.versions.others ?? [])],\n };\n const versionEntries = await scanVersionFrontmatter({\n projectRoot,\n versions: resolved,\n });\n versionAlternates = buildVersionAlternates(resolved, versionEntries);\n versionRedirects = computeMissingPageRedirects(\n resolved,\n versionAlternates,\n versionEntries,\n );\n }\n\n // MDX is always added; sitemap only when `site` is configured.\n integrationsToAdd.push(mdx(options.mdx ?? {}));\n // Layer 4: when incremental is on, we emit the sitemap ourselves at\n // build:done so cached routes (which Astro never renders) still\n // appear. Registering @astrojs/sitemap here would produce a sitemap\n // missing those routes — broken on every warm build.\n const wantSitemap = options.sitemap !== false && Boolean(config.site);\n const sitemapOpts =\n typeof options.sitemap === \"object\" ? options.sitemap : undefined;\n if (wantSitemap && !options.incrementalBuilds) {\n integrationsToAdd.push(\n sitemap({\n ...(sitemapOpts?.serialize && { serialize: sitemapOpts.serialize }),\n ...(sitemapOpts?.customPages && { customPages: sitemapOpts.customPages }),\n }),\n );\n }\n\n // Admonition transform plugin: only constructed when enabled\n // (default on). Same `contentDirs` defaulting as the MDX\n // validator — keeps the two scans aligned.\n const admonitionVitePlugins = [] as Array<ReturnType<typeof admonitionPlugin>>;\n if (options.admonitions !== false) {\n const admoOpts =\n typeof options.admonitions === \"object\" ? options.admonitions : {};\n const projectRoot = fileURLToPath(astroConfig.root);\n const contentDirs = (admoOpts.contentDirs ?? [\"src/content\"]).map((d) =>\n path.isAbsolute(d) ? d : path.join(projectRoot, d),\n );\n admonitionVitePlugins.push(\n admonitionPlugin({\n contentDirs,\n typeAliases: admoOpts.typeAliases,\n skip: admoOpts.skip,\n }),\n );\n }\n\n updateConfig({\n // Bridge `nimbusConfig.site` → Astro's top-level `site`. The\n // sitemap integration and `Astro.site` both read this; without\n // it, sitemap warns \"missing `site` astro.config option\" at\n // build time even though nimbus has a site URL right there.\n // Only set when configured (validate.ts already enforces it,\n // but stay defensive for future optionality).\n ...(config.site ? { site: config.site } : {}),\n // Astro deep-merges arrays in updateConfig, so user-declared\n // integrations are preserved.\n integrations: integrationsToAdd,\n // Markdown processor. Defaults to Sätteri (Rust-based, fast);\n // heading IDs, image collection, and Shiki highlighting wired\n // internally by Sätteri's default plugin set — no manual\n // registration needed. MDX inherits via @astrojs/mdx's\n // `extendMarkdownConfig: true`. Users can override via\n // `nimbus(config, { markdown: { processor: unified(...) } })`\n // when they need remark/rehype plugin extensibility (Sätteri\n // disables `mdx({ remarkPlugins })`).\n //\n // The `as never` cast is a structural escape: Astro's\n // `processor` is typed as `MarkdownProcessor`, but we accept\n // the broader `unknown` at the public surface to avoid leaking\n // Astro's internal-helpers types. Astro validates at use time.\n markdown: {\n processor: (options.markdown?.processor ?? satteri()) as never,\n // Dual-theme Shiki output. `defaultColor: false` makes Shiki\n // emit BOTH themes as inline CSS variables (`--shiki-light`,\n // `--shiki-dark`, `--shiki-light-bg`, `--shiki-dark-bg`)\n // rather than baking one theme into the HTML. The starter's\n // globals.css then switches between them based on the\n // `<html data-mode=\"dark\">` attribute the theme toggle flips.\n //\n // `defaultCodeTransformers()` is the single source of truth\n // for the premium code-block features — diff/highlight/focus/\n // error/word notations, meta highlight, and the title-frame +\n // lang badge transformer. The same factory is exported as a\n // named entry from `nimbus-docs` so the starter's `Code.astro`\n // can wire them into Astro's built-in `<Code>` component\n // (Astro's `<Code>` doesn't auto-read `shikiConfig`).\n //\n // Users can override these defaults by passing their own\n // shikiConfig at the user-config level (Astro merges shallowly).\n shikiConfig: {\n themes: {\n light: \"github-light\",\n dark: \"github-dark\",\n },\n defaultColor: false,\n transformers: defaultCodeTransformers(),\n // Common shorthand fences that Shiki doesn't recognise out\n // of the box. Without these, ` ```curl ` (and similar) emit\n // a per-file build warning and fall through to plaintext.\n // Mapped to the closest highlighter that produces useful\n // colouring. Users can extend via Astro's shallow merge of\n // `markdown.shikiConfig` at the user-config level.\n langAlias: SHIKI_LANG_ALIAS,\n // Eager-load every language used anywhere in the project's\n // MDX/MD content. Shiki's lazy load otherwise assumes every\n // file gets processed during the build — an assumption that\n // Layer 2 of incremental builds violates (cached MDX files\n // never enter the markdown pipeline, so languages that only\n // appear in cached files would never trigger a grammar\n // load, and any non-cached file using those languages would\n // render plaintext on warm builds). Eager loading also\n // makes cold-build output stable regardless of file order.\n langs: codeBlockLangs,\n },\n },\n // Versioning P1: auto-redirects from old-version URLs whose\n // slug no longer exists in that version to the current-version\n // sibling. Astro merges `redirects` shallowly across calls; the\n // user's hand-written redirects (if any) win on conflict because\n // their config runs after this hook.\n ...(versionRedirects.length > 0\n ? {\n redirects: Object.fromEntries(\n versionRedirects.map(({ from, to }) => [from, to]),\n ),\n }\n : {}),\n // Vite plugins. Order is significant:\n // 1. `admonitionPlugin` (enforce: \"pre\") — rewrites `:::type`\n // directives to `<Aside>` so the markdown compiler sees\n // JSX rather than literal `:::` text. Must run before\n // @astrojs/mdx parses the file.\n // 2. `virtualConfigPlugin` — exposes the validated config via\n // `virtual:nimbus/config`, plus the build-time-resolved\n // `indexedCollections` list (see `getIndexedEntries()` and\n // the llms.txt routes) and the versioning alternates\n // table (P1).\n vite: {\n plugins: [\n ...admonitionVitePlugins,\n virtualConfigPlugin(config, {\n indexedCollections,\n versionAlternates,\n }),\n ...(options.incrementalBuilds ? [mdxSkipPlugin(mdxSkipCtx)] : []),\n ],\n },\n });\n },\n \"astro:config:done\": ({ injectTypes }) => {\n // TypeScript declaration for the virtual module. Written to\n // `.astro/integrations/nimbus-docs/virtual-config.d.ts` and\n // auto-referenced by the project tsconfig via Astro's generated\n // types.\n injectTypes({\n filename: \"virtual-config.d.ts\",\n content: [\n 'declare module \"virtual:nimbus/config\" {',\n ' import type { NimbusConfig, VersionAlternatesTable } from \"nimbus-docs/types\";',\n \" export const config: NimbusConfig;\",\n \" /** Build-time list of indexable collection names. See `getIndexedEntries()`. */\",\n \" export const indexedCollections: readonly string[];\",\n \" /** Build-time cross-version alternates table. See `getVersionAlternates()`. */\",\n \" export const versionAlternates: VersionAlternatesTable;\",\n \"}\",\n \"\",\n ].join(\"\\n\"),\n });\n },\n \"astro:build:start\": async ({ setPrerenderer, logger }) => {\n if (!options.incrementalBuilds) return;\n if (!projectRootForBuild) {\n logger.warn(\n \"[incremental] project root unknown at build:start; cache disabled this run\",\n );\n return;\n }\n incrementalCtx = await setupIncrementalContext(\n projectRootForBuild,\n logger,\n options.partialResolver,\n );\n // Layer 2 — populate the MDX-skip plugin's cached set from the\n // pathnames whose cached HTML we trust. The plugin reads this set\n // at every `resolveId`; updating it now is in time for Vite's\n // bundling phase.\n mdxSkipCtx.cachedAbsolutePaths.clear();\n for (const pathname of incrementalCtx.cacheableHits) {\n const filePath = incrementalCtx.filePathByPathname.get(pathname);\n if (filePath) mdxSkipCtx.cachedAbsolutePaths.add(filePath);\n }\n mdxSkipCtx.enabled = true;\n logger.info(\n `[incremental] mdx-skip plugin armed for ${mdxSkipCtx.cachedAbsolutePaths.size} cached MDX files`,\n );\n setPrerenderer((defaultPrerenderer) =>\n wrapPrerenderer(defaultPrerenderer, incrementalCtx!),\n );\n },\n \"astro:build:done\": async ({ dir, pages, logger }) => {\n // Materialize the site's route truth from Astro's emitted `pages`\n // array — the single source of truth: every URL on this list is a\n // page Astro just wrote to disk. No reconstruction, no slug\n // mirroring, no Astro-internals coupling. The build/lint\n // contract is \"after `astro build`, `.nimbus/routes.json` reflects\n // exactly what the site serves.\" Lint that runs without a prior\n // build silently skips `internal-link`.\n //\n // Duplicate-slug detection happens in `astro:config:setup`, not\n // here: Astro silently dedupes colliding routes before this hook\n // fires, so the collisions are invisible post-build.\n // Order of operations under incremental builds:\n // 1. Restore cached pages to dist. This also prunes `cacheableHits`\n // to the set whose HTML actually landed on disk.\n // 2. Materialize route truth from the *pruned* set. Doing this\n // BEFORE restore would write ghost routes to .nimbus/routes.json\n // (BUG-101): the lint CLI's `internal-link` rule would treat\n // stale cache entries as valid targets.\n // 3. Emit sitemap from the same pruned set.\n // 4. Snapshot assets, write manifest.\n if (incrementalCtx) {\n const distDir = fileURLToPath(dir);\n await restoreCachedPagesToDist(incrementalCtx, distDir);\n\n // Layer 6 — under incremental builds, `pages` only contains routes\n // Astro just rendered. Cached routes are filtered out. Merge in the\n // confirmed-restored cache hits so route truth reflects the full\n // route set actually on disk.\n const fullPagesForTruth = [\n ...pages,\n ...[...incrementalCtx.cacheableHits]\n .filter((p) => !pages.some((q) => \"/\" + q.pathname === (p === \"/\" ? \"/\" : p + \"/\")))\n .map((p) => ({\n pathname: p === \"/\" ? \"\" : p.slice(1) + \"/\",\n })),\n ];\n materializeRouteTruthFromPages(\n projectRootForBuild,\n astroBaseForBuild,\n fullPagesForTruth,\n logger,\n );\n\n // Layer 4: emit sitemap from the union of Astro-built pages and\n // confirmed-restored cached pathnames. User-supplied `serialize`\n // runs on every URL — cached and dirty alike — so the warm-build\n // sitemap matches the cold-build sitemap when a serializer is in\n // play (the cloudflare-docs git-lastmod case).\n if (options.sitemap !== false && config.site) {\n const sitemapOptsResolved =\n typeof options.sitemap === \"object\" ? options.sitemap : undefined;\n const result = await emitIncrementalSitemap({\n siteUrl: config.site,\n builtPages: pages,\n cachedPathnames: incrementalCtx.cacheableHits,\n distDir,\n base: astroBaseForBuild,\n serialize: sitemapOptsResolved?.serialize,\n customPages: sitemapOptsResolved?.customPages,\n });\n logger.info(`[incremental] sitemap emitted (${result.urlCount} urls)`);\n }\n await snapshotAssetsToCache(incrementalCtx, distDir);\n await finaliseIncrementalContext(incrementalCtx);\n } else {\n // Non-incremental path — same logic as before.\n materializeRouteTruthFromPages(\n projectRootForBuild,\n astroBaseForBuild,\n pages,\n logger,\n );\n }\n\n if (config.search === false || config.search?.provider === \"custom\") {\n incrementalCtx = null;\n return;\n }\n\n // Pagefind reindexes the full dist on every run, setting a ~10s\n // floor at 7k pages regardless of how many pages actually changed.\n // On a zero-miss warm build, the prior index is still correct —\n // restore it from cache and skip the rerun entirely. The snapshot\n // is taken after every Pagefind run that *did* execute (i.e. any\n // build with at least one miss, plus all non-incremental builds).\n const distDir = fileURLToPath(dir);\n const pagefindDistDir = path.join(distDir, \"pagefind\");\n const zeroMissIncremental =\n incrementalCtx !== null &&\n incrementalCtx.stats.misses === 0 &&\n (await incrementalCtx.cache.hasPagefindSnapshot());\n if (zeroMissIncremental) {\n const restored = await incrementalCtx!.cache.restorePagefind(\n pagefindDistDir,\n );\n logger.info(\n `[incremental] Pagefind skipped — restored ${restored} cached index file(s)`,\n );\n } else {\n await runPagefind(distDir);\n if (incrementalCtx) {\n const snapped = await incrementalCtx.cache.snapshotPagefind(\n pagefindDistDir,\n );\n if (snapped > 0) {\n logger.info(\n `[incremental] snapshotted ${snapped} Pagefind index file(s) to cache`,\n );\n }\n }\n }\n incrementalCtx = null;\n },\n },\n };\n}\n\n/**\n * Write the resolved authoring-lint config to `<root>/.nimbus/lint.json`\n * for the standalone CLI. Best-effort: any filesystem error is swallowed\n * so it can't fail an `astro build`. `.nimbus/` is a gitignored scratch\n * dir (same home the Vale recipe uses).\n *\n * `site` is materialized alongside the rules so site-aware rules\n * (`no-self-host-url`) get the project's deploy host without making the\n * user duplicate it in their lint config.\n */\nfunction materializeLintConfig(\n projectRoot: string,\n rules: RulesConfig,\n collections: CollectionsConfig,\n site: string,\n): void {\n try {\n const dir = path.join(projectRoot, \".nimbus\");\n fs.mkdirSync(dir, { recursive: true });\n fs.writeFileSync(\n path.join(dir, \"lint.json\"),\n JSON.stringify({ version: 1, rules, collections, site }, null, 2) + \"\\n\",\n \"utf8\",\n );\n } catch {\n // Non-fatal — `nimbus-docs lint` falls back to all-rules-on defaults.\n }\n}\n\n/**\n * Write the site's route truth to `<root>/.nimbus/routes.json` from the\n * `pages` array Astro hands us at `astro:build:done`. Each entry in `pages`\n * is a real emitted URL — no reconstruction, no slug mirroring.\n *\n * Best-effort write, same as `materializeLintConfig`. When the file is\n * missing (e.g. lint ran before any `astro build`), `internal-link` skips\n * silently rather than false-positive.\n *\n * Duplicate-slug detection lives in `astro:config:setup` (above), not\n * here. Astro silently dedupes colliding routes before this hook fires,\n * so a post-build collision check on `pages` would never see the\n * collisions it claims to catch.\n */\nfunction materializeRouteTruthFromPages(\n projectRoot: string,\n base: string,\n pages: readonly { pathname: string }[],\n logger: { warn: (msg: string) => void; debug?: (msg: string) => void },\n): void {\n // Normalize and dedupe pathnames into the canonical `/foo` form used by\n // the lookup logic in `internal-link.ts`. The dedupe is defensive —\n // Astro already deduped before this hook, so `pages` shouldn't contain\n // collisions; we still tolerate it in case a route re-emits across\n // formats (e.g. `.html` + `.md` siblings).\n const canonical = new Set<string>();\n for (const { pathname } of pages) {\n canonical.add(canonicalizePathname(pathname));\n }\n\n const truth: RouteTruth = {\n version: 1,\n base,\n knownRoutes: [...canonical].sort(),\n // With `pages` as the truth, every emitted URL is in `knownRoutes` —\n // there are no opaque namespaces. The field stays in the schema for\n // forward-compat with future SSR-route handling.\n opaqueNamespaces: [],\n };\n\n try {\n const dir = path.join(projectRoot, \".nimbus\");\n fs.mkdirSync(dir, { recursive: true });\n fs.writeFileSync(\n path.join(dir, \"routes.json\"),\n JSON.stringify(truth, null, 2) + \"\\n\",\n \"utf8\",\n );\n } catch (err) {\n logger.debug?.(\n `failed to write .nimbus/routes.json — internal-link will skip: ${(err as Error).message}`,\n );\n }\n}\n\nfunction canonicalizePathname(pathname: string): string {\n // Astro's `pages.pathname` comes in two flavors:\n // - Root: literal `/`.\n // - Non-root: leading slash absent in some emissions (\"cli\"), present in\n // others (\"/cli\"). Trailing slash also varies by `trailingSlash` config.\n // Canonical form: leading `/`, no trailing `/` (except for root itself).\n let s = pathname;\n if (s === \"\") return \"/\";\n if (!s.startsWith(\"/\")) s = `/${s}`;\n if (s.length > 1 && s.endsWith(\"/\")) s = s.slice(0, -1);\n return s;\n}\n\nfunction runPagefind(siteDir: string): Promise<void> {\n const bin = process.platform === \"win32\" ? \"pagefind.cmd\" : \"pagefind\";\n return new Promise((resolve) => {\n execFile(bin, [\"--site\", siteDir], (error, stdout, stderr) => {\n if (stdout) process.stdout.write(stdout);\n if (stderr) process.stderr.write(stderr);\n if (error) {\n console.warn(\n `[nimbus-docs] Pagefind did not run. Install pagefind as a devDependency or set search: false in your Nimbus config.\\n${error.message}`,\n );\n }\n resolve();\n });\n });\n}\n","/**\n * Main entry for `nimbus-docs`.\n *\n * Exports the Astro integration (default), config helper, and the four\n * data helpers (sidebar, prev/next, breadcrumbs, TOC). Phase 6 will\n * add page composition helpers (`getDocsStaticPaths`, `getDocsPageProps`).\n *\n * Helpers read the user's config from `virtual:nimbus/config` (provided\n * by our Vite plugin) and content entries from `astro:content`. Both\n * are external in tsdown and resolved at the consumer's build time.\n */\n\nimport {\n loadIndexedCollections,\n loadNimbusConfig,\n loadVersionAlternates,\n} from \"./_internal/runtime-config.js\";\nimport {\n getVisibleEntries,\n getVisibleEntriesByCollection,\n} from \"./_internal/content.js\";\nimport {\n buildSidebarTree,\n collectSidebarCollectionRefs,\n deriveSidebarSections,\n scopeToCurrentSection,\n sidebarHash,\n} from \"./_internal/sidebar.js\";\nimport { canonicalEntryUrl } from \"./_internal/astro-slug.js\";\nimport { toBrowserHref, toRouteKey } from \"./_internal/url.js\";\nimport {\n PRIMARY_COLLECTION,\n collectionLabel as resolveCollectionSlug,\n collectionMountPrefix as resolveCollectionPrefix,\n} from \"./_internal/collection-mount.js\";\nimport { renderEntryAsMarkdown } from \"./_internal/transform.js\";\nimport {\n getBreadcrumbs as buildBreadcrumbs,\n getPrevNext as buildPrevNext,\n} from \"./_internal/navigation.js\";\nimport { getHeadings } from \"./_internal/toc.js\";\nimport { getLastUpdatedFromGit } from \"./_internal/git-last-updated.js\";\n\nimport type {\n Breadcrumb,\n NimbusConfig,\n PrevNext,\n PrevNextOverrides,\n ResolvedVersions,\n SidebarItem,\n SidebarSection,\n TOCItem,\n VersionAlternateRecord,\n VersionPageRef,\n VersionStatus,\n} from \"./types.js\";\n\nexport { nimbus as default } from \"./integration.js\";\nexport type { NimbusIntegrationOptions } from \"./integration.js\";\n\nexport type {\n BadgeVariant,\n Breadcrumb,\n NimbusConfig,\n PrevNext,\n PrevNextLink,\n PrevNextOverrides,\n ResolvedVersions,\n SearchProvider,\n SearchResult,\n SidebarBadge,\n SidebarConfig,\n SidebarConfigItem,\n SidebarExternalLinkItem,\n SidebarGroupItem,\n SidebarItem,\n SidebarLinkItem,\n SidebarSection,\n TOCItem,\n VersionAlternateRecord,\n VersionAlternatesTable,\n VersionPageRef,\n VersionStatus,\n VersionsConfig,\n} from \"./types.js\";\n\n/**\n * Define a typed Nimbus config. Returns the config unchanged but inferred.\n */\nexport function defineConfig<T extends NimbusConfig>(config: T): T {\n return config;\n}\n\n/** Deterministic short hash of the sidebar structure (for sessionStorage invalidation). */\nexport { sidebarHash };\n\n/** Render an Astro content entry's raw MDX body as clean markdown. */\nexport { renderEntryAsMarkdown };\n\n/**\n * The canonical Shiki transformer chain — diff / highlight / focus /\n * error-level / word notations, meta highlight, plus the title-frame +\n * language-badge transformer. Pre-wired into the markdown pipeline for\n * fenced MDX blocks; re-export it so `Code.astro` can pass the same\n * list to Astro's built-in `<Code>` component (which accepts\n * `transformers` as a prop but doesn't auto-read `shikiConfig`).\n */\nexport { defaultCodeTransformers } from \"./_internal/code-transformers.js\";\n\n/**\n * Return visible entries across the user's configured `collections`.\n * Drafts are filtered in production builds. Pass an explicit\n * `collections` argument to scope the query to a subset.\n *\n * Replaces the old `getVisibleDocs()` — same draft-filtering semantics,\n * but now collection-aware. Returns `CollectionEntry<string>[]` so\n * cross-collection traversal doesn't need per-name type narrowing.\n */\nexport { getVisibleEntries };\n\n// ---------------------------------------------------------------------------\n// Agent-facing indexing\n// ---------------------------------------------------------------------------\n\nexport interface IndexedEntry {\n /** The Astro CollectionEntry, untyped at the union level. */\n entry: import(\"astro:content\").CollectionEntry<string>;\n /** Collection this entry belongs to (e.g. `\"docs\"`, `\"blog\"`). */\n collection: string;\n /** Display title — schema field if present, otherwise the entry id. */\n title: string;\n /** Description — undefined when the schema doesn't expose one or it's empty. */\n description: string | undefined;\n /**\n * Page URL path under the site (no origin). Browser-facing form:\n * trailing slash on HTML document routes (`/getting-started/`) so\n * `<a href>` consumers don't trigger a redirect on static hosts that\n * canonicalize directory-index pages. The primary docs collection\n * mounts at the site root, every other collection mounts under its\n * name (`/api/payments/create/`, `/blog/my-first-post/`). Routes\n * building `.md` alternates should consume `markdownUrl` rather than\n * deriving from this field — the root-index case (`/`) needs a\n * different shape than the trailing-slash-strip recipe produces.\n */\n url: string;\n /**\n * Site-relative URL of the page's clean-markdown alternate. Mirrors\n * the path the per-page `.md` route emits: `/getting-started/index.md`\n * for an entry at `/getting-started/`, `/index.md` for the root-index\n * entry. Consumers (`llms.txt`, the `.md` route's `Source:` line)\n * should use this directly instead of synthesizing from `url`.\n */\n markdownUrl: string;\n}\n\nexport interface IndexedTopLevelGroup {\n /** Top-level slug — first URL segment under root. */\n slug: string;\n /** Display label (today: identical to `slug`; reserved for future sidebar-label integration). */\n label: string;\n /** Entries inside this group, sorted alphabetically by url. */\n members: IndexedEntry[];\n /**\n * What kind of group this is:\n * - `\"primary\"` — a folder inside the primary `docs` collection.\n * - `\"secondary\"` — a separate non-version, non-primary collection\n * (`blog`, `api`, `changelog`, …).\n * - `\"version\"` — an older docs version (`docs-v1`, `docs-v2`, …)\n * when versioning is configured. Routes that build the **root**\n * `/llms.txt` should typically filter these out (old versions\n * pollute the entry point); routes that emit per-section files\n * should include them so `/v1/llms.txt` still ships.\n */\n kind: \"primary\" | \"secondary\" | \"version\";\n /**\n * True when this group is a version collection listed in\n * `versions.hidden`. Hidden versions are URL-reachable but should\n * not surface on any indexing or discovery surface — neither root\n * `/llms.txt` nor a per-section `/<slug>/llms.txt`. Pagefind\n * exclusion happens per-page via `data-pagefind-ignore`. Routes that\n * emit per-section files should skip groups where `hidden === true`.\n */\n hidden: boolean;\n}\n\nexport interface IndexedTopLevel {\n /**\n * Single-entry top-level items — the root `/llms.txt` links directly\n * to each leaf's `.md` alternate. Sorted alphabetically by `url`.\n */\n leaves: IndexedEntry[];\n /**\n * Multi-entry top-level items — each becomes a section file at\n * `/<slug>/llms.txt`. Sorted alphabetically by `slug`.\n */\n groups: IndexedTopLevelGroup[];\n}\n\n/**\n * Cross-collection entry list for the agent-facing routes\n * (`llms.txt`, per-page `.md` alternates, future `llms-full.txt` and\n * `rag.jsonl`). Implements the indexing baseline of the two-layer\n * architecture documented at `/features/llms-txt`:\n *\n * - **Multi-collection by default, zero opt-in.** Iterates every\n * collection registered in `src/content.config.ts` except names\n * matching `partials` or starting with `_` (reserved).\n * - **Schema-tolerant.** Reads `title` and `description` if present;\n * falls back to the entry id for the title and omits the\n * description otherwise.\n * - **Per-page filters baked in.** Drops entries with `draft: true`;\n * absent fields read as the docs-schema default (`draft: false`).\n * All published pages are indexed — there is no per-page opt-out.\n * A page that genuinely shouldn't be agent-readable should be kept\n * out of the content collection entirely.\n *\n * The returned shape is identical regardless of which factory created\n * the collection: hand-rolled `defineCollection({ loader, schema })`\n * collections work without modification.\n */\nexport async function getIndexedEntries(): Promise<IndexedEntry[]> {\n const { getCollection } = await import(\"astro:content\");\n const collectionNames = await loadIndexedCollections();\n // Fall back to the primary collection name if the build-time parse\n // came up empty. Belt-and-braces: the integration also defaults to\n // [\"docs\"] when content.config.ts is missing.\n const names = collectionNames.length > 0 ? collectionNames : [PRIMARY_COLLECTION];\n const versions = await getVersions();\n\n const indexed: IndexedEntry[] = [];\n for (const name of names) {\n let entries: import(\"astro:content\").CollectionEntry<string>[];\n try {\n entries = await getCollection(name as any);\n } catch {\n // Astro throws when the collection name isn't actually registered.\n // Swallow and move on — the parser may have picked up a name that\n // was added to content.config.ts but isn't yet wired through\n // Astro's content layer.\n continue;\n }\n const prefix = resolveCollectionPrefix(name, versions);\n for (const entry of entries) {\n const data = (entry.data ?? {}) as Record<string, unknown>;\n if (data.draft === true) continue;\n\n const title =\n typeof data.title === \"string\" && data.title.length > 0\n ? data.title\n : entry.id;\n const rawDescription = data.description;\n const description =\n typeof rawDescription === \"string\" && rawDescription.length > 0\n ? rawDescription\n : undefined;\n\n // `canonicalEntryUrl` applies the same slug normalization Astro's\n // content layer does — lowercase + folder-index strip — so the URL\n // matches what the site actually serves. See `_internal/astro-slug.ts`\n // for the why and the known caveats. `toBrowserHref` adds the trailing\n // slash that the directory-index page is served at, so `url` consumers\n // can emit the value straight into `<a href>` without a redirect.\n const canonicalUrl = canonicalEntryUrl(prefix, entry.id);\n // The `.md` alternate lives at `<page>/index.md`. For the root index\n // of a collection (canonical URL is the bare prefix or `/`), append\n // directly rather than re-derive from the trailing-slash form — the\n // strip-trailing-slash recipe collapses `/` to `\"\"` and produces the\n // wrong path.\n const markdownUrl =\n canonicalUrl === \"/\" ? \"/index.md\" : `${canonicalUrl}/index.md`;\n indexed.push({\n entry,\n collection: name,\n title,\n description,\n url: toBrowserHref(canonicalUrl),\n markdownUrl,\n });\n }\n }\n return indexed;\n}\n\n/**\n * Partition the indexed entries into the shape the root `/llms.txt`\n * and `/[section]/llms.txt` routes need.\n *\n * Convention:\n * - Primary `\"docs\"` entries follow the leaf/group rule based on\n * their `entry.id` top segment (matches single-collection behavior).\n * - Every other collection becomes a single top-level group named\n * after the collection, regardless of how many entries it has.\n * This matches the URL convention (`/api/...`, `/blog/...`).\n */\nexport async function getIndexedTopLevel(): Promise<IndexedTopLevel> {\n const items = await getIndexedEntries();\n const versions = await getVersions();\n\n // Build two buckets keyed by their URL-facing slug:\n // - primary: top-level slug under the `docs` collection\n // - secondary: every other collection (versions tagged separately\n // below so consumers can filter the root listing)\n const primaryBuckets = new Map<string, IndexedEntry[]>();\n const secondaryBuckets = new Map<string, IndexedEntry[]>();\n const versionSlugs = new Set<string>(versions?.others ?? []);\n const hiddenSlugs = new Set<string>(versions?.hidden ?? []);\n\n for (const item of items) {\n if (item.collection === PRIMARY_COLLECTION) {\n const top = item.entry.id.split(\"/\")[0]!;\n const bucket = primaryBuckets.get(top);\n if (bucket) bucket.push(item);\n else primaryBuckets.set(top, [item]);\n } else {\n // Bucket secondary collections by their URL-facing slug (version\n // slug for `docs-<v>` collections, raw collection ID otherwise) so\n // the emitted group label and URL prefix match the route shape.\n const slug = resolveCollectionSlug(item.collection, versions);\n const bucket = secondaryBuckets.get(slug);\n if (bucket) bucket.push(item);\n else secondaryBuckets.set(slug, [item]);\n }\n }\n\n const leaves: IndexedEntry[] = [];\n const groups: IndexedTopLevelGroup[] = [];\n\n for (const [slug, members] of primaryBuckets) {\n const isLeaf = members.length === 1 && members[0]!.entry.id === slug;\n if (isLeaf) {\n leaves.push(members[0]!);\n } else {\n groups.push({ slug, label: slug, members, kind: \"primary\", hidden: false });\n }\n }\n for (const [slug, members] of secondaryBuckets) {\n const kind: \"version\" | \"secondary\" = versionSlugs.has(slug)\n ? \"version\"\n : \"secondary\";\n groups.push({ slug, label: slug, members, kind, hidden: hiddenSlugs.has(slug) });\n }\n\n leaves.sort((a, b) => a.url.localeCompare(b.url));\n groups.sort((a, b) => a.slug.localeCompare(b.slug));\n for (const g of groups) {\n g.members.sort((a, b) => a.url.localeCompare(b.url));\n }\n\n return { leaves, groups };\n}\n\n// ---------------------------------------------------------------------------\n// Data helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Build the sidebar tree for the given current path, scoped to the\n * top-level section containing that page.\n *\n * Reads `sidebar` from the user's nimbus.config. If `sidebar.items` is set,\n * resolves config-driven sidebar. Otherwise auto-generates from filesystem\n * (i.e. the `docs` collection's entry IDs).\n *\n * Returned shape depends on `sidebar.scope` in `nimbus.config.ts`:\n * - `\"full\"` (default) — every top-level item on every page.\n * - `\"section\"` — only the current top-level section's children. Use\n * the header section-tab strip (via `getSidebarSections`) for\n * cross-section nav when this mode is on.\n *\n * **Versioning awareness.** When the page is in a version collection\n * (`docs-<v>` where `<v>` is in `versions.others`), pass `collection` as\n * the second argument. The sidebar build will swap any\n * `{ autogenerate: { collection: \"docs\" } }` items to autogenerate from\n * that version's collection instead, and treat it as the primary for\n * the build. Without this, version pages render the current-version\n * sidebar and prev/next derives from the wrong tree.\n *\n * @param currentSlug - The current page's URL path (e.g. \"/getting-started\").\n * Used to set `isCurrent` on matching links and to pick\n * which top-level section to surface when scoping.\n * @param options.collection - The current page's Astro collection ID.\n * Pass `entry.collection` from your route.\n */\nexport async function getSidebar(\n currentSlug: string,\n options?: { collection?: string },\n): Promise<SidebarItem[]> {\n const config = await loadNimbusConfig();\n const tree = await buildFullSidebarTree(currentSlug, options?.collection);\n return config.sidebar?.scope === \"section\"\n ? scopeToCurrentSection(tree, currentSlug)\n : tree;\n}\n\n/**\n * Derive one section per top-level group in the sidebar — used by\n * `Header.astro` to render the section tab strip (and by any other\n * cross-section navigation).\n *\n * Reads the un-scoped tree so every section is visible, then collapses\n * each top-level group to `{ label, href, isActive }`.\n *\n * Accepts the same `collection` option as `getSidebar` so version pages\n * see version-scoped section tabs.\n */\nexport async function getSidebarSections(\n currentSlug: string,\n options?: { collection?: string },\n): Promise<SidebarSection[]> {\n const tree = await buildFullSidebarTree(currentSlug, options?.collection);\n return deriveSidebarSections(tree);\n}\n\n/**\n * Internal: build the un-scoped sidebar tree. Shared by `getSidebar` and\n * `getSidebarSections`.\n *\n * When `pageCollection` names a registered version collection\n * (`docs-<v>` with `<v>` in `versions.others`), the sidebar treats that\n * collection as the primary for this build: autogen items referencing\n * `docs` are rewritten to reference the version collection, and the\n * `primary` argument to `buildSidebarTree` is set accordingly. The net\n * effect: version pages get the right sidebar tree and the right\n * prev/next ordering.\n */\nasync function buildFullSidebarTree(\n currentSlug: string,\n pageCollection?: string,\n): Promise<SidebarItem[]> {\n const runtimeConfig = await loadNimbusConfig();\n const versions = await getVersions();\n\n // Resolve the effective \"primary\" collection for THIS sidebar build.\n // For pages in a non-current version collection, the primary IS that\n // collection (the sidebar should walk docs-v0, not docs).\n let effectivePrimary = PRIMARY_COLLECTION;\n let primaryPrefix = \"\";\n if (\n versions &&\n pageCollection &&\n pageCollection.startsWith(\"docs-\") &&\n versions.others.includes(pageCollection.slice(\"docs-\".length))\n ) {\n effectivePrimary = pageCollection;\n primaryPrefix = resolveCollectionPrefix(pageCollection, versions);\n }\n\n // Rewrite sidebar items so `{ autogenerate: { collection: \"docs\" } }`\n // becomes `{ autogenerate: { collection: \"docs-v0\" } }` on v0 pages.\n // Items that name a different collection (api, blog) are untouched —\n // they keep their global scope.\n // Cast at the boundary: `runtimeConfig.sidebar?.items` is `unknown[] | undefined`\n // because runtimeConfig is loaded through a virtual module whose data is\n // already Zod-validated at integration setup (`validateNimbusConfig`).\n // The cast restores the shape downstream functions expect.\n const rewrittenItems = (\n effectivePrimary !== PRIMARY_COLLECTION\n ? rewriteSidebarItemsForVersion(\n runtimeConfig.sidebar?.items,\n effectivePrimary,\n )\n : runtimeConfig.sidebar?.items\n ) as Parameters<typeof collectSidebarCollectionRefs>[0];\n\n const referenced = collectSidebarCollectionRefs(rewrittenItems);\n const collections = [\n effectivePrimary,\n ...referenced.filter((c) => c !== effectivePrimary),\n ];\n const entriesByCollection = await getVisibleEntriesByCollection(collections);\n return buildSidebarTree(\n // Cast: `astro:content` `CollectionEntry<string>` has `data: Record<string, unknown>`\n // in our stub; sidebar.ts's local `CollectionEntry` shapes `data` with `title`\n // required. Runtime entries always carry `title` (schema-enforced); the cast\n // documents that guarantee. `unknown` bridge is required because the two\n // CollectionEntry shapes don't structurally overlap on the `data` field.\n entriesByCollection as unknown as Parameters<typeof buildSidebarTree>[0],\n effectivePrimary,\n currentSlug,\n runtimeConfig.sidebar\n ? { ...runtimeConfig.sidebar, items: rewrittenItems }\n : undefined,\n primaryPrefix,\n );\n}\n\n/**\n * Substitute the primary collection (`docs`) for `effectivePrimary`\n * inside any sidebar item that autogenerates from a named collection.\n * Used by `buildFullSidebarTree` to make version pages render their\n * own collection's sidebar instead of the current version's.\n */\nfunction rewriteSidebarItemsForVersion(\n items: unknown[] | undefined,\n effectivePrimary: string,\n): unknown[] | undefined {\n if (!items) return items;\n return items.map((item) => {\n if (!item || typeof item !== \"object\") return item;\n const o = item as Record<string, unknown>;\n const autogen = o.autogenerate as { collection?: string; directory?: string } | undefined;\n if (autogen && autogen.collection === PRIMARY_COLLECTION) {\n return { ...o, autogenerate: { ...autogen, collection: effectivePrimary } };\n }\n // Nested groups recurse so per-group autogen items rewrite too.\n if (Array.isArray(o.items)) {\n return { ...o, items: rewriteSidebarItemsForVersion(o.items, effectivePrimary) };\n }\n return item;\n });\n}\n\n/**\n * Resolve prev/next links for the current page.\n *\n * Walks the flattened sidebar; returns the surrounding entries. Honors\n * `prev`/`next` frontmatter overrides if provided.\n *\n * When an override uses the object form with an internal `link`\n * (e.g. `prev: { link: \"/getting-started\" }`), the link is validated\n * against every visible content entry's URL at build time. A pointer\n * to a missing page fails the build with a clear error — the same\n * staleness-detection mechanism used for `previousSlug` in versioning.\n * The string form (`prev: \"Custom label\"`) is a label-only override\n * and doesn't go through link validation.\n */\nexport async function getPrevNext(\n currentSlug: string,\n options?: {\n overrides?: PrevNextOverrides;\n sidebarTree?: SidebarItem[];\n },\n): Promise<PrevNext> {\n const tree = options?.sidebarTree ?? (await getSidebar(currentSlug));\n // Build the set of valid internal route keys (slashless) from indexed\n // entries so object-form `prev: { link: \"/x\" }` overrides fail loudly\n // when the target doesn't exist. The set holds route keys, not browser\n // hrefs, so a `/cli`, `/cli/`, or `/cli/?ref=x` override all resolve\n // to the same canonical entry. Cheap: indexed entries are cached per\n // build.\n const indexed = await getIndexedEntries();\n const validInternalLinks = new Set(indexed.map((e) => toRouteKey(e.url)));\n return buildPrevNext(currentSlug, tree, options?.overrides, validInternalLinks);\n}\n\n/**\n * Build breadcrumb trail from \"/\" to the current page.\n *\n * Phase 5: simple URL-segment derivation. Later phases may enrich with\n * sidebar-aware labels.\n */\nexport async function getBreadcrumbs(\n currentSlug: string,\n options?: { homeLabel?: string },\n): Promise<Breadcrumb[]> {\n return buildBreadcrumbs(currentSlug, options?.homeLabel ?? \"Home\");\n}\n\n/**\n * Build an edit URL for a content entry using `config.editPattern`.\n *\n * `{path}` is replaced with the entry's source path when Astro provides it,\n * falling back to the default docs collection path convention.\n */\nexport async function getEditUrl(entry: {\n id: string;\n filePath?: string;\n}): Promise<string | undefined> {\n const runtimeConfig = await loadNimbusConfig();\n if (!runtimeConfig.editPattern) return undefined;\n\n const path = entry.filePath ?? `src/content/docs/${entry.id}.mdx`;\n return runtimeConfig.editPattern.replace(\"{path}\", path);\n}\n\n/**\n * Resolve a content entry's `lastUpdated` date from `git log`.\n *\n * Reads the author date (`%aI`) of the most recent commit that touched\n * the entry's source file. Author date is stable across rebases — the\n * value reflects when the content was actually changed, not when the\n * commit happened to land in this branch.\n *\n * Returns `undefined` when git can't answer (no `.git`, shallow clone,\n * file untracked, command not on PATH, etc.) so the caller can chain a\n * fallback:\n *\n * const lastUpdated = entry.data.lastUpdated ?? await getLastUpdated(entry);\n *\n * Frontmatter always wins. Per-process cached so repeated calls for\n * the same entry don't re-spawn `git`.\n *\n * Production note: most CI/CD systems do shallow clones by default\n * (Vercel, Cloudflare Pages, GitHub Actions checkout@v4) — set\n * `fetch-depth: 0` to make full history available, otherwise git\n * returns nothing and the helper falls back to frontmatter or nothing.\n */\nexport async function getLastUpdated(entry: {\n id: string;\n filePath?: string;\n}): Promise<Date | undefined> {\n const path = entry.filePath ?? `src/content/docs/${entry.id}.mdx`;\n return getLastUpdatedFromGit(path);\n}\n\n/**\n * Filter heading list to the configured min/max heading levels.\n *\n * @param headings - Raw `headings` from Astro's `render(entry)` return value.\n * @param options - Override min/max heading levels. Defaults: min=2, max=3.\n */\nexport function getTOC(\n headings: { depth: number; text: string; slug: string }[],\n options?: { minHeadingLevel?: number; maxHeadingLevel?: number },\n): TOCItem[] {\n return getHeadings(headings, options);\n}\n\n// ---------------------------------------------------------------------------\n// Page composition helpers\n// ---------------------------------------------------------------------------\n\nimport type { AstroGlobal, GetStaticPaths } from \"astro\";\n\n/**\n * `getStaticPaths` implementation for a docs catch-all route.\n *\n * Returns one path per visible entry in the `docs` collection. Drafts are\n * filtered in production. Each path passes `{ entry }` as props so the\n * page component can access it via `getDocsPageProps(Astro)`.\n *\n * Usage:\n *\n * // src/pages/[...slug].astro\n * export const prerender = true;\n * export const getStaticPaths = getDocsStaticPaths;\n *\n * The entry's `id` is used verbatim as the slug. So `docs/index.mdx` →\n * `/index`, `docs/guides/setup.mdx` → `/guides/setup`. If you want a docs\n * entry at the root URL, name it appropriately and decide whether to use\n * a static `pages/index.astro` or let the catch-all handle root.\n */\nexport const getDocsStaticPaths: GetStaticPaths = async () => {\n // Docs-specific helper: always reads the `docs` collection. Other\n // collections require their own `pages/<name>/[...slug].astro` with\n // a one-line `getCollection(\"<name>\")`-based getStaticPaths.\n const entries = await getVisibleEntries([\"docs\"]);\n return entries.map((entry) => ({\n params: { slug: entry.id },\n props: { entry },\n }));\n};\n\n/**\n * Read the current entry from `Astro.props`, render it, and return the\n * pieces a docs page needs: the typed entry, the renderable `<Content />`\n * component, and the headings list (for TOC generation).\n *\n * Pass the page's `Astro` global. Throws if `Astro.props.entry` is missing,\n * which indicates the page didn't wire `getDocsStaticPaths` (or a custom\n * equivalent) correctly.\n *\n * Usage:\n *\n * const { entry, Content, headings } = await getDocsPageProps(Astro);\n */\nexport async function getDocsPageProps(astro: AstroGlobal): Promise<{\n entry: import(\"astro:content\").CollectionEntry<\"docs\">;\n Content: unknown;\n headings: { depth: number; text: string; slug: string }[];\n}> {\n const entry = (astro.props as { entry?: import(\"astro:content\").CollectionEntry<\"docs\"> })\n .entry;\n if (!entry) {\n throw new Error(\n \"getDocsPageProps(): expected `entry` in Astro.props. \" +\n \"Ensure your route uses `getStaticPaths = getDocsStaticPaths` \" +\n \"(or passes an entry via custom getStaticPaths).\",\n );\n }\n const { render } = await import(\"astro:content\");\n const { Content, headings } = await render(entry);\n return { entry, Content, headings };\n}\n\n/**\n * `getStaticPaths` implementation for a catch-all route over a non-primary\n * collection (`api`, `blog`, …). Companion to `getDocsStaticPaths`.\n *\n * Returns one path per visible entry in the named collection. Drafts are\n * filtered in production (same rule as `getDocsStaticPaths`). Each path\n * passes `{ entry }` as props for `getCollectionPageProps()`.\n *\n * Usage:\n *\n * // src/pages/api/[...slug].astro\n * export const prerender = true;\n * export const getStaticPaths = getCollectionStaticPaths(\"api\");\n *\n * Why a sibling helper instead of an option on `getDocsStaticPaths`: the\n * `Docs` name carries the \"primary collection mounted at root\" semantic.\n * Non-primary collections mount under their own URL namespace\n * (`/<collection>/...`) by convention; the helper name reflects that.\n */\nexport function getCollectionStaticPaths(collection: string): GetStaticPaths {\n return async () => {\n const entries = await getVisibleEntries([collection]);\n return entries.map((entry) => ({\n params: { slug: entry.id },\n props: { entry },\n }));\n };\n}\n\n/**\n * Read the current entry from `Astro.props`, render it, and return the\n * pieces a docs-style page needs — typed for an arbitrary collection.\n *\n * Companion to `getCollectionStaticPaths`. Use this in routes mounted at\n * non-primary collections (`api`, `blog`, …) instead of `getDocsPageProps`,\n * which is typed to the `docs` collection.\n *\n * Pass the collection name as a type parameter for the entry's data\n * shape to narrow correctly:\n *\n * const { entry, Content, headings } = await getCollectionPageProps<\"api\">(Astro);\n */\nexport async function getCollectionPageProps<C extends string>(\n astro: AstroGlobal,\n): Promise<{\n entry: import(\"astro:content\").CollectionEntry<C>;\n Content: unknown;\n headings: { depth: number; text: string; slug: string }[];\n}> {\n const entry = (astro.props as { entry?: import(\"astro:content\").CollectionEntry<C> })\n .entry;\n if (!entry) {\n throw new Error(\n \"getCollectionPageProps(): expected `entry` in Astro.props. \" +\n \"Ensure your route uses `getStaticPaths = getCollectionStaticPaths(<collection>)`.\",\n );\n }\n const { render } = await import(\"astro:content\");\n const { Content, headings } = await render(entry);\n return { entry, Content, headings };\n}\n\n// ---------------------------------------------------------------------------\n// Versioning (P0 — data layer only)\n// ---------------------------------------------------------------------------\n\n/**\n * Return the resolved versioning manifest for the current site, or `null`\n * if the site is unversioned (`nimbus.config.ts` has no `versions` block).\n *\n * Optional fields are normalised to empty arrays (`deprecated`, `hidden`)\n * and `all` is `[current, ...others]` in manifest order — convenient for\n * picker enumeration or anywhere you need every known version slug.\n *\n * Usage:\n *\n * const versions = await getVersions();\n * if (versions) {\n * for (const slug of versions.all) {\n * // …enumerate\n * }\n * }\n *\n * Reads from `virtual:nimbus/config`, so the cost is one cached dynamic\n * import per build.\n */\nexport async function getVersions(): Promise<ResolvedVersions | null> {\n const config = await loadNimbusConfig();\n const v = config.versions;\n if (!v) return null;\n const others = v.others ?? [];\n return {\n current: v.current,\n others,\n deprecated: v.deprecated ?? [],\n hidden: v.hidden ?? [],\n all: [v.current, ...others],\n };\n}\n\n/**\n * Return the version slug a given Astro content collection ID belongs to,\n * or `null` if the collection is not a version of the primary docs.\n *\n * Rules:\n * - `\"docs\"` → `versions.current` (the current version's label).\n * - `\"docs-<slug>\"` where `<slug>` appears in `versions.current` or\n * `versions.others` → `<slug>`.\n * - Anything else (e.g. `\"blog\"`, `\"api\"`, `\"docs-archive\"` when\n * `archive` isn't in the manifest) → `null`.\n *\n * Returns `null` whenever the site has no `versions` config at all,\n * regardless of collection ID.\n *\n * Usage in a route:\n *\n * const { entry } = Astro.props;\n * const version = await getCurrentVersion(entry.collection);\n * // version === \"v3\" for entries in `docs`, \"v2\" for entries in `docs-v2`, …\n */\nexport async function getCurrentVersion(\n collectionId: string,\n): Promise<string | null> {\n const versions = await getVersions();\n if (!versions) return null;\n if (collectionId === PRIMARY_COLLECTION) return versions.current;\n if (!collectionId.startsWith(\"docs-\")) return null;\n const suffix = collectionId.slice(\"docs-\".length);\n return versions.all.includes(suffix) ? suffix : null;\n}\n\n/**\n * Look up the cross-version alternates for a given Astro entry.\n *\n * Returns `null` when the entry is not part of a versioning manifest\n * (unversioned site, non-`docs` collection like `blog`/`api`, or the\n * lookup misses for any other reason). Otherwise returns a record with:\n *\n * - `self`: the entry being looked up, expressed as a `VersionPageRef`.\n * - `alternates`: every other version's sibling page for the same\n * logical content (same slug or linked via `previousSlug`). Sorted\n * in manifest version order.\n * - `canonical`: the current-version sibling when one exists and\n * isn't `self`. `null` when `self` is already the current version\n * or no current-version sibling exists.\n *\n * Routes inject `<link rel=\"alternate\">` for every entry in\n * `alternates`, and `<link rel=\"canonical\">` pointing at `canonical.url`\n * when canonical is non-null.\n *\n * Usage in a route:\n *\n * const { entry } = Astro.props;\n * const alts = await getVersionAlternates(entry.collection, entry.id);\n *\n * {alts?.alternates.map((a) => (\n * <link rel=\"alternate\" data-version={a.version} href={a.url} />\n * ))}\n * {alts?.canonical && <link rel=\"canonical\" href={alts.canonical.url} />}\n */\nexport async function getVersionAlternates(\n collectionId: string,\n entryId: string,\n): Promise<VersionAlternateRecord | null> {\n const table = await loadVersionAlternates();\n const key = `${collectionId}:${entryId}`;\n return table[key] ?? null;\n}\n\n/**\n * Convenience wrapper: returns just the canonical URL for an entry, or\n * `null` when none applies. Equivalent to\n * `(await getVersionAlternates(c, e))?.canonical?.url ?? null` — handy\n * when a route only needs the canonical and not the full alternates list.\n */\nexport async function getCanonicalUrl(\n collectionId: string,\n entryId: string,\n): Promise<string | null> {\n const record = await getVersionAlternates(collectionId, entryId);\n return record?.canonical?.url ?? null;\n}\n\n/**\n * Return the agent index URL path (the `/llms.txt` route) that\n * corresponds to a given Astro collection. The path is mount-point\n * aware: pages in version collections point at the per-version index,\n * pages in non-primary collections point at their per-collection index,\n * and the primary `docs` collection points at the root.\n *\n * - `\"docs\"` → `\"/llms.txt\"`\n * - `\"docs-v1\"` → `\"/v1/llms.txt\"` (when `v1` is in `versions.others`)\n * - `\"blog\"` → `\"/blog/llms.txt\"`\n * - `\"api\"` → `\"/api/llms.txt\"`\n * - `\"docs-archive\"` (unrecognised version slug) → `\"/docs-archive/llms.txt\"`\n *\n * Returns a path with a leading slash and no trailing slash. Routes\n * resolve it against `Astro.site` to produce a full URL.\n *\n * Used by `BaseLayout` and `AgentDirective` to surface the correct\n * agent index hint on every page — readers landing on `/v1/foo` get\n * pointed at `/v1/llms.txt`, not `/llms.txt`, so agents don't crawl\n * the wrong section.\n */\nexport async function getCollectionLlmsUrl(\n collectionId: string,\n): Promise<string> {\n if (collectionId === PRIMARY_COLLECTION) return \"/llms.txt\";\n const versions = await getVersions();\n if (versions && collectionId.startsWith(\"docs-\")) {\n const slug = collectionId.slice(\"docs-\".length);\n if (versions.others.includes(slug)) {\n // Hidden versions do NOT emit a per-section /<v>/llms.txt — the\n // [section] route filters them out. Pointing readers at a 404\n // breaks the agent-discovery contract. Fall back to the root\n // index for hidden version pages instead.\n if (versions.hidden.includes(slug)) return \"/llms.txt\";\n return `/${slug}/llms.txt`;\n }\n }\n return `/${collectionId}/llms.txt`;\n}\n\n/**\n * Look up the versioning status for a page's collection — what the\n * layout needs to decide whether to render the deprecation banner,\n * apply the Pagefind facet filters, or exclude the page from search\n * entirely.\n *\n * Returns `null` when the site is unversioned or the page is not part\n * of a version collection (regular `docs`, `blog`, `api`, …). Layouts\n * treat that as \"no versioning UI to apply\" — render normally.\n *\n * Usage:\n *\n * const status = await getVersionStatus(entry.collection);\n * if (status?.isDeprecated) {\n * // render the deprecation banner\n * }\n */\n/**\n * Resolve a URL that's guaranteed to exist within a given version's\n * collection. Used by the picker (and any other \"jump to that version\"\n * surface) to avoid landing readers on a 404 when the current page has\n * no same-logical-page sibling in the target version.\n *\n * Resolution order:\n * 1. If `docs-<v>/index` exists, return its URL (the conventional\n * \"version landing page\").\n * 2. If `docs-<v>/overview` exists, return its URL (common alternate\n * name for a landing page).\n * 3. Otherwise return the first indexed entry's URL in that version,\n * sorted by URL — matches `getIndexedTopLevel()`'s sort so the\n * choice is deterministic across builds.\n * 4. If the version has no indexed entries at all, return `null`.\n * Callers should treat that as \"this version has nothing to link\n * to\" and either omit the picker entry or fall back to the\n * version's URL prefix root (which may still 404, but that's the\n * authoring problem to fix, not the picker's).\n *\n * `version` is the manifest slug (e.g. `\"v0\"`), NOT the collection ID\n * (`\"docs-v0\"`). For the current version, returns `\"/\"` when at least\n * one current-version entry exists, else `null`.\n *\n * Reads from `getIndexedEntries()`, so the cost is one cached lookup\n * per build (the indexed list is computed once per page render).\n */\nexport async function getVersionLandingUrl(\n version: string,\n): Promise<string | null> {\n const versions = await getVersions();\n if (!versions) return null;\n if (!versions.all.includes(version)) return null;\n\n const targetCollection =\n version === versions.current ? PRIMARY_COLLECTION : `docs-${version}`;\n const items = await getIndexedEntries();\n const inVersion = items.filter((i) => i.collection === targetCollection);\n if (inVersion.length === 0) return null;\n\n const byId = new Map(inVersion.map((i) => [i.entry.id, i]));\n // Prefer index / overview by convention.\n const preferred = byId.get(\"index\") ?? byId.get(\"overview\");\n // `IndexedEntry.url` is already the trailing-slash browser-href form\n // the version picker renders, so no extra normalization here.\n if (preferred) return preferred.url;\n // Else first by URL (sort is alphabetical → deterministic).\n inVersion.sort((a, b) => a.url.localeCompare(b.url));\n return inVersion[0]!.url;\n}\n\nexport async function getVersionStatus(\n collectionId: string,\n): Promise<VersionStatus | null> {\n const versions = await getVersions();\n if (!versions) return null;\n const version = await getCurrentVersion(collectionId);\n if (version === null) return null;\n return {\n version,\n isCurrent: version === versions.current,\n isDeprecated: versions.deprecated.includes(version),\n isHidden: versions.hidden.includes(version),\n };\n}\n"],"x_google_ignoreList":[2,3],"mappings":"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"}