nimbus-docs 0.1.4 → 0.1.5

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","getBreadcrumbs","getPrevNext","splitTopLevelCommas","PRIMARY_COLLECTION","PRIMARY_COLLECTION","buildPrevNext","buildBreadcrumbs"],"sources":["../src/_internal/runtime-config.ts","../src/_internal/content.ts","../src/_internal/sidebar.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/_internal/parse-content-collections.ts","../src/_internal/code-transformers.ts","../src/_internal/levenshtein.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\ndeclare module \"virtual:nimbus/config\" {\n export const config: NimbusConfig;\n export const indexedCollections: readonly string[];\n export const versionAlternates: VersionAlternatesTable;\n}\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 _cached = mod.config;\n return _cached;\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 _cachedCollections = mod.indexedCollections;\n return _cachedCollections;\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 _cachedAlternates = mod.versionAlternates ?? {};\n return _cachedAlternates;\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","// ---------------------------------------------------------------------------\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\";\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// Link normalization\n// ---------------------------------------------------------------------------\n\n/** Ensure internal href has leading /, no trailing slash (except root) */\nfunction normalizeInternalHref(href: string): string {\n let h = href.split(\"?\")[0].split(\"#\")[0];\n if (!h.startsWith(\"/\")) h = `/${h}`;\n if (h.length > 1 && h.endsWith(\"/\")) h = h.slice(0, -1);\n return h;\n}\n\n/** Strip query and hash for active-state matching */\nfunction stripQueryHash(href: string): string {\n return href.split(\"?\")[0].split(\"#\")[0];\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/** Compose a final href for an entry. `hrefPrefix` is the collection mount path (e.g. `/api`). */\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 `${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: currentPath === 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 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 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 const isExternal = !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\n const href = normalizeInternalHref(item.link);\n const matchPath = stripQueryHash(href);\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 = href.slice(1);\n const looksLikePrimaryRoot = !lookup.includes(\"/\");\n if (looksLikePrimaryRoot && href !== \"/\" && !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: currentPath === matchPath,\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.\n href: 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 (by matching href to the index ID)\n const indexHref = `/${item._indexId}`;\n const indexLink = item.children.find((c): c is SidebarLinkItem => c.type === \"link\" && c.href === indexHref);\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 * 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\";\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: path,\n });\n }\n\n return crumbs;\n}\n\ntype PrevNextOverride = { link?: string; label?: string };\n\nfunction normalizeInternalPath(path: string): string {\n const [withoutHash] = path.split(\"#\", 1);\n const [pathname] = withoutHash.split(\"?\", 1);\n if (!pathname) return \"/\";\n // Strip trailing slash so `/getting-started/` matches the indexed\n // URL `/getting-started`. Keep \"/\" as-is — that's the root, not a\n // trailing-slash artifact.\n if (pathname.length > 1 && pathname.endsWith(\"/\")) {\n return pathname.slice(0, -1);\n }\n return pathname;\n}\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 const targetPath = normalizeInternalPath(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 const href = 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 const index = flat.findIndex((item) => item.href === currentPath);\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 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 * 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 * 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\nimport type { ShikiTransformer } from \"shiki\";\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 * Tiny Levenshtein distance + \"did you mean\" suggester.\n *\n * Used by the MDX PascalCase validator and any framework diagnostic that\n * wants to suggest a near-match on a misspelled name. Kept internal — user\n * code that wants the same hint duplicates ~10 lines rather than depending\n * on a framework wrapper. See the north-star guardrail on thin wrappers.\n */\n\nexport function levenshtein(a: string, b: string): number {\n if (a === b) return 0;\n if (a.length === 0) return b.length;\n if (b.length === 0) return a.length;\n const v0 = new Array<number>(b.length + 1);\n const v1 = new Array<number>(b.length + 1);\n for (let i = 0; i <= b.length; i++) v0[i] = i;\n for (let i = 0; i < a.length; i++) {\n v1[0] = i + 1;\n for (let j = 0; j < b.length; j++) {\n const cost = a[i] === b[j] ? 0 : 1;\n v1[j + 1] = Math.min(v1[j] + 1, v0[j + 1] + 1, v0[j] + cost);\n }\n for (let j = 0; j <= b.length; j++) v0[j] = v1[j];\n }\n return v1[b.length];\n}\n\n/**\n * Return the closest candidate within `maxDist`, or null.\n *\n * Comparison is case-insensitive (so \"tabs\" suggests \"Tabs\"), but the\n * returned name keeps its original casing.\n */\nexport function suggest(\n target: string,\n candidates: Iterable<string>,\n maxDist = 3,\n): string | null {\n const targetLower = target.toLowerCase();\n let best: { name: string; dist: number } | null = null;\n for (const c of candidates) {\n const dist = levenshtein(targetLower, c.toLowerCase());\n if (dist <= maxDist && (!best || dist < best.dist)) {\n best = { name: c, dist };\n }\n }\n return best?.name ?? null;\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 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 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 return result.data 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\";\n\nconst PRIMARY_COLLECTION = \"docs\";\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 /** Resolved URL path with leading slash, no trailing slash. */\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, leading slash, no\n * trailing slash. Astro applies trailing-slash normalisation on output.\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 const record = table[refKey({ collection: entry.collection, version, slug: entry.id, url: currentUrl })];\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 */\nfunction pageUrl(versions: ResolvedVersions, version: string, slug: string): string {\n if (version === versions.current) return `/${slug}`;\n return `/${version}/${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 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 filterIndexableCollections,\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\nexport function nimbus(\n rawConfig: NimbusConfig,\n options: NimbusIntegrationOptions = {},\n): AstroIntegration {\n const config = validateNimbusConfig(rawConfig);\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 // 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 const contentConfigPath = path.join(projectRoot, \"src/content.config.ts\");\n const rawCollections = await parseContentCollections(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 // 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 }) => {\n if (config.search === false || config.search?.provider === \"custom\") {\n return;\n }\n\n await runPagefind(fileURLToPath(dir));\n },\n },\n };\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 { renderEntryAsMarkdown } from \"./_internal/transform.js\";\n\n/** Primary collection name — kept in sync with `_internal/content.ts`. */\nconst PRIMARY_COLLECTION = \"docs\";\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, no trailing slash unless\n * empty). Computed via the convention that the **primary** docs\n * collection mounts at the site root (e.g. `/getting-started`) while\n * every other collection mounts under its name (`/api/payments/create`,\n * `/blog/my-first-post`). Routes building `.md` alternates append\n * `/index.md`; chrome building HTML links append a trailing slash.\n */\n url: 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 */\n/**\n * Resolve the URL-prefix segment for a given collection ID.\n *\n * Rules, in order:\n * 1. Primary `docs` collection mounts at root → returns `\"\"`.\n * 2. When `versions` is configured, a `docs-<slug>` collection whose\n * slug appears in `versions.others` mounts under `/<slug>/` (not\n * `/docs-<slug>/`). This is the versioning URL convention — a\n * version is reached by its short label, not its collection ID.\n * 3. Any other collection (`api`, `blog`, …) mounts at `/<collection>/`\n * — the default multi-collection convention.\n *\n * Returned shape: empty string OR `/<segment>` with leading slash, no\n * trailing slash. Callers append `/<entryId>` or `/index.md`.\n */\nfunction resolveCollectionPrefix(\n versions: ResolvedVersions | null,\n collection: string,\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 slug a collection should be referenced by — the\n * label that appears in URLs and section headers. For version\n * collections this is the manifest's short slug; for everything else\n * it's the collection ID.\n */\nfunction resolveCollectionSlug(\n versions: ResolvedVersions | null,\n collection: string,\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\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(versions, name);\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 indexed.push({\n entry,\n collection: name,\n title,\n description,\n url: `${prefix}/${entry.id}`,\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(versions, item.collection);\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(versions, pageCollection);\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 const rewrittenItems =\n effectivePrimary !== PRIMARY_COLLECTION\n ? rewriteSidebarItemsForVersion(\n runtimeConfig.sidebar?.items,\n effectivePrimary,\n )\n : runtimeConfig.sidebar?.items;\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 entriesByCollection,\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 hrefs from indexed entries so that\n // object-form `prev: { link: \"/x\" }` overrides fail loudly when the\n // target doesn't exist. Cheap: indexed entries are cached per build.\n const indexed = await getIndexedEntries();\n const validInternalLinks = new Set(indexed.map((e) => 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 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"],"mappings":"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"}
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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"}