nimbus-docs 0.0.2 → 0.1.0

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # nimbus-docs
2
2
 
3
- Astro integration, content schemas, data helpers (`getSidebar`, `getPrevNext`, `getTOC`, `getBreadcrumbs`, `getEditUrl`), the MDX→markdown primitive (`renderEntryAsMarkdown`), client-side primitives (`nimbus-docs/client`), package-manager command translator (`nimbus-docs/lib/pkgm`), and the `nimbus` CLI for installing components, utilities, and features into your project.
3
+ Astro integration, content schemas, data helpers (`getSidebar`, `getPrevNext`, `getTOC`, `getBreadcrumbs`, `getEditUrl`), the MDX→markdown primitive (`renderEntryAsMarkdown`), client-side primitives (`nimbus-docs/client`), package-manager command translator (`nimbus-docs/lib/pkgm`), and the `nimbus-docs` CLI for installing components, utilities, and features into your project.
4
4
 
5
5
  Most users install via the scaffolder:
6
6
 
package/dist/cli/index.js CHANGED
@@ -131,6 +131,12 @@ const BUNDLED_INDEX = {
131
131
  "title": "PackageManagers",
132
132
  "description": "Tabbed install command block translated across npm / pnpm / yarn / bun."
133
133
  },
134
+ "page-actions": {
135
+ "name": "page-actions",
136
+ "type": "registry:ui",
137
+ "title": "PageActions",
138
+ "description": "Inline page-header actions: copy the page as markdown, open the raw .md."
139
+ },
134
140
  "pagination": {
135
141
  "name": "pagination",
136
142
  "type": "registry:ui",
@@ -285,7 +291,12 @@ async function installComponents(items, options) {
285
291
  };
286
292
  });
287
293
  const conflicts = filePlans.filter((f) => f.exists);
288
- if (conflicts.length > 0 && !options.yes) {
294
+ if (item.type === "registry:lib") {
295
+ if (conflicts.length > 0) {
296
+ report.skipped.push(item.name);
297
+ continue;
298
+ }
299
+ } else if (conflicts.length > 0 && !options.yes) {
289
300
  const total = filePlans.length;
290
301
  const message = conflicts.length === total ? `${item.name} is already installed (${total} file${total === 1 ? "" : "s"}). Overwrite?` : `${item.name} is partially installed (${conflicts.length} of ${total} file${total === 1 ? "" : "s"} present). Overwrite all?`;
291
302
  const choice = await p.select({
@@ -451,9 +462,9 @@ async function httpGet(url) {
451
462
  res = await fetch(url);
452
463
  } catch (err) {
453
464
  const cause = err.message;
454
- throw new Error(`Could not reach the registry at ${url}.\n Underlying error: ${cause}\n\n Things to try:\n - Is the registry server running? Start it with \`pnpm local\` (in the monorepo root).\n - Override the URL: NIMBUS_REGISTRY_URL=https://example.com nimbus add ...\n - Check the value in your project's .env file.`);
465
+ throw new Error(`Could not reach the registry at ${url}.\n Underlying error: ${cause}\n\n Things to try:\n - Is the registry server running? Start it with \`pnpm local\` (in the monorepo root).\n - Override the URL: NIMBUS_REGISTRY_URL=https://example.com nimbus-docs add ...\n - Check the value in your project's .env file.`);
455
466
  }
456
- if (!res.ok) throw new Error(`Registry returned ${res.status} ${res.statusText} for ${url}. The server is up but doesn't know about this slug — check \`nimbus list\` for valid names.`);
467
+ if (!res.ok) throw new Error(`Registry returned ${res.status} ${res.statusText} for ${url}. The server is up but doesn't know about this slug — check \`nimbus-docs list\` for valid names.`);
457
468
  return res;
458
469
  }
459
470
  async function fetchComponent(slug) {
@@ -512,7 +523,7 @@ async function installFeature(slug, options) {
512
523
  * and the rest (opencode/pi).
513
524
  */
514
525
  function printHumanInstructions(slug) {
515
- const cmd = `nimbus add ${slug}`;
526
+ const cmd = `nimbus-docs add ${slug}`;
516
527
  const stream = process.stderr;
517
528
  stream.write(`${cmd}\n\n`);
518
529
  stream.write("To install this feature, pipe it to your coding agent:\n\n");
@@ -528,17 +539,17 @@ function printHumanInstructions(slug) {
528
539
  //#endregion
529
540
  //#region src/cli/index.ts
530
541
  /**
531
- * `nimbus` CLI entry.
542
+ * `nimbus-docs` CLI entry.
532
543
  *
533
544
  * Surface:
534
545
  *
535
546
  * nimbus → list (table of installable items)
536
- * nimbus list → list
537
- * nimbus list --type ui|lib|feature
538
- * nimbus add → list
539
- * nimbus add <slug> → install (component path or feature path)
540
- * nimbus add <slug> --yes → component: skip overwrite prompts
541
- * nimbus add <slug> --print → feature: print markdown to stdout (skip detect)
547
+ * nimbus-docs list → list
548
+ * nimbus-docs list --type ui|lib|feature
549
+ * nimbus-docs add → list
550
+ * nimbus-docs add <slug> → install (component path or feature path)
551
+ * nimbus-docs add <slug> --yes → component: skip overwrite prompts
552
+ * nimbus-docs add <slug> --print → feature: print markdown to stdout (skip detect)
542
553
  *
543
554
  * Feature behavior mirrors Flue's `add` command: print markdown to stdout
544
555
  * iff `--print` OR an agent is detected; otherwise print human-friendly
@@ -550,7 +561,7 @@ function printHumanInstructions(slug) {
550
561
  */
551
562
  loadDotenv(process.cwd());
552
563
  const HELP = `
553
- Usage: nimbus <command> [args]
564
+ Usage: nimbus-docs <command> [args]
554
565
 
555
566
  Commands:
556
567
  list [--type ui|lib|feature] List available registry items
@@ -565,11 +576,11 @@ const HELP = `
565
576
  --version, -v
566
577
 
567
578
  Examples:
568
- nimbus add dialog # component: resolve + install
569
- nimbus add 404-page # feature: detect agent or print
579
+ nimbus-docs add dialog # component: resolve + install
580
+ nimbus-docs add 404-page # feature: detect agent or print
570
581
  # pipe instructions for humans
571
- nimbus add 404-page --print | claude # explicit pipe to claude
572
- nimbus add 404-page --print | codex # …or any other agent
582
+ nimbus-docs add 404-page --print | claude # explicit pipe to claude
583
+ nimbus-docs add 404-page --print | codex # …or any other agent
573
584
  `;
574
585
  async function main() {
575
586
  const args = mri(process.argv.slice(2), {
@@ -591,7 +602,7 @@ async function main() {
591
602
  return;
592
603
  }
593
604
  if (args.version) {
594
- process.stdout.write(`0.0.2\n`);
605
+ process.stdout.write(`0.1.0\n`);
595
606
  return;
596
607
  }
597
608
  const [command, slug] = args._;
@@ -606,7 +617,7 @@ async function main() {
606
617
  });
607
618
  return;
608
619
  }
609
- p.log.error(`Unknown command: \`${command}\`. Try \`nimbus --help\`.`);
620
+ p.log.error(`Unknown command: \`${command}\`. Try \`nimbus-docs --help\`.`);
610
621
  process.exit(1);
611
622
  }
612
623
  function listCommand(typeFilter) {
@@ -645,19 +656,19 @@ function listCommand(typeFilter) {
645
656
  for (const item of group) process.stdout.write(` ${item.name.padEnd(widths + 2)}${item.description}\n`);
646
657
  process.stdout.write("\n");
647
658
  }
648
- process.stdout.write(` Install: nimbus add <name> · ${items.length} item${items.length === 1 ? "" : "s"}\n\n`);
659
+ process.stdout.write(` Install: nimbus-docs add <name> · ${items.length} item${items.length === 1 ? "" : "s"}\n\n`);
649
660
  }
650
661
  async function addCommand(slug, flags) {
651
662
  const entry = getIndexEntry(slug);
652
663
  if (!entry) {
653
- p.log.error(`Unknown registry item: \`${slug}\`. Try \`nimbus list\` to see what's available.`);
664
+ p.log.error(`Unknown registry item: \`${slug}\`. Try \`nimbus-docs list\` to see what's available.`);
654
665
  process.exit(1);
655
666
  }
656
667
  if (entry.type === "registry:feature") {
657
668
  await installFeature(slug, { print: flags.print });
658
669
  return;
659
670
  }
660
- p.intro(`nimbus add ${slug}`);
671
+ p.intro(`nimbus-docs add ${slug}`);
661
672
  p.log.info(`${entry.title} — ${entry.description}`);
662
673
  const spinner = p.spinner();
663
674
  spinner.start("Resolving dependencies");
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["addCommand","addCommand"],"sources":["../../src/cli/_registry.generated.ts","../../src/cli/pm.ts","../../src/cli/component.ts","../../src/cli/dotenv.ts","../../src/cli/resolver.ts","../../src/cli/feature.ts","../../src/cli/index.ts"],"sourcesContent":["/**\n * Bundled registry index — auto-generated by\n * apps/www/scripts/generate-registry.ts. Do not edit by hand.\n *\n * Re-run via: pnpm --filter @nimbus/www generate-registry\n */\n\nexport type RegistryEntryType =\n | \"registry:ui\"\n | \"registry:lib\"\n | \"registry:feature\";\n\nexport interface RegistryIndexEntry {\n name: string;\n type: RegistryEntryType;\n title: string;\n description: string;\n}\n\nexport interface BundledIndex {\n version: 1;\n items: Record<string, RegistryIndexEntry>;\n}\n\nexport const REGISTRY_BASE_URL = \"https://nimbus-docs.com/registry\";\n\nexport const BUNDLED_INDEX: BundledIndex = {\n \"version\": 1,\n \"items\": {\n \"cn\": {\n \"name\": \"cn\",\n \"type\": \"registry:lib\",\n \"title\": \"cn\",\n \"description\": \"Tailwind-aware className merger built on clsx + tailwind-merge.\"\n },\n \"accordion\": {\n \"name\": \"accordion\",\n \"type\": \"registry:ui\",\n \"title\": \"Accordion\",\n \"description\": \"Vertically stacked collapsible sections.\"\n },\n \"aside\": {\n \"name\": \"aside\",\n \"type\": \"registry:ui\",\n \"title\": \"Aside\",\n \"description\": \"Generic boxed callout. Building block for Callout, Note, Warning.\"\n },\n \"badge\": {\n \"name\": \"badge\",\n \"type\": \"registry:ui\",\n \"title\": \"Badge\",\n \"description\": \"Small status / category pill.\"\n },\n \"banner\": {\n \"name\": \"banner\",\n \"type\": \"registry:ui\",\n \"title\": \"Banner\",\n \"description\": \"Site-wide dismissible announcement bar.\"\n },\n \"breadcrumbs\": {\n \"name\": \"breadcrumbs\",\n \"type\": \"registry:ui\",\n \"title\": \"Breadcrumbs\",\n \"description\": \"Page-context navigation crumbs.\"\n },\n \"callout\": {\n \"name\": \"callout\",\n \"type\": \"registry:ui\",\n \"title\": \"Callout\",\n \"description\": \"Inline note / tip / warning / danger / info card.\"\n },\n \"card\": {\n \"name\": \"card\",\n \"type\": \"registry:ui\",\n \"title\": \"Card\",\n \"description\": \"Generic content card with optional title and footer.\"\n },\n \"card-grid\": {\n \"name\": \"card-grid\",\n \"type\": \"registry:ui\",\n \"title\": \"CardGrid\",\n \"description\": \"Responsive grid layout for cards.\"\n },\n \"code\": {\n \"name\": \"code\",\n \"type\": \"registry:ui\",\n \"title\": \"Code\",\n \"description\": \"Inline / block code wrapper. Re-exports Astro's built-in <Code> (Shiki).\"\n },\n \"code-group\": {\n \"name\": \"code-group\",\n \"type\": \"registry:ui\",\n \"title\": \"CodeGroup\",\n \"description\": \"Tabbed group of code blocks.\"\n },\n \"collapsible\": {\n \"name\": \"collapsible\",\n \"type\": \"registry:ui\",\n \"title\": \"Collapsible\",\n \"description\": \"Headless show/hide primitive — building block for Accordion and Sidebar groups.\"\n },\n \"dialog\": {\n \"name\": \"dialog\",\n \"type\": \"registry:ui\",\n \"title\": \"Dialog\",\n \"description\": \"Modal dialog with focus-trap and body-scroll lock.\"\n },\n \"embed\": {\n \"name\": \"embed\",\n \"type\": \"registry:ui\",\n \"title\": \"Embed\",\n \"description\": \"Responsive iframe / video / external content wrapper.\"\n },\n \"file-tree\": {\n \"name\": \"file-tree\",\n \"type\": \"registry:ui\",\n \"title\": \"FileTree\",\n \"description\": \"Render a directory tree as nested markup.\"\n },\n \"frame\": {\n \"name\": \"frame\",\n \"type\": \"registry:ui\",\n \"title\": \"Frame\",\n \"description\": \"Decorative outer frame for screenshots and demos.\"\n },\n \"layer-card\": {\n \"name\": \"layer-card\",\n \"type\": \"registry:ui\",\n \"title\": \"LayerCard\",\n \"description\": \"Stacked-card container with sticky header. Base for CodeGroup and PackageManagers.\"\n },\n \"link-button\": {\n \"name\": \"link-button\",\n \"type\": \"registry:ui\",\n \"title\": \"LinkButton\",\n \"description\": \"Anchor styled as a button.\"\n },\n \"link-card\": {\n \"name\": \"link-card\",\n \"type\": \"registry:ui\",\n \"title\": \"LinkCard\",\n \"description\": \"Card whose entire surface is a link.\"\n },\n \"package-managers\": {\n \"name\": \"package-managers\",\n \"type\": \"registry:ui\",\n \"title\": \"PackageManagers\",\n \"description\": \"Tabbed install command block translated across npm / pnpm / yarn / bun.\"\n },\n \"pagination\": {\n \"name\": \"pagination\",\n \"type\": \"registry:ui\",\n \"title\": \"Pagination\",\n \"description\": \"Prev / next page navigation.\"\n },\n \"popover\": {\n \"name\": \"popover\",\n \"type\": \"registry:ui\",\n \"title\": \"Popover\",\n \"description\": \"Floating panel anchored to a trigger element.\"\n },\n \"search\": {\n \"name\": \"search\",\n \"type\": \"registry:ui\",\n \"title\": \"Search\",\n \"description\": \"Command-palette search dialog with a provider seam. Defaults to Pagefind.\"\n },\n \"sidebar\": {\n \"name\": \"sidebar\",\n \"type\": \"registry:ui\",\n \"title\": \"Sidebar\",\n \"description\": \"Docs sidebar with nested groups and active-link tracking.\"\n },\n \"steps\": {\n \"name\": \"steps\",\n \"type\": \"registry:ui\",\n \"title\": \"Steps\",\n \"description\": \"Numbered ordered-list with vertical connectors.\"\n },\n \"tabs\": {\n \"name\": \"tabs\",\n \"type\": \"registry:ui\",\n \"title\": \"Tabs\",\n \"description\": \"Tabbed content panels (manual + Starlight-compatible modes).\"\n },\n \"theme-toggle\": {\n \"name\": \"theme-toggle\",\n \"type\": \"registry:ui\",\n \"title\": \"ThemeToggle\",\n \"description\": \"Light / dark theme switcher button.\"\n },\n \"toc\": {\n \"name\": \"toc\",\n \"type\": \"registry:ui\",\n \"title\": \"TOC\",\n \"description\": \"On-page table of contents with active-heading tracking.\"\n },\n \"404-page\": {\n \"name\": \"404-page\",\n \"type\": \"registry:feature\",\n \"title\": \"Custom 404 page\",\n \"description\": \"Generate a brand-matched 404 page for the docs site.\"\n },\n \"ai-native\": {\n \"name\": \"ai-native\",\n \"type\": \"registry:feature\",\n \"title\": \"AI-native static surface\",\n \"description\": \"Add llms.txt, markdown variants, robots.txt, and an AgentDirective to a Nimbus docs site.\"\n },\n \"pagefind-search\": {\n \"name\": \"pagefind-search\",\n \"type\": \"registry:feature\",\n \"title\": \"Pagefind search\",\n \"description\": \"Add static Pagefind indexing and the Nimbus search dialog to an existing docs site.\"\n }\n }\n};\n","/**\n * Package-manager detection + install command helpers.\n *\n * Detection prefers lockfile presence in the user's cwd, then falls back\n * to the `npm_config_user_agent` env var the active package manager sets\n * when invoking the CLI. Finally falls back to `npm`.\n */\n\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nexport type PackageManager = \"npm\" | \"pnpm\" | \"yarn\" | \"bun\";\n\nconst LOCKFILES: ReadonlyArray<readonly [string, PackageManager]> = [\n [\"pnpm-lock.yaml\", \"pnpm\"],\n [\"yarn.lock\", \"yarn\"],\n [\"bun.lockb\", \"bun\"],\n [\"bun.lock\", \"bun\"],\n [\"package-lock.json\", \"npm\"],\n];\n\nexport function detectPackageManager(cwd: string): PackageManager {\n for (const [lockfile, pm] of LOCKFILES) {\n if (existsSync(join(cwd, lockfile))) return pm;\n }\n const ua = process.env.npm_config_user_agent ?? \"\";\n if (ua.startsWith(\"pnpm\")) return \"pnpm\";\n if (ua.startsWith(\"yarn\")) return \"yarn\";\n if (ua.startsWith(\"bun\")) return \"bun\";\n return \"npm\";\n}\n\n/**\n * Command + args to install one or more new npm deps. Each PM picks the\n * verb that both adds to package.json AND installs:\n *\n * npm install <deps...>\n * pnpm add <deps...>\n * yarn add <deps...>\n * bun add <deps...>\n */\nexport function addCommand(\n pm: PackageManager,\n deps: string[],\n): { bin: string; args: string[] } {\n if (deps.length === 0) {\n throw new Error(\"addCommand called with empty deps\");\n }\n switch (pm) {\n case \"npm\":\n return { bin: \"npm\", args: [\"install\", ...deps] };\n case \"pnpm\":\n return { bin: \"pnpm\", args: [\"add\", ...deps] };\n case \"yarn\":\n return { bin: \"yarn\", args: [\"add\", ...deps] };\n case \"bun\":\n return { bin: \"bun\", args: [\"add\", ...deps] };\n }\n}\n","/**\n * Component / utility installer.\n *\n * Walks the resolved list of items, writes each file (per-file overwrite\n * prompt on conflict), then collects all npm `dependencies` across the\n * tree and runs `<pm> add` once for the dedup'd set.\n *\n * File destination: `<cwd>/src/<path>`. The `path` field already encodes\n * the directory layout (e.g. `components/ui/dialog/Dialog.astro`).\n */\n\nimport { spawn } from \"node:child_process\";\nimport {\n existsSync,\n mkdirSync,\n readFileSync,\n writeFileSync,\n} from \"node:fs\";\nimport { dirname, join, relative } from \"node:path\";\n\nimport * as p from \"@clack/prompts\";\n\nimport { addCommand, detectPackageManager } from \"./pm.js\";\nimport type { ComponentItem } from \"./resolver.js\";\n\nexport interface InstallOptions {\n /** User's project root. */\n cwd: string;\n /** Skip overwrite prompts; assume \"overwrite\" on every conflict. */\n yes: boolean;\n}\n\nexport interface InstallReport {\n /** Individual files actually written. */\n written: string[];\n /** Registry slugs skipped wholesale by the user. */\n skipped: string[];\n npmDepsInstalled: string[];\n}\n\nexport async function installComponents(\n items: ComponentItem[],\n options: InstallOptions,\n): Promise<InstallReport> {\n const report: InstallReport = {\n written: [],\n skipped: [],\n npmDepsInstalled: [],\n };\n\n // ---- 1. Write files — atomic per registry item -------------------------\n //\n // Each item (e.g. `dialog`) is treated as an indivisible unit: when any\n // of its files conflict, we prompt once for the whole slug. Letting users\n // overwrite Dialog.astro while keeping DialogContent.astro is a footgun\n // — components are cohesive and meant to evolve together.\n const srcDir = join(options.cwd, \"src\");\n\n for (const item of items) {\n const filePlans = item.files.map((file) => {\n const targetAbs = join(srcDir, file.path);\n return {\n targetAbs,\n targetRel: relative(options.cwd, targetAbs),\n content: file.content,\n exists: existsSync(targetAbs),\n };\n });\n\n const conflicts = filePlans.filter((f) => f.exists);\n\n if (conflicts.length > 0 && !options.yes) {\n const total = filePlans.length;\n const message =\n conflicts.length === total\n ? `${item.name} is already installed (${total} file${total === 1 ? \"\" : \"s\"}). Overwrite?`\n : `${item.name} is partially installed (${conflicts.length} of ${total} file${total === 1 ? \"\" : \"s\"} present). Overwrite all?`;\n\n const choice = await p.select({\n message,\n options: [\n { value: \"overwrite\", label: \"Overwrite — replace existing files\" },\n { value: \"skip\", label: \"Skip — leave files as-is\" },\n { value: \"cancel\", label: \"Cancel install\" },\n ],\n initialValue: \"overwrite\",\n });\n\n if (p.isCancel(choice) || choice === \"cancel\") {\n p.cancel(\"Cancelled.\");\n process.exit(0);\n }\n if (choice === \"skip\") {\n report.skipped.push(item.name);\n continue;\n }\n }\n\n // Either no conflicts, --yes, or user chose overwrite. Write every file.\n for (const plan of filePlans) {\n mkdirSync(dirname(plan.targetAbs), { recursive: true });\n writeFileSync(plan.targetAbs, plan.content);\n report.written.push(plan.targetRel);\n }\n }\n\n // ---- 2. Install missing npm deps ---------------------------------------\n const allDeps = new Set<string>();\n for (const item of items) {\n for (const dep of item.dependencies) allDeps.add(dep);\n }\n\n if (allDeps.size > 0) {\n const newDeps = filterAlreadyInstalled(options.cwd, [...allDeps]);\n if (newDeps.length > 0) {\n const pm = detectPackageManager(options.cwd);\n const { bin, args } = addCommand(pm, newDeps);\n const spinner = p.spinner();\n spinner.start(`${pm} add ${newDeps.join(\" \")}`);\n try {\n await runCommand(bin, args, options.cwd);\n spinner.stop(\n `Installed ${newDeps.length} dep${newDeps.length === 1 ? \"\" : \"s\"}.`,\n );\n report.npmDepsInstalled = newDeps;\n } catch (err) {\n spinner.stop(\"Dependency install failed.\");\n p.log.warn(\n `Could not install ${newDeps.join(\", \")}. Run \\`${bin} ${args.join(\" \")}\\` manually.`,\n );\n }\n }\n }\n\n return report;\n}\n\n/**\n * Filter out deps already present in `dependencies` or `devDependencies`\n * of the user's package.json. If package.json is missing, returns all.\n */\nfunction filterAlreadyInstalled(cwd: string, deps: string[]): string[] {\n const pkgPath = join(cwd, \"package.json\");\n if (!existsSync(pkgPath)) return deps;\n try {\n const pkg = JSON.parse(readFileSync(pkgPath, \"utf8\")) as {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n };\n const installed = new Set([\n ...Object.keys(pkg.dependencies ?? {}),\n ...Object.keys(pkg.devDependencies ?? {}),\n ]);\n return deps.filter((d) => !installed.has(d));\n } catch {\n return deps;\n }\n}\n\nfunction runCommand(\n bin: string,\n args: string[],\n cwd: string,\n): Promise<void> {\n return new Promise((resolveP, rejectP) => {\n const child = spawn(bin, args, {\n cwd,\n stdio: [\"ignore\", \"ignore\", \"inherit\"],\n });\n child.on(\"close\", (code) =>\n code === 0\n ? resolveP()\n : rejectP(new Error(`${bin} ${args.join(\" \")} exited ${code}`)),\n );\n child.on(\"error\", rejectP);\n });\n}\n","/**\n * Tiny .env loader — no dependency.\n *\n * Reads `.env` from the user's cwd at CLI startup and sets any KEY=VALUE\n * pairs into `process.env` IF the variable isn't already set (so a shell-\n * provided env always wins over the file). Supports the basic cases:\n *\n * KEY=value\n * KEY=\"quoted value\"\n * KEY='quoted value'\n * # comments\n *\n * Used so `examples/local/.env` can carry `NIMBUS_REGISTRY_URL=...` without\n * the user having to prefix every CLI invocation.\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nexport function loadDotenv(cwd: string): void {\n const path = join(cwd, \".env\");\n if (!existsSync(path)) return;\n\n let raw: string;\n try {\n raw = readFileSync(path, \"utf8\");\n } catch {\n return;\n }\n\n for (const rawLine of raw.split(/\\r?\\n/)) {\n const line = rawLine.trim();\n if (!line || line.startsWith(\"#\")) continue;\n\n const eq = line.indexOf(\"=\");\n if (eq <= 0) continue;\n\n const key = line.slice(0, eq).trim();\n if (!/^[A-Z_][A-Z0-9_]*$/i.test(key)) continue;\n\n let value = line.slice(eq + 1).trim();\n if (\n (value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))\n ) {\n value = value.slice(1, -1);\n }\n\n if (process.env[key] === undefined) {\n process.env[key] = value;\n }\n }\n}\n","/**\n * Registry resolver.\n *\n * Two entry points:\n *\n * - `resolveComponentTree(slug)` walks `registryDependencies` transitively\n * and returns a flat ordered list of components/utilities to install\n * (dependencies first, root last). Cycles are detected as repeated\n * visits and skipped.\n *\n * - `fetchFeatureMarkdown(slug)` returns the raw markdown for an\n * agent-handoff feature; the caller decides what to do with it.\n *\n * The base URL for hosted artifacts is read from the bundled index, with\n * an `NIMBUS_REGISTRY_URL` env override for local development.\n */\n\nimport {\n BUNDLED_INDEX,\n REGISTRY_BASE_URL,\n type RegistryIndexEntry,\n} from \"./_registry.generated.js\";\n\nexport interface RegistryFile {\n path: string;\n content: string;\n}\n\nexport interface ComponentItem {\n name: string;\n type: \"registry:ui\" | \"registry:lib\";\n title: string;\n description: string;\n dependencies: string[];\n registryDependencies: string[];\n files: RegistryFile[];\n}\n\n/**\n * Read the registry base URL on every call so `.env` files loaded after\n * module-import time (see cli/dotenv.ts) are picked up. The cost is\n * negligible — string interpolation of an env var.\n */\nfunction getBaseUrl(): string {\n return (process.env.NIMBUS_REGISTRY_URL ?? REGISTRY_BASE_URL).replace(\n /\\/$/,\n \"\",\n );\n}\n\n// ---------------------------------------------------------------------------\n// Index lookup (offline — no network)\n// ---------------------------------------------------------------------------\n\nexport function getIndexEntry(slug: string): RegistryIndexEntry | undefined {\n return BUNDLED_INDEX.items[slug];\n}\n\nexport function listEntries(filter?: {\n type?: RegistryIndexEntry[\"type\"];\n}): RegistryIndexEntry[] {\n const all = Object.values(BUNDLED_INDEX.items);\n if (!filter?.type) return all;\n return all.filter((e) => e.type === filter.type);\n}\n\n// ---------------------------------------------------------------------------\n// Network: component JSON + feature markdown\n// ---------------------------------------------------------------------------\n\nasync function httpGet(url: string): Promise<Response> {\n let res: Response;\n try {\n res = await fetch(url);\n } catch (err) {\n const cause = (err as Error).message;\n throw new Error(\n `Could not reach the registry at ${url}.\\n` +\n ` Underlying error: ${cause}\\n\\n` +\n ` Things to try:\\n` +\n ` - Is the registry server running? Start it with \\`pnpm local\\` (in the monorepo root).\\n` +\n ` - Override the URL: NIMBUS_REGISTRY_URL=https://example.com nimbus add ...\\n` +\n ` - Check the value in your project's .env file.`,\n );\n }\n if (!res.ok) {\n throw new Error(\n `Registry returned ${res.status} ${res.statusText} for ${url}. ` +\n `The server is up but doesn't know about this slug — check \\`nimbus list\\` for valid names.`,\n );\n }\n return res;\n}\n\nexport async function fetchComponent(slug: string): Promise<ComponentItem> {\n const res = await httpGet(`${getBaseUrl()}/components/${slug}.json`);\n return (await res.json()) as ComponentItem;\n}\n\nexport async function fetchFeatureMarkdown(slug: string): Promise<string> {\n const res = await httpGet(`${getBaseUrl()}/features/${slug}.md`);\n return await res.text();\n}\n\n// ---------------------------------------------------------------------------\n// Transitive dep resolution\n// ---------------------------------------------------------------------------\n\n/**\n * Depth-first walk of registryDependencies. Returns items in install order\n * (deps before dependents), deduplicated by slug.\n */\nexport async function resolveComponentTree(\n rootSlug: string,\n): Promise<ComponentItem[]> {\n const visited = new Set<string>();\n const ordered: ComponentItem[] = [];\n\n async function visit(slug: string): Promise<void> {\n if (visited.has(slug)) return;\n visited.add(slug);\n\n const item = await fetchComponent(slug);\n\n // Walk deps first so they're earlier in the install order.\n for (const dep of item.registryDependencies) {\n await visit(dep);\n }\n\n ordered.push(item);\n }\n\n await visit(rootSlug);\n return ordered;\n}\n","/**\n * Feature installer — Flue-style agent-handoff.\n *\n * Same rule Flue uses for `flue add`: if `--print` is set OR\n * `determineAgent()` says the CLI is running inside a known coding agent,\n * the markdown is piped to stdout for the agent to consume. Otherwise we\n * print human-friendly instructions on stderr telling the user exactly\n * how to pipe the output to their agent of choice.\n *\n * No picker, no clipboard mode — the printed pipe commands cover both.\n */\n\nimport { determineAgent } from \"@vercel/detect-agent\";\n\nimport { fetchFeatureMarkdown } from \"./resolver.js\";\n\nexport interface FeatureInstallOptions {\n /** Force markdown to stdout regardless of agent detection. */\n print: boolean;\n}\n\nexport async function installFeature(\n slug: string,\n options: FeatureInstallOptions,\n): Promise<void> {\n const markdown = await fetchFeatureMarkdown(slug);\n\n // Same predicate as Flue: explicit --print, or detection says we're\n // running inside a known agent (which captures our stdout).\n const detected = await determineAgent().catch(() => ({\n isAgent: false as const,\n }));\n const isAgentMode = options.print || detected.isAgent === true;\n\n if (isAgentMode) {\n process.stdout.write(markdown);\n if (!markdown.endsWith(\"\\n\")) process.stdout.write(\"\\n\");\n return;\n }\n\n printHumanInstructions(slug);\n}\n\n/**\n * Stderr-only. We don't put this on stdout because if the user pipes our\n * output anywhere by accident, only the markdown should reach the agent.\n *\n * Formatting mirrors Flue's `printHumanInstructions` 1:1 — agents listed\n * with a blank line between the \"first-tier\" CLIs (claude/codex/cursor-agent)\n * and the rest (opencode/pi).\n */\nfunction printHumanInstructions(slug: string): void {\n const cmd = `nimbus add ${slug}`;\n const stream = process.stderr;\n stream.write(`${cmd}\\n\\n`);\n stream.write(\"To install this feature, pipe it to your coding agent:\\n\\n\");\n stream.write(` ${cmd} --print | claude\\n`);\n stream.write(` ${cmd} --print | codex\\n`);\n stream.write(` ${cmd} --print | cursor-agent\\n\\n`);\n stream.write(` ${cmd} --print | opencode\\n`);\n stream.write(` ${cmd} --print | pi\\n`);\n stream.write(\"Or paste this prompt into any agent:\\n\\n\");\n stream.write(` Run \"${cmd} --print\" and follow the instructions.\\n`);\n}\n","#!/usr/bin/env node\n\n/**\n * `nimbus` CLI entry.\n *\n * Surface:\n *\n * nimbus → list (table of installable items)\n * nimbus list → list\n * nimbus list --type ui|lib|feature\n * nimbus add → list\n * nimbus add <slug> → install (component path or feature path)\n * nimbus add <slug> --yes → component: skip overwrite prompts\n * nimbus add <slug> --print → feature: print markdown to stdout (skip detect)\n *\n * Feature behavior mirrors Flue's `add` command: print markdown to stdout\n * iff `--print` OR an agent is detected; otherwise print human-friendly\n * pipe instructions to stderr.\n *\n * The bundled index makes `list` (and `add` with no slug) work offline.\n * Per-item content is fetched from `REGISTRY_BASE_URL` only when actually\n * installing a slug — override via `NIMBUS_REGISTRY_URL` for local dev.\n */\n\nimport mri from \"mri\";\nimport * as p from \"@clack/prompts\";\n\nimport { BUNDLED_INDEX } from \"./_registry.generated.js\";\nimport { installComponents } from \"./component.js\";\nimport { loadDotenv } from \"./dotenv.js\";\nimport { installFeature } from \"./feature.js\";\nimport {\n getIndexEntry,\n listEntries,\n resolveComponentTree,\n} from \"./resolver.js\";\n\n// Load .env from the user's cwd so per-project NIMBUS_REGISTRY_URL (and\n// any future env vars) work without shell prefixes. Shell-provided vars\n// always win (loadDotenv only sets undefined keys).\nloadDotenv(process.cwd());\n\ndeclare const __APP_VERSION__: string;\n\ninterface CliArgs {\n _: string[];\n yes: boolean;\n print: boolean;\n help: boolean;\n version: boolean;\n type?: string;\n}\n\nconst HELP = `\n Usage: nimbus <command> [args]\n\n Commands:\n list [--type ui|lib|feature] List available registry items\n add Same as \\`list\\`\n add <slug> Install a component or hand off a feature\n\n Flags:\n --yes, -y Component: overwrite conflicts without prompting\n --print Feature: print markdown to stdout (skip agent detect)\n --type <ui|lib|feature> \\`list\\`: filter by type\n --help, -h\n --version, -v\n\n Examples:\n nimbus add dialog # component: resolve + install\n nimbus add 404-page # feature: detect agent or print\n # pipe instructions for humans\n nimbus add 404-page --print | claude # explicit pipe to claude\n nimbus add 404-page --print | codex # …or any other agent\n`;\n\nasync function main(): Promise<void> {\n const args = mri(process.argv.slice(2), {\n boolean: [\"yes\", \"print\", \"help\", \"version\"],\n string: [\"type\"],\n alias: { y: \"yes\", h: \"help\", v: \"version\" },\n }) as unknown as CliArgs;\n\n if (args.help) {\n process.stdout.write(HELP);\n return;\n }\n if (args.version) {\n process.stdout.write(`${__APP_VERSION__}\\n`);\n return;\n }\n\n const [command, slug] = args._;\n\n if (command === \"list\" || (command === \"add\" && !slug) || !command) {\n listCommand(args.type);\n return;\n }\n\n if (command === \"add\") {\n await addCommand(slug!, {\n yes: args.yes,\n print: args.print,\n });\n return;\n }\n\n p.log.error(`Unknown command: \\`${command}\\`. Try \\`nimbus --help\\`.`);\n process.exit(1);\n}\n\n// ---------------------------------------------------------------------------\n// `nimbus list`\n// ---------------------------------------------------------------------------\n\nfunction listCommand(typeFilter: string | undefined): void {\n const typeMap: Record<string, \"registry:ui\" | \"registry:lib\" | \"registry:feature\"> = {\n ui: \"registry:ui\",\n lib: \"registry:lib\",\n feature: \"registry:feature\",\n };\n\n const filter =\n typeFilter && typeFilter in typeMap\n ? { type: typeMap[typeFilter] }\n : undefined;\n\n if (typeFilter && !(typeFilter in typeMap)) {\n p.log.error(\n `Unknown --type \"${typeFilter}\". Valid: ui, lib, feature.`,\n );\n process.exit(1);\n }\n\n const items = listEntries(filter);\n if (items.length === 0) {\n p.log.info(\"No items match the filter.\");\n return;\n }\n\n // Group by type for readability.\n const grouped: Record<string, typeof items> = {\n \"registry:ui\": [],\n \"registry:lib\": [],\n \"registry:feature\": [],\n };\n for (const item of items) grouped[item.type]!.push(item);\n\n const labels: Record<string, string> = {\n \"registry:ui\": \"Components\",\n \"registry:lib\": \"Utilities\",\n \"registry:feature\": \"Features\",\n };\n const widths = items.reduce(\n (m, i) => Math.max(m, i.name.length),\n 0,\n );\n\n process.stdout.write(\"\\n\");\n for (const [type, label] of Object.entries(labels)) {\n const group = grouped[type];\n if (!group || group.length === 0) continue;\n process.stdout.write(` ${label}\\n`);\n for (const item of group) {\n process.stdout.write(\n ` ${item.name.padEnd(widths + 2)}${item.description}\\n`,\n );\n }\n process.stdout.write(\"\\n\");\n }\n process.stdout.write(\n ` Install: nimbus add <name> · ${items.length} item${items.length === 1 ? \"\" : \"s\"}\\n\\n`,\n );\n}\n\n// ---------------------------------------------------------------------------\n// `nimbus add <slug>`\n// ---------------------------------------------------------------------------\n\nasync function addCommand(\n slug: string,\n flags: { yes: boolean; print: boolean },\n): Promise<void> {\n const entry = getIndexEntry(slug);\n if (!entry) {\n p.log.error(\n `Unknown registry item: \\`${slug}\\`. Try \\`nimbus list\\` to see what's available.`,\n );\n process.exit(1);\n }\n\n if (entry.type === \"registry:feature\") {\n await installFeature(slug, { print: flags.print });\n return;\n }\n\n // Component / utility path.\n p.intro(`nimbus add ${slug}`);\n p.log.info(`${entry.title} — ${entry.description}`);\n\n const spinner = p.spinner();\n spinner.start(\"Resolving dependencies\");\n let items;\n try {\n items = await resolveComponentTree(slug);\n spinner.stop(\n `Resolved ${items.length} item${items.length === 1 ? \"\" : \"s\"}.`,\n );\n } catch (err) {\n spinner.stop(\"Failed to resolve.\");\n p.log.error((err as Error).message);\n process.exit(1);\n }\n\n if (items.length > 1) {\n p.log.message(\n \"Install order:\\n \" + items.map((i) => i.name).join(\" → \"),\n );\n }\n\n const report = await installComponents(items, {\n cwd: process.cwd(),\n yes: flags.yes,\n });\n\n const lines: string[] = [];\n if (report.written.length > 0) {\n lines.push(`✓ Wrote ${report.written.length} file${report.written.length === 1 ? \"\" : \"s\"}`);\n }\n if (report.skipped.length > 0) {\n lines.push(`↷ Skipped: ${report.skipped.join(\", \")}`);\n }\n if (report.npmDepsInstalled.length > 0) {\n lines.push(\n `+ Installed ${report.npmDepsInstalled.length} npm dep${report.npmDepsInstalled.length === 1 ? \"\" : \"s\"}: ${report.npmDepsInstalled.join(\", \")}`,\n );\n }\n\n if (lines.length === 0) {\n p.outro(\"Nothing to do.\");\n } else {\n p.outro(lines.join(\"\\n\"));\n }\n}\n\n// ---------------------------------------------------------------------------\n// Entrypoint\n// ---------------------------------------------------------------------------\n\nmain().catch((err) => {\n p.log.error(`${(err as Error).message}`);\n process.exit(1);\n});\n\n// Tell TS BUNDLED_INDEX is used (so no `verbatimModuleSyntax` warning).\nvoid BUNDLED_INDEX;\n"],"mappings":";;;;;;;;;AAwBA,MAAa,oBAAoB;AAEjC,MAAa,gBAA8B;CACzC,WAAW;CACX,SAAS;EACP,MAAM;GACJ,QAAQ;GACR,QAAQ;GACR,SAAS;GACT,eAAe;GAChB;EACD,aAAa;GACX,QAAQ;GACR,QAAQ;GACR,SAAS;GACT,eAAe;GAChB;EACD,SAAS;GACP,QAAQ;GACR,QAAQ;GACR,SAAS;GACT,eAAe;GAChB;EACD,SAAS;GACP,QAAQ;GACR,QAAQ;GACR,SAAS;GACT,eAAe;GAChB;EACD,UAAU;GACR,QAAQ;GACR,QAAQ;GACR,SAAS;GACT,eAAe;GAChB;EACD,eAAe;GACb,QAAQ;GACR,QAAQ;GACR,SAAS;GACT,eAAe;GAChB;EACD,WAAW;GACT,QAAQ;GACR,QAAQ;GACR,SAAS;GACT,eAAe;GAChB;EACD,QAAQ;GACN,QAAQ;GACR,QAAQ;GACR,SAAS;GACT,eAAe;GAChB;EACD,aAAa;GACX,QAAQ;GACR,QAAQ;GACR,SAAS;GACT,eAAe;GAChB;EACD,QAAQ;GACN,QAAQ;GACR,QAAQ;GACR,SAAS;GACT,eAAe;GAChB;EACD,cAAc;GACZ,QAAQ;GACR,QAAQ;GACR,SAAS;GACT,eAAe;GAChB;EACD,eAAe;GACb,QAAQ;GACR,QAAQ;GACR,SAAS;GACT,eAAe;GAChB;EACD,UAAU;GACR,QAAQ;GACR,QAAQ;GACR,SAAS;GACT,eAAe;GAChB;EACD,SAAS;GACP,QAAQ;GACR,QAAQ;GACR,SAAS;GACT,eAAe;GAChB;EACD,aAAa;GACX,QAAQ;GACR,QAAQ;GACR,SAAS;GACT,eAAe;GAChB;EACD,SAAS;GACP,QAAQ;GACR,QAAQ;GACR,SAAS;GACT,eAAe;GAChB;EACD,cAAc;GACZ,QAAQ;GACR,QAAQ;GACR,SAAS;GACT,eAAe;GAChB;EACD,eAAe;GACb,QAAQ;GACR,QAAQ;GACR,SAAS;GACT,eAAe;GAChB;EACD,aAAa;GACX,QAAQ;GACR,QAAQ;GACR,SAAS;GACT,eAAe;GAChB;EACD,oBAAoB;GAClB,QAAQ;GACR,QAAQ;GACR,SAAS;GACT,eAAe;GAChB;EACD,cAAc;GACZ,QAAQ;GACR,QAAQ;GACR,SAAS;GACT,eAAe;GAChB;EACD,WAAW;GACT,QAAQ;GACR,QAAQ;GACR,SAAS;GACT,eAAe;GAChB;EACD,UAAU;GACR,QAAQ;GACR,QAAQ;GACR,SAAS;GACT,eAAe;GAChB;EACD,WAAW;GACT,QAAQ;GACR,QAAQ;GACR,SAAS;GACT,eAAe;GAChB;EACD,SAAS;GACP,QAAQ;GACR,QAAQ;GACR,SAAS;GACT,eAAe;GAChB;EACD,QAAQ;GACN,QAAQ;GACR,QAAQ;GACR,SAAS;GACT,eAAe;GAChB;EACD,gBAAgB;GACd,QAAQ;GACR,QAAQ;GACR,SAAS;GACT,eAAe;GAChB;EACD,OAAO;GACL,QAAQ;GACR,QAAQ;GACR,SAAS;GACT,eAAe;GAChB;EACD,YAAY;GACV,QAAQ;GACR,QAAQ;GACR,SAAS;GACT,eAAe;GAChB;EACD,aAAa;GACX,QAAQ;GACR,QAAQ;GACR,SAAS;GACT,eAAe;GAChB;EACD,mBAAmB;GACjB,QAAQ;GACR,QAAQ;GACR,SAAS;GACT,eAAe;GAChB;EACF;CACF;;;;;;;;;;;AC3MD,MAAM,YAA8D;CAClE,CAAC,kBAAkB,OAAO;CAC1B,CAAC,aAAa,OAAO;CACrB,CAAC,aAAa,MAAM;CACpB,CAAC,YAAY,MAAM;CACnB,CAAC,qBAAqB,MAAM;CAC7B;AAED,SAAgB,qBAAqB,KAA6B;AAChE,MAAK,MAAM,CAAC,UAAU,OAAO,UAC3B,KAAI,WAAW,KAAK,KAAK,SAAS,CAAC,CAAE,QAAO;CAE9C,MAAM,KAAK,QAAQ,IAAI,yBAAyB;AAChD,KAAI,GAAG,WAAW,OAAO,CAAE,QAAO;AAClC,KAAI,GAAG,WAAW,OAAO,CAAE,QAAO;AAClC,KAAI,GAAG,WAAW,MAAM,CAAE,QAAO;AACjC,QAAO;;;;;;;;;;;AAYT,SAAgBA,aACd,IACA,MACiC;AACjC,KAAI,KAAK,WAAW,EAClB,OAAM,IAAI,MAAM,oCAAoC;AAEtD,SAAQ,IAAR;EACE,KAAK,MACH,QAAO;GAAE,KAAK;GAAO,MAAM,CAAC,WAAW,GAAG,KAAK;GAAE;EACnD,KAAK,OACH,QAAO;GAAE,KAAK;GAAQ,MAAM,CAAC,OAAO,GAAG,KAAK;GAAE;EAChD,KAAK,OACH,QAAO;GAAE,KAAK;GAAQ,MAAM,CAAC,OAAO,GAAG,KAAK;GAAE;EAChD,KAAK,MACH,QAAO;GAAE,KAAK;GAAO,MAAM,CAAC,OAAO,GAAG,KAAK;GAAE;;;;;;;;;;;;;;;;AChBnD,eAAsB,kBACpB,OACA,SACwB;CACxB,MAAM,SAAwB;EAC5B,SAAS,EAAE;EACX,SAAS,EAAE;EACX,kBAAkB,EAAE;EACrB;CAQD,MAAM,SAAS,KAAK,QAAQ,KAAK,MAAM;AAEvC,MAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,YAAY,KAAK,MAAM,KAAK,SAAS;GACzC,MAAM,YAAY,KAAK,QAAQ,KAAK,KAAK;AACzC,UAAO;IACL;IACA,WAAW,SAAS,QAAQ,KAAK,UAAU;IAC3C,SAAS,KAAK;IACd,QAAQ,WAAW,UAAU;IAC9B;IACD;EAEF,MAAM,YAAY,UAAU,QAAQ,MAAM,EAAE,OAAO;AAEnD,MAAI,UAAU,SAAS,KAAK,CAAC,QAAQ,KAAK;GACxC,MAAM,QAAQ,UAAU;GACxB,MAAM,UACJ,UAAU,WAAW,QACjB,GAAG,KAAK,KAAK,yBAAyB,MAAM,OAAO,UAAU,IAAI,KAAK,IAAI,iBAC1E,GAAG,KAAK,KAAK,2BAA2B,UAAU,OAAO,MAAM,MAAM,OAAO,UAAU,IAAI,KAAK,IAAI;GAEzG,MAAM,SAAS,MAAM,EAAE,OAAO;IAC5B;IACA,SAAS;KACP;MAAE,OAAO;MAAa,OAAO;MAAsC;KACnE;MAAE,OAAO;MAAQ,OAAO;MAA4B;KACpD;MAAE,OAAO;MAAU,OAAO;MAAkB;KAC7C;IACD,cAAc;IACf,CAAC;AAEF,OAAI,EAAE,SAAS,OAAO,IAAI,WAAW,UAAU;AAC7C,MAAE,OAAO,aAAa;AACtB,YAAQ,KAAK,EAAE;;AAEjB,OAAI,WAAW,QAAQ;AACrB,WAAO,QAAQ,KAAK,KAAK,KAAK;AAC9B;;;AAKJ,OAAK,MAAM,QAAQ,WAAW;AAC5B,aAAU,QAAQ,KAAK,UAAU,EAAE,EAAE,WAAW,MAAM,CAAC;AACvD,iBAAc,KAAK,WAAW,KAAK,QAAQ;AAC3C,UAAO,QAAQ,KAAK,KAAK,UAAU;;;CAKvC,MAAM,0BAAU,IAAI,KAAa;AACjC,MAAK,MAAM,QAAQ,MACjB,MAAK,MAAM,OAAO,KAAK,aAAc,SAAQ,IAAI,IAAI;AAGvD,KAAI,QAAQ,OAAO,GAAG;EACpB,MAAM,UAAU,uBAAuB,QAAQ,KAAK,CAAC,GAAG,QAAQ,CAAC;AACjE,MAAI,QAAQ,SAAS,GAAG;GACtB,MAAM,KAAK,qBAAqB,QAAQ,IAAI;GAC5C,MAAM,EAAE,KAAK,SAASC,aAAW,IAAI,QAAQ;GAC7C,MAAM,UAAU,EAAE,SAAS;AAC3B,WAAQ,MAAM,GAAG,GAAG,OAAO,QAAQ,KAAK,IAAI,GAAG;AAC/C,OAAI;AACF,UAAM,WAAW,KAAK,MAAM,QAAQ,IAAI;AACxC,YAAQ,KACN,aAAa,QAAQ,OAAO,MAAM,QAAQ,WAAW,IAAI,KAAK,IAAI,GACnE;AACD,WAAO,mBAAmB;YACnB,KAAK;AACZ,YAAQ,KAAK,6BAA6B;AAC1C,MAAE,IAAI,KACJ,qBAAqB,QAAQ,KAAK,KAAK,CAAC,UAAU,IAAI,GAAG,KAAK,KAAK,IAAI,CAAC,cACzE;;;;AAKP,QAAO;;;;;;AAOT,SAAS,uBAAuB,KAAa,MAA0B;CACrE,MAAM,UAAU,KAAK,KAAK,eAAe;AACzC,KAAI,CAAC,WAAW,QAAQ,CAAE,QAAO;AACjC,KAAI;EACF,MAAM,MAAM,KAAK,MAAM,aAAa,SAAS,OAAO,CAAC;EAIrD,MAAM,YAAY,IAAI,IAAI,CACxB,GAAG,OAAO,KAAK,IAAI,gBAAgB,EAAE,CAAC,EACtC,GAAG,OAAO,KAAK,IAAI,mBAAmB,EAAE,CAAC,CAC1C,CAAC;AACF,SAAO,KAAK,QAAQ,MAAM,CAAC,UAAU,IAAI,EAAE,CAAC;SACtC;AACN,SAAO;;;AAIX,SAAS,WACP,KACA,MACA,KACe;AACf,QAAO,IAAI,SAAS,UAAU,YAAY;EACxC,MAAM,QAAQ,MAAM,KAAK,MAAM;GAC7B;GACA,OAAO;IAAC;IAAU;IAAU;IAAU;GACvC,CAAC;AACF,QAAM,GAAG,UAAU,SACjB,SAAS,IACL,UAAU,GACV,wBAAQ,IAAI,MAAM,GAAG,IAAI,GAAG,KAAK,KAAK,IAAI,CAAC,UAAU,OAAO,CAAC,CAClE;AACD,QAAM,GAAG,SAAS,QAAQ;GAC1B;;;;;;;;;;;;;;;;;;;;AC5JJ,SAAgB,WAAW,KAAmB;CAC5C,MAAM,OAAO,KAAK,KAAK,OAAO;AAC9B,KAAI,CAAC,WAAW,KAAK,CAAE;CAEvB,IAAI;AACJ,KAAI;AACF,QAAM,aAAa,MAAM,OAAO;SAC1B;AACN;;AAGF,MAAK,MAAM,WAAW,IAAI,MAAM,QAAQ,EAAE;EACxC,MAAM,OAAO,QAAQ,MAAM;AAC3B,MAAI,CAAC,QAAQ,KAAK,WAAW,IAAI,CAAE;EAEnC,MAAM,KAAK,KAAK,QAAQ,IAAI;AAC5B,MAAI,MAAM,EAAG;EAEb,MAAM,MAAM,KAAK,MAAM,GAAG,GAAG,CAAC,MAAM;AACpC,MAAI,CAAC,sBAAsB,KAAK,IAAI,CAAE;EAEtC,IAAI,QAAQ,KAAK,MAAM,KAAK,EAAE,CAAC,MAAM;AACrC,MACG,MAAM,WAAW,KAAI,IAAI,MAAM,SAAS,KAAI,IAC5C,MAAM,WAAW,IAAI,IAAI,MAAM,SAAS,IAAI,CAE7C,SAAQ,MAAM,MAAM,GAAG,GAAG;AAG5B,MAAI,QAAQ,IAAI,SAAS,OACvB,SAAQ,IAAI,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;ACNzB,SAAS,aAAqB;AAC5B,SAAQ,QAAQ,IAAI,uBAAuB,mBAAmB,QAC5D,OACA,GACD;;AAOH,SAAgB,cAAc,MAA8C;AAC1E,QAAO,cAAc,MAAM;;AAG7B,SAAgB,YAAY,QAEH;CACvB,MAAM,MAAM,OAAO,OAAO,cAAc,MAAM;AAC9C,KAAI,CAAC,QAAQ,KAAM,QAAO;AAC1B,QAAO,IAAI,QAAQ,MAAM,EAAE,SAAS,OAAO,KAAK;;AAOlD,eAAe,QAAQ,KAAgC;CACrD,IAAI;AACJ,KAAI;AACF,QAAM,MAAM,MAAM,IAAI;UACf,KAAK;EACZ,MAAM,QAAS,IAAc;AAC7B,QAAM,IAAI,MACR,mCAAmC,IAAI,yBACd,MAAM,sPAKhC;;AAEH,KAAI,CAAC,IAAI,GACP,OAAM,IAAI,MACR,qBAAqB,IAAI,OAAO,GAAG,IAAI,WAAW,OAAO,IAAI,8FAE9D;AAEH,QAAO;;AAGT,eAAsB,eAAe,MAAsC;AAEzE,QAAQ,OADI,MAAM,QAAQ,GAAG,YAAY,CAAC,cAAc,KAAK,OAAO,EAClD,MAAM;;AAG1B,eAAsB,qBAAqB,MAA+B;AAExE,QAAO,OADK,MAAM,QAAQ,GAAG,YAAY,CAAC,YAAY,KAAK,KAAK,EAC/C,MAAM;;;;;;AAWzB,eAAsB,qBACpB,UAC0B;CAC1B,MAAM,0BAAU,IAAI,KAAa;CACjC,MAAM,UAA2B,EAAE;CAEnC,eAAe,MAAM,MAA6B;AAChD,MAAI,QAAQ,IAAI,KAAK,CAAE;AACvB,UAAQ,IAAI,KAAK;EAEjB,MAAM,OAAO,MAAM,eAAe,KAAK;AAGvC,OAAK,MAAM,OAAO,KAAK,qBACrB,OAAM,MAAM,IAAI;AAGlB,UAAQ,KAAK,KAAK;;AAGpB,OAAM,MAAM,SAAS;AACrB,QAAO;;;;;;;;;;;;;;;;AChHT,eAAsB,eACpB,MACA,SACe;CACf,MAAM,WAAW,MAAM,qBAAqB,KAAK;CAIjD,MAAM,WAAW,MAAM,gBAAgB,CAAC,aAAa,EACnD,SAAS,OACV,EAAE;AAGH,KAFoB,QAAQ,SAAS,SAAS,YAAY,MAEzC;AACf,UAAQ,OAAO,MAAM,SAAS;AAC9B,MAAI,CAAC,SAAS,SAAS,KAAK,CAAE,SAAQ,OAAO,MAAM,KAAK;AACxD;;AAGF,wBAAuB,KAAK;;;;;;;;;;AAW9B,SAAS,uBAAuB,MAAoB;CAClD,MAAM,MAAM,cAAc;CAC1B,MAAM,SAAS,QAAQ;AACvB,QAAO,MAAM,GAAG,IAAI,MAAM;AAC1B,QAAO,MAAM,6DAA6D;AAC1E,QAAO,MAAM,KAAK,IAAI,qBAAqB;AAC3C,QAAO,MAAM,KAAK,IAAI,oBAAoB;AAC1C,QAAO,MAAM,KAAK,IAAI,6BAA6B;AACnD,QAAO,MAAM,KAAK,IAAI,uBAAuB;AAC7C,QAAO,MAAM,KAAK,IAAI,iBAAiB;AACvC,QAAO,MAAM,2CAA2C;AACxD,QAAO,MAAM,UAAU,IAAI,0CAA0C;;;;;;;;;;;;;;;;;;;;;;;;;;ACtBvE,WAAW,QAAQ,KAAK,CAAC;AAazB,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;AAuBb,eAAe,OAAsB;CACnC,MAAM,OAAO,IAAI,QAAQ,KAAK,MAAM,EAAE,EAAE;EACtC,SAAS;GAAC;GAAO;GAAS;GAAQ;GAAU;EAC5C,QAAQ,CAAC,OAAO;EAChB,OAAO;GAAE,GAAG;GAAO,GAAG;GAAQ,GAAG;GAAW;EAC7C,CAAC;AAEF,KAAI,KAAK,MAAM;AACb,UAAQ,OAAO,MAAM,KAAK;AAC1B;;AAEF,KAAI,KAAK,SAAS;AAChB,UAAQ,OAAO,MAAM,UAAuB;AAC5C;;CAGF,MAAM,CAAC,SAAS,QAAQ,KAAK;AAE7B,KAAI,YAAY,UAAW,YAAY,SAAS,CAAC,QAAS,CAAC,SAAS;AAClE,cAAY,KAAK,KAAK;AACtB;;AAGF,KAAI,YAAY,OAAO;AACrB,QAAM,WAAW,MAAO;GACtB,KAAK,KAAK;GACV,OAAO,KAAK;GACb,CAAC;AACF;;AAGF,GAAE,IAAI,MAAM,sBAAsB,QAAQ,4BAA4B;AACtE,SAAQ,KAAK,EAAE;;AAOjB,SAAS,YAAY,YAAsC;CACzD,MAAM,UAA+E;EACnF,IAAI;EACJ,KAAK;EACL,SAAS;EACV;CAED,MAAM,SACJ,cAAc,cAAc,UACxB,EAAE,MAAM,QAAQ,aAAa,GAC7B;AAEN,KAAI,cAAc,EAAE,cAAc,UAAU;AAC1C,IAAE,IAAI,MACJ,mBAAmB,WAAW,6BAC/B;AACD,UAAQ,KAAK,EAAE;;CAGjB,MAAM,QAAQ,YAAY,OAAO;AACjC,KAAI,MAAM,WAAW,GAAG;AACtB,IAAE,IAAI,KAAK,6BAA6B;AACxC;;CAIF,MAAM,UAAwC;EAC5C,eAAe,EAAE;EACjB,gBAAgB,EAAE;EAClB,oBAAoB,EAAE;EACvB;AACD,MAAK,MAAM,QAAQ,MAAO,SAAQ,KAAK,MAAO,KAAK,KAAK;CAExD,MAAM,SAAiC;EACrC,eAAe;EACf,gBAAgB;EAChB,oBAAoB;EACrB;CACD,MAAM,SAAS,MAAM,QAClB,GAAG,MAAM,KAAK,IAAI,GAAG,EAAE,KAAK,OAAO,EACpC,EACD;AAED,SAAQ,OAAO,MAAM,KAAK;AAC1B,MAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,OAAO,EAAE;EAClD,MAAM,QAAQ,QAAQ;AACtB,MAAI,CAAC,SAAS,MAAM,WAAW,EAAG;AAClC,UAAQ,OAAO,MAAM,KAAK,MAAM,IAAI;AACpC,OAAK,MAAM,QAAQ,MACjB,SAAQ,OAAO,MACb,OAAO,KAAK,KAAK,OAAO,SAAS,EAAE,GAAG,KAAK,YAAY,IACxD;AAEH,UAAQ,OAAO,MAAM,KAAK;;AAE5B,SAAQ,OAAO,MACb,wCAAwC,MAAM,OAAO,OAAO,MAAM,WAAW,IAAI,KAAK,IAAI,MAC3F;;AAOH,eAAe,WACb,MACA,OACe;CACf,MAAM,QAAQ,cAAc,KAAK;AACjC,KAAI,CAAC,OAAO;AACV,IAAE,IAAI,MACJ,4BAA4B,KAAK,kDAClC;AACD,UAAQ,KAAK,EAAE;;AAGjB,KAAI,MAAM,SAAS,oBAAoB;AACrC,QAAM,eAAe,MAAM,EAAE,OAAO,MAAM,OAAO,CAAC;AAClD;;AAIF,GAAE,MAAM,cAAc,OAAO;AAC7B,GAAE,IAAI,KAAK,GAAG,MAAM,MAAM,KAAK,MAAM,cAAc;CAEnD,MAAM,UAAU,EAAE,SAAS;AAC3B,SAAQ,MAAM,yBAAyB;CACvC,IAAI;AACJ,KAAI;AACF,UAAQ,MAAM,qBAAqB,KAAK;AACxC,UAAQ,KACN,YAAY,MAAM,OAAO,OAAO,MAAM,WAAW,IAAI,KAAK,IAAI,GAC/D;UACM,KAAK;AACZ,UAAQ,KAAK,qBAAqB;AAClC,IAAE,IAAI,MAAO,IAAc,QAAQ;AACnC,UAAQ,KAAK,EAAE;;AAGjB,KAAI,MAAM,SAAS,EACjB,GAAE,IAAI,QACJ,uBAAuB,MAAM,KAAK,MAAM,EAAE,KAAK,CAAC,KAAK,MAAM,CAC5D;CAGH,MAAM,SAAS,MAAM,kBAAkB,OAAO;EAC5C,KAAK,QAAQ,KAAK;EAClB,KAAK,MAAM;EACZ,CAAC;CAEF,MAAM,QAAkB,EAAE;AAC1B,KAAI,OAAO,QAAQ,SAAS,EAC1B,OAAM,KAAK,WAAW,OAAO,QAAQ,OAAO,OAAO,OAAO,QAAQ,WAAW,IAAI,KAAK,MAAM;AAE9F,KAAI,OAAO,QAAQ,SAAS,EAC1B,OAAM,KAAK,cAAc,OAAO,QAAQ,KAAK,KAAK,GAAG;AAEvD,KAAI,OAAO,iBAAiB,SAAS,EACnC,OAAM,KACJ,eAAe,OAAO,iBAAiB,OAAO,UAAU,OAAO,iBAAiB,WAAW,IAAI,KAAK,IAAI,IAAI,OAAO,iBAAiB,KAAK,KAAK,GAC/I;AAGH,KAAI,MAAM,WAAW,EACnB,GAAE,MAAM,iBAAiB;KAEzB,GAAE,MAAM,MAAM,KAAK,KAAK,CAAC;;AAQ7B,MAAM,CAAC,OAAO,QAAQ;AACpB,GAAE,IAAI,MAAM,GAAI,IAAc,UAAU;AACxC,SAAQ,KAAK,EAAE;EACf"}
1
+ {"version":3,"file":"index.js","names":["addCommand","addCommand"],"sources":["../../src/cli/_registry.generated.ts","../../src/cli/pm.ts","../../src/cli/component.ts","../../src/cli/dotenv.ts","../../src/cli/resolver.ts","../../src/cli/feature.ts","../../src/cli/index.ts"],"sourcesContent":["/**\n * Bundled registry index — auto-generated by\n * apps/www/scripts/generate-registry.ts. Do not edit by hand.\n *\n * Re-run via: pnpm --filter @nimbus/www generate-registry\n */\n\nexport type RegistryEntryType =\n | \"registry:ui\"\n | \"registry:lib\"\n | \"registry:feature\";\n\nexport interface RegistryIndexEntry {\n name: string;\n type: RegistryEntryType;\n title: string;\n description: string;\n}\n\nexport interface BundledIndex {\n version: 1;\n items: Record<string, RegistryIndexEntry>;\n}\n\nexport const REGISTRY_BASE_URL = \"https://nimbus-docs.com/registry\";\n\nexport const BUNDLED_INDEX: BundledIndex = {\n \"version\": 1,\n \"items\": {\n \"cn\": {\n \"name\": \"cn\",\n \"type\": \"registry:lib\",\n \"title\": \"cn\",\n \"description\": \"Tailwind-aware className merger built on clsx + tailwind-merge.\"\n },\n \"accordion\": {\n \"name\": \"accordion\",\n \"type\": \"registry:ui\",\n \"title\": \"Accordion\",\n \"description\": \"Vertically stacked collapsible sections.\"\n },\n \"aside\": {\n \"name\": \"aside\",\n \"type\": \"registry:ui\",\n \"title\": \"Aside\",\n \"description\": \"Generic boxed callout. Building block for Callout, Note, Warning.\"\n },\n \"badge\": {\n \"name\": \"badge\",\n \"type\": \"registry:ui\",\n \"title\": \"Badge\",\n \"description\": \"Small status / category pill.\"\n },\n \"banner\": {\n \"name\": \"banner\",\n \"type\": \"registry:ui\",\n \"title\": \"Banner\",\n \"description\": \"Site-wide dismissible announcement bar.\"\n },\n \"breadcrumbs\": {\n \"name\": \"breadcrumbs\",\n \"type\": \"registry:ui\",\n \"title\": \"Breadcrumbs\",\n \"description\": \"Page-context navigation crumbs.\"\n },\n \"callout\": {\n \"name\": \"callout\",\n \"type\": \"registry:ui\",\n \"title\": \"Callout\",\n \"description\": \"Inline note / tip / warning / danger / info card.\"\n },\n \"card\": {\n \"name\": \"card\",\n \"type\": \"registry:ui\",\n \"title\": \"Card\",\n \"description\": \"Generic content card with optional title and footer.\"\n },\n \"card-grid\": {\n \"name\": \"card-grid\",\n \"type\": \"registry:ui\",\n \"title\": \"CardGrid\",\n \"description\": \"Responsive grid layout for cards.\"\n },\n \"code\": {\n \"name\": \"code\",\n \"type\": \"registry:ui\",\n \"title\": \"Code\",\n \"description\": \"Inline / block code wrapper. Re-exports Astro's built-in <Code> (Shiki).\"\n },\n \"code-group\": {\n \"name\": \"code-group\",\n \"type\": \"registry:ui\",\n \"title\": \"CodeGroup\",\n \"description\": \"Tabbed group of code blocks.\"\n },\n \"collapsible\": {\n \"name\": \"collapsible\",\n \"type\": \"registry:ui\",\n \"title\": \"Collapsible\",\n \"description\": \"Headless show/hide primitive — building block for Accordion and Sidebar groups.\"\n },\n \"dialog\": {\n \"name\": \"dialog\",\n \"type\": \"registry:ui\",\n \"title\": \"Dialog\",\n \"description\": \"Modal dialog with focus-trap and body-scroll lock.\"\n },\n \"embed\": {\n \"name\": \"embed\",\n \"type\": \"registry:ui\",\n \"title\": \"Embed\",\n \"description\": \"Responsive iframe / video / external content wrapper.\"\n },\n \"file-tree\": {\n \"name\": \"file-tree\",\n \"type\": \"registry:ui\",\n \"title\": \"FileTree\",\n \"description\": \"Render a directory tree as nested markup.\"\n },\n \"frame\": {\n \"name\": \"frame\",\n \"type\": \"registry:ui\",\n \"title\": \"Frame\",\n \"description\": \"Decorative outer frame for screenshots and demos.\"\n },\n \"layer-card\": {\n \"name\": \"layer-card\",\n \"type\": \"registry:ui\",\n \"title\": \"LayerCard\",\n \"description\": \"Stacked-card container with sticky header. Base for CodeGroup and PackageManagers.\"\n },\n \"link-button\": {\n \"name\": \"link-button\",\n \"type\": \"registry:ui\",\n \"title\": \"LinkButton\",\n \"description\": \"Anchor styled as a button.\"\n },\n \"link-card\": {\n \"name\": \"link-card\",\n \"type\": \"registry:ui\",\n \"title\": \"LinkCard\",\n \"description\": \"Card whose entire surface is a link.\"\n },\n \"package-managers\": {\n \"name\": \"package-managers\",\n \"type\": \"registry:ui\",\n \"title\": \"PackageManagers\",\n \"description\": \"Tabbed install command block translated across npm / pnpm / yarn / bun.\"\n },\n \"page-actions\": {\n \"name\": \"page-actions\",\n \"type\": \"registry:ui\",\n \"title\": \"PageActions\",\n \"description\": \"Inline page-header actions: copy the page as markdown, open the raw .md.\"\n },\n \"pagination\": {\n \"name\": \"pagination\",\n \"type\": \"registry:ui\",\n \"title\": \"Pagination\",\n \"description\": \"Prev / next page navigation.\"\n },\n \"popover\": {\n \"name\": \"popover\",\n \"type\": \"registry:ui\",\n \"title\": \"Popover\",\n \"description\": \"Floating panel anchored to a trigger element.\"\n },\n \"search\": {\n \"name\": \"search\",\n \"type\": \"registry:ui\",\n \"title\": \"Search\",\n \"description\": \"Command-palette search dialog with a provider seam. Defaults to Pagefind.\"\n },\n \"sidebar\": {\n \"name\": \"sidebar\",\n \"type\": \"registry:ui\",\n \"title\": \"Sidebar\",\n \"description\": \"Docs sidebar with nested groups and active-link tracking.\"\n },\n \"steps\": {\n \"name\": \"steps\",\n \"type\": \"registry:ui\",\n \"title\": \"Steps\",\n \"description\": \"Numbered ordered-list with vertical connectors.\"\n },\n \"tabs\": {\n \"name\": \"tabs\",\n \"type\": \"registry:ui\",\n \"title\": \"Tabs\",\n \"description\": \"Tabbed content panels (manual + Starlight-compatible modes).\"\n },\n \"theme-toggle\": {\n \"name\": \"theme-toggle\",\n \"type\": \"registry:ui\",\n \"title\": \"ThemeToggle\",\n \"description\": \"Light / dark theme switcher button.\"\n },\n \"toc\": {\n \"name\": \"toc\",\n \"type\": \"registry:ui\",\n \"title\": \"TOC\",\n \"description\": \"On-page table of contents with active-heading tracking.\"\n },\n \"404-page\": {\n \"name\": \"404-page\",\n \"type\": \"registry:feature\",\n \"title\": \"Custom 404 page\",\n \"description\": \"Generate a brand-matched 404 page for the docs site.\"\n },\n \"ai-native\": {\n \"name\": \"ai-native\",\n \"type\": \"registry:feature\",\n \"title\": \"AI-native static surface\",\n \"description\": \"Add llms.txt, markdown variants, robots.txt, and an AgentDirective to a Nimbus docs site.\"\n },\n \"pagefind-search\": {\n \"name\": \"pagefind-search\",\n \"type\": \"registry:feature\",\n \"title\": \"Pagefind search\",\n \"description\": \"Add static Pagefind indexing and the Nimbus search dialog to an existing docs site.\"\n }\n }\n};\n","/**\n * Package-manager detection + install command helpers.\n *\n * Detection prefers lockfile presence in the user's cwd, then falls back\n * to the `npm_config_user_agent` env var the active package manager sets\n * when invoking the CLI. Finally falls back to `npm`.\n */\n\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nexport type PackageManager = \"npm\" | \"pnpm\" | \"yarn\" | \"bun\";\n\nconst LOCKFILES: ReadonlyArray<readonly [string, PackageManager]> = [\n [\"pnpm-lock.yaml\", \"pnpm\"],\n [\"yarn.lock\", \"yarn\"],\n [\"bun.lockb\", \"bun\"],\n [\"bun.lock\", \"bun\"],\n [\"package-lock.json\", \"npm\"],\n];\n\nexport function detectPackageManager(cwd: string): PackageManager {\n for (const [lockfile, pm] of LOCKFILES) {\n if (existsSync(join(cwd, lockfile))) return pm;\n }\n const ua = process.env.npm_config_user_agent ?? \"\";\n if (ua.startsWith(\"pnpm\")) return \"pnpm\";\n if (ua.startsWith(\"yarn\")) return \"yarn\";\n if (ua.startsWith(\"bun\")) return \"bun\";\n return \"npm\";\n}\n\n/**\n * Command + args to install one or more new npm deps. Each PM picks the\n * verb that both adds to package.json AND installs:\n *\n * npm install <deps...>\n * pnpm add <deps...>\n * yarn add <deps...>\n * bun add <deps...>\n */\nexport function addCommand(\n pm: PackageManager,\n deps: string[],\n): { bin: string; args: string[] } {\n if (deps.length === 0) {\n throw new Error(\"addCommand called with empty deps\");\n }\n switch (pm) {\n case \"npm\":\n return { bin: \"npm\", args: [\"install\", ...deps] };\n case \"pnpm\":\n return { bin: \"pnpm\", args: [\"add\", ...deps] };\n case \"yarn\":\n return { bin: \"yarn\", args: [\"add\", ...deps] };\n case \"bun\":\n return { bin: \"bun\", args: [\"add\", ...deps] };\n }\n}\n","/**\n * Component / utility installer.\n *\n * Walks the resolved list of items, writes each file (per-file overwrite\n * prompt on conflict), then collects all npm `dependencies` across the\n * tree and runs `<pm> add` once for the dedup'd set.\n *\n * File destination: `<cwd>/src/<path>`. The `path` field already encodes\n * the directory layout (e.g. `components/ui/dialog/Dialog.astro`).\n */\n\nimport { spawn } from \"node:child_process\";\nimport {\n existsSync,\n mkdirSync,\n readFileSync,\n writeFileSync,\n} from \"node:fs\";\nimport { dirname, join, relative } from \"node:path\";\n\nimport * as p from \"@clack/prompts\";\n\nimport { addCommand, detectPackageManager } from \"./pm.js\";\nimport type { ComponentItem } from \"./resolver.js\";\n\nexport interface InstallOptions {\n /** User's project root. */\n cwd: string;\n /** Skip overwrite prompts; assume \"overwrite\" on every conflict. */\n yes: boolean;\n}\n\nexport interface InstallReport {\n /** Individual files actually written. */\n written: string[];\n /** Registry slugs skipped wholesale by the user. */\n skipped: string[];\n npmDepsInstalled: string[];\n}\n\nexport async function installComponents(\n items: ComponentItem[],\n options: InstallOptions,\n): Promise<InstallReport> {\n const report: InstallReport = {\n written: [],\n skipped: [],\n npmDepsInstalled: [],\n };\n\n // ---- 1. Write files — atomic per registry item -------------------------\n //\n // Each item (e.g. `dialog`) is treated as an indivisible unit: when any\n // of its files conflict, we prompt once for the whole slug. Letting users\n // overwrite Dialog.astro while keeping DialogContent.astro is a footgun\n // — components are cohesive and meant to evolve together.\n const srcDir = join(options.cwd, \"src\");\n\n for (const item of items) {\n const filePlans = item.files.map((file) => {\n const targetAbs = join(srcDir, file.path);\n return {\n targetAbs,\n targetRel: relative(options.cwd, targetAbs),\n content: file.content,\n exists: existsSync(targetAbs),\n };\n });\n\n const conflicts = filePlans.filter((f) => f.exists);\n\n // Utilities (registry:lib) are transitive dependencies of UI\n // components — install silently when missing, skip silently when\n // present. Never prompt or overwrite: users may have customized\n // them (e.g. cn) and being asked about `cn` every time you `add` a\n // component is noise.\n if (item.type === \"registry:lib\") {\n if (conflicts.length > 0) {\n report.skipped.push(item.name);\n continue;\n }\n } else if (conflicts.length > 0 && !options.yes) {\n const total = filePlans.length;\n const message =\n conflicts.length === total\n ? `${item.name} is already installed (${total} file${total === 1 ? \"\" : \"s\"}). Overwrite?`\n : `${item.name} is partially installed (${conflicts.length} of ${total} file${total === 1 ? \"\" : \"s\"} present). Overwrite all?`;\n\n const choice = await p.select({\n message,\n options: [\n { value: \"overwrite\", label: \"Overwrite — replace existing files\" },\n { value: \"skip\", label: \"Skip — leave files as-is\" },\n { value: \"cancel\", label: \"Cancel install\" },\n ],\n initialValue: \"overwrite\",\n });\n\n if (p.isCancel(choice) || choice === \"cancel\") {\n p.cancel(\"Cancelled.\");\n process.exit(0);\n }\n if (choice === \"skip\") {\n report.skipped.push(item.name);\n continue;\n }\n }\n\n // Either no conflicts, --yes, or user chose overwrite. Write every file.\n for (const plan of filePlans) {\n mkdirSync(dirname(plan.targetAbs), { recursive: true });\n writeFileSync(plan.targetAbs, plan.content);\n report.written.push(plan.targetRel);\n }\n }\n\n // ---- 2. Install missing npm deps ---------------------------------------\n const allDeps = new Set<string>();\n for (const item of items) {\n for (const dep of item.dependencies) allDeps.add(dep);\n }\n\n if (allDeps.size > 0) {\n const newDeps = filterAlreadyInstalled(options.cwd, [...allDeps]);\n if (newDeps.length > 0) {\n const pm = detectPackageManager(options.cwd);\n const { bin, args } = addCommand(pm, newDeps);\n const spinner = p.spinner();\n spinner.start(`${pm} add ${newDeps.join(\" \")}`);\n try {\n await runCommand(bin, args, options.cwd);\n spinner.stop(\n `Installed ${newDeps.length} dep${newDeps.length === 1 ? \"\" : \"s\"}.`,\n );\n report.npmDepsInstalled = newDeps;\n } catch (err) {\n spinner.stop(\"Dependency install failed.\");\n p.log.warn(\n `Could not install ${newDeps.join(\", \")}. Run \\`${bin} ${args.join(\" \")}\\` manually.`,\n );\n }\n }\n }\n\n return report;\n}\n\n/**\n * Filter out deps already present in `dependencies` or `devDependencies`\n * of the user's package.json. If package.json is missing, returns all.\n */\nfunction filterAlreadyInstalled(cwd: string, deps: string[]): string[] {\n const pkgPath = join(cwd, \"package.json\");\n if (!existsSync(pkgPath)) return deps;\n try {\n const pkg = JSON.parse(readFileSync(pkgPath, \"utf8\")) as {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n };\n const installed = new Set([\n ...Object.keys(pkg.dependencies ?? {}),\n ...Object.keys(pkg.devDependencies ?? {}),\n ]);\n return deps.filter((d) => !installed.has(d));\n } catch {\n return deps;\n }\n}\n\nfunction runCommand(\n bin: string,\n args: string[],\n cwd: string,\n): Promise<void> {\n return new Promise((resolveP, rejectP) => {\n const child = spawn(bin, args, {\n cwd,\n stdio: [\"ignore\", \"ignore\", \"inherit\"],\n });\n child.on(\"close\", (code) =>\n code === 0\n ? resolveP()\n : rejectP(new Error(`${bin} ${args.join(\" \")} exited ${code}`)),\n );\n child.on(\"error\", rejectP);\n });\n}\n","/**\n * Tiny .env loader — no dependency.\n *\n * Reads `.env` from the user's cwd at CLI startup and sets any KEY=VALUE\n * pairs into `process.env` IF the variable isn't already set (so a shell-\n * provided env always wins over the file). Supports the basic cases:\n *\n * KEY=value\n * KEY=\"quoted value\"\n * KEY='quoted value'\n * # comments\n *\n * Used so `examples/local/.env` can carry `NIMBUS_REGISTRY_URL=...` without\n * the user having to prefix every CLI invocation.\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nexport function loadDotenv(cwd: string): void {\n const path = join(cwd, \".env\");\n if (!existsSync(path)) return;\n\n let raw: string;\n try {\n raw = readFileSync(path, \"utf8\");\n } catch {\n return;\n }\n\n for (const rawLine of raw.split(/\\r?\\n/)) {\n const line = rawLine.trim();\n if (!line || line.startsWith(\"#\")) continue;\n\n const eq = line.indexOf(\"=\");\n if (eq <= 0) continue;\n\n const key = line.slice(0, eq).trim();\n if (!/^[A-Z_][A-Z0-9_]*$/i.test(key)) continue;\n\n let value = line.slice(eq + 1).trim();\n if (\n (value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))\n ) {\n value = value.slice(1, -1);\n }\n\n if (process.env[key] === undefined) {\n process.env[key] = value;\n }\n }\n}\n","/**\n * Registry resolver.\n *\n * Two entry points:\n *\n * - `resolveComponentTree(slug)` walks `registryDependencies` transitively\n * and returns a flat ordered list of components/utilities to install\n * (dependencies first, root last). Cycles are detected as repeated\n * visits and skipped.\n *\n * - `fetchFeatureMarkdown(slug)` returns the raw markdown for an\n * agent-handoff feature; the caller decides what to do with it.\n *\n * The base URL for hosted artifacts is read from the bundled index, with\n * an `NIMBUS_REGISTRY_URL` env override for local development.\n */\n\nimport {\n BUNDLED_INDEX,\n REGISTRY_BASE_URL,\n type RegistryIndexEntry,\n} from \"./_registry.generated.js\";\n\nexport interface RegistryFile {\n path: string;\n content: string;\n}\n\nexport interface ComponentItem {\n name: string;\n type: \"registry:ui\" | \"registry:lib\";\n title: string;\n description: string;\n dependencies: string[];\n registryDependencies: string[];\n files: RegistryFile[];\n}\n\n/**\n * Read the registry base URL on every call so `.env` files loaded after\n * module-import time (see cli/dotenv.ts) are picked up. The cost is\n * negligible — string interpolation of an env var.\n */\nfunction getBaseUrl(): string {\n return (process.env.NIMBUS_REGISTRY_URL ?? REGISTRY_BASE_URL).replace(\n /\\/$/,\n \"\",\n );\n}\n\n// ---------------------------------------------------------------------------\n// Index lookup (offline — no network)\n// ---------------------------------------------------------------------------\n\nexport function getIndexEntry(slug: string): RegistryIndexEntry | undefined {\n return BUNDLED_INDEX.items[slug];\n}\n\nexport function listEntries(filter?: {\n type?: RegistryIndexEntry[\"type\"];\n}): RegistryIndexEntry[] {\n const all = Object.values(BUNDLED_INDEX.items);\n if (!filter?.type) return all;\n return all.filter((e) => e.type === filter.type);\n}\n\n// ---------------------------------------------------------------------------\n// Network: component JSON + feature markdown\n// ---------------------------------------------------------------------------\n\nasync function httpGet(url: string): Promise<Response> {\n let res: Response;\n try {\n res = await fetch(url);\n } catch (err) {\n const cause = (err as Error).message;\n throw new Error(\n `Could not reach the registry at ${url}.\\n` +\n ` Underlying error: ${cause}\\n\\n` +\n ` Things to try:\\n` +\n ` - Is the registry server running? Start it with \\`pnpm local\\` (in the monorepo root).\\n` +\n ` - Override the URL: NIMBUS_REGISTRY_URL=https://example.com nimbus-docs add ...\\n` +\n ` - Check the value in your project's .env file.`,\n );\n }\n if (!res.ok) {\n throw new Error(\n `Registry returned ${res.status} ${res.statusText} for ${url}. ` +\n `The server is up but doesn't know about this slug — check \\`nimbus-docs list\\` for valid names.`,\n );\n }\n return res;\n}\n\nexport async function fetchComponent(slug: string): Promise<ComponentItem> {\n const res = await httpGet(`${getBaseUrl()}/components/${slug}.json`);\n return (await res.json()) as ComponentItem;\n}\n\nexport async function fetchFeatureMarkdown(slug: string): Promise<string> {\n const res = await httpGet(`${getBaseUrl()}/features/${slug}.md`);\n return await res.text();\n}\n\n// ---------------------------------------------------------------------------\n// Transitive dep resolution\n// ---------------------------------------------------------------------------\n\n/**\n * Depth-first walk of registryDependencies. Returns items in install order\n * (deps before dependents), deduplicated by slug.\n */\nexport async function resolveComponentTree(\n rootSlug: string,\n): Promise<ComponentItem[]> {\n const visited = new Set<string>();\n const ordered: ComponentItem[] = [];\n\n async function visit(slug: string): Promise<void> {\n if (visited.has(slug)) return;\n visited.add(slug);\n\n const item = await fetchComponent(slug);\n\n // Walk deps first so they're earlier in the install order.\n for (const dep of item.registryDependencies) {\n await visit(dep);\n }\n\n ordered.push(item);\n }\n\n await visit(rootSlug);\n return ordered;\n}\n","/**\n * Feature installer — Flue-style agent-handoff.\n *\n * Same rule Flue uses for `flue add`: if `--print` is set OR\n * `determineAgent()` says the CLI is running inside a known coding agent,\n * the markdown is piped to stdout for the agent to consume. Otherwise we\n * print human-friendly instructions on stderr telling the user exactly\n * how to pipe the output to their agent of choice.\n *\n * No picker, no clipboard mode — the printed pipe commands cover both.\n */\n\nimport { determineAgent } from \"@vercel/detect-agent\";\n\nimport { fetchFeatureMarkdown } from \"./resolver.js\";\n\nexport interface FeatureInstallOptions {\n /** Force markdown to stdout regardless of agent detection. */\n print: boolean;\n}\n\nexport async function installFeature(\n slug: string,\n options: FeatureInstallOptions,\n): Promise<void> {\n const markdown = await fetchFeatureMarkdown(slug);\n\n // Same predicate as Flue: explicit --print, or detection says we're\n // running inside a known agent (which captures our stdout).\n const detected = await determineAgent().catch(() => ({\n isAgent: false as const,\n }));\n const isAgentMode = options.print || detected.isAgent === true;\n\n if (isAgentMode) {\n process.stdout.write(markdown);\n if (!markdown.endsWith(\"\\n\")) process.stdout.write(\"\\n\");\n return;\n }\n\n printHumanInstructions(slug);\n}\n\n/**\n * Stderr-only. We don't put this on stdout because if the user pipes our\n * output anywhere by accident, only the markdown should reach the agent.\n *\n * Formatting mirrors Flue's `printHumanInstructions` 1:1 — agents listed\n * with a blank line between the \"first-tier\" CLIs (claude/codex/cursor-agent)\n * and the rest (opencode/pi).\n */\nfunction printHumanInstructions(slug: string): void {\n const cmd = `nimbus-docs add ${slug}`;\n const stream = process.stderr;\n stream.write(`${cmd}\\n\\n`);\n stream.write(\"To install this feature, pipe it to your coding agent:\\n\\n\");\n stream.write(` ${cmd} --print | claude\\n`);\n stream.write(` ${cmd} --print | codex\\n`);\n stream.write(` ${cmd} --print | cursor-agent\\n\\n`);\n stream.write(` ${cmd} --print | opencode\\n`);\n stream.write(` ${cmd} --print | pi\\n`);\n stream.write(\"Or paste this prompt into any agent:\\n\\n\");\n stream.write(` Run \"${cmd} --print\" and follow the instructions.\\n`);\n}\n","#!/usr/bin/env node\n\n/**\n * `nimbus-docs` CLI entry.\n *\n * Surface:\n *\n * nimbus → list (table of installable items)\n * nimbus-docs list → list\n * nimbus-docs list --type ui|lib|feature\n * nimbus-docs add → list\n * nimbus-docs add <slug> → install (component path or feature path)\n * nimbus-docs add <slug> --yes → component: skip overwrite prompts\n * nimbus-docs add <slug> --print → feature: print markdown to stdout (skip detect)\n *\n * Feature behavior mirrors Flue's `add` command: print markdown to stdout\n * iff `--print` OR an agent is detected; otherwise print human-friendly\n * pipe instructions to stderr.\n *\n * The bundled index makes `list` (and `add` with no slug) work offline.\n * Per-item content is fetched from `REGISTRY_BASE_URL` only when actually\n * installing a slug — override via `NIMBUS_REGISTRY_URL` for local dev.\n */\n\nimport mri from \"mri\";\nimport * as p from \"@clack/prompts\";\n\nimport { BUNDLED_INDEX } from \"./_registry.generated.js\";\nimport { installComponents } from \"./component.js\";\nimport { loadDotenv } from \"./dotenv.js\";\nimport { installFeature } from \"./feature.js\";\nimport {\n getIndexEntry,\n listEntries,\n resolveComponentTree,\n} from \"./resolver.js\";\n\n// Load .env from the user's cwd so per-project NIMBUS_REGISTRY_URL (and\n// any future env vars) work without shell prefixes. Shell-provided vars\n// always win (loadDotenv only sets undefined keys).\nloadDotenv(process.cwd());\n\ndeclare const __APP_VERSION__: string;\n\ninterface CliArgs {\n _: string[];\n yes: boolean;\n print: boolean;\n help: boolean;\n version: boolean;\n type?: string;\n}\n\nconst HELP = `\n Usage: nimbus-docs <command> [args]\n\n Commands:\n list [--type ui|lib|feature] List available registry items\n add Same as \\`list\\`\n add <slug> Install a component or hand off a feature\n\n Flags:\n --yes, -y Component: overwrite conflicts without prompting\n --print Feature: print markdown to stdout (skip agent detect)\n --type <ui|lib|feature> \\`list\\`: filter by type\n --help, -h\n --version, -v\n\n Examples:\n nimbus-docs add dialog # component: resolve + install\n nimbus-docs add 404-page # feature: detect agent or print\n # pipe instructions for humans\n nimbus-docs add 404-page --print | claude # explicit pipe to claude\n nimbus-docs add 404-page --print | codex # …or any other agent\n`;\n\nasync function main(): Promise<void> {\n const args = mri(process.argv.slice(2), {\n boolean: [\"yes\", \"print\", \"help\", \"version\"],\n string: [\"type\"],\n alias: { y: \"yes\", h: \"help\", v: \"version\" },\n }) as unknown as CliArgs;\n\n if (args.help) {\n process.stdout.write(HELP);\n return;\n }\n if (args.version) {\n process.stdout.write(`${__APP_VERSION__}\\n`);\n return;\n }\n\n const [command, slug] = args._;\n\n if (command === \"list\" || (command === \"add\" && !slug) || !command) {\n listCommand(args.type);\n return;\n }\n\n if (command === \"add\") {\n await addCommand(slug!, {\n yes: args.yes,\n print: args.print,\n });\n return;\n }\n\n p.log.error(`Unknown command: \\`${command}\\`. Try \\`nimbus-docs --help\\`.`);\n process.exit(1);\n}\n\n// ---------------------------------------------------------------------------\n// `nimbus-docs list`\n// ---------------------------------------------------------------------------\n\nfunction listCommand(typeFilter: string | undefined): void {\n const typeMap: Record<string, \"registry:ui\" | \"registry:lib\" | \"registry:feature\"> = {\n ui: \"registry:ui\",\n lib: \"registry:lib\",\n feature: \"registry:feature\",\n };\n\n const filter =\n typeFilter && typeFilter in typeMap\n ? { type: typeMap[typeFilter] }\n : undefined;\n\n if (typeFilter && !(typeFilter in typeMap)) {\n p.log.error(\n `Unknown --type \"${typeFilter}\". Valid: ui, lib, feature.`,\n );\n process.exit(1);\n }\n\n const items = listEntries(filter);\n if (items.length === 0) {\n p.log.info(\"No items match the filter.\");\n return;\n }\n\n // Group by type for readability.\n const grouped: Record<string, typeof items> = {\n \"registry:ui\": [],\n \"registry:lib\": [],\n \"registry:feature\": [],\n };\n for (const item of items) grouped[item.type]!.push(item);\n\n const labels: Record<string, string> = {\n \"registry:ui\": \"Components\",\n \"registry:lib\": \"Utilities\",\n \"registry:feature\": \"Features\",\n };\n const widths = items.reduce(\n (m, i) => Math.max(m, i.name.length),\n 0,\n );\n\n process.stdout.write(\"\\n\");\n for (const [type, label] of Object.entries(labels)) {\n const group = grouped[type];\n if (!group || group.length === 0) continue;\n process.stdout.write(` ${label}\\n`);\n for (const item of group) {\n process.stdout.write(\n ` ${item.name.padEnd(widths + 2)}${item.description}\\n`,\n );\n }\n process.stdout.write(\"\\n\");\n }\n process.stdout.write(\n ` Install: nimbus-docs add <name> · ${items.length} item${items.length === 1 ? \"\" : \"s\"}\\n\\n`,\n );\n}\n\n// ---------------------------------------------------------------------------\n// `nimbus-docs add <slug>`\n// ---------------------------------------------------------------------------\n\nasync function addCommand(\n slug: string,\n flags: { yes: boolean; print: boolean },\n): Promise<void> {\n const entry = getIndexEntry(slug);\n if (!entry) {\n p.log.error(\n `Unknown registry item: \\`${slug}\\`. Try \\`nimbus-docs list\\` to see what's available.`,\n );\n process.exit(1);\n }\n\n if (entry.type === \"registry:feature\") {\n await installFeature(slug, { print: flags.print });\n return;\n }\n\n // Component / utility path.\n p.intro(`nimbus-docs add ${slug}`);\n p.log.info(`${entry.title} — ${entry.description}`);\n\n const spinner = p.spinner();\n spinner.start(\"Resolving dependencies\");\n let items;\n try {\n items = await resolveComponentTree(slug);\n spinner.stop(\n `Resolved ${items.length} item${items.length === 1 ? \"\" : \"s\"}.`,\n );\n } catch (err) {\n spinner.stop(\"Failed to resolve.\");\n p.log.error((err as Error).message);\n process.exit(1);\n }\n\n if (items.length > 1) {\n p.log.message(\n \"Install order:\\n \" + items.map((i) => i.name).join(\" → \"),\n );\n }\n\n const report = await installComponents(items, {\n cwd: process.cwd(),\n yes: flags.yes,\n });\n\n const lines: string[] = [];\n if (report.written.length > 0) {\n lines.push(`✓ Wrote ${report.written.length} file${report.written.length === 1 ? \"\" : \"s\"}`);\n }\n if (report.skipped.length > 0) {\n lines.push(`↷ Skipped: ${report.skipped.join(\", \")}`);\n }\n if (report.npmDepsInstalled.length > 0) {\n lines.push(\n `+ Installed ${report.npmDepsInstalled.length} npm dep${report.npmDepsInstalled.length === 1 ? \"\" : \"s\"}: ${report.npmDepsInstalled.join(\", \")}`,\n );\n }\n\n if (lines.length === 0) {\n p.outro(\"Nothing to do.\");\n } else {\n p.outro(lines.join(\"\\n\"));\n }\n}\n\n// ---------------------------------------------------------------------------\n// Entrypoint\n// ---------------------------------------------------------------------------\n\nmain().catch((err) => {\n p.log.error(`${(err as Error).message}`);\n process.exit(1);\n});\n\n// Tell TS BUNDLED_INDEX is used (so no `verbatimModuleSyntax` warning).\nvoid BUNDLED_INDEX;\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","names":[],"sources":["../src/client/mount.ts","../src/client/disclosure.ts","../src/client/tabs-controller.ts","../src/client/scroll-lock.ts","../src/client/ids.ts","../src/client/dom.ts","../src/client/code-copy.ts","../src/client/heading-anchors.ts"],"mappings":";;;;;;;AA0BA;;;;;;;;;;;;ACNA;;;;;KDIK,IAAA,IAAQ,IAAA,EAAM,WAAA;AAAA,iBAEH,KAAA,CAAM,QAAA,UAAkB,IAAA,EAAM,IAAA;;;;;;;;;AAA9C;;;;;;;;;;;UCNiB,iBAAA;EAAA;EAEf,OAAA,EAAS,WAAA;;EAET,OAAA,EAAS,WAAA;EAFT;EAIA,WAAA;EAFA;EAIA,YAAA,IAAgB,IAAA;AAAA;AAAA,UAGD,kBAAA;EACf,IAAA;EACA,KAAA;EACA,MAAA;EACA,MAAA;EACA,OAAA;AAAA;AAAA,iBAGc,cAAA,CAAe,IAAA,EAAM,iBAAA,GAAoB,kBAAA;;;;;;;;;ADbzD;;UEfiB,UAAA;EFeiC;EEbhD,SAAA,EAAW,OAAA;EFaiC;EEX5C,WAAA;EFWgD;EEThD,aAAA;;EAEA,SAAA,GAAY,WAAA;;EAEZ,cAAA;EDDgC;ECGhC,IAAA;IACE,GAAA;IACA,OAAA,wBDHO;ICKP,QAAA,IAAY,GAAA,EAAK,WAAA;EAAA;EDDnB;ECIA,UAAA,IAAc,KAAA;AAAA;AAAA,UAGC,YAAA;EACf,QAAA,CAAS,KAAA,UAAe,OAAA;IAAY,QAAA;EAAA;EAAA,SAC3B,YAAA;EACT,OAAA;AAAA;AAAA,iBAGc,QAAA,CAAS,MAAA,EAAQ,UAAA,GAAa,YAAA;;;;;;;;;AFb9C;;;;;;iBGRgB,UAAA,CAAA;AAAA,iBAaA,YAAA,CAAA;;;;;;;;;iBCtBA,UAAA,CAAW,MAAA;;;;;;;cCJd,SAAA;;;;;;;;;ALqBb;;;;;;;;;;;;ACNA;AAAA,iBK6DgB,QAAA,CAAA;;;;;;;;;iBC5DA,cAAA,CAAA"}
1
+ {"version":3,"file":"client.d.ts","names":[],"sources":["../src/client/mount.ts","../src/client/disclosure.ts","../src/client/tabs-controller.ts","../src/client/scroll-lock.ts","../src/client/ids.ts","../src/client/dom.ts","../src/client/code-copy.ts","../src/client/heading-anchors.ts"],"mappings":";;;;;;;AA0BA;;;;;;;;;;;;ACNA;;;;;KDIK,IAAA,IAAQ,IAAA,EAAM,WAAA;AAAA,iBAEH,KAAA,CAAM,QAAA,UAAkB,IAAA,EAAM,IAAA;;;;;;;;;AAA9C;;;;;;;;;;;UCNiB,iBAAA;EAAA;EAEf,OAAA,EAAS,WAAA;;EAET,OAAA,EAAS,WAAA;EAFT;EAIA,WAAA;EAFA;EAIA,YAAA,IAAgB,IAAA;AAAA;AAAA,UAGD,kBAAA;EACf,IAAA;EACA,KAAA;EACA,MAAA;EACA,MAAA;EACA,OAAA;AAAA;AAAA,iBAGc,cAAA,CAAe,IAAA,EAAM,iBAAA,GAAoB,kBAAA;;;;;;;;;ADbzD;;UEfiB,UAAA;EFeiC;EEbhD,SAAA,EAAW,OAAA;EFaiC;EEX5C,WAAA;EFWgD;EEThD,aAAA;;EAEA,SAAA,GAAY,WAAA;;EAEZ,cAAA;EDDgC;ECGhC,IAAA;IACE,GAAA;IACA,OAAA,wBDHO;ICKP,QAAA,IAAY,GAAA,EAAK,WAAA;EAAA;EDDnB;ECIA,UAAA,IAAc,KAAA;AAAA;AAAA,UAGC,YAAA;EACf,QAAA,CAAS,KAAA,UAAe,OAAA;IAAY,QAAA;EAAA;EAAA,SAC3B,YAAA;EACT,OAAA;AAAA;AAAA,iBAGc,QAAA,CAAS,MAAA,EAAQ,UAAA,GAAa,YAAA;;;;;;;;;AFb9C;;;;;;iBGRgB,UAAA,CAAA;AAAA,iBAaA,YAAA,CAAA;;;;;;;;;iBCtBA,UAAA,CAAW,MAAA;;;;;;;cCJd,SAAA;;;;;;;;;ALqBb;;;;;;;;;;;;ACNA;AAAA,iBKkEgB,QAAA,CAAA;;;;;;;;;iBCjEA,cAAA,CAAA"}
package/dist/client.js CHANGED
@@ -298,7 +298,8 @@ function getCodeText(pre) {
298
298
  return pre.querySelector("code")?.textContent ?? pre.textContent ?? "";
299
299
  }
300
300
  function initCodeCopy(pre) {
301
- if (pre.closest("[data-cg-row]")) return () => {};
301
+ if (pre.closest("[data-cg-panels-raw], [data-cg-row]")) return () => {};
302
+ const host = pre.closest(".nb-code-figure") ?? pre;
302
303
  const btn = document.createElement("button");
303
304
  btn.type = "button";
304
305
  btn.className = "nb-code-copy";
@@ -324,8 +325,8 @@ function initCodeCopy(pre) {
324
325
  }, 1500);
325
326
  }
326
327
  btn.addEventListener("click", handleClick);
327
- if (getComputedStyle(pre).position === "static") pre.style.position = "relative";
328
- pre.appendChild(btn);
328
+ if (getComputedStyle(host).position === "static") host.style.position = "relative";
329
+ host.appendChild(btn);
329
330
  return () => {
330
331
  if (resetTimer) window.clearTimeout(resetTimer);
331
332
  btn.removeEventListener("click", handleClick);
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":[],"sources":["../src/client/mount.ts","../src/client/ids.ts","../src/client/disclosure.ts","../src/client/dom.ts","../src/client/tabs-controller.ts","../src/client/scroll-lock.ts","../src/client/code-copy.ts","../src/client/heading-anchors.ts"],"sourcesContent":["/**\n * mount.ts — Discover, mount, and unmount component instances.\n *\n * The single entry point used by every `*.client.ts` to wire its component.\n * Handles three concerns:\n *\n * 1. Initial discovery — finds elements matching `selector` and calls `init`\n * on each, storing the returned teardown.\n * 2. View transitions — on `astro:before-swap`, runs every teardown so\n * document/window listeners come down before the DOM is replaced.\n * 3. Re-mount — on `astro:page-load`, re-runs discovery against the new DOM.\n *\n * The init function receives the root element and returns a `destroy()`\n * function. The root element is the keying mechanism — calling mount again\n * against an already-tracked element is a no-op.\n *\n * Usage:\n *\n * mount(\"[data-nb-collapsible]\", (root) => {\n * const disclosure = makeDisclosure({ ... });\n * return () => disclosure.destroy();\n * });\n */\n\ntype Init = (root: HTMLElement) => () => void;\n\nexport function mount(selector: string, init: Init): void {\n const instances = new Map<HTMLElement, () => void>();\n\n function setup() {\n document.querySelectorAll<HTMLElement>(selector).forEach((el) => {\n if (instances.has(el)) return;\n instances.set(el, init(el));\n });\n }\n\n function teardown() {\n instances.forEach((destroy) => destroy());\n instances.clear();\n }\n\n // Module scripts are deferred, so DOM is parsed by the time this runs.\n // Belt-and-braces check anyway.\n if (document.readyState === \"loading\") {\n document.addEventListener(\"DOMContentLoaded\", setup, { once: true });\n } else {\n setup();\n }\n\n document.addEventListener(\"astro:before-swap\", teardown);\n document.addEventListener(\"astro:page-load\", setup);\n}\n","/**\n * Generate a unique ID with a prefix, scoped to the page.\n *\n * Used to build ARIA relationships (`aria-controls` / `aria-labelledby`)\n * between elements that don't have a stable author-provided id.\n */\n\nlet counter = 0;\n\nexport function generateId(prefix: string): string {\n counter += 1;\n return `${prefix}-${counter}`;\n}\n","/**\n * disclosure.ts — Open/close state with ARIA wiring.\n *\n * The shared module for any \"click trigger, reveals content\" pattern.\n * Owns:\n * - open/closed state (in-memory + reflected as `data-nb-state` on both\n * trigger and content)\n * - ARIA: `aria-expanded` on trigger, `aria-controls` linking to content\n * - Click handler on the trigger\n *\n * Animation is intentionally out of scope — CSS targets the `data-nb-state`\n * attribute and runs whatever transition the component wants. Returning\n * a teardown means the caller can clean up on unmount.\n *\n * Used by: Collapsible, and any future Accordion / Sidebar group /\n * dismissable Banner that wants the standard disclosure semantics.\n */\n\nimport { generateId } from \"./ids\";\n\nexport interface DisclosureOptions {\n /** The element users click to toggle. Usually a `<button>`. */\n trigger: HTMLElement;\n /** The element that's shown/hidden. Gets `id` + `data-nb-state`. */\n content: HTMLElement;\n /** Initial open state. Default `false`. */\n defaultOpen?: boolean;\n /** Called whenever open changes. */\n onOpenChange?: (open: boolean) => void;\n}\n\nexport interface DisclosureInstance {\n open(): void;\n close(): void;\n toggle(): void;\n isOpen(): boolean;\n destroy(): void;\n}\n\nexport function makeDisclosure(opts: DisclosureOptions): DisclosureInstance {\n const { trigger, content, defaultOpen = false, onOpenChange } = opts;\n\n let open = defaultOpen;\n\n // Ensure ARIA relationship exists.\n if (!content.id) {\n content.id = generateId(\"nb-disclosure\");\n }\n trigger.setAttribute(\"aria-controls\", content.id);\n\n function syncState() {\n const state = open ? \"open\" : \"closed\";\n trigger.setAttribute(\"data-nb-state\", state);\n content.setAttribute(\"data-nb-state\", state);\n trigger.setAttribute(\"aria-expanded\", String(open));\n }\n\n function setOpen(value: boolean) {\n if (open === value) return;\n open = value;\n syncState();\n onOpenChange?.(value);\n }\n\n function handleClick(e: MouseEvent) {\n e.preventDefault();\n setOpen(!open);\n }\n\n syncState();\n trigger.addEventListener(\"click\", handleClick);\n\n return {\n open() {\n setOpen(true);\n },\n close() {\n setOpen(false);\n },\n toggle() {\n setOpen(!open);\n },\n isOpen() {\n return open;\n },\n destroy() {\n trigger.removeEventListener(\"click\", handleClick);\n },\n };\n}\n","/**\n * Shared DOM constants for client-side interaction primitives.\n */\n\n/** CSS selector for focusable elements within a container. */\nexport const FOCUSABLE =\n 'a[href], button:not([disabled]), input:not([disabled]):not([type=\"hidden\"]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([disabled]):not([tabindex=\"-1\"])';\n","/**\n * Shared tab activation primitive.\n *\n * Handles aria-selected, panel visibility, roving tabindex,\n * keyboard navigation, sliding indicator, and cross-instance sync.\n *\n * Used by: Tabs.astro, PackageManagers.astro\n */\n\nimport { FOCUSABLE } from \"./dom\";\n\nexport interface TabsConfig {\n /** Root container holding tabs + panels */\n container: Element;\n /** CSS selector for tab trigger buttons */\n tabSelector: string;\n /** CSS selector for tab panels */\n panelSelector: string;\n /** Optional sliding indicator element */\n indicator?: HTMLElement | null;\n /** Enable roving tabindex + arrow key navigation (default: true) */\n rovingTabindex?: boolean;\n /** Cross-instance persistence config */\n sync?: {\n key: string;\n storage?: \"local\" | \"session\";\n /** Extract sync label from a tab element. Default: textContent.trim() */\n getLabel?: (tab: HTMLElement) => string;\n };\n /** Called after a tab is activated */\n onActivate?: (index: number) => void;\n}\n\nexport interface TabsInstance {\n activate(index: number, options?: { emitSync?: boolean }): void;\n readonly currentIndex: number;\n destroy(): void;\n}\n\nexport function initTabs(config: TabsConfig): TabsInstance {\n const { container, tabSelector, panelSelector, indicator = null, rovingTabindex = true, sync, onActivate } = config;\n\n const tabs = Array.from(container.querySelectorAll<HTMLElement>(tabSelector));\n const panels = Array.from(container.querySelectorAll<HTMLElement>(panelSelector));\n const tablist = container.querySelector(\"[role=tablist]\") ?? container;\n\n let currentIndex = -1;\n let isInitialActivation = true;\n\n function getStorage(kind: \"local\" | \"session\"): Storage | null {\n try {\n return kind === \"session\" ? sessionStorage : localStorage;\n } catch {\n return null;\n }\n }\n\n function getLabel(tab: HTMLElement): string {\n return sync?.getLabel?.(tab) ?? tab.textContent?.trim() ?? \"\";\n }\n\n function updateIndicator(index: number) {\n if (!indicator || !tabs[index]) return;\n indicator.style.left = `${tabs[index].offsetLeft}px`;\n indicator.style.width = `${tabs[index].offsetWidth}px`;\n }\n\n function activate(index: number, options?: { emitSync?: boolean }) {\n const emitSync = options?.emitSync ?? true;\n if (index === currentIndex) return;\n\n // Capture scroll position before DOM changes to prevent layout jump\n const rect = container.getBoundingClientRect();\n const scrollBefore = rect.top;\n\n currentIndex = index;\n\n tabs.forEach((tab, i) => {\n const active = i === index;\n tab.setAttribute(\"aria-selected\", String(active));\n if (rovingTabindex) {\n tab.setAttribute(\"tabindex\", active ? \"0\" : \"-1\");\n }\n });\n\n panels.forEach((panel, i) => {\n const visible = i === index;\n panel.hidden = !visible;\n // Panels with no focusable children need tabindex=\"0\" so keyboard\n // users can Tab into the content (WAI-ARIA Tabs pattern).\n if (visible) {\n const hasFocusable = panel.querySelector(FOCUSABLE) !== null;\n if (!hasFocusable) {\n panel.setAttribute(\"tabindex\", \"0\");\n } else {\n panel.removeAttribute(\"tabindex\");\n }\n }\n });\n\n updateIndicator(index);\n onActivate?.(index);\n\n // Compensate scroll position after panel height change (skip on first paint)\n if (emitSync && !isInitialActivation) {\n const scrollAfter = container.getBoundingClientRect().top;\n const delta = scrollAfter - scrollBefore;\n if (Math.abs(delta) > 1) {\n window.scrollTo({\n top: window.scrollY + delta,\n behavior: \"instant\",\n });\n }\n }\n isInitialActivation = false;\n\n if (sync && emitSync) {\n const label = getLabel(tabs[index]);\n const store = getStorage(sync.storage === \"session\" ? \"session\" : \"local\");\n store?.setItem(sync.key, label);\n window.dispatchEvent(\n new CustomEvent(\"ui-tab-sync\", {\n detail: { key: sync.key, label, origin: container },\n }),\n );\n }\n }\n\n // Resolve initial index from sync storage\n let initialIndex = 0;\n if (sync) {\n const store = getStorage(sync.storage === \"session\" ? \"session\" : \"local\");\n const saved = store?.getItem(sync.key);\n if (saved) {\n const idx = tabs.findIndex((t) => getLabel(t) === saved);\n if (idx >= 0) initialIndex = idx;\n }\n }\n\n // Click delegation on tablist\n function handleClick(e: Event) {\n const target = (e.target as HTMLElement).closest(tabSelector);\n if (!target) return;\n const idx = tabs.indexOf(target as HTMLElement);\n if (idx >= 0) {\n activate(idx);\n if (rovingTabindex) (target as HTMLElement).focus();\n }\n }\n\n // Keyboard navigation (roving tabindex)\n function handleKeydown(e: KeyboardEvent) {\n if (!rovingTabindex) return;\n const ci = tabs.indexOf(e.target as HTMLElement);\n if (ci < 0) return;\n\n let next: number;\n switch (e.key) {\n case \"ArrowRight\":\n next = ci + 1;\n break;\n case \"ArrowLeft\":\n next = ci - 1;\n break;\n case \"Home\":\n next = 0;\n break;\n case \"End\":\n next = tabs.length - 1;\n break;\n default:\n return;\n }\n // No-wrap: ignore if out of bounds\n if (!tabs[next]) return;\n e.preventDefault();\n activate(next);\n tabs[next].focus();\n }\n\n // Cross-instance sync via CustomEvent\n function handleSync(e: Event) {\n const detail = (e as CustomEvent).detail;\n if (detail.key === sync?.key && detail.origin !== container) {\n const idx = tabs.findIndex((t) => getLabel(t) === detail.label);\n if (idx >= 0) activate(idx, { emitSync: false });\n }\n }\n\n // Wire events\n tablist.addEventListener(\"click\", handleClick);\n tablist.addEventListener(\"keydown\", handleKeydown as EventListener);\n if (sync) window.addEventListener(\"ui-tab-sync\", handleSync);\n\n // Initial activation — skip indicator transition for first paint\n if (indicator) indicator.style.transition = \"none\";\n activate(initialIndex);\n if (indicator) {\n void indicator.offsetHeight; // force reflow\n indicator.style.transition = \"\";\n }\n\n return {\n activate,\n get currentIndex() {\n return currentIndex;\n },\n destroy() {\n tablist.removeEventListener(\"click\", handleClick);\n tablist.removeEventListener(\"keydown\", handleKeydown as EventListener);\n if (sync) window.removeEventListener(\"ui-tab-sync\", handleSync);\n },\n };\n}\n","/**\n * scroll-lock.ts — Body scroll lock with scrollbar-width compensation.\n *\n * Prevents background scrolling when a modal/overlay is open.\n * Compensates for the scrollbar disappearing to avoid layout shift\n * (visible on Windows/Linux where scrollbars have width).\n *\n * Uses a data attribute + CSS for the overflow lock, and inline\n * paddingRight for the scrollbar compensation.\n *\n * Used by: Dialog (and any future overlay primitive).\n */\n\nconst ATTR = \"data-scroll-locked\";\n\nlet lockCount = 0;\nlet savedPaddingRight = \"\";\n\nexport function lockScroll(): void {\n lockCount++;\n if (lockCount > 1) return;\n\n const scrollbarW = window.innerWidth - document.documentElement.clientWidth;\n savedPaddingRight = document.body.style.paddingRight;\n\n document.body.setAttribute(ATTR, \"\");\n if (scrollbarW > 0) {\n document.body.style.paddingRight = `${scrollbarW}px`;\n }\n}\n\nexport function unlockScroll(): void {\n if (lockCount === 0) return;\n lockCount--;\n if (lockCount > 0) return;\n\n document.body.removeAttribute(ATTR);\n document.body.style.paddingRight = savedPaddingRight;\n}\n","/**\n * code-copy.ts — Injects a Nimbus-styled copy button into every Shiki\n * code block rendered by Astro's built-in `<Code>` and fenced\n * code blocks in MDX.\n *\n * Astro emits `<pre class=\"astro-code shiki ...\">` for syntax-highlighted\n * blocks. We attach a copy button positioned in the top-right corner.\n *\n * Page-wide enhancement, not tied to a single component. Call `codeCopy()`\n * once (e.g. from BaseLayout). Uses `mount` for lifecycle so the buttons\n * are torn down on view transitions and re-added against the new DOM.\n *\n * The original code text comes from the `<code>` element's textContent —\n * Shiki's wrapper spans flatten down to the raw source.\n *\n * Code blocks rendered inside [data-cg-row] (CodeGroup) are skipped\n * because CodeGroup brings its own copy button per panel.\n */\n\nimport { mount } from \"./mount\";\n\nconst COPY_ICON = `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 256 256\" fill=\"currentColor\" aria-hidden=\"true\"><path d=\"M216,32H88a8,8,0,0,0-8,8V80H40a8,8,0,0,0-8,8V216a8,8,0,0,0,8,8H168a8,8,0,0,0,8-8V176h40a8,8,0,0,0,8-8V40A8,8,0,0,0,216,32ZM160,208H48V96H160Zm48-48H176V88a8,8,0,0,0-8-8H96V48H208Z\"/></svg>`;\nconst CHECK_ICON = `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 256 256\" fill=\"currentColor\" aria-hidden=\"true\"><path d=\"M173.66,98.34a8,8,0,0,1,0,11.32l-56,56a8,8,0,0,1-11.32,0l-24-24a8,8,0,0,1,11.32-11.32L112,148.69l50.34-50.35A8,8,0,0,1,173.66,98.34Z\"/></svg>`;\n\nfunction getCodeText(pre: HTMLElement): string {\n const codeEl = pre.querySelector<HTMLElement>(\"code\");\n return codeEl?.textContent ?? pre.textContent ?? \"\";\n}\n\nfunction initCodeCopy(pre: HTMLElement): () => void {\n // Skip code blocks owned by CodeGroup — that component renders its own\n // copy button per panel.\n if (pre.closest(\"[data-cg-row]\")) return () => {};\n\n const btn = document.createElement(\"button\");\n btn.type = \"button\";\n btn.className = \"nb-code-copy\";\n btn.setAttribute(\"aria-label\", \"Copy code to clipboard\");\n btn.innerHTML = COPY_ICON;\n\n let resetTimer: number | undefined;\n\n async function handleClick() {\n const text = getCodeText(pre);\n if (!text) return;\n\n try {\n await navigator.clipboard.writeText(text);\n } catch {\n return;\n }\n\n btn.innerHTML = CHECK_ICON;\n btn.dataset.state = \"copied\";\n btn.setAttribute(\"aria-label\", \"Copied!\");\n\n if (resetTimer) window.clearTimeout(resetTimer);\n resetTimer = window.setTimeout(() => {\n btn.innerHTML = COPY_ICON;\n delete btn.dataset.state;\n btn.setAttribute(\"aria-label\", \"Copy code to clipboard\");\n }, 1500);\n }\n\n btn.addEventListener(\"click\", handleClick);\n\n // The button positions absolutely against the <pre>; make sure <pre> is\n // a positioning context.\n if (getComputedStyle(pre).position === \"static\") {\n pre.style.position = \"relative\";\n }\n pre.appendChild(btn);\n\n return () => {\n if (resetTimer) window.clearTimeout(resetTimer);\n btn.removeEventListener(\"click\", handleClick);\n btn.remove();\n };\n}\n\n/** Wire copy buttons onto all Shiki code blocks on the page. Call once. */\nexport function codeCopy(): void {\n mount(\"pre.astro-code\", initCodeCopy);\n}\n","/** Add hoverable self-links to markdown headings with ids. */\n\nfunction applyHeadingAnchors() {\n document.querySelectorAll<HTMLElement>(\".docs-content :is(h2, h3, h4)[id]\").forEach((heading) => {\n if (heading.hasAttribute(\"data-heading-anchor-ready\")) return;\n heading.setAttribute(\"data-heading-anchor-ready\", \"true\");\n\n const link = document.createElement(\"a\");\n link.href = `#${heading.id}`;\n link.className = \"heading-anchor\";\n link.setAttribute(\"aria-label\", `Link to ${heading.textContent?.trim() ?? \"section\"}`);\n link.textContent = \"#\";\n heading.appendChild(link);\n });\n}\n\n/**\n * Add hoverable `#` self-links to all `h2`–`h4` headings in `.docs-content`.\n * Re-runs on `astro:page-load` for View Transitions. Call once (e.g. from\n * BaseLayout).\n */\nexport function headingAnchors(): void {\n applyHeadingAnchors();\n document.addEventListener(\"astro:page-load\", applyHeadingAnchors);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.js","names":[],"sources":["../src/client/mount.ts","../src/client/ids.ts","../src/client/disclosure.ts","../src/client/dom.ts","../src/client/tabs-controller.ts","../src/client/scroll-lock.ts","../src/client/code-copy.ts","../src/client/heading-anchors.ts"],"sourcesContent":["/**\n * mount.ts — Discover, mount, and unmount component instances.\n *\n * The single entry point used by every `*.client.ts` to wire its component.\n * Handles three concerns:\n *\n * 1. Initial discovery — finds elements matching `selector` and calls `init`\n * on each, storing the returned teardown.\n * 2. View transitions — on `astro:before-swap`, runs every teardown so\n * document/window listeners come down before the DOM is replaced.\n * 3. Re-mount — on `astro:page-load`, re-runs discovery against the new DOM.\n *\n * The init function receives the root element and returns a `destroy()`\n * function. The root element is the keying mechanism — calling mount again\n * against an already-tracked element is a no-op.\n *\n * Usage:\n *\n * mount(\"[data-nb-collapsible]\", (root) => {\n * const disclosure = makeDisclosure({ ... });\n * return () => disclosure.destroy();\n * });\n */\n\ntype Init = (root: HTMLElement) => () => void;\n\nexport function mount(selector: string, init: Init): void {\n const instances = new Map<HTMLElement, () => void>();\n\n function setup() {\n document.querySelectorAll<HTMLElement>(selector).forEach((el) => {\n if (instances.has(el)) return;\n instances.set(el, init(el));\n });\n }\n\n function teardown() {\n instances.forEach((destroy) => destroy());\n instances.clear();\n }\n\n // Module scripts are deferred, so DOM is parsed by the time this runs.\n // Belt-and-braces check anyway.\n if (document.readyState === \"loading\") {\n document.addEventListener(\"DOMContentLoaded\", setup, { once: true });\n } else {\n setup();\n }\n\n document.addEventListener(\"astro:before-swap\", teardown);\n document.addEventListener(\"astro:page-load\", setup);\n}\n","/**\n * Generate a unique ID with a prefix, scoped to the page.\n *\n * Used to build ARIA relationships (`aria-controls` / `aria-labelledby`)\n * between elements that don't have a stable author-provided id.\n */\n\nlet counter = 0;\n\nexport function generateId(prefix: string): string {\n counter += 1;\n return `${prefix}-${counter}`;\n}\n","/**\n * disclosure.ts — Open/close state with ARIA wiring.\n *\n * The shared module for any \"click trigger, reveals content\" pattern.\n * Owns:\n * - open/closed state (in-memory + reflected as `data-nb-state` on both\n * trigger and content)\n * - ARIA: `aria-expanded` on trigger, `aria-controls` linking to content\n * - Click handler on the trigger\n *\n * Animation is intentionally out of scope — CSS targets the `data-nb-state`\n * attribute and runs whatever transition the component wants. Returning\n * a teardown means the caller can clean up on unmount.\n *\n * Used by: Collapsible, and any future Accordion / Sidebar group /\n * dismissable Banner that wants the standard disclosure semantics.\n */\n\nimport { generateId } from \"./ids\";\n\nexport interface DisclosureOptions {\n /** The element users click to toggle. Usually a `<button>`. */\n trigger: HTMLElement;\n /** The element that's shown/hidden. Gets `id` + `data-nb-state`. */\n content: HTMLElement;\n /** Initial open state. Default `false`. */\n defaultOpen?: boolean;\n /** Called whenever open changes. */\n onOpenChange?: (open: boolean) => void;\n}\n\nexport interface DisclosureInstance {\n open(): void;\n close(): void;\n toggle(): void;\n isOpen(): boolean;\n destroy(): void;\n}\n\nexport function makeDisclosure(opts: DisclosureOptions): DisclosureInstance {\n const { trigger, content, defaultOpen = false, onOpenChange } = opts;\n\n let open = defaultOpen;\n\n // Ensure ARIA relationship exists.\n if (!content.id) {\n content.id = generateId(\"nb-disclosure\");\n }\n trigger.setAttribute(\"aria-controls\", content.id);\n\n function syncState() {\n const state = open ? \"open\" : \"closed\";\n trigger.setAttribute(\"data-nb-state\", state);\n content.setAttribute(\"data-nb-state\", state);\n trigger.setAttribute(\"aria-expanded\", String(open));\n }\n\n function setOpen(value: boolean) {\n if (open === value) return;\n open = value;\n syncState();\n onOpenChange?.(value);\n }\n\n function handleClick(e: MouseEvent) {\n e.preventDefault();\n setOpen(!open);\n }\n\n syncState();\n trigger.addEventListener(\"click\", handleClick);\n\n return {\n open() {\n setOpen(true);\n },\n close() {\n setOpen(false);\n },\n toggle() {\n setOpen(!open);\n },\n isOpen() {\n return open;\n },\n destroy() {\n trigger.removeEventListener(\"click\", handleClick);\n },\n };\n}\n","/**\n * Shared DOM constants for client-side interaction primitives.\n */\n\n/** CSS selector for focusable elements within a container. */\nexport const FOCUSABLE =\n 'a[href], button:not([disabled]), input:not([disabled]):not([type=\"hidden\"]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([disabled]):not([tabindex=\"-1\"])';\n","/**\n * Shared tab activation primitive.\n *\n * Handles aria-selected, panel visibility, roving tabindex,\n * keyboard navigation, sliding indicator, and cross-instance sync.\n *\n * Used by: Tabs.astro, PackageManagers.astro\n */\n\nimport { FOCUSABLE } from \"./dom\";\n\nexport interface TabsConfig {\n /** Root container holding tabs + panels */\n container: Element;\n /** CSS selector for tab trigger buttons */\n tabSelector: string;\n /** CSS selector for tab panels */\n panelSelector: string;\n /** Optional sliding indicator element */\n indicator?: HTMLElement | null;\n /** Enable roving tabindex + arrow key navigation (default: true) */\n rovingTabindex?: boolean;\n /** Cross-instance persistence config */\n sync?: {\n key: string;\n storage?: \"local\" | \"session\";\n /** Extract sync label from a tab element. Default: textContent.trim() */\n getLabel?: (tab: HTMLElement) => string;\n };\n /** Called after a tab is activated */\n onActivate?: (index: number) => void;\n}\n\nexport interface TabsInstance {\n activate(index: number, options?: { emitSync?: boolean }): void;\n readonly currentIndex: number;\n destroy(): void;\n}\n\nexport function initTabs(config: TabsConfig): TabsInstance {\n const { container, tabSelector, panelSelector, indicator = null, rovingTabindex = true, sync, onActivate } = config;\n\n const tabs = Array.from(container.querySelectorAll<HTMLElement>(tabSelector));\n const panels = Array.from(container.querySelectorAll<HTMLElement>(panelSelector));\n const tablist = container.querySelector(\"[role=tablist]\") ?? container;\n\n let currentIndex = -1;\n let isInitialActivation = true;\n\n function getStorage(kind: \"local\" | \"session\"): Storage | null {\n try {\n return kind === \"session\" ? sessionStorage : localStorage;\n } catch {\n return null;\n }\n }\n\n function getLabel(tab: HTMLElement): string {\n return sync?.getLabel?.(tab) ?? tab.textContent?.trim() ?? \"\";\n }\n\n function updateIndicator(index: number) {\n if (!indicator || !tabs[index]) return;\n indicator.style.left = `${tabs[index].offsetLeft}px`;\n indicator.style.width = `${tabs[index].offsetWidth}px`;\n }\n\n function activate(index: number, options?: { emitSync?: boolean }) {\n const emitSync = options?.emitSync ?? true;\n if (index === currentIndex) return;\n\n // Capture scroll position before DOM changes to prevent layout jump\n const rect = container.getBoundingClientRect();\n const scrollBefore = rect.top;\n\n currentIndex = index;\n\n tabs.forEach((tab, i) => {\n const active = i === index;\n tab.setAttribute(\"aria-selected\", String(active));\n if (rovingTabindex) {\n tab.setAttribute(\"tabindex\", active ? \"0\" : \"-1\");\n }\n });\n\n panels.forEach((panel, i) => {\n const visible = i === index;\n panel.hidden = !visible;\n // Panels with no focusable children need tabindex=\"0\" so keyboard\n // users can Tab into the content (WAI-ARIA Tabs pattern).\n if (visible) {\n const hasFocusable = panel.querySelector(FOCUSABLE) !== null;\n if (!hasFocusable) {\n panel.setAttribute(\"tabindex\", \"0\");\n } else {\n panel.removeAttribute(\"tabindex\");\n }\n }\n });\n\n updateIndicator(index);\n onActivate?.(index);\n\n // Compensate scroll position after panel height change (skip on first paint)\n if (emitSync && !isInitialActivation) {\n const scrollAfter = container.getBoundingClientRect().top;\n const delta = scrollAfter - scrollBefore;\n if (Math.abs(delta) > 1) {\n window.scrollTo({\n top: window.scrollY + delta,\n behavior: \"instant\",\n });\n }\n }\n isInitialActivation = false;\n\n if (sync && emitSync) {\n const label = getLabel(tabs[index]);\n const store = getStorage(sync.storage === \"session\" ? \"session\" : \"local\");\n store?.setItem(sync.key, label);\n window.dispatchEvent(\n new CustomEvent(\"ui-tab-sync\", {\n detail: { key: sync.key, label, origin: container },\n }),\n );\n }\n }\n\n // Resolve initial index from sync storage\n let initialIndex = 0;\n if (sync) {\n const store = getStorage(sync.storage === \"session\" ? \"session\" : \"local\");\n const saved = store?.getItem(sync.key);\n if (saved) {\n const idx = tabs.findIndex((t) => getLabel(t) === saved);\n if (idx >= 0) initialIndex = idx;\n }\n }\n\n // Click delegation on tablist\n function handleClick(e: Event) {\n const target = (e.target as HTMLElement).closest(tabSelector);\n if (!target) return;\n const idx = tabs.indexOf(target as HTMLElement);\n if (idx >= 0) {\n activate(idx);\n if (rovingTabindex) (target as HTMLElement).focus();\n }\n }\n\n // Keyboard navigation (roving tabindex)\n function handleKeydown(e: KeyboardEvent) {\n if (!rovingTabindex) return;\n const ci = tabs.indexOf(e.target as HTMLElement);\n if (ci < 0) return;\n\n let next: number;\n switch (e.key) {\n case \"ArrowRight\":\n next = ci + 1;\n break;\n case \"ArrowLeft\":\n next = ci - 1;\n break;\n case \"Home\":\n next = 0;\n break;\n case \"End\":\n next = tabs.length - 1;\n break;\n default:\n return;\n }\n // No-wrap: ignore if out of bounds\n if (!tabs[next]) return;\n e.preventDefault();\n activate(next);\n tabs[next].focus();\n }\n\n // Cross-instance sync via CustomEvent\n function handleSync(e: Event) {\n const detail = (e as CustomEvent).detail;\n if (detail.key === sync?.key && detail.origin !== container) {\n const idx = tabs.findIndex((t) => getLabel(t) === detail.label);\n if (idx >= 0) activate(idx, { emitSync: false });\n }\n }\n\n // Wire events\n tablist.addEventListener(\"click\", handleClick);\n tablist.addEventListener(\"keydown\", handleKeydown as EventListener);\n if (sync) window.addEventListener(\"ui-tab-sync\", handleSync);\n\n // Initial activation — skip indicator transition for first paint\n if (indicator) indicator.style.transition = \"none\";\n activate(initialIndex);\n if (indicator) {\n void indicator.offsetHeight; // force reflow\n indicator.style.transition = \"\";\n }\n\n return {\n activate,\n get currentIndex() {\n return currentIndex;\n },\n destroy() {\n tablist.removeEventListener(\"click\", handleClick);\n tablist.removeEventListener(\"keydown\", handleKeydown as EventListener);\n if (sync) window.removeEventListener(\"ui-tab-sync\", handleSync);\n },\n };\n}\n","/**\n * scroll-lock.ts — Body scroll lock with scrollbar-width compensation.\n *\n * Prevents background scrolling when a modal/overlay is open.\n * Compensates for the scrollbar disappearing to avoid layout shift\n * (visible on Windows/Linux where scrollbars have width).\n *\n * Uses a data attribute + CSS for the overflow lock, and inline\n * paddingRight for the scrollbar compensation.\n *\n * Used by: Dialog (and any future overlay primitive).\n */\n\nconst ATTR = \"data-scroll-locked\";\n\nlet lockCount = 0;\nlet savedPaddingRight = \"\";\n\nexport function lockScroll(): void {\n lockCount++;\n if (lockCount > 1) return;\n\n const scrollbarW = window.innerWidth - document.documentElement.clientWidth;\n savedPaddingRight = document.body.style.paddingRight;\n\n document.body.setAttribute(ATTR, \"\");\n if (scrollbarW > 0) {\n document.body.style.paddingRight = `${scrollbarW}px`;\n }\n}\n\nexport function unlockScroll(): void {\n if (lockCount === 0) return;\n lockCount--;\n if (lockCount > 0) return;\n\n document.body.removeAttribute(ATTR);\n document.body.style.paddingRight = savedPaddingRight;\n}\n","/**\n * code-copy.ts — Injects a Nimbus-styled copy button into every Shiki\n * code block rendered by Astro's built-in `<Code>` and fenced\n * code blocks in MDX.\n *\n * Astro emits `<pre class=\"astro-code shiki ...\">` for syntax-highlighted\n * blocks. We attach a copy button positioned in the top-right corner.\n *\n * Page-wide enhancement, not tied to a single component. Call `codeCopy()`\n * once (e.g. from BaseLayout). Uses `mount` for lifecycle so the buttons\n * are torn down on view transitions and re-added against the new DOM.\n *\n * The original code text comes from the `<code>` element's textContent —\n * Shiki's wrapper spans flatten down to the raw source.\n *\n * Code blocks rendered inside [data-cg-row] (CodeGroup) are skipped\n * because CodeGroup brings its own copy button per panel.\n */\n\nimport { mount } from \"./mount\";\n\nconst COPY_ICON = `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 256 256\" fill=\"currentColor\" aria-hidden=\"true\"><path d=\"M216,32H88a8,8,0,0,0-8,8V80H40a8,8,0,0,0-8,8V216a8,8,0,0,0,8,8H168a8,8,0,0,0,8-8V176h40a8,8,0,0,0,8-8V40A8,8,0,0,0,216,32ZM160,208H48V96H160Zm48-48H176V88a8,8,0,0,0-8-8H96V48H208Z\"/></svg>`;\nconst CHECK_ICON = `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 256 256\" fill=\"currentColor\" aria-hidden=\"true\"><path d=\"M173.66,98.34a8,8,0,0,1,0,11.32l-56,56a8,8,0,0,1-11.32,0l-24-24a8,8,0,0,1,11.32-11.32L112,148.69l50.34-50.35A8,8,0,0,1,173.66,98.34Z\"/></svg>`;\n\nfunction getCodeText(pre: HTMLElement): string {\n const codeEl = pre.querySelector<HTMLElement>(\"code\");\n return codeEl?.textContent ?? pre.textContent ?? \"\";\n}\n\nfunction initCodeCopy(pre: HTMLElement): () => void {\n // Skip code blocks owned by CodeGroup. `data-cg-panels-raw` is SSR'd\n // (catches pres before CodeGroup's client script reparents them);\n // `data-cg-row` catches them after reparenting.\n if (pre.closest(\"[data-cg-panels-raw], [data-cg-row]\")) return () => {};\n\n // Append the button to the figure wrapper, not the pre. The pre has\n // `overflow-x: auto`, and absolutely-positioned children of overflow:auto\n // containers slide along with horizontal scroll on iOS Safari. The figure\n // is the non-scrolling wrapper emitted by titleAndLangTransformer.\n const host = (pre.closest(\".nb-code-figure\") as HTMLElement | null) ?? pre;\n\n const btn = document.createElement(\"button\");\n btn.type = \"button\";\n btn.className = \"nb-code-copy\";\n btn.setAttribute(\"aria-label\", \"Copy code to clipboard\");\n btn.innerHTML = COPY_ICON;\n\n let resetTimer: number | undefined;\n\n async function handleClick() {\n const text = getCodeText(pre);\n if (!text) return;\n\n try {\n await navigator.clipboard.writeText(text);\n } catch {\n return;\n }\n\n btn.innerHTML = CHECK_ICON;\n btn.dataset.state = \"copied\";\n btn.setAttribute(\"aria-label\", \"Copied!\");\n\n if (resetTimer) window.clearTimeout(resetTimer);\n resetTimer = window.setTimeout(() => {\n btn.innerHTML = COPY_ICON;\n delete btn.dataset.state;\n btn.setAttribute(\"aria-label\", \"Copy code to clipboard\");\n }, 1500);\n }\n\n btn.addEventListener(\"click\", handleClick);\n\n if (getComputedStyle(host).position === \"static\") {\n host.style.position = \"relative\";\n }\n host.appendChild(btn);\n\n return () => {\n if (resetTimer) window.clearTimeout(resetTimer);\n btn.removeEventListener(\"click\", handleClick);\n btn.remove();\n };\n}\n\n/** Wire copy buttons onto all Shiki code blocks on the page. Call once. */\nexport function codeCopy(): void {\n mount(\"pre.astro-code\", initCodeCopy);\n}\n","/** Add hoverable self-links to markdown headings with ids. */\n\nfunction applyHeadingAnchors() {\n document.querySelectorAll<HTMLElement>(\".docs-content :is(h2, h3, h4)[id]\").forEach((heading) => {\n if (heading.hasAttribute(\"data-heading-anchor-ready\")) return;\n heading.setAttribute(\"data-heading-anchor-ready\", \"true\");\n\n const link = document.createElement(\"a\");\n link.href = `#${heading.id}`;\n link.className = \"heading-anchor\";\n link.setAttribute(\"aria-label\", `Link to ${heading.textContent?.trim() ?? \"section\"}`);\n link.textContent = \"#\";\n heading.appendChild(link);\n });\n}\n\n/**\n * Add hoverable `#` self-links to all `h2`–`h4` headings in `.docs-content`.\n * Re-runs on `astro:page-load` for View Transitions. Call once (e.g. from\n * BaseLayout).\n */\nexport function headingAnchors(): void {\n applyHeadingAnchors();\n document.addEventListener(\"astro:page-load\", applyHeadingAnchors);\n}\n"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -832,36 +832,38 @@ function titleAndLangTransformer() {
832
832
  const title = parseTitle(meta);
833
833
  preNode.properties = preNode.properties ?? {};
834
834
  preNode.properties["data-nb-lang"] = lang;
835
- if (!title) return preNode;
835
+ const children = [];
836
+ if (title) children.push({
837
+ type: "element",
838
+ tagName: "figcaption",
839
+ properties: { class: "nb-code-title" },
840
+ children: [{
841
+ type: "element",
842
+ tagName: "span",
843
+ properties: { class: "nb-code-title-name" },
844
+ children: [{
845
+ type: "text",
846
+ value: title
847
+ }]
848
+ }, {
849
+ type: "element",
850
+ tagName: "span",
851
+ properties: { class: "nb-code-title-lang" },
852
+ children: [{
853
+ type: "text",
854
+ value: lang
855
+ }]
856
+ }]
857
+ });
858
+ children.push(preNode);
836
859
  return {
837
860
  type: "element",
838
861
  tagName: "figure",
839
862
  properties: {
840
- class: "nb-code-figure",
863
+ class: title ? "nb-code-figure nb-code-figure-titled" : "nb-code-figure",
841
864
  "data-nb-lang": lang
842
865
  },
843
- children: [{
844
- type: "element",
845
- tagName: "figcaption",
846
- properties: { class: "nb-code-title" },
847
- children: [{
848
- type: "element",
849
- tagName: "span",
850
- properties: { class: "nb-code-title-name" },
851
- children: [{
852
- type: "text",
853
- value: title
854
- }]
855
- }, {
856
- type: "element",
857
- tagName: "span",
858
- properties: { class: "nb-code-title-lang" },
859
- children: [{
860
- type: "text",
861
- value: lang
862
- }]
863
- }]
864
- }, preNode]
866
+ children
865
867
  };
866
868
  }
867
869
  };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["PRIMARY_COLLECTION","getBreadcrumbs","getPrevNext","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/code-transformers.ts","../src/_internal/levenshtein.ts","../src/_internal/validate-mdx-content.ts","../src/_internal/validate.ts","../src/_internal/virtual-config.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\";\n\ndeclare module \"virtual:nimbus/config\" {\n export const config: NimbusConfig;\n}\n\nlet _cached: NimbusConfig | 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 * 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): 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);\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 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. Primary collection mounts at root\n // (no prefix) by convention; 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 ? \"\" : `/${collectionName}`);\n autoItems = buildFilesystemTree(\n collectionEntries,\n currentPath,\n undefined,\n 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 );\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 };\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 );\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 href: 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): 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 );\n } else {\n // Filesystem fallback — primary collection only. Cross-collection\n // sidebars require explicit config items.\n items = buildFilesystemTree(primaryEntries, currentPath);\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 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 * 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 if (!title) return preNode;\n\n // Wrap in a <figure> with a <figcaption> header bar.\n return {\n type: \"element\",\n tagName: \"figure\",\n properties: {\n class: \"nb-code-figure\",\n \"data-nb-lang\": lang,\n },\n children: [\n {\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 preNode,\n ],\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\";\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\nconst featuresSchema = z\n .object({\n search: z.boolean().default(true),\n editLinks: z.boolean().default(true),\n pagination: z.boolean().default(true),\n toc: z.boolean().default(true),\n })\n .default({ search: true, editLinks: true, pagination: true, toc: 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 })\n .passthrough()\n .optional();\n\nconst nimbusConfigSchema = z.object({\n site: z.string().url({ message: '\"site\" must be a valid URL' }),\n title: z.string(),\n description: z.string().optional(),\n logo: z.string().max(2),\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 footer: z.string().default(\"Built with Nimbus\"),\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});\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 } 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.\n */\n\nimport type { NimbusConfig } from \"../types.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 function virtualConfigPlugin(config: NimbusConfig): 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 `export const config = ${JSON.stringify(config)};\\n`;\n }\n return undefined;\n },\n };\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 { 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 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 // 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 // Vite plugin exposing the validated config to user-land via\n // the `virtual:nimbus/config` import.\n vite: {\n plugins: [virtualConfigPlugin(config)],\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 } from \"nimbus-docs/types\";',\n \" export const config: NimbusConfig;\",\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 { loadNimbusConfig } 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 SidebarItem,\n SidebarSection,\n TOCItem,\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 SearchProvider,\n SearchResult,\n SidebarBadge,\n SidebarConfig,\n SidebarConfigItem,\n SidebarExternalLinkItem,\n SidebarGroupItem,\n SidebarItem,\n SidebarLinkItem,\n SidebarSection,\n TOCItem,\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// 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 * The returned tree is always scoped: only the current section's children\n * are returned. To enumerate every top-level section (for header tabs or\n * a section switcher), use `getSidebarSections`.\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.\n */\nexport async function getSidebar(currentSlug: string): Promise<SidebarItem[]> {\n const tree = await buildFullSidebarTree(currentSlug);\n return scopeToCurrentSection(tree, currentSlug);\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 */\nexport async function getSidebarSections(currentSlug: string): Promise<SidebarSection[]> {\n const tree = await buildFullSidebarTree(currentSlug);\n return deriveSidebarSections(tree);\n}\n\n/**\n * Internal: build the un-scoped sidebar tree. Shared by `getSidebar` and\n * `getSidebarSections`.\n */\nasync function buildFullSidebarTree(currentSlug: string): Promise<SidebarItem[]> {\n const runtimeConfig = await loadNimbusConfig();\n // Collections to load are derived from sidebar items: always the\n // primary (`docs`) plus any extra collection named by an\n // `autogenerate: { collection }` reference. No separate config list.\n const referenced = collectSidebarCollectionRefs(runtimeConfig.sidebar?.items);\n const collections = [\n PRIMARY_COLLECTION,\n ...referenced.filter((c) => c !== PRIMARY_COLLECTION),\n ];\n const entriesByCollection = await getVisibleEntriesByCollection(collections);\n return buildSidebarTree(\n entriesByCollection,\n PRIMARY_COLLECTION,\n currentSlug,\n runtimeConfig.sidebar,\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 */\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 return buildPrevNext(currentSlug, tree, options?.overrides);\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"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["PRIMARY_COLLECTION","getBreadcrumbs","getPrevNext","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/code-transformers.ts","../src/_internal/levenshtein.ts","../src/_internal/validate-mdx-content.ts","../src/_internal/validate.ts","../src/_internal/virtual-config.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\";\n\ndeclare module \"virtual:nimbus/config\" {\n export const config: NimbusConfig;\n}\n\nlet _cached: NimbusConfig | 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 * 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): 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);\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 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. Primary collection mounts at root\n // (no prefix) by convention; 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 ? \"\" : `/${collectionName}`);\n autoItems = buildFilesystemTree(\n collectionEntries,\n currentPath,\n undefined,\n 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 );\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 };\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 );\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 href: 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): 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 );\n } else {\n // Filesystem fallback — primary collection only. Cross-collection\n // sidebars require explicit config items.\n items = buildFilesystemTree(primaryEntries, currentPath);\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 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 * 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\";\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\nconst featuresSchema = z\n .object({\n search: z.boolean().default(true),\n editLinks: z.boolean().default(true),\n pagination: z.boolean().default(true),\n toc: z.boolean().default(true),\n })\n .default({ search: true, editLinks: true, pagination: true, toc: 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 })\n .passthrough()\n .optional();\n\nconst nimbusConfigSchema = z.object({\n site: z.string().url({ message: '\"site\" must be a valid URL' }),\n title: z.string(),\n description: z.string().optional(),\n logo: z.string().max(2),\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 footer: z.string().default(\"Built with Nimbus\"),\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});\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 } 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.\n */\n\nimport type { NimbusConfig } from \"../types.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 function virtualConfigPlugin(config: NimbusConfig): 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 `export const config = ${JSON.stringify(config)};\\n`;\n }\n return undefined;\n },\n };\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 { 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 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 // 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 // Vite plugin exposing the validated config to user-land via\n // the `virtual:nimbus/config` import.\n vite: {\n plugins: [virtualConfigPlugin(config)],\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 } from \"nimbus-docs/types\";',\n \" export const config: NimbusConfig;\",\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 { loadNimbusConfig } 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 SidebarItem,\n SidebarSection,\n TOCItem,\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 SearchProvider,\n SearchResult,\n SidebarBadge,\n SidebarConfig,\n SidebarConfigItem,\n SidebarExternalLinkItem,\n SidebarGroupItem,\n SidebarItem,\n SidebarLinkItem,\n SidebarSection,\n TOCItem,\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// 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 * The returned tree is always scoped: only the current section's children\n * are returned. To enumerate every top-level section (for header tabs or\n * a section switcher), use `getSidebarSections`.\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.\n */\nexport async function getSidebar(currentSlug: string): Promise<SidebarItem[]> {\n const tree = await buildFullSidebarTree(currentSlug);\n return scopeToCurrentSection(tree, currentSlug);\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 */\nexport async function getSidebarSections(currentSlug: string): Promise<SidebarSection[]> {\n const tree = await buildFullSidebarTree(currentSlug);\n return deriveSidebarSections(tree);\n}\n\n/**\n * Internal: build the un-scoped sidebar tree. Shared by `getSidebar` and\n * `getSidebarSections`.\n */\nasync function buildFullSidebarTree(currentSlug: string): Promise<SidebarItem[]> {\n const runtimeConfig = await loadNimbusConfig();\n // Collections to load are derived from sidebar items: always the\n // primary (`docs`) plus any extra collection named by an\n // `autogenerate: { collection }` reference. No separate config list.\n const referenced = collectSidebarCollectionRefs(runtimeConfig.sidebar?.items);\n const collections = [\n PRIMARY_COLLECTION,\n ...referenced.filter((c) => c !== PRIMARY_COLLECTION),\n ];\n const entriesByCollection = await getVisibleEntriesByCollection(collections);\n return buildSidebarTree(\n entriesByCollection,\n PRIMARY_COLLECTION,\n currentSlug,\n runtimeConfig.sidebar,\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 */\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 return buildPrevNext(currentSlug, tree, options?.overrides);\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"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nimbus-docs",
3
- "version": "0.0.2",
3
+ "version": "0.1.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Docs, for humans and agents",
@@ -23,7 +23,7 @@
23
23
  "license": "MIT",
24
24
  "author": "Mohamed Hassan",
25
25
  "bin": {
26
- "nimbus": "./dist/cli/index.js"
26
+ "nimbus-docs": "./dist/cli/index.js"
27
27
  },
28
28
  "exports": {
29
29
  ".": {
@@ -56,11 +56,6 @@
56
56
  "dist",
57
57
  "src/components"
58
58
  ],
59
- "scripts": {
60
- "build": "tsdown",
61
- "dev": "tsdown --watch",
62
- "typecheck": "tsc --noEmit"
63
- },
64
59
  "peerDependencies": {
65
60
  "astro": ">=6.4.0"
66
61
  },
@@ -77,5 +72,10 @@
77
72
  "astro": "^6.4.0",
78
73
  "tsdown": "^0.20.3",
79
74
  "typescript": "^5.8.3"
75
+ },
76
+ "scripts": {
77
+ "build": "tsdown",
78
+ "dev": "tsdown --watch",
79
+ "typecheck": "tsc --noEmit"
80
80
  }
81
- }
81
+ }