blume 0.6.6 → 0.7.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.
Files changed (109) hide show
  1. package/dist/cli/index.js +1180 -739
  2. package/dist/cli/index.js.map +52 -51
  3. package/dist/types/core/base-path.d.ts +38 -0
  4. package/dist/types/core/config-input.d.ts +74 -10
  5. package/dist/types/core/config.d.ts +3 -2
  6. package/dist/types/core/data.d.ts +2 -0
  7. package/dist/types/core/i18n-ui.d.ts +1 -3
  8. package/dist/types/core/schema.d.ts +95 -52
  9. package/dist/types/core/sources/types.d.ts +2 -0
  10. package/dist/types/core/types.d.ts +6 -1
  11. package/docs/02-deployment.mdx +16 -1
  12. package/docs/03-faq.mdx +8 -8
  13. package/docs/configuration/index.mdx +6 -0
  14. package/docs/content/components.mdx +29 -2
  15. package/docs/content/islands.mdx +8 -0
  16. package/docs/content/syntax.mdx +13 -0
  17. package/package.json +2 -1
  18. package/src/ai/agent-readability.ts +7 -2
  19. package/src/ai/ask.ts +12 -7
  20. package/src/ai/llms.ts +15 -4
  21. package/src/ai/mcp/data.ts +8 -4
  22. package/src/ai/mcp/server.ts +3 -0
  23. package/src/astro/component-slots.ts +5 -3
  24. package/src/astro/examples.ts +12 -7
  25. package/src/astro/generate.ts +317 -144
  26. package/src/astro/index.ts +5 -1
  27. package/src/astro/integration.ts +8 -4
  28. package/src/astro/islands.ts +11 -5
  29. package/src/astro/markdown-negotiation.ts +1 -1
  30. package/src/astro/pages.ts +8 -3
  31. package/src/astro/templates.ts +166 -19
  32. package/src/cli/commands/build.ts +32 -19
  33. package/src/cli/commands/dev.ts +48 -15
  34. package/src/cli/commands/doctor.ts +2 -2
  35. package/src/cli/commands/validate.ts +1 -0
  36. package/src/cli/dev-lock.ts +26 -15
  37. package/src/cli/required-secrets.ts +2 -1
  38. package/src/components/content/CodeBlock.astro +3 -0
  39. package/src/components/content/Component.astro +30 -16
  40. package/src/components/content/Diff.astro +3 -1
  41. package/src/components/content/Update.astro +1 -1
  42. package/src/components/content/auto-type-table.ts +18 -8
  43. package/src/components/content/diff.ts +12 -6
  44. package/src/components/content/mermaid-element.ts +3 -0
  45. package/src/components/index.ts +23 -1
  46. package/src/components/islands/ask-ai.tsx +12 -6
  47. package/src/components/islands/base-path.ts +28 -0
  48. package/src/components/islands/hooks.ts +16 -1
  49. package/src/components/layout/Banner.astro +2 -1
  50. package/src/components/layout/Breadcrumbs.astro +2 -1
  51. package/src/components/layout/Favicon.astro +3 -2
  52. package/src/components/layout/Header.astro +2 -1
  53. package/src/components/layout/LanguageSwitcher.astro +2 -1
  54. package/src/components/layout/Logo.astro +2 -1
  55. package/src/components/layout/NavSelector.astro +2 -1
  56. package/src/components/layout/NavTree.astro +5 -4
  57. package/src/components/layout/PageFeedback.astro +4 -1
  58. package/src/components/layout/PageLayout.astro +9 -4
  59. package/src/components/layout/Pagination.astro +3 -2
  60. package/src/components/layout/RootLayout.astro +7 -4
  61. package/src/components/layout/Search.astro +13 -5
  62. package/src/components/layout/nav-utils.ts +18 -10
  63. package/src/components/layout/search/pagefind.ts +3 -0
  64. package/src/components/layout/toc-element.ts +7 -1
  65. package/src/components/openapi/RequestPanel.astro +7 -1
  66. package/src/components/openapi/snippets.ts +25 -11
  67. package/src/core/base-path.ts +70 -0
  68. package/src/core/component-overrides.ts +103 -74
  69. package/src/core/config-input.ts +81 -15
  70. package/src/core/config.ts +5 -3
  71. package/src/core/content.ts +2 -0
  72. package/src/core/data.ts +2 -0
  73. package/src/core/diagnostics.ts +54 -34
  74. package/src/core/gitignore.ts +4 -1
  75. package/src/core/graph.ts +156 -88
  76. package/src/core/i18n-ui.ts +18 -3
  77. package/src/core/last-modified.ts +2 -0
  78. package/src/core/links.ts +38 -18
  79. package/src/core/manifest.ts +62 -45
  80. package/src/core/nav-diagnostics.ts +1 -1
  81. package/src/core/navigation.ts +116 -55
  82. package/src/core/project-graph.ts +10 -9
  83. package/src/core/schema.ts +572 -621
  84. package/src/core/sources/github-releases.ts +2 -1
  85. package/src/core/sources/mdx-remote.ts +58 -54
  86. package/src/core/sources/normalize.ts +116 -73
  87. package/src/core/sources/notion.ts +19 -10
  88. package/src/core/sources/types.ts +2 -0
  89. package/src/core/tsconfig-aliases.ts +59 -30
  90. package/src/core/types.ts +6 -1
  91. package/src/deploy/redirects.ts +18 -0
  92. package/src/deploy/robots.ts +6 -1
  93. package/src/deploy/rss.ts +10 -3
  94. package/src/deploy/sitemap.ts +14 -10
  95. package/src/markdown/base-links.ts +58 -0
  96. package/src/markdown/code-title.ts +11 -14
  97. package/src/markdown/index.ts +34 -9
  98. package/src/markdown/inline-code.ts +7 -2
  99. package/src/markdown/themes.ts +24 -0
  100. package/src/openapi/model.ts +3 -1
  101. package/src/openapi/references.ts +41 -17
  102. package/src/openapi/render-mdx.ts +11 -6
  103. package/src/openapi/scalar.ts +32 -16
  104. package/src/registry/eject.ts +64 -8
  105. package/src/search/build.ts +3 -0
  106. package/src/search/documents.ts +2 -2
  107. package/src/search/sync/typesense.ts +6 -4
  108. package/src/seo/jsonld.ts +16 -6
  109. package/src/theme/entry.ts +86 -21
package/dist/cli/index.js CHANGED
@@ -293,34 +293,38 @@ class BlumeError extends Error {
293
293
  }
294
294
  }
295
295
  var DOCS_BASE = "https://useblume.dev";
296
+ var DOCS_DEPLOYMENT = "/docs/deployment";
297
+ var DOCS_REFERENCE_CLI = "/docs/reference/cli";
298
+ var DOCS_CONTENT_SOURCES = "/docs/content/sources";
299
+ var DOCS_CONTENT_NAVIGATION = "/docs/content/navigation";
296
300
  var DOCS_PATHS = {
297
- BLUME_ADAPTER_REQUIRED: "/docs/deployment",
298
- BLUME_ASSETS_UNCHECKED: "/docs/reference/cli",
299
- BLUME_ASSET_FETCH_FAILED: "/docs/content/sources",
300
- BLUME_BROKEN_ANCHOR: "/docs/reference/cli",
301
- BLUME_BROKEN_ASSET: "/docs/reference/cli",
302
- BLUME_BROKEN_LINK: "/docs/reference/cli",
301
+ BLUME_ADAPTER_REQUIRED: DOCS_DEPLOYMENT,
302
+ BLUME_ASSETS_UNCHECKED: DOCS_REFERENCE_CLI,
303
+ BLUME_ASSET_FETCH_FAILED: DOCS_CONTENT_SOURCES,
304
+ BLUME_BROKEN_ANCHOR: DOCS_REFERENCE_CLI,
305
+ BLUME_BROKEN_ASSET: DOCS_REFERENCE_CLI,
306
+ BLUME_BROKEN_LINK: DOCS_REFERENCE_CLI,
303
307
  BLUME_CONFIG_INVALID: "/docs/configuration",
304
308
  BLUME_CONFIG_LOAD_FAILED: "/docs/configuration",
305
- BLUME_CONTENT_ROOT_MISSING: "/docs/content/sources",
306
- BLUME_DEAD_LINK: "/docs/reference/cli",
307
- BLUME_DUPLICATE_ROUTE: "/docs/content/navigation",
309
+ BLUME_CONTENT_ROOT_MISSING: DOCS_CONTENT_SOURCES,
310
+ BLUME_DEAD_LINK: DOCS_REFERENCE_CLI,
311
+ BLUME_DUPLICATE_ROUTE: DOCS_CONTENT_NAVIGATION,
308
312
  BLUME_FRONTMATTER_INVALID: "/docs/reference/frontmatter",
309
313
  BLUME_META_INVALID: "/docs/content/meta",
310
314
  BLUME_META_LOAD_FAILED: "/docs/content/meta",
311
- BLUME_MISSING_SECRET: "/docs/deployment",
312
- BLUME_NAV_DUPLICATE_LABEL: "/docs/content/navigation",
313
- BLUME_NAV_HIDDEN_IN_SIDEBAR: "/docs/content/navigation",
314
- BLUME_NAV_MISSING_PAGE: "/docs/content/navigation",
315
+ BLUME_MISSING_SECRET: DOCS_DEPLOYMENT,
316
+ BLUME_NAV_DUPLICATE_LABEL: DOCS_CONTENT_NAVIGATION,
317
+ BLUME_NAV_HIDDEN_IN_SIDEBAR: DOCS_CONTENT_NAVIGATION,
318
+ BLUME_NAV_MISSING_PAGE: DOCS_CONTENT_NAVIGATION,
315
319
  BLUME_NODE_VERSION: "/docs/quickstart",
316
- BLUME_SERVER_FEATURE_REQUIRED: "/docs/deployment",
317
- BLUME_SOURCE_FETCH_FAILED: "/docs/content/sources",
318
- BLUME_SOURCE_MISCONFIGURED: "/docs/content/sources",
319
- BLUME_SOURCE_OFFLINE: "/docs/content/sources",
320
- BLUME_SOURCE_SDK_MISSING: "/docs/content/sources",
321
- BLUME_SOURCE_UNAVAILABLE: "/docs/content/sources",
320
+ BLUME_SERVER_FEATURE_REQUIRED: DOCS_DEPLOYMENT,
321
+ BLUME_SOURCE_FETCH_FAILED: DOCS_CONTENT_SOURCES,
322
+ BLUME_SOURCE_MISCONFIGURED: DOCS_CONTENT_SOURCES,
323
+ BLUME_SOURCE_OFFLINE: DOCS_CONTENT_SOURCES,
324
+ BLUME_SOURCE_SDK_MISSING: DOCS_CONTENT_SOURCES,
325
+ BLUME_SOURCE_UNAVAILABLE: DOCS_CONTENT_SOURCES,
322
326
  BLUME_UNKNOWN_COMPONENT: "/docs/configuration/customization",
323
- BLUME_UNKNOWN_ICON: "/docs/content/navigation"
327
+ BLUME_UNKNOWN_ICON: DOCS_CONTENT_NAVIGATION
324
328
  };
325
329
  var resolveDocsUrl = (code) => {
326
330
  const path = DOCS_PATHS[code];
@@ -329,21 +333,30 @@ var resolveDocsUrl = (code) => {
329
333
  var enrichDiagnostic = (diagnostic) => diagnostic.docsUrl ? diagnostic : { ...diagnostic, docsUrl: resolveDocsUrl(diagnostic.code) };
330
334
  var REGEXP_SPECIAL = /[$()*+.?[\\\]^{|}]/gu;
331
335
  var escapeRegExp = (value) => value.replaceAll(REGEXP_SPECIAL, String.raw`\$&`);
336
+ var stepSegment = (source, segment, cursor) => {
337
+ if (typeof segment !== "string") {
338
+ return { index: -1, next: cursor, stop: false };
339
+ }
340
+ const matcher = new RegExp(`(?<![\\w$])${escapeRegExp(segment)}\\s*[:=]`, "gu");
341
+ matcher.lastIndex = cursor;
342
+ const match = matcher.exec(source);
343
+ if (!match) {
344
+ return { index: -1, next: cursor, stop: true };
345
+ }
346
+ return { index: match.index, next: matcher.lastIndex, stop: false };
347
+ };
332
348
  var locatePath = (source, path) => {
333
349
  let cursor = 0;
334
350
  let found = -1;
335
351
  for (const segment of path) {
336
- if (typeof segment !== "string") {
337
- continue;
338
- }
339
- const matcher = new RegExp(`(?<![\\w$])${escapeRegExp(segment)}\\s*[:=]`, "gu");
340
- matcher.lastIndex = cursor;
341
- const match = matcher.exec(source);
342
- if (!match) {
352
+ const step = stepSegment(source, segment, cursor);
353
+ if (step.stop) {
343
354
  break;
344
355
  }
345
- found = match.index;
346
- cursor = matcher.lastIndex;
356
+ cursor = step.next;
357
+ if (step.index >= 0) {
358
+ found = step.index;
359
+ }
347
360
  }
348
361
  if (found < 0) {
349
362
  return;
@@ -519,6 +532,34 @@ import { build } from "astro";
519
532
  import { defineCommand as defineCommand2 } from "citty";
520
533
  import { join as join24 } from "pathe";
521
534
 
535
+ // src/core/base-path.ts
536
+ var normalizeBasePath = (input) => {
537
+ if (!input) {
538
+ return "";
539
+ }
540
+ const trimmed = input.trim().replaceAll(/^\/+|\/+$/gu, "").replaceAll(/\/{2,}/gu, "/");
541
+ return trimmed === "" ? "" : `/${trimmed}`;
542
+ };
543
+ var isInternalPath = (target) => target.startsWith("/") && !target.startsWith("//");
544
+ var withBasePath = (basePath, route) => {
545
+ if (!basePath || !isInternalPath(route)) {
546
+ return route;
547
+ }
548
+ if (route === basePath || route.startsWith(`${basePath}/`)) {
549
+ return route;
550
+ }
551
+ return route === "/" ? basePath : `${basePath}${route}`;
552
+ };
553
+ var stripBasePath = (basePath, route) => {
554
+ if (!basePath) {
555
+ return route;
556
+ }
557
+ if (route === basePath) {
558
+ return "/";
559
+ }
560
+ return route.startsWith(`${basePath}/`) ? route.slice(basePath.length) : route;
561
+ };
562
+
522
563
  // src/deploy/xml.ts
523
564
  var escapeXml = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
524
565
 
@@ -540,6 +581,8 @@ var buildRssFeeds = (project) => {
540
581
  return [];
541
582
  }
542
583
  const base = site.replace(/\/$/u, "");
584
+ const deployBase = normalizeBasePath(config.deployment.base);
585
+ const rootLink = `${base}${deployBase}`;
543
586
  const feeds = [];
544
587
  for (const type of rss.types) {
545
588
  const pages = project.graph.pages.filter((page) => page.contentType === type && !(page.meta.draft || page.meta.sidebar.hidden));
@@ -549,13 +592,13 @@ var buildRssFeeds = (project) => {
549
592
  const items = pages.map((page) => ({
550
593
  date: pageDate(page),
551
594
  description: page.description,
552
- link: encodeURI(`${base}${page.route}`),
595
+ link: encodeURI(`${base}${withBasePath(deployBase, page.route)}`),
553
596
  title: page.title
554
597
  })).toSorted((a, b) => (b.date?.getTime() ?? 0) - (a.date?.getTime() ?? 0)).slice(0, rss.limit);
555
598
  feeds.push({
556
599
  description: config.description,
557
600
  items,
558
- link: base,
601
+ link: rootLink,
559
602
  path: `/${type}/rss.xml`,
560
603
  title: `${config.title} — ${capitalize(type)}`,
561
604
  type
@@ -581,11 +624,12 @@ ${parts.join(`
581
624
  </item>`;
582
625
  };
583
626
  var renderRssFeed = (feed) => {
627
+ const feedSelfHref = `${feed.link}${feed.path}`;
584
628
  const channel = [
585
629
  ` <title>${escapeXml(feed.title)}</title>`,
586
630
  ` <link>${escapeXml(feed.link)}</link>`,
587
631
  ` <description>${escapeXml(feed.description ?? feed.title)}</description>`,
588
- ` <atom:link href="${escapeXml(`${feed.link}${feed.path}`)}" rel="self" type="application/rss+xml" />`
632
+ ` <atom:link href="${escapeXml(feedSelfHref)}" rel="self" type="application/rss+xml" />`
589
633
  ];
590
634
  const items = feed.items.map(renderItem).join(`
591
635
  `);
@@ -618,7 +662,8 @@ var buildAgentReadability = (project) => {
618
662
  return null;
619
663
  }
620
664
  const site = config.deployment.site ?? null;
621
- const abs = (path) => site ? `${site.replace(/\/+$/u, "")}${path}` : path;
665
+ const deployBase = normalizeBasePath(config.deployment.base);
666
+ const abs = (path) => site ? `${site.replace(/\/+$/u, "")}${withBasePath(deployBase, path)}` : path;
622
667
  const artifacts = {
623
668
  markdown: {
624
669
  contentNegotiation: "text/markdown",
@@ -700,11 +745,11 @@ var readEntryText = async (ctx, page) => {
700
745
  };
701
746
 
702
747
  // src/ai/llms.ts
703
- var pageUrl = (route, site) => {
748
+ var pageUrl = (route, site, base = "") => {
704
749
  if (!site) {
705
750
  return route;
706
751
  }
707
- return `${site.replace(/\/$/u, "")}${route}`;
752
+ return `${site.replace(/\/$/u, "")}${withBasePath(base, route)}`;
708
753
  };
709
754
  var orderedPages = (project) => [...project.graph.pages].filter((page) => !page.meta.draft).sort((a, b) => a.route.localeCompare(b.route));
710
755
  var buildIndex = (project) => {
@@ -716,7 +761,7 @@ var buildIndex = (project) => {
716
761
  }
717
762
  lines.push("", "## Docs", "");
718
763
  for (const page of orderedPages(project)) {
719
- const url = pageUrl(page.route, site);
764
+ const url = pageUrl(page.route, site, normalizeBasePath(config.deployment.base));
720
765
  const summary = page.description ? `: ${page.description}` : "";
721
766
  lines.push(`- [${page.title}](${url})${summary}`);
722
767
  }
@@ -730,7 +775,7 @@ var buildFull = async (project) => {
730
775
  const sections = await Promise.all(pages.map(async (page) => {
731
776
  const raw = await readEntryText(project, page);
732
777
  const body = frontmatter_default(raw).content.trim();
733
- const url = pageUrl(page.route, config.deployment.site);
778
+ const url = pageUrl(page.route, config.deployment.site, normalizeBasePath(config.deployment.base));
734
779
  return [`# ${page.title}`, `Source: ${url}`, "", body].join(`
735
780
  `);
736
781
  }));
@@ -761,7 +806,10 @@ var ensureGitignore = async (root, entries) => {
761
806
  const path = join5(root, ".gitignore");
762
807
  const existing = existsSync3(path) ? await readFile3(path, "utf-8") : "";
763
808
  const present = new Set(existing.split(`
764
- `).map(gitignoreKey).filter(Boolean));
809
+ `).flatMap((line) => {
810
+ const key = gitignoreKey(line);
811
+ return key ? [key] : [];
812
+ }));
765
813
  const added = entries.filter((entry) => !present.has(gitignoreKey(entry)));
766
814
  if (added.length === 0) {
767
815
  return [];
@@ -881,6 +929,11 @@ var surfaceAdapterOutput = async (config, context) => {
881
929
  };
882
930
 
883
931
  // src/deploy/redirects.ts
932
+ var applyBaseToRedirects = (redirects, basePath) => basePath ? redirects.map((redirect) => ({
933
+ ...redirect,
934
+ from: withBasePath(basePath, redirect.from),
935
+ to: withBasePath(basePath, redirect.to)
936
+ })) : redirects;
884
937
  var buildNetlifyRedirects = (redirects) => `${redirects.map((redirect) => `${redirect.from} ${redirect.to} ${redirect.status}`).join(`
885
938
  `)}
886
939
  `;
@@ -925,7 +978,8 @@ var buildRobots = (project) => {
925
978
  lines.push("Allow: /");
926
979
  const { site } = config.deployment;
927
980
  if (site && config.seo.sitemap) {
928
- lines.push("", `Sitemap: ${site.replace(/\/$/u, "")}/sitemap.xml`);
981
+ const sitemapPath = withBasePath(normalizeBasePath(config.deployment.base), "/sitemap.xml");
982
+ lines.push("", `Sitemap: ${site.replace(/\/$/u, "")}${sitemapPath}`);
929
983
  }
930
984
  return `${lines.join(`
931
985
  `)}
@@ -946,7 +1000,16 @@ var buildSitemap = (project) => {
946
1000
  return null;
947
1001
  }
948
1002
  const base = site.replace(/\/$/u, "");
949
- const urls = project.graph.pages.filter((page) => !(page.meta.draft || page.meta.sidebar.hidden || page.meta.seo.noindex)).map((page) => ` <url><loc>${escapeXml(encodeURI(`${base}${page.route}`))}</loc>${lastmodTag(page.lastModified)}</url>`).toSorted();
1003
+ const deployBase = normalizeBasePath(project.config.deployment.base);
1004
+ const urls = [];
1005
+ for (const page of project.graph.pages) {
1006
+ if (page.meta.draft || page.meta.sidebar.hidden || page.meta.seo.noindex) {
1007
+ continue;
1008
+ }
1009
+ const loc = escapeXml(encodeURI(`${base}${withBasePath(deployBase, page.route)}`));
1010
+ urls.push(` <url><loc>${loc}</loc>${lastmodTag(page.lastModified)}</url>`);
1011
+ }
1012
+ urls.sort();
950
1013
  return `<?xml version="1.0" encoding="UTF-8"?>
951
1014
  <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
952
1015
  ${urls.join(`
@@ -2736,6 +2799,44 @@ var i18nDiagnostics = (pages, i18n) => {
2736
2799
  // src/core/manifest.ts
2737
2800
  var MANIFEST_VERSION = 1;
2738
2801
  var contentIndexable = (page, config) => !page.meta.search.exclude && (!page.meta.sidebar.hidden || config.search.indexing.includeHiddenPages);
2802
+ var buildFallbackRoutes = (graph, i18n, alternatesByKey, basePath) => {
2803
+ const fallback = resolveFallbackLocale(i18n);
2804
+ if (!fallback) {
2805
+ return [];
2806
+ }
2807
+ const fallbackPages = new Map(graph.pages.flatMap((page) => page.locale === fallback ? [[page.translationKey, page]] : []));
2808
+ const routes = [];
2809
+ for (const { code } of i18n.locales) {
2810
+ if (code === fallback) {
2811
+ continue;
2812
+ }
2813
+ const present = new Set(graph.pages.flatMap((page) => page.locale === code ? [page.translationKey] : []));
2814
+ for (const [key, source] of fallbackPages) {
2815
+ if (present.has(key)) {
2816
+ continue;
2817
+ }
2818
+ routes.push({
2819
+ alternates: alternatesByKey.get(key) ?? [],
2820
+ collection: source.collection ?? "docs",
2821
+ contentType: source.contentType,
2822
+ draft: source.meta.draft,
2823
+ editUrl: source.editUrl,
2824
+ entryId: source.entryId ?? source.source.ref,
2825
+ fallback: true,
2826
+ hidden: source.meta.sidebar.hidden,
2827
+ id: source.id,
2828
+ indexable: false,
2829
+ lastModified: source.lastModified,
2830
+ locale: code,
2831
+ path: withBasePath(basePath, localizeRoute(key, code, i18n)),
2832
+ source: source.source,
2833
+ sourcePath: source.sourcePath,
2834
+ title: source.title
2835
+ });
2836
+ }
2837
+ }
2838
+ return routes;
2839
+ };
2739
2840
  var buildManifest = (options) => {
2740
2841
  const { context, config, graph } = options;
2741
2842
  const searchEnabled = config.search.provider !== "none";
@@ -2766,39 +2867,7 @@ var buildManifest = (options) => {
2766
2867
  title: page.title
2767
2868
  }));
2768
2869
  if (i18n) {
2769
- const fallback = resolveFallbackLocale(i18n);
2770
- if (fallback) {
2771
- const fallbackPages = new Map(graph.pages.filter((page) => page.locale === fallback).map((page) => [page.translationKey, page]));
2772
- for (const { code } of i18n.locales) {
2773
- if (code === fallback) {
2774
- continue;
2775
- }
2776
- const present = new Set(graph.pages.filter((page) => page.locale === code).map((page) => page.translationKey));
2777
- for (const [key, source] of fallbackPages) {
2778
- if (present.has(key)) {
2779
- continue;
2780
- }
2781
- routes.push({
2782
- alternates: alternatesByKey.get(key) ?? [],
2783
- collection: source.collection ?? "docs",
2784
- contentType: source.contentType,
2785
- draft: source.meta.draft,
2786
- editUrl: source.editUrl,
2787
- entryId: source.entryId ?? source.source.ref,
2788
- fallback: true,
2789
- hidden: source.meta.sidebar.hidden,
2790
- id: source.id,
2791
- indexable: false,
2792
- lastModified: source.lastModified,
2793
- locale: code,
2794
- path: localizeRoute(key, code, i18n),
2795
- source: source.source,
2796
- sourcePath: source.sourcePath,
2797
- title: source.title
2798
- });
2799
- }
2800
- }
2801
- }
2870
+ routes.push(...buildFallbackRoutes(graph, i18n, alternatesByKey, config.basePath));
2802
2871
  }
2803
2872
  routes.sort((a, b) => a.path.localeCompare(b.path));
2804
2873
  return {
@@ -2826,8 +2895,7 @@ var toPlainText = (markdown) => {
2826
2895
  let cursor = 0;
2827
2896
  for (const match of withoutBlocks.matchAll(INLINE_CODE)) {
2828
2897
  const start = match.index ?? 0;
2829
- pieces.push(withoutBlocks.slice(cursor, start).replaceAll(HTML_OR_JSX, " "));
2830
- pieces.push(match.groups?.code ?? "");
2898
+ pieces.push(withoutBlocks.slice(cursor, start).replaceAll(HTML_OR_JSX, " "), match.groups?.code ?? "");
2831
2899
  cursor = start + match[0].length;
2832
2900
  }
2833
2901
  pieces.push(withoutBlocks.slice(cursor).replaceAll(HTML_OR_JSX, " "));
@@ -2961,7 +3029,12 @@ var syncTypesense = async (records, config) => {
2961
3029
  ]
2962
3030
  });
2963
3031
  const collection = client.collections(config.collection);
2964
- const exists = await collection.retrieve().then(() => true).catch(() => false);
3032
+ let exists = true;
3033
+ try {
3034
+ await collection.retrieve();
3035
+ } catch {
3036
+ exists = false;
3037
+ }
2965
3038
  if (exists) {
2966
3039
  await collection.delete();
2967
3040
  }
@@ -3124,23 +3197,25 @@ var ownsLock = (outDir) => {
3124
3197
  return false;
3125
3198
  }
3126
3199
  };
3127
- var acquireDevLock = (outDir, port) => {
3128
- mkdirSync(outDir, { recursive: true });
3129
- for (;; ) {
3130
- try {
3131
- writeFileSync(lockPath(outDir), lockPayload(port), { flag: "wx" });
3132
- break;
3133
- } catch (error) {
3134
- if (error.code !== "EEXIST") {
3135
- throw error;
3136
- }
3137
- const existing = readDevLock(outDir);
3138
- if (existing && existing.pid !== process.pid) {
3139
- throw new DevLockHeldError(existing);
3140
- }
3141
- rmSync(lockPath(outDir), { force: true });
3200
+ var tryClaimLock = (outDir, port) => {
3201
+ try {
3202
+ writeFileSync(lockPath(outDir), lockPayload(port), { flag: "wx" });
3203
+ return true;
3204
+ } catch (error) {
3205
+ if (error.code !== "EEXIST") {
3206
+ throw error;
3207
+ }
3208
+ const existing = readDevLock(outDir);
3209
+ if (existing && existing.pid !== process.pid) {
3210
+ throw new DevLockHeldError(existing);
3142
3211
  }
3212
+ rmSync(lockPath(outDir), { force: true });
3213
+ return false;
3143
3214
  }
3215
+ };
3216
+ var acquireDevLock = (outDir, port) => {
3217
+ mkdirSync(outDir, { recursive: true });
3218
+ while (!tryClaimLock(outDir, port)) {}
3144
3219
  let released = false;
3145
3220
  return () => {
3146
3221
  if (released) {
@@ -3180,7 +3255,7 @@ import {
3180
3255
  writeFile as writeFile6
3181
3256
  } from "node:fs/promises";
3182
3257
  import { createRequire as createRequire5 } from "node:module";
3183
- import { pathToFileURL as pathToFileURL2 } from "node:url";
3258
+ import { pathToFileURL as pathToFileURL3 } from "node:url";
3184
3259
  import { basename as basename2, dirname as dirname8, join as join22, normalize as normalize3, relative as relative8 } from "pathe";
3185
3260
  import { glob as glob5 } from "tinyglobby";
3186
3261
 
@@ -3203,20 +3278,22 @@ var buildAskData = async (project) => {
3203
3278
  };
3204
3279
 
3205
3280
  // src/ai/ask.ts
3281
+ var OPENAI_COMPATIBLE = "openai-compatible";
3282
+ var OPENAI_COMPATIBLE_DEP = "@ai-sdk/openai-compatible";
3206
3283
  var ASK_PRESETS = {
3207
3284
  inkeep: {
3208
3285
  apiKeyEnv: "INKEEP_API_KEY",
3209
3286
  baseUrl: "https://api.inkeep.com/v1",
3210
- kind: "openai-compatible",
3287
+ kind: OPENAI_COMPATIBLE,
3211
3288
  name: "inkeep",
3212
- runtimeDep: "@ai-sdk/openai-compatible"
3289
+ runtimeDep: OPENAI_COMPATIBLE_DEP
3213
3290
  },
3214
3291
  llmgateway: {
3215
3292
  apiKeyEnv: "LLMGATEWAY_API_KEY",
3216
3293
  baseUrl: "https://api.llmgateway.io/v1",
3217
- kind: "openai-compatible",
3294
+ kind: OPENAI_COMPATIBLE,
3218
3295
  name: "llmgateway",
3219
- runtimeDep: "@ai-sdk/openai-compatible"
3296
+ runtimeDep: OPENAI_COMPATIBLE_DEP
3220
3297
  },
3221
3298
  openrouter: {
3222
3299
  apiKeyEnv: "OPENROUTER_API_KEY",
@@ -3240,9 +3317,9 @@ var resolveAskBackend = (ask) => {
3240
3317
  return {
3241
3318
  apiKeyEnv,
3242
3319
  baseUrl: ask?.baseUrl ?? preset?.baseUrl ?? "",
3243
- kind: "openai-compatible",
3320
+ kind: OPENAI_COMPATIBLE,
3244
3321
  model,
3245
- name: preset?.name ?? "openai-compatible"
3322
+ name: preset?.name ?? OPENAI_COMPATIBLE
3246
3323
  };
3247
3324
  };
3248
3325
  var askBackendRuntimeDep = (ask) => {
@@ -3250,7 +3327,7 @@ var askBackendRuntimeDep = (ask) => {
3250
3327
  if (provider === "gateway") {
3251
3328
  return;
3252
3329
  }
3253
- return ASK_PRESETS[provider]?.runtimeDep ?? "@ai-sdk/openai-compatible";
3330
+ return ASK_PRESETS[provider]?.runtimeDep ?? OPENAI_COMPATIBLE_DEP;
3254
3331
  };
3255
3332
 
3256
3333
  // src/ai/markdown.ts
@@ -3276,14 +3353,20 @@ var buildMcpData = async (project) => {
3276
3353
  buildRawMarkdown(project)
3277
3354
  ]);
3278
3355
  const descriptionById = new Map(graph.pages.map((page) => [page.id, page.description]));
3279
- const routes = manifest.routes.filter((route) => !route.hidden).map((route) => ({
3280
- contentType: route.contentType,
3281
- description: descriptionById.get(route.id),
3282
- indexable: route.indexable,
3283
- lastModified: route.lastModified ?? null,
3284
- route: route.path,
3285
- title: route.title
3286
- }));
3356
+ const routes = [];
3357
+ for (const route of manifest.routes) {
3358
+ if (route.hidden) {
3359
+ continue;
3360
+ }
3361
+ routes.push({
3362
+ contentType: route.contentType,
3363
+ description: descriptionById.get(route.id),
3364
+ indexable: route.indexable,
3365
+ lastModified: route.lastModified ?? null,
3366
+ route: route.path,
3367
+ title: route.title
3368
+ });
3369
+ }
3287
3370
  return {
3288
3371
  documents: documents.map((doc) => ({
3289
3372
  content: doc.content,
@@ -3424,6 +3507,7 @@ import { existsSync as existsSync7 } from "node:fs";
3424
3507
  import { dirname as dirname5, extname, isAbsolute as isAbsolute2, resolve as resolve3 } from "pathe";
3425
3508
  import ts from "typescript";
3426
3509
  var GROUPS = ["mdx", "layout", "islands"];
3510
+ var GROUP_SET = new Set(GROUPS);
3427
3511
  var FRAMEWORK_BY_EXT = {
3428
3512
  jsx: "react",
3429
3513
  svelte: "svelte",
@@ -3459,29 +3543,32 @@ var emptyAnalysis = () => ({
3459
3543
  warnings: []
3460
3544
  });
3461
3545
  var propName = (name) => ts.isIdentifier(name) || ts.isStringLiteral(name) ? name.text : undefined;
3546
+ var addImportBindings = (map, statement) => {
3547
+ if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier)) {
3548
+ return;
3549
+ }
3550
+ const specifier = statement.moduleSpecifier.text;
3551
+ const clause = statement.importClause;
3552
+ if (!clause) {
3553
+ return;
3554
+ }
3555
+ if (clause.name) {
3556
+ map.set(clause.name.text, { imported: "default", specifier });
3557
+ }
3558
+ const named = clause.namedBindings;
3559
+ if (named && ts.isNamedImports(named)) {
3560
+ for (const element of named.elements) {
3561
+ map.set(element.name.text, {
3562
+ imported: (element.propertyName ?? element.name).text,
3563
+ specifier
3564
+ });
3565
+ }
3566
+ }
3567
+ };
3462
3568
  var collectImports = (sourceFile) => {
3463
3569
  const map = new Map;
3464
3570
  for (const statement of sourceFile.statements) {
3465
- if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier)) {
3466
- continue;
3467
- }
3468
- const specifier = statement.moduleSpecifier.text;
3469
- const clause = statement.importClause;
3470
- if (!clause) {
3471
- continue;
3472
- }
3473
- if (clause.name) {
3474
- map.set(clause.name.text, { imported: "default", specifier });
3475
- }
3476
- const named = clause.namedBindings;
3477
- if (named && ts.isNamedImports(named)) {
3478
- for (const element of named.elements) {
3479
- map.set(element.name.text, {
3480
- imported: (element.propertyName ?? element.name).text,
3481
- specifier
3482
- });
3483
- }
3484
- }
3571
+ addImportBindings(map, statement);
3485
3572
  }
3486
3573
  return map;
3487
3574
  };
@@ -3539,33 +3626,36 @@ var resolveIdentifier = (name, imports, dir) => {
3539
3626
  const binding = imports.get(name);
3540
3627
  return binding ? toImport(binding.specifier, binding.imported, dir) : null;
3541
3628
  };
3629
+ var applyDescriptorProperty = (descriptor, property, imports, dir) => {
3630
+ if (ts.isShorthandPropertyAssignment(property)) {
3631
+ if (property.name.text === "component") {
3632
+ descriptor.hadComponent = true;
3633
+ descriptor.source = resolveIdentifier(property.name.text, imports, dir);
3634
+ }
3635
+ return;
3636
+ }
3637
+ if (!ts.isPropertyAssignment(property)) {
3638
+ return;
3639
+ }
3640
+ const name = propName(property.name);
3641
+ const init = property.initializer;
3642
+ if (name === "component") {
3643
+ descriptor.hadComponent = true;
3644
+ if (ts.isStringLiteral(init)) {
3645
+ descriptor.source = toImport(init.text, "default", dir);
3646
+ } else if (ts.isIdentifier(init)) {
3647
+ descriptor.source = resolveIdentifier(init.text, imports, dir);
3648
+ }
3649
+ } else if (name === "client" && ts.isStringLiteral(init) && HYDRATION_MODES.has(init.text)) {
3650
+ descriptor.client = init.text;
3651
+ } else if (name === "media" && ts.isStringLiteral(init)) {
3652
+ descriptor.media = init.text;
3653
+ }
3654
+ };
3542
3655
  var readDescriptor = (object, imports, dir) => {
3543
3656
  const descriptor = { hadComponent: false, source: null };
3544
3657
  for (const property of object.properties) {
3545
- if (ts.isShorthandPropertyAssignment(property)) {
3546
- if (property.name.text === "component") {
3547
- descriptor.hadComponent = true;
3548
- descriptor.source = resolveIdentifier(property.name.text, imports, dir);
3549
- }
3550
- continue;
3551
- }
3552
- if (!ts.isPropertyAssignment(property)) {
3553
- continue;
3554
- }
3555
- const name = propName(property.name);
3556
- const init = property.initializer;
3557
- if (name === "component") {
3558
- descriptor.hadComponent = true;
3559
- if (ts.isStringLiteral(init)) {
3560
- descriptor.source = toImport(init.text, "default", dir);
3561
- } else if (ts.isIdentifier(init)) {
3562
- descriptor.source = resolveIdentifier(init.text, imports, dir);
3563
- }
3564
- } else if (name === "client" && ts.isStringLiteral(init) && HYDRATION_MODES.has(init.text)) {
3565
- descriptor.client = init.text;
3566
- } else if (name === "media" && ts.isStringLiteral(init)) {
3567
- descriptor.media = init.text;
3568
- }
3658
+ applyDescriptorProperty(descriptor, property, imports, dir);
3569
3659
  }
3570
3660
  return descriptor;
3571
3661
  };
@@ -3648,6 +3738,22 @@ var normalizeEntry = (entry, group, imports, dir, warnings) => {
3648
3738
  }
3649
3739
  return { identifier: false, key, source: null };
3650
3740
  };
3741
+ var collectGroupOverrides = (property, imports, dir, result) => {
3742
+ if (!ts.isPropertyAssignment(property)) {
3743
+ return;
3744
+ }
3745
+ const name = propName(property.name);
3746
+ if (!(name && GROUP_SET.has(name)) || !ts.isObjectLiteralExpression(property.initializer)) {
3747
+ return;
3748
+ }
3749
+ const group = name;
3750
+ for (const entry of property.initializer.properties) {
3751
+ const normalized = normalizeEntry(entry, group, imports, dir, result.warnings);
3752
+ if (normalized) {
3753
+ result[group].push(normalized);
3754
+ }
3755
+ }
3756
+ };
3651
3757
  var analyzeComponentOverrides = (source, filePath) => {
3652
3758
  const result = emptyAnalysis();
3653
3759
  const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, filePath.endsWith("tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
@@ -3658,20 +3764,7 @@ var analyzeComponentOverrides = (source, filePath) => {
3658
3764
  const imports = collectImports(sourceFile);
3659
3765
  const dir = dirname5(filePath);
3660
3766
  for (const property of object.properties) {
3661
- if (!ts.isPropertyAssignment(property)) {
3662
- continue;
3663
- }
3664
- const name = propName(property.name);
3665
- if (!(name && GROUPS.includes(name)) || !ts.isObjectLiteralExpression(property.initializer)) {
3666
- continue;
3667
- }
3668
- const group = name;
3669
- for (const entry of property.initializer.properties) {
3670
- const normalized = normalizeEntry(entry, group, imports, dir, result.warnings);
3671
- if (normalized) {
3672
- result[group].push(normalized);
3673
- }
3674
- }
3767
+ collectGroupOverrides(property, imports, dir, result);
3675
3768
  }
3676
3769
  return result;
3677
3770
  };
@@ -3739,7 +3832,8 @@ var uiStringsObject = z.object({
3739
3832
  }).default({})
3740
3833
  });
3741
3834
  var uiStringsSchema = uiStringsObject.default({});
3742
- var EN_UI = uiStringsObject.parse({});
3835
+ var EN_UI_INPUT = Object.fromEntries(Object.keys(uiStringsObject.shape).map((group) => [group, {}]));
3836
+ var EN_UI = uiStringsObject.parse(EN_UI_INPUT);
3743
3837
  var uiStringsOverrideSchema = z.record(z.string(), z.record(z.string(), z.string()));
3744
3838
  var uiLocaleOverridesSchema = z.record(z.string(), uiStringsOverrideSchema);
3745
3839
  var mergeUI = (base, override) => {
@@ -3916,7 +4010,7 @@ var duplicateLabelDiagnostics = (navigation) => {
3916
4010
  return diagnostics;
3917
4011
  };
3918
4012
  var hiddenInSidebarDiagnostics = (navigation, pages) => {
3919
- const hidden = new Set(pages.filter((page) => page.meta.sidebar.hidden).map((page) => page.id));
4013
+ const hidden = new Set(pages.flatMap((page) => page.meta.sidebar.hidden ? [page.id] : []));
3920
4014
  if (hidden.size === 0) {
3921
4015
  return [];
3922
4016
  }
@@ -4005,26 +4099,34 @@ var resolveReferences = (config) => [
4005
4099
  ];
4006
4100
  var referenceTabs = (config) => resolveReferences(config).map((ref) => ({
4007
4101
  label: ref.label,
4008
- path: ref.route
4102
+ path: ref.renderer === "blume" ? withBasePath(config.basePath, ref.route) : ref.route
4009
4103
  }));
4104
+ var blumeReferenceOf = (ref, seen, usedSlugs) => {
4105
+ if (ref.kind !== "openapi" || ref.renderer !== "blume") {
4106
+ return null;
4107
+ }
4108
+ if (seen.has(ref.route)) {
4109
+ return null;
4110
+ }
4111
+ seen.add(ref.route);
4112
+ let { slug } = ref;
4113
+ let n = 2;
4114
+ while (usedSlugs.has(slug)) {
4115
+ slug = `${ref.slug}-${n}`;
4116
+ n += 1;
4117
+ }
4118
+ usedSlugs.add(slug);
4119
+ return slug === ref.slug ? ref : { ...ref, slug };
4120
+ };
4010
4121
  var blumeReferences = (config) => {
4011
4122
  const seen = new Set;
4012
4123
  const usedSlugs = new Set;
4013
4124
  const result = [];
4014
4125
  for (const ref of resolveReferences(config)) {
4015
- if (ref.kind !== "openapi" || ref.renderer !== "blume") {
4016
- continue;
4017
- }
4018
- if (seen.has(ref.route)) {
4019
- continue;
4020
- }
4021
- seen.add(ref.route);
4022
- let { slug } = ref;
4023
- for (let n = 2;usedSlugs.has(slug); n += 1) {
4024
- slug = `${ref.slug}-${n}`;
4126
+ const accepted = blumeReferenceOf(ref, seen, usedSlugs);
4127
+ if (accepted) {
4128
+ result.push(accepted);
4025
4129
  }
4026
- usedSlugs.add(slug);
4027
- result.push(slug === ref.slug ? ref : { ...ref, slug });
4028
4130
  }
4029
4131
  return result;
4030
4132
  };
@@ -4138,6 +4240,7 @@ var isOperation = (value) => typeof value === "object" && value !== null;
4138
4240
  var extractOperations = (document, baseRoute) => {
4139
4241
  const operations = [];
4140
4242
  const tagOrder = [];
4243
+ const tagsSeen = new Set;
4141
4244
  const tagMeta = new Map((document.tags ?? []).map((tag) => [tag.name, tag.description ?? ""]));
4142
4245
  const seen = new Set;
4143
4246
  for (const [path, rawItem] of Object.entries(document.paths ?? {})) {
@@ -4152,7 +4255,8 @@ var extractOperations = (document, baseRoute) => {
4152
4255
  }
4153
4256
  const tag = operation.tags?.[0] ?? UNTAGGED;
4154
4257
  const tagSlug = slugify2(tag) || "operations";
4155
- if (!tagOrder.includes(tag)) {
4258
+ if (!tagsSeen.has(tag)) {
4259
+ tagsSeen.add(tag);
4156
4260
  tagOrder.push(tag);
4157
4261
  }
4158
4262
  let key = operationKey(method, path, operation.operationId);
@@ -4376,13 +4480,20 @@ var overviewMdx = (spec) => {
4376
4480
  });
4377
4481
  }
4378
4482
  }
4379
- const tagSections = sections.filter((tag) => operations.some((operation) => operation.tagSlug === tag.slug)).map((tag) => [
4380
- `## ${mdxSafe(tag.name)}`,
4381
- ...tag.description.trim() ? [mdxSafe(tag.description.trim())] : [],
4382
- `<ApiTagOperations source="${spec.slug}" tag="${tag.slug}" />`
4383
- ].join(`
4483
+ const tagSections = [];
4484
+ for (const tag of sections) {
4485
+ if (!operations.some((operation) => operation.tagSlug === tag.slug)) {
4486
+ continue;
4487
+ }
4488
+ const description = tag.description.trim() ? [mdxSafe(tag.description.trim())] : [];
4489
+ tagSections.push([
4490
+ `## ${mdxSafe(tag.name)}`,
4491
+ ...description,
4492
+ `<ApiTagOperations source="${spec.slug}" tag="${tag.slug}" />`
4493
+ ].join(`
4384
4494
 
4385
4495
  `));
4496
+ }
4386
4497
  return {
4387
4498
  body: [
4388
4499
  withDescription(spec.description, `<ApiOverview source="${spec.slug}" />`),
@@ -4603,7 +4714,8 @@ var releaseToEntry = (release) => {
4603
4714
  };
4604
4715
  const raw = frontmatter_default.stringify(`${body}
4605
4716
  `, data);
4606
- const ref = `${slugifyTag(release.tag_name) || `release-${release.id}`}.md`;
4717
+ const fallbackRef = `release-${release.id}`;
4718
+ const ref = `${slugifyTag(release.tag_name) || fallbackRef}.md`;
4607
4719
  return {
4608
4720
  body: { format: "md", text: body },
4609
4721
  data,
@@ -4686,42 +4798,36 @@ var githubReleasesSource = (options, ctx) => {
4686
4798
  // src/core/sources/mdx-remote.ts
4687
4799
  var REGEX_SPECIAL = /[.*+?^${}()|[\]\\]/u;
4688
4800
  var escapeChar = (char) => REGEX_SPECIAL.test(char) ? `\\${char}` : char;
4801
+ var globToken = (pattern, i) => {
4802
+ const char = pattern[i] ?? "";
4803
+ if (char === "*") {
4804
+ if (pattern[i + 1] === "*") {
4805
+ if (pattern[i + 2] === "/") {
4806
+ return { next: i + 3, source: "(?:.*/)?" };
4807
+ }
4808
+ return { next: i + 2, source: ".*" };
4809
+ }
4810
+ return { next: i + 1, source: "[^/]*" };
4811
+ }
4812
+ if (char === "?") {
4813
+ return { next: i + 1, source: "[^/]" };
4814
+ }
4815
+ if (char === "{") {
4816
+ const end = pattern.indexOf("}", i);
4817
+ if (end !== -1) {
4818
+ const options = pattern.slice(i + 1, end).split(",").map((part) => [...part].map(escapeChar).join("")).join("|");
4819
+ return { next: end + 1, source: `(?:${options})` };
4820
+ }
4821
+ }
4822
+ return { next: i + 1, source: escapeChar(char) };
4823
+ };
4689
4824
  var globToRegExp = (pattern) => {
4690
4825
  let source = "";
4691
4826
  let i = 0;
4692
4827
  while (i < pattern.length) {
4693
- const char = pattern[i] ?? "";
4694
- if (char === "*") {
4695
- if (pattern[i + 1] === "*") {
4696
- i += 2;
4697
- if (pattern[i] === "/") {
4698
- i += 1;
4699
- source += "(?:.*/)?";
4700
- } else {
4701
- source += ".*";
4702
- }
4703
- continue;
4704
- }
4705
- source += "[^/]*";
4706
- i += 1;
4707
- continue;
4708
- }
4709
- if (char === "?") {
4710
- source += "[^/]";
4711
- i += 1;
4712
- continue;
4713
- }
4714
- if (char === "{") {
4715
- const end = pattern.indexOf("}", i);
4716
- if (end !== -1) {
4717
- const options = pattern.slice(i + 1, end).split(",").map((part) => [...part].map(escapeChar).join("")).join("|");
4718
- source += `(?:${options})`;
4719
- i = end + 1;
4720
- continue;
4721
- }
4722
- }
4723
- source += escapeChar(char);
4724
- i += 1;
4828
+ const token = globToken(pattern, i);
4829
+ source += token.source;
4830
+ i = token.next;
4725
4831
  }
4726
4832
  return new RegExp(`^${source}$`, "u");
4727
4833
  };
@@ -4750,11 +4856,22 @@ var enumerateGithub = async (github, include, doFetch) => {
4750
4856
  }
4751
4857
  const body = await res.json();
4752
4858
  const prefix = base ? `${base}/` : "";
4753
- const refs = (body.tree ?? []).filter((node) => node.type === "blob" && node.path.startsWith(prefix)).map((node) => node.path.slice(prefix.length)).filter((rel) => matchesInclude(rel, include)).map((rel) => ({
4754
- editUrl: `https://github.com/${owner}/${repo}/edit/${ref}/${prefix}${rel}`,
4755
- fetchUrl: `https://raw.githubusercontent.com/${owner}/${repo}/${ref}/${prefix}${rel}`,
4756
- ref: rel
4757
- }));
4859
+ const refs = (body.tree ?? []).flatMap((node) => {
4860
+ if (!(node.type === "blob" && node.path.startsWith(prefix))) {
4861
+ return [];
4862
+ }
4863
+ const rel = node.path.slice(prefix.length);
4864
+ if (!matchesInclude(rel, include)) {
4865
+ return [];
4866
+ }
4867
+ return [
4868
+ {
4869
+ editUrl: `https://github.com/${owner}/${repo}/edit/${ref}/${prefix}${rel}`,
4870
+ fetchUrl: `https://raw.githubusercontent.com/${owner}/${repo}/${ref}/${prefix}${rel}`,
4871
+ ref: rel
4872
+ }
4873
+ ];
4874
+ });
4758
4875
  return { refs, truncated: body.truncated === true };
4759
4876
  };
4760
4877
  var mdxRemoteSource = (options, ctx) => {
@@ -4767,11 +4884,7 @@ var mdxRemoteSource = (options, ctx) => {
4767
4884
  }
4768
4885
  if (options.files && options.url) {
4769
4886
  const base = options.url.replace(/\/$/u, "");
4770
- const refs = options.files.filter((ref) => matchesInclude(ref, options.include)).map((ref) => ({
4771
- editUrl: `${base}/${ref}`,
4772
- fetchUrl: `${base}/${ref}`,
4773
- ref
4774
- }));
4887
+ const refs = options.files.flatMap((ref) => matchesInclude(ref, options.include) ? [{ editUrl: `${base}/${ref}`, fetchUrl: `${base}/${ref}`, ref }] : []);
4775
4888
  return { refs, truncated: false };
4776
4889
  }
4777
4890
  throw new BlumeError({
@@ -5068,32 +5181,33 @@ var configuredCssVars = (fonts) => buildFontEntries(fonts).map((entry) => entry.
5068
5181
 
5069
5182
  // src/core/schema.ts
5070
5183
  var iconName = z2.string().min(1);
5184
+ var DEFAULT_CONTENT_GLOB = "**/*.{md,mdx}";
5071
5185
  var hydrationMode = z2.enum(["load", "idle", "visible", "media", "only"]);
5072
5186
  var dateSchema = z2.union([z2.string(), z2.date()]).transform((value) => value instanceof Date ? value.toISOString() : value);
5073
- var sidebarMetaSchema = z2.object({
5187
+ var sidebarMetaSchema = z2.strictObject({
5074
5188
  badge: z2.string().optional(),
5075
5189
  hidden: z2.boolean().default(false),
5076
5190
  icon: iconName.optional(),
5077
5191
  label: z2.string().optional(),
5078
5192
  order: z2.number().optional()
5079
- }).strict();
5080
- var seoMetaSchema = z2.object({
5193
+ });
5194
+ var seoMetaSchema = z2.strictObject({
5081
5195
  canonical: z2.string().url().optional(),
5082
5196
  description: z2.string().optional(),
5083
5197
  image: z2.string().optional(),
5084
5198
  noindex: z2.boolean().default(false),
5085
5199
  title: z2.string().optional()
5086
- }).strict();
5087
- var searchMetaSchema = z2.object({
5200
+ });
5201
+ var searchMetaSchema = z2.strictObject({
5088
5202
  boost: z2.number().optional(),
5089
5203
  exclude: z2.boolean().default(false),
5090
5204
  tags: z2.array(z2.string()).optional()
5091
- }).strict();
5092
- var changelogMetaSchema = z2.object({
5205
+ });
5206
+ var changelogMetaSchema = z2.strictObject({
5093
5207
  category: z2.string().optional(),
5094
5208
  date: dateSchema.optional(),
5095
5209
  version: z2.string().optional()
5096
- }).strict();
5210
+ });
5097
5211
  var authorSchema = z2.union([
5098
5212
  z2.string(),
5099
5213
  z2.object({
@@ -5101,9 +5215,9 @@ var authorSchema = z2.union([
5101
5215
  image: z2.string().optional(),
5102
5216
  name: z2.string(),
5103
5217
  url: z2.string().optional()
5104
- }).passthrough()
5218
+ }).catchall(z2.unknown())
5105
5219
  ]);
5106
- var pageMetaBaseSchema = z2.object({
5220
+ var pageMetaBaseSchema = z2.strictObject({
5107
5221
  authors: z2.union([authorSchema, z2.array(authorSchema)]).optional(),
5108
5222
  changelog: changelogMetaSchema.optional(),
5109
5223
  date: dateSchema.optional(),
@@ -5120,72 +5234,72 @@ var pageMetaBaseSchema = z2.object({
5120
5234
  slug: z2.string().optional(),
5121
5235
  title: z2.string().optional(),
5122
5236
  type: z2.string().optional()
5123
- }).strict();
5237
+ });
5124
5238
  var pageMetaSchema = pageMetaBaseSchema;
5125
5239
  var sidebarDisplaySchema = z2.enum(["flat", "group", "page"]);
5126
- var folderMetaSchema = z2.object({
5240
+ var folderMetaSchema = z2.strictObject({
5127
5241
  collapsed: z2.boolean().optional(),
5128
5242
  icon: iconName.optional(),
5129
5243
  order: z2.number().optional(),
5130
5244
  pages: z2.array(z2.string()).optional(),
5131
5245
  title: z2.string().optional()
5132
- }).strict();
5246
+ });
5133
5247
  var logoImageSchema = z2.union([
5134
5248
  z2.string(),
5135
- z2.object({
5249
+ z2.strictObject({
5136
5250
  alt: z2.string().optional(),
5137
5251
  dark: z2.string().optional(),
5138
5252
  light: z2.string().optional()
5139
- }).strict()
5253
+ })
5140
5254
  ]);
5141
5255
  var logoConfigSchema = z2.union([
5142
5256
  z2.string(),
5143
- z2.object({
5257
+ z2.strictObject({
5144
5258
  href: z2.string().optional(),
5145
5259
  image: logoImageSchema.optional(),
5146
5260
  text: z2.string().optional()
5147
- }).strict()
5261
+ })
5148
5262
  ]);
5149
5263
  var bannerConfigSchema = z2.union([
5150
5264
  z2.string(),
5151
- z2.object({
5265
+ z2.strictObject({
5152
5266
  content: z2.string(),
5153
5267
  dismissible: z2.boolean().default(false),
5154
5268
  id: z2.string().optional(),
5155
- link: z2.object({ href: z2.string(), text: z2.string() }).strict().optional()
5156
- }).strict()
5269
+ link: z2.strictObject({ href: z2.string(), text: z2.string() }).optional()
5270
+ })
5157
5271
  ]);
5158
- var filesystemSourceSchema = z2.object({
5272
+ var filesystemSourceSchema = z2.strictObject({
5159
5273
  exclude: z2.array(z2.string()).default(["**/_*", "**/.*"]),
5160
- include: z2.array(z2.string()).default(["**/*.{md,mdx}"]),
5274
+ include: z2.array(z2.string()).default([DEFAULT_CONTENT_GLOB]),
5161
5275
  prefix: z2.string().optional(),
5162
5276
  root: z2.string().default("docs"),
5163
5277
  type: z2.literal("filesystem")
5164
- }).strict();
5165
- var mdxRemoteSourceSchema = z2.object({
5278
+ });
5279
+ var mdxRemoteSourceSchema = z2.strictObject({
5166
5280
  files: z2.array(z2.string()).optional(),
5167
- github: z2.object({
5281
+ github: z2.strictObject({
5168
5282
  owner: z2.string(),
5169
5283
  path: z2.string().default(""),
5170
5284
  ref: z2.string().default("main"),
5171
5285
  repo: z2.string()
5172
- }).strict().optional(),
5173
- include: z2.array(z2.string()).default(["**/*.{md,mdx}"]),
5286
+ }).optional(),
5287
+ include: z2.array(z2.string()).default([DEFAULT_CONTENT_GLOB]),
5174
5288
  pollInterval: z2.number().positive().optional(),
5175
5289
  prefix: z2.string().optional(),
5176
5290
  type: z2.literal("mdx-remote"),
5177
5291
  url: z2.string().optional()
5178
- }).strict();
5292
+ });
5179
5293
  var sanitySourceSchema = z2.object({
5180
5294
  apiVersion: z2.string().optional(),
5181
5295
  dataset: z2.string(),
5182
- fields: z2.object({
5296
+ fields: z2.strictObject({
5183
5297
  body: z2.string().optional(),
5184
5298
  description: z2.string().optional(),
5185
5299
  lastModified: z2.string().optional(),
5186
5300
  slug: z2.string().optional(),
5187
5301
  title: z2.string().optional()
5188
- }).strict().optional(),
5302
+ }).optional(),
5189
5303
  pollInterval: z2.number().positive().optional(),
5190
5304
  prefix: z2.string().optional(),
5191
5305
  projectId: z2.string(),
@@ -5196,17 +5310,17 @@ var notionSourceSchema = z2.object({
5196
5310
  database: z2.string(),
5197
5311
  pollInterval: z2.number().positive().optional(),
5198
5312
  prefix: z2.string().optional(),
5199
- properties: z2.object({
5313
+ properties: z2.strictObject({
5200
5314
  description: z2.string().optional(),
5201
5315
  order: z2.string().optional(),
5202
5316
  slug: z2.string().optional(),
5203
5317
  status: z2.string().optional(),
5204
5318
  title: z2.string().optional()
5205
- }).strict().optional(),
5319
+ }).optional(),
5206
5320
  publishedValue: z2.string().optional(),
5207
5321
  type: z2.literal("notion")
5208
5322
  });
5209
- var githubReleasesSourceSchema = z2.object({
5323
+ var githubReleasesSourceSchema = z2.strictObject({
5210
5324
  drafts: z2.boolean().optional(),
5211
5325
  limit: z2.number().positive().optional(),
5212
5326
  owner: z2.string(),
@@ -5215,7 +5329,7 @@ var githubReleasesSourceSchema = z2.object({
5215
5329
  prereleases: z2.boolean().optional(),
5216
5330
  repo: z2.string(),
5217
5331
  type: z2.literal("github-releases")
5218
- }).strict();
5332
+ });
5219
5333
  var customSourceSchema = z2.object({
5220
5334
  source: z2.custom((val) => typeof val === "object" && val !== null && typeof val.load === "function" && typeof val.name === "string", { message: "custom source must be a ContentSource (with name + load)" }),
5221
5335
  type: z2.literal("custom")
@@ -5228,42 +5342,42 @@ var contentSourceSchema = z2.discriminatedUnion("type", [
5228
5342
  notionSourceSchema,
5229
5343
  customSourceSchema
5230
5344
  ]);
5231
- var contentConfigSchema = z2.object({
5345
+ var contentConfigSchema = z2.strictObject({
5232
5346
  defaultType: z2.string().default("doc"),
5233
5347
  exclude: z2.array(z2.string()).default(["**/_*", "**/.*"]),
5234
- include: z2.array(z2.string()).default(["**/*.{md,mdx}"]),
5348
+ include: z2.array(z2.string()).default([DEFAULT_CONTENT_GLOB]),
5235
5349
  pages: z2.string().default("pages"),
5236
5350
  root: z2.string().default("docs"),
5237
5351
  sources: z2.array(contentSourceSchema).optional()
5238
- }).strict();
5239
- var navTabSchema = z2.object({
5352
+ });
5353
+ var navTabSchema = z2.strictObject({
5240
5354
  icon: iconName.optional(),
5241
- items: z2.array(z2.object({
5355
+ items: z2.array(z2.strictObject({
5242
5356
  description: z2.string().optional(),
5243
5357
  icon: iconName.optional(),
5244
5358
  label: z2.string(),
5245
5359
  path: z2.string(),
5246
5360
  tag: z2.string().optional()
5247
- }).strict()).optional(),
5361
+ })).optional(),
5248
5362
  label: z2.string(),
5249
5363
  path: z2.string()
5250
- }).strict();
5251
- var navSelectorItemSchema = z2.object({
5364
+ });
5365
+ var navSelectorItemSchema = z2.strictObject({
5252
5366
  description: z2.string().optional(),
5253
5367
  icon: iconName.optional(),
5254
5368
  label: z2.string(),
5255
5369
  path: z2.string(),
5256
5370
  tag: z2.string().optional()
5257
- }).strict();
5258
- var navSelectorSchema = z2.object({
5371
+ });
5372
+ var navSelectorSchema = z2.strictObject({
5259
5373
  items: z2.array(navSelectorItemSchema).default([]),
5260
5374
  kind: z2.enum(["dropdown", "language", "product", "version"]),
5261
5375
  label: z2.string()
5262
- }).strict();
5376
+ });
5263
5377
  var directoryModeSchema = z2.enum(["accordion", "card", "none"]);
5264
5378
  var sidebarItemSchema = z2.lazy(() => z2.union([
5265
5379
  z2.string(),
5266
- z2.object({
5380
+ z2.strictObject({
5267
5381
  badge: z2.string().optional(),
5268
5382
  collapsed: z2.boolean().optional(),
5269
5383
  directory: directoryModeSchema.optional(),
@@ -5273,52 +5387,60 @@ var sidebarItemSchema = z2.lazy(() => z2.union([
5273
5387
  items: z2.array(sidebarItemSchema).optional(),
5274
5388
  label: z2.string(),
5275
5389
  root: z2.string().optional()
5276
- }).strict()
5390
+ })
5277
5391
  ]));
5278
- var fontSlug = z2.string().refine(isFontSlug, (value) => ({
5279
- message: `Unknown font "${value}". Supported fonts: ${FONT_SLUGS.join(", ")}.`
5280
- }));
5392
+ var fontSlug = z2.string().superRefine((value, ctx) => {
5393
+ if (!isFontSlug(value)) {
5394
+ ctx.addIssue({
5395
+ code: z2.ZodIssueCode.custom,
5396
+ message: `Unknown font "${value}". Supported fonts: ${FONT_SLUGS.join(", ")}.`
5397
+ });
5398
+ }
5399
+ });
5281
5400
  var perModeValueSchema = z2.union([
5282
5401
  z2.string(),
5283
- z2.object({ dark: z2.string().optional(), light: z2.string().optional() }).strict()
5402
+ z2.strictObject({
5403
+ dark: z2.string().optional(),
5404
+ light: z2.string().optional()
5405
+ })
5284
5406
  ]).optional().transform((value) => typeof value === "string" ? { dark: value, light: value } : value);
5285
- var themeConfigSchema = z2.object({
5407
+ var themeConfigSchema = z2.strictObject({
5286
5408
  accent: z2.union([
5287
5409
  z2.string(),
5288
- z2.object({ dark: z2.string(), light: z2.string() }).strict()
5410
+ z2.strictObject({ dark: z2.string(), light: z2.string() })
5289
5411
  ]).default("blue").transform((value) => typeof value === "string" ? { dark: value, light: value } : value),
5290
5412
  action: z2.string().optional(),
5291
5413
  background: perModeValueSchema,
5292
5414
  backgroundImage: perModeValueSchema,
5293
- fonts: z2.object({
5415
+ fonts: z2.strictObject({
5294
5416
  body: fontSlug.default("inter"),
5295
5417
  display: fontSlug.default("inter-tight"),
5296
5418
  mono: fontSlug.default("ibm-plex-mono")
5297
- }).strict().default({}),
5419
+ }).default({}),
5298
5420
  layout: z2.enum(["sidebar"]).default("sidebar"),
5299
5421
  mode: z2.enum(["system", "light", "dark"]).default("system"),
5300
5422
  radius: z2.enum(["none", "sm", "md", "lg"]).default("md")
5301
- }).strict();
5302
- var algoliaSearchSchema = z2.object({
5423
+ });
5424
+ var algoliaSearchSchema = z2.strictObject({
5303
5425
  appId: z2.string(),
5304
5426
  indexName: z2.string(),
5305
5427
  searchApiKey: z2.string()
5306
- }).strict();
5307
- var oramaCloudSearchSchema = z2.object({
5428
+ });
5429
+ var oramaCloudSearchSchema = z2.strictObject({
5308
5430
  apiKey: z2.string(),
5309
5431
  endpoint: z2.string(),
5310
5432
  indexId: z2.string().optional()
5311
- }).strict();
5312
- var typesenseSearchSchema = z2.object({
5433
+ });
5434
+ var typesenseSearchSchema = z2.strictObject({
5313
5435
  collection: z2.string(),
5314
5436
  host: z2.string(),
5315
5437
  port: z2.number().int().positive().optional(),
5316
5438
  protocol: z2.enum(["http", "https"]).optional(),
5317
5439
  searchApiKey: z2.string()
5318
- }).strict();
5319
- var mixedbreadSearchSchema = z2.object({
5440
+ });
5441
+ var mixedbreadSearchSchema = z2.strictObject({
5320
5442
  storeId: z2.string()
5321
- }).strict();
5443
+ });
5322
5444
  var searchProviders = [
5323
5445
  "orama",
5324
5446
  "pagefind",
@@ -5335,16 +5457,16 @@ var PROVIDER_CONFIG_KEY = {
5335
5457
  "orama-cloud": "oramaCloud",
5336
5458
  typesense: "typesense"
5337
5459
  };
5338
- var searchConfigSchema = z2.object({
5460
+ var searchConfigSchema = z2.strictObject({
5339
5461
  algolia: algoliaSearchSchema.optional(),
5340
- indexing: z2.object({
5462
+ indexing: z2.strictObject({
5341
5463
  includeHiddenPages: z2.boolean().default(false)
5342
- }).strict().default({}),
5464
+ }).default({}),
5343
5465
  mixedbread: mixedbreadSearchSchema.optional(),
5344
5466
  oramaCloud: oramaCloudSearchSchema.optional(),
5345
5467
  provider: z2.enum(searchProviders).default("orama"),
5346
5468
  typesense: typesenseSearchSchema.optional()
5347
- }).strict().superRefine((value, ctx) => {
5469
+ }).superRefine((value, ctx) => {
5348
5470
  const field = PROVIDER_CONFIG_KEY[value.provider];
5349
5471
  if (field && !value[field]) {
5350
5472
  ctx.addIssue({
@@ -5361,18 +5483,18 @@ var askAiProviders = [
5361
5483
  "inkeep",
5362
5484
  "openai-compatible"
5363
5485
  ];
5364
- var aiConfigSchema = z2.object({
5365
- ask: z2.object({
5486
+ var aiConfigSchema = z2.strictObject({
5487
+ ask: z2.strictObject({
5366
5488
  apiKeyEnv: z2.string().optional(),
5367
5489
  baseUrl: z2.string().url().optional(),
5368
5490
  enabled: z2.boolean().default(false),
5369
5491
  model: z2.string().default("openai/gpt-5.5"),
5370
5492
  provider: z2.enum(askAiProviders).default("gateway"),
5371
- suggestions: z2.array(z2.object({
5493
+ suggestions: z2.array(z2.strictObject({
5372
5494
  icon: iconName.optional(),
5373
5495
  label: z2.string().min(1)
5374
- }).strict()).default([])
5375
- }).strict().superRefine((value, ctx) => {
5496
+ })).default([])
5497
+ }).superRefine((value, ctx) => {
5376
5498
  if (value.provider === "openai-compatible" && !value.baseUrl) {
5377
5499
  ctx.addIssue({
5378
5500
  code: z2.ZodIssueCode.custom,
@@ -5382,51 +5504,51 @@ var aiConfigSchema = z2.object({
5382
5504
  }
5383
5505
  }).optional(),
5384
5506
  llmsTxt: z2.boolean().default(true)
5385
- }).strict();
5386
- var featuredLinkSchema = z2.object({
5507
+ });
5508
+ var featuredLinkSchema = z2.strictObject({
5387
5509
  href: z2.string(),
5388
5510
  icon: iconName.optional(),
5389
5511
  label: z2.string()
5390
- }).strict();
5391
- var navigationConfigSchema = z2.object({
5512
+ });
5513
+ var navigationConfigSchema = z2.strictObject({
5392
5514
  featured: z2.array(featuredLinkSchema).default([]),
5393
5515
  repo: z2.boolean().default(true),
5394
5516
  selectors: z2.array(navSelectorSchema).default([]),
5395
5517
  sidebar: z2.union([
5396
5518
  z2.array(sidebarItemSchema),
5397
- z2.object({
5519
+ z2.strictObject({
5398
5520
  display: sidebarDisplaySchema.default("flat"),
5399
5521
  items: z2.array(sidebarItemSchema).optional()
5400
- }).strict()
5522
+ })
5401
5523
  ]).default({}).transform((value) => Array.isArray(value) ? { display: "flat", items: value } : value),
5402
5524
  tabs: z2.array(navTabSchema).optional()
5403
- }).strict();
5525
+ });
5404
5526
  var exportConfigSchema = z2.union([
5405
5527
  z2.boolean(),
5406
- z2.object({
5528
+ z2.strictObject({
5407
5529
  epub: z2.boolean().default(false),
5408
5530
  pdf: z2.boolean().default(false)
5409
- }).strict()
5531
+ })
5410
5532
  ]).transform((value) => typeof value === "boolean" ? { epub: value, pdf: value } : value);
5411
- var mcpConfigSchema = z2.object({
5533
+ var mcpConfigSchema = z2.strictObject({
5412
5534
  enabled: z2.boolean().default(false),
5413
5535
  instructions: z2.string().optional(),
5414
5536
  name: z2.string().optional(),
5415
5537
  route: z2.string().default("/mcp")
5416
- }).strict();
5417
- var localeSchema = z2.object({
5538
+ });
5539
+ var localeSchema = z2.strictObject({
5418
5540
  code: z2.string().min(1),
5419
5541
  dir: z2.enum(["ltr", "rtl"]).default("ltr"),
5420
5542
  label: z2.string()
5421
- }).strict();
5422
- var i18nConfigSchema = z2.object({
5543
+ });
5544
+ var i18nConfigSchema = z2.strictObject({
5423
5545
  defaultLocale: z2.string().default("en"),
5424
5546
  fallbackLocale: z2.string().nullable().optional(),
5425
5547
  hideDefaultLocalePrefix: z2.boolean().default(true),
5426
5548
  locales: z2.array(localeSchema).min(1),
5427
5549
  parser: z2.enum(["dir", "dot"]).default("dir"),
5428
5550
  ui: uiLocaleOverridesSchema.optional()
5429
- }).strict().superRefine((value, ctx) => {
5551
+ }).superRefine((value, ctx) => {
5430
5552
  const codes = new Set(value.locales.map((locale) => locale.code));
5431
5553
  if (!codes.has(value.defaultLocale)) {
5432
5554
  ctx.addIssue({
@@ -5443,46 +5565,46 @@ var i18nConfigSchema = z2.object({
5443
5565
  });
5444
5566
  }
5445
5567
  });
5446
- var analyticsScriptSchema = z2.object({
5568
+ var analyticsScriptSchema = z2.strictObject({
5447
5569
  attributes: z2.record(z2.string(), z2.string()).optional(),
5448
5570
  content: z2.string().optional(),
5449
5571
  src: z2.string().optional(),
5450
5572
  strategy: z2.enum(["async", "defer"]).optional()
5451
- }).strict().refine((value) => Boolean(value.src) !== Boolean(value.content), {
5573
+ }).refine((value) => Boolean(value.src) !== Boolean(value.content), {
5452
5574
  message: "An analytics script must set exactly one of `src` or `content`."
5453
5575
  });
5454
- var analyticsConfigSchema = z2.object({
5455
- posthog: z2.object({
5576
+ var analyticsConfigSchema = z2.strictObject({
5577
+ posthog: z2.strictObject({
5456
5578
  host: z2.string().optional(),
5457
5579
  key: z2.string()
5458
- }).strict().optional(),
5580
+ }).optional(),
5459
5581
  scripts: z2.array(analyticsScriptSchema).optional(),
5460
5582
  vercel: z2.boolean().optional()
5461
- }).strict();
5462
- var deploymentConfigSchema = z2.object({
5583
+ });
5584
+ var deploymentConfigSchema = z2.strictObject({
5463
5585
  adapter: z2.enum(["vercel", "node", "netlify", "cloudflare"]).nullable().default(null),
5464
5586
  base: z2.string().optional(),
5465
5587
  output: z2.enum(["static", "server"]).default("static"),
5466
5588
  site: z2.string().url().optional()
5467
- }).strict();
5468
- var redirectSchema = z2.object({
5589
+ });
5590
+ var redirectSchema = z2.strictObject({
5469
5591
  from: z2.string(),
5470
5592
  status: z2.union([z2.literal(301), z2.literal(302), z2.literal(307), z2.literal(308)]).default(301),
5471
5593
  to: z2.string()
5472
- }).strict();
5473
- var ogConfigSchema = z2.object({
5594
+ });
5595
+ var ogConfigSchema = z2.strictObject({
5474
5596
  enabled: z2.boolean().optional()
5475
- }).strict();
5476
- var rssConfigSchema = z2.object({
5597
+ });
5598
+ var rssConfigSchema = z2.strictObject({
5477
5599
  enabled: z2.boolean().default(true),
5478
5600
  limit: z2.number().int().positive().default(50),
5479
5601
  types: z2.array(z2.string()).default(["blog", "changelog"])
5480
- }).strict();
5481
- var contentSignalsObjectSchema = z2.object({
5602
+ });
5603
+ var contentSignalsObjectSchema = z2.strictObject({
5482
5604
  aiInput: z2.boolean().default(true),
5483
5605
  aiTrain: z2.boolean().default(true),
5484
5606
  search: z2.boolean().default(true)
5485
- }).strict();
5607
+ });
5486
5608
  var contentSignalsSchema = z2.union([z2.boolean(), contentSignalsObjectSchema]).transform((value) => {
5487
5609
  if (value === true) {
5488
5610
  return contentSignalsObjectSchema.parse({});
@@ -5492,7 +5614,7 @@ var contentSignalsSchema = z2.union([z2.boolean(), contentSignalsObjectSchema]).
5492
5614
  }
5493
5615
  return value;
5494
5616
  });
5495
- var seoConfigSchema = z2.object({
5617
+ var seoConfigSchema = z2.strictObject({
5496
5618
  agentReadability: z2.boolean().default(true),
5497
5619
  contentSignals: contentSignalsSchema.default(true),
5498
5620
  og: ogConfigSchema.default({}),
@@ -5500,40 +5622,50 @@ var seoConfigSchema = z2.object({
5500
5622
  rss: rssConfigSchema.default({}),
5501
5623
  sitemap: z2.boolean().default(true),
5502
5624
  structuredData: z2.boolean().default(true)
5503
- }).strict();
5504
- var githubConfigSchema = z2.object({
5625
+ });
5626
+ var githubConfigSchema = z2.strictObject({
5505
5627
  branch: z2.string().default("main"),
5506
5628
  dir: z2.string().optional(),
5507
5629
  owner: z2.string(),
5508
5630
  repo: z2.string()
5509
- }).strict();
5510
- var codeBlockThemeSchema = z2.object({
5631
+ });
5632
+ var codeBlockThemeSchema = z2.strictObject({
5511
5633
  dark: z2.string().default("github-dark"),
5512
5634
  light: z2.string().default("github-light")
5513
- }).strict();
5514
- var codeBlocksConfigSchema = z2.object({
5635
+ });
5636
+ var codeBlocksConfigSchema = z2.strictObject({
5515
5637
  theme: codeBlockThemeSchema.default({})
5516
- }).strict();
5638
+ });
5639
+ var examplesConfigSchema = z2.union([
5640
+ z2.string(),
5641
+ z2.strictObject({
5642
+ css: z2.string().optional(),
5643
+ source: z2.string().default("examples")
5644
+ })
5645
+ ]).transform((value) => typeof value === "string" ? { source: value } : value);
5517
5646
  var lastModifiedConfigSchema = z2.union([
5518
5647
  z2.boolean(),
5519
- z2.object({ type: z2.enum(["git", "frontmatter"]).default("git") }).strict()
5648
+ z2.strictObject({ type: z2.enum(["git", "frontmatter"]).default("git") })
5520
5649
  ]);
5521
- var codeConfigSchema = z2.object({
5650
+ var codeConfigSchema = z2.strictObject({
5522
5651
  icons: z2.boolean().default(true),
5523
5652
  wrap: z2.boolean().default(false)
5524
- }).strict();
5525
- var markdownConfigSchema = z2.object({
5653
+ });
5654
+ var markdownConfigSchema = z2.strictObject({
5526
5655
  code: codeConfigSchema.default({}),
5527
5656
  codeBlocks: codeBlocksConfigSchema.default({}),
5528
5657
  headingAnchors: z2.boolean().default(true),
5529
5658
  imageZoom: z2.boolean().default(true)
5530
- }).strict();
5531
- var openapiSourceSchema = z2.object({
5659
+ });
5660
+ var reactConfigSchema = z2.strictObject({
5661
+ compiler: z2.boolean().default(true)
5662
+ });
5663
+ var openapiSourceSchema = z2.strictObject({
5532
5664
  label: z2.string().optional(),
5533
5665
  route: z2.string().optional(),
5534
5666
  spec: z2.string()
5535
- }).strict();
5536
- var openapiConfigSchema = z2.object({
5667
+ });
5668
+ var openapiConfigSchema = z2.strictObject({
5537
5669
  codeSamples: z2.array(z2.string()).default(["curl", "js", "python"]),
5538
5670
  enabled: z2.boolean().default(false),
5539
5671
  expandSchemas: z2.boolean().default(false),
@@ -5542,20 +5674,20 @@ var openapiConfigSchema = z2.object({
5542
5674
  sources: z2.array(openapiSourceSchema).default([]),
5543
5675
  spec: z2.string().optional(),
5544
5676
  theme: z2.string().optional()
5545
- }).strict();
5546
- var asyncapiConfigSchema = z2.object({
5677
+ });
5678
+ var asyncapiConfigSchema = z2.strictObject({
5547
5679
  enabled: z2.boolean().default(false),
5548
5680
  route: z2.string().default("/events"),
5549
5681
  sources: z2.array(openapiSourceSchema).default([]),
5550
5682
  spec: z2.string().optional(),
5551
5683
  theme: z2.string().optional()
5552
- }).strict();
5684
+ });
5553
5685
  var tocConfigSchema = z2.union([
5554
5686
  z2.boolean(),
5555
- z2.object({
5687
+ z2.strictObject({
5556
5688
  maxHeadingLevel: z2.number().int().min(1).max(6).optional(),
5557
5689
  minHeadingLevel: z2.number().int().min(1).max(6).optional()
5558
- }).strict()
5690
+ })
5559
5691
  ]).default(true).transform((value) => {
5560
5692
  if (typeof value === "boolean") {
5561
5693
  return { enabled: value, maxLevel: 3, minLevel: 2 };
@@ -5566,15 +5698,16 @@ var tocConfigSchema = z2.union([
5566
5698
  minLevel: value.minHeadingLevel ?? 2
5567
5699
  };
5568
5700
  });
5569
- var blumeConfigSchema = z2.object({
5701
+ var blumeConfigSchema = z2.strictObject({
5570
5702
  ai: aiConfigSchema.default({}),
5571
5703
  analytics: analyticsConfigSchema.optional(),
5572
5704
  asyncapi: asyncapiConfigSchema.default({}),
5573
5705
  banner: bannerConfigSchema.optional(),
5706
+ basePath: z2.string().optional().transform((value) => normalizeBasePath(value)),
5574
5707
  content: contentConfigSchema.default({}),
5575
5708
  deployment: deploymentConfigSchema.default({}),
5576
5709
  description: z2.string().optional(),
5577
- examples: z2.string().default("examples"),
5710
+ examples: examplesConfigSchema.default("examples"),
5578
5711
  export: exportConfigSchema.default(false),
5579
5712
  feedback: z2.boolean().default(true),
5580
5713
  github: githubConfigSchema.optional(),
@@ -5585,13 +5718,14 @@ var blumeConfigSchema = z2.object({
5585
5718
  mcp: mcpConfigSchema.default({}),
5586
5719
  navigation: navigationConfigSchema.default({}),
5587
5720
  openapi: openapiConfigSchema.default({}),
5721
+ react: reactConfigSchema.default({}),
5588
5722
  redirects: z2.array(redirectSchema).default([]),
5589
5723
  search: searchConfigSchema.default({}),
5590
5724
  seo: seoConfigSchema.default({}),
5591
5725
  theme: themeConfigSchema.default({}),
5592
5726
  title: z2.string().default("Documentation"),
5593
5727
  toc: tocConfigSchema
5594
- }).strict();
5728
+ });
5595
5729
 
5596
5730
  // src/core/sources/normalize.ts
5597
5731
  var NUMERIC_PREFIX = /^\d+[-_.]/u;
@@ -5601,55 +5735,83 @@ var stripNumericPrefix = (segment) => segment.replace(NUMERIC_PREFIX, "");
5601
5735
  var groupLabel = (segment) => segment.match(GROUP_FOLDER)?.groups?.label ?? null;
5602
5736
  var slugify3 = (text) => text.toLowerCase().trim().replaceAll(/[^\w\s-]/gu, "").replaceAll(/[\s_]+/gu, "-").replaceAll(/-+/gu, "-").replaceAll(/^-|-$/gu, "");
5603
5737
  var titleCase = (value) => value.split(WORD_SPLIT).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
5738
+ var addRouteSegment = (part, segments, groups) => {
5739
+ if (part === "") {
5740
+ return;
5741
+ }
5742
+ const group = groupLabel(part);
5743
+ if (group !== null) {
5744
+ groups.push(group);
5745
+ return;
5746
+ }
5747
+ const clean = stripNumericPrefix(part);
5748
+ if (clean === "index") {
5749
+ return;
5750
+ }
5751
+ segments.push(clean);
5752
+ };
5604
5753
  var mapRoute = (relativePath) => {
5605
5754
  const withoutExt = relativePath.slice(0, relativePath.length - extname4(relativePath).length);
5606
5755
  const rawParts = withoutExt.split("/");
5607
5756
  const segments = [];
5608
5757
  const groups = [];
5609
5758
  for (const part of rawParts) {
5610
- if (part === "") {
5611
- continue;
5612
- }
5613
- const group = groupLabel(part);
5614
- if (group !== null) {
5615
- groups.push(group);
5616
- continue;
5617
- }
5618
- const clean = stripNumericPrefix(part);
5619
- if (clean === "index") {
5620
- continue;
5621
- }
5622
- segments.push(clean);
5759
+ addRouteSegment(part, segments, groups);
5623
5760
  }
5624
5761
  const route = segments.length === 0 ? "/" : `/${segments.join("/")}`;
5625
5762
  return { groups, route, segments };
5626
5763
  };
5627
5764
  var CODE_FENCE2 = /^```/u;
5628
5765
  var ATX_HEADING = /^(?<hashes>#{1,6})\s+(?<text>.+?)(?:\s+#+)?\s*$/u;
5766
+ var scanHeadingLine = (line, inFence, slugger, headings) => {
5767
+ if (CODE_FENCE2.test(line.trimStart())) {
5768
+ return !inFence;
5769
+ }
5770
+ if (inFence) {
5771
+ return inFence;
5772
+ }
5773
+ const match = line.match(ATX_HEADING);
5774
+ if (match?.groups) {
5775
+ const depth = match.groups.hashes?.length ?? 1;
5776
+ const text = (match.groups.text ?? "").trim();
5777
+ headings.push({ depth, slug: slugger.slug(text), text });
5778
+ }
5779
+ return inFence;
5780
+ };
5629
5781
  var extractHeadings = (body) => {
5630
5782
  const headings = [];
5631
5783
  const slugger = new GithubSlugger;
5632
5784
  let inFence = false;
5633
5785
  for (const line of body.split(`
5634
5786
  `)) {
5635
- if (CODE_FENCE2.test(line.trimStart())) {
5636
- inFence = !inFence;
5637
- continue;
5638
- }
5639
- if (inFence) {
5640
- continue;
5641
- }
5642
- const match = line.match(ATX_HEADING);
5643
- if (match?.groups) {
5644
- const depth = match.groups.hashes?.length ?? 1;
5645
- const text = (match.groups.text ?? "").trim();
5646
- headings.push({ depth, slug: slugger.slug(text), text });
5647
- }
5787
+ inFence = scanHeadingLine(line, inFence, slugger, headings);
5648
5788
  }
5649
5789
  return headings;
5650
5790
  };
5651
5791
  var MD_LINK = /\[[^\]]*\]\((?<target>[^)\s]+)(?:\s+"[^"]*")?\)/gu;
5652
5792
  var INLINE_CODE2 = /`[^`]*`/gu;
5793
+ var scanLinkLine = (line, lineNumber, inFence, links) => {
5794
+ if (CODE_FENCE2.test(line.trimStart())) {
5795
+ return !inFence;
5796
+ }
5797
+ if (inFence) {
5798
+ return inFence;
5799
+ }
5800
+ const masked = line.replaceAll(INLINE_CODE2, (span) => " ".repeat(span.length));
5801
+ for (const match of masked.matchAll(MD_LINK)) {
5802
+ const target = match.groups?.target;
5803
+ if (target === undefined || match.index === undefined) {
5804
+ continue;
5805
+ }
5806
+ const targetOffset = match.index + match[0].indexOf("](") + "](".length;
5807
+ links.push({
5808
+ column: targetOffset + 1,
5809
+ line: lineNumber,
5810
+ target
5811
+ });
5812
+ }
5813
+ return inFence;
5814
+ };
5653
5815
  var extractLinks = (body) => {
5654
5816
  const links = [];
5655
5817
  let inFence = false;
@@ -5657,50 +5819,34 @@ var extractLinks = (body) => {
5657
5819
  for (const line of body.split(`
5658
5820
  `)) {
5659
5821
  lineNumber += 1;
5660
- if (CODE_FENCE2.test(line.trimStart())) {
5661
- inFence = !inFence;
5662
- continue;
5663
- }
5664
- if (inFence) {
5665
- continue;
5666
- }
5667
- const masked = line.replaceAll(INLINE_CODE2, (span) => " ".repeat(span.length));
5668
- for (const match of masked.matchAll(MD_LINK)) {
5669
- const target = match.groups?.target;
5670
- if (target === undefined || match.index === undefined) {
5671
- continue;
5672
- }
5673
- const targetOffset = match.index + match[0].indexOf("](") + "](".length;
5674
- links.push({
5675
- column: targetOffset + 1,
5676
- line: lineNumber,
5677
- target
5678
- });
5679
- }
5822
+ inFence = scanLinkLine(line, lineNumber, inFence, links);
5680
5823
  }
5681
5824
  return links;
5682
5825
  };
5683
5826
  var DOUBLE_QUOTED = /"[^"]*"/gu;
5684
5827
  var JSX_OPEN = /<(?<tag>[A-Z][A-Za-z0-9]*)/gu;
5828
+ var scanTagLine = (line, inFence, tags) => {
5829
+ if (CODE_FENCE2.test(line.trimStart())) {
5830
+ return !inFence;
5831
+ }
5832
+ if (inFence) {
5833
+ return inFence;
5834
+ }
5835
+ const clean = line.replaceAll(INLINE_CODE2, "").replaceAll(DOUBLE_QUOTED, "");
5836
+ for (const match of clean.matchAll(JSX_OPEN)) {
5837
+ const tag = match.groups?.tag;
5838
+ if (tag) {
5839
+ tags.add(tag);
5840
+ }
5841
+ }
5842
+ return inFence;
5843
+ };
5685
5844
  var extractComponentTags = (body) => {
5686
5845
  const tags = new Set;
5687
5846
  let inFence = false;
5688
5847
  for (const line of body.split(`
5689
5848
  `)) {
5690
- if (CODE_FENCE2.test(line.trimStart())) {
5691
- inFence = !inFence;
5692
- continue;
5693
- }
5694
- if (inFence) {
5695
- continue;
5696
- }
5697
- const clean = line.replaceAll(INLINE_CODE2, "").replaceAll(DOUBLE_QUOTED, "");
5698
- for (const match of clean.matchAll(JSX_OPEN)) {
5699
- const tag = match.groups?.tag;
5700
- if (tag) {
5701
- tags.add(tag);
5702
- }
5703
- }
5849
+ inFence = scanTagLine(line, inFence, tags);
5704
5850
  }
5705
5851
  return [...tags];
5706
5852
  };
@@ -5776,7 +5922,7 @@ var normalizeEntry2 = (entry, ctx) => {
5776
5922
  const pages = locales.map((locale) => ({
5777
5923
  ...base,
5778
5924
  locale,
5779
- route: i18n ? localizeRoute(logicalRoute, locale, i18n) : logicalRoute
5925
+ route: withBasePath(ctx.basePath ?? "", i18n ? localizeRoute(logicalRoute, locale, i18n) : logicalRoute)
5780
5926
  }));
5781
5927
  return { diagnostics: [], pages };
5782
5928
  };
@@ -5961,7 +6107,10 @@ ${indented}`;
5961
6107
 
5962
6108
  ${nested}`;
5963
6109
  }));
5964
- const pairs = blocks.map((block, i) => ({ block, text: parts[i] ?? "" })).filter((pair) => pair.text);
6110
+ const pairs = blocks.flatMap((block, i) => {
6111
+ const text = parts[i] ?? "";
6112
+ return text ? [{ block, text }] : [];
6113
+ });
5965
6114
  return pairs.map((pair, i) => {
5966
6115
  if (i === 0) {
5967
6116
  return pair.text;
@@ -6033,14 +6182,15 @@ ${nested}`;
6033
6182
  }
6034
6183
  };
6035
6184
  };
6185
+ const queryDatabase = (client, cursor) => withNotionRetry(() => client.databases.query({
6186
+ database_id: options.database,
6187
+ start_cursor: cursor
6188
+ }));
6036
6189
  const load2 = async (refresh = ctx?.refresh ?? true) => {
6037
6190
  const assetDiagnostics = [];
6038
6191
  const result = await loadWithCache(options.name, cache, async () => {
6039
6192
  const client = await resolveClient();
6040
- const pages = await collectAll((cursor) => withNotionRetry(() => client.databases.query({
6041
- database_id: options.database,
6042
- start_cursor: cursor
6043
- })));
6193
+ const pages = await collectAll((cursor) => queryDatabase(client, cursor));
6044
6194
  const built = await Promise.all(pages.map((page) => toEntry2(client, page)));
6045
6195
  for (const item of built) {
6046
6196
  assetDiagnostics.push(...item.diagnostics);
@@ -6404,38 +6554,53 @@ import { existsSync as existsSync10, readFileSync as readFileSync4, statSync } f
6404
6554
  import { createRequire as createRequire3 } from "node:module";
6405
6555
  import { pathToFileURL } from "node:url";
6406
6556
  import { dirname as dirname6, isAbsolute as isAbsolute6, join as join17, resolve as resolve6 } from "pathe";
6557
+ var scanJsonChar = (text, index, inString) => {
6558
+ const char = text[index];
6559
+ if (inString) {
6560
+ if (char === "\\") {
6561
+ return {
6562
+ append: char + (text[index + 1] ?? ""),
6563
+ inString: true,
6564
+ next: index + 2
6565
+ };
6566
+ }
6567
+ return { append: char ?? "", inString: char !== '"', next: index + 1 };
6568
+ }
6569
+ if (char === '"') {
6570
+ return { append: char, inString: true, next: index + 1 };
6571
+ }
6572
+ if (char === "/" && text[index + 1] === "/") {
6573
+ const newline = text.indexOf(`
6574
+ `, index + 2);
6575
+ return {
6576
+ append: "",
6577
+ inString: false,
6578
+ next: newline === -1 ? text.length : newline
6579
+ };
6580
+ }
6581
+ if (char === "/" && text[index + 1] === "*") {
6582
+ const end = text.indexOf("*/", index + 2);
6583
+ return {
6584
+ append: "",
6585
+ inString: false,
6586
+ next: end === -1 ? text.length : end + 2
6587
+ };
6588
+ }
6589
+ return { append: char ?? "", inString: false, next: index + 1 };
6590
+ };
6407
6591
  var stripJsonComments = (text) => {
6408
6592
  let out = "";
6409
6593
  let inString = false;
6410
- for (let index = 0;index < text.length; index += 1) {
6411
- const char = text[index];
6412
- if (inString) {
6413
- out += char;
6414
- if (char === "\\") {
6415
- out += text[index + 1] ?? "";
6416
- index += 1;
6417
- } else if (char === '"') {
6418
- inString = false;
6419
- }
6420
- continue;
6421
- }
6422
- if (char === '"') {
6423
- inString = true;
6424
- out += char;
6425
- continue;
6426
- }
6427
- if (char === "/" && text[index + 1] === "/") {
6428
- const newline = text.indexOf(`
6429
- `, index + 2);
6430
- index = newline === -1 ? text.length : newline - 1;
6431
- continue;
6432
- }
6433
- if (char === "/" && text[index + 1] === "*") {
6434
- const end = text.indexOf("*/", index + 2);
6435
- index = end === -1 ? text.length : end + 1;
6436
- continue;
6437
- }
6438
- out += char;
6594
+ let index = 0;
6595
+ while (index < text.length) {
6596
+ const {
6597
+ append,
6598
+ inString: nextInString,
6599
+ next
6600
+ } = scanJsonChar(text, index, inString);
6601
+ out += append;
6602
+ inString = nextInString;
6603
+ index = next;
6439
6604
  }
6440
6605
  return out;
6441
6606
  };
@@ -6466,10 +6631,10 @@ var resolveExtends = (spec, fromDir) => {
6466
6631
  return candidates.find(isFile) ?? null;
6467
6632
  }
6468
6633
  try {
6469
- const require_ = createRequire3(pathToFileURL(join17(fromDir, "_.js")).href);
6634
+ const requireFromDir = createRequire3(pathToFileURL(join17(fromDir, "_.js")).href);
6470
6635
  for (const sub of [`${spec}/tsconfig.json`, spec]) {
6471
6636
  try {
6472
- return require_.resolve(sub);
6637
+ return requireFromDir.resolve(sub);
6473
6638
  } catch {}
6474
6639
  }
6475
6640
  } catch {}
@@ -6542,6 +6707,7 @@ import { isAbsolute as isAbsolute8, join as join19 } from "pathe";
6542
6707
 
6543
6708
  // src/astro/templates.ts
6544
6709
  import { existsSync as existsSync11, readFileSync as readFileSync5 } from "node:fs";
6710
+ import { pathToFileURL as pathToFileURL2 } from "node:url";
6545
6711
  import { dirname as dirname7, isAbsolute as isAbsolute7, join as join18, relative as relative5 } from "pathe";
6546
6712
  var WORKSPACE_MARKERS = [
6547
6713
  ".git",
@@ -6632,11 +6798,13 @@ var RENDER_EXTERNAL_DEPS = [
6632
6798
  var renderUserAliases = (aliases) => Object.entries(aliases ?? {}).toSorted(([a], [b]) => b.length - a.length).map(([find, replacement]) => `
6633
6799
  ${JSON.stringify(find)}: ${JSON.stringify(replacement)},`).join("");
6634
6800
  var astroOutDir = (context) => context.distDir ?? `${context.root}/dist`;
6801
+ var reactIntegration = (compilerPath) => compilerPath ? `react({ babel: { plugins: [[${JSON.stringify(compilerPath)}, { target: "19" }]] } })` : "react()";
6635
6802
  var astroConfigTemplate = (options) => {
6636
6803
  const { context, config, needsReact, pages, dataPath, themePath } = options;
6637
6804
  const {
6638
6805
  contentRoutes,
6639
6806
  examplesPath,
6807
+ examplesThemePath,
6640
6808
  needsSvelte,
6641
6809
  needsVue,
6642
6810
  openapiPath,
@@ -6663,8 +6831,9 @@ var astroConfigTemplate = (options) => {
6663
6831
  prefixDefaultLocale: !config.i18n.hideDefaultLocalePrefix
6664
6832
  }
6665
6833
  })},` : "";
6666
- const redirectsOption = config.redirects.length > 0 ? `
6667
- redirects: ${JSON.stringify(Object.fromEntries(config.redirects.map((redirect) => [
6834
+ const basedRedirects = applyBaseToRedirects(config.redirects, config.basePath);
6835
+ const redirectsOption = basedRedirects.length > 0 ? `
6836
+ redirects: ${JSON.stringify(Object.fromEntries(basedRedirects.map((redirect) => [
6668
6837
  redirect.from,
6669
6838
  { destination: redirect.to, status: redirect.status }
6670
6839
  ])))},` : "";
@@ -6678,18 +6847,21 @@ var astroConfigTemplate = (options) => {
6678
6847
  ` : "";
6679
6848
  const svelteImport = needsSvelte ? `import svelte from "@astrojs/svelte";
6680
6849
  ` : "";
6681
- const blumeImport = `import { blumeIntegration, prerenderDepsPlugin } from "blume/astro";
6850
+ const blumeImport = `import { blumeIntegration, prerenderDepsPlugin, serverAppResolvePlugin } from "blume/astro";
6682
6851
  `;
6683
6852
  const twoslashImport = `import { transformerTwoslash } from "@shikijs/twoslash";
6684
6853
  `;
6685
6854
  const twoslashTransformer = "transformerTwoslash({ explicitTrigger: true }), ";
6855
+ const contentLinkBase = normalizeBasePath(deployment.base) + config.basePath;
6686
6856
  const integrations = [
6687
6857
  `mdx({ processor: blumeMdxProcessor(${JSON.stringify({
6858
+ basePath: contentLinkBase,
6859
+ codeThemes: config.markdown.codeBlocks.theme,
6688
6860
  headingAnchors: config.markdown.headingAnchors
6689
6861
  })}) })`
6690
6862
  ];
6691
6863
  if (needsReact) {
6692
- integrations.push("react()");
6864
+ integrations.push(reactIntegration(options.reactCompilerPath));
6693
6865
  }
6694
6866
  if (needsVue) {
6695
6867
  integrations.push("vue()");
@@ -6713,12 +6885,14 @@ export default defineConfig({
6713
6885
  integrations: [${integrations.join(", ")}],
6714
6886
  markdown: {
6715
6887
  processor: blumeMarkdownProcessor(${JSON.stringify({
6888
+ basePath: contentLinkBase,
6889
+ codeThemes: config.markdown.codeBlocks.theme,
6716
6890
  headingAnchors: config.markdown.headingAnchors
6717
6891
  })}),
6718
6892
  shikiConfig: {
6719
6893
  themes: {
6720
- light: "github-light",
6721
- dark: "github-dark",
6894
+ light: ${JSON.stringify(config.markdown.codeBlocks.theme.light)},
6895
+ dark: ${JSON.stringify(config.markdown.codeBlocks.theme.dark)},
6722
6896
  },
6723
6897
  defaultColor: false,
6724
6898
  transformers: [${twoslashTransformer}...blumeShikiTransformers(${JSON.stringify({ icons: config.markdown.code.icons })})],
@@ -6726,7 +6900,7 @@ export default defineConfig({
6726
6900
  },
6727
6901
  devToolbar: { enabled: false },
6728
6902
  vite: {
6729
- plugins: [tailwindcss(), prerenderDepsPlugin()],
6903
+ plugins: [tailwindcss(), prerenderDepsPlugin(), serverAppResolvePlugin()],
6730
6904
  // Blume's render-time deps are forced external on both build environments so
6731
6905
  // native bindings resolve at runtime and isolated linkers don't bundle
6732
6906
  // symlinked store copies (which would surface their children as unresolvable
@@ -6747,6 +6921,7 @@ export default defineConfig({
6747
6921
  alias: {
6748
6922
  "blume:data": ${JSON.stringify(dataPath)},
6749
6923
  "blume:examples": ${JSON.stringify(examplesPath)},
6924
+ "blume:examples-theme": ${JSON.stringify(examplesThemePath)},
6750
6925
  "blume:openapi": ${JSON.stringify(openapiPath)},
6751
6926
  "blume:search-client": ${JSON.stringify(searchClientPath)},
6752
6927
  "blume:theme": ${JSON.stringify(themePath)},${userAliasLines}
@@ -6771,6 +6946,7 @@ export default defineConfig({
6771
6946
  `;
6772
6947
  };
6773
6948
  var stagedContentDir = (outDir) => join18(outDir, "content");
6949
+ var astroGlobBase = (base) => isAbsolute7(base) ? pathToFileURL2(base).href : base;
6774
6950
  var contentConfigTemplate = (options) => {
6775
6951
  const { context, config } = options;
6776
6952
  const stagedBase = options.stagedBase ?? stagedContentDir(context.outDir);
@@ -6783,14 +6959,14 @@ var contentConfigTemplate = (options) => {
6783
6959
  const docsPattern = filesystem ? [
6784
6960
  ...includeGlobs,
6785
6961
  ...(excludeGlobs ?? []).map((pattern) => `!${pattern}`),
6786
- ...BLUME_IGNORE_DIRS.filter((dir) => dir !== ".blume").map((dir) => `!**/${dir}/**`),
6962
+ ...BLUME_IGNORE_DIRS.flatMap((dir) => dir === ".blume" ? [] : [`!**/${dir}/**`]),
6787
6963
  ...outDirIgnore
6788
6964
  ] : [];
6789
6965
  const stagedBlock = options.staged ? `
6790
6966
  const staged = defineCollection({
6791
6967
  loader: glob({
6792
6968
  pattern: ["**/*.{md,mdx}"],
6793
- base: ${JSON.stringify(stagedBase)},
6969
+ base: ${JSON.stringify(astroGlobBase(stagedBase))},
6794
6970
  generateId: ({ entry }) => entry,
6795
6971
  }),
6796
6972
  });
@@ -6802,7 +6978,7 @@ import { glob } from "astro/loaders";
6802
6978
  const docs = defineCollection({
6803
6979
  loader: glob({
6804
6980
  pattern: ${JSON.stringify(docsPattern)},
6805
- base: ${JSON.stringify(collectionBase)},
6981
+ base: ${JSON.stringify(astroGlobBase(collectionBase))},
6806
6982
  generateId: ({ entry }) => entry,
6807
6983
  }),
6808
6984
  });
@@ -7178,6 +7354,7 @@ var catchAllPageTemplate = (options) => {
7178
7354
  // Generated by Blume. Do not edit.
7179
7355
  import { getEntry, render } from "astro:content";
7180
7356
  import RootLayout from "blume/components/layout/RootLayout.astro";
7357
+ import { withBase } from "blume/components/islands/base-path.ts";
7181
7358
  import { resolveSlot } from "blume/components/layout/overrides.ts";
7182
7359
  ${askImport}
7183
7360
  import Accordion from "blume/components/content/Accordion.astro";
@@ -7303,13 +7480,16 @@ const ogPath = data.config.og.enabled
7303
7480
  ? \`/og/\${route === "/" ? "index" : route.slice(1)}.png\`
7304
7481
  : null;
7305
7482
  const ogRel = seo.image ?? ogPath;
7306
- // Only absolutize root-relative paths: \`seo.image\` may be an external URL,
7307
- // which must pass through verbatim (mirrors PageLayout's absolutizeOgImage).
7483
+ // Absolute URLs also carry the deployment base (the page is served under it):
7484
+ // \`site + base + path\`. Only absolutize root-relative paths: \`seo.image\` may be
7485
+ // an external URL, which passes through verbatim (mirrors PageLayout).
7308
7486
  const ogImage =
7309
- ogRel && base && ogRel.startsWith("/") ? \`\${base}\${ogRel}\` : ogRel;
7487
+ ogRel && base && ogRel.startsWith("/") ? \`\${base}\${withBase(ogRel)}\` : ogRel;
7310
7488
 
7489
+ const basedRoute = withBase(route);
7311
7490
  const canonical =
7312
- seo.canonical ?? (base ? \`\${base}\${route === "/" ? "" : route}\` : null);
7491
+ seo.canonical ??
7492
+ (base ? \`\${base}\${basedRoute === "/" ? "" : basedRoute}\` : null);
7313
7493
 
7314
7494
  // Locale resolution. With i18n on, pick the active locale's nav + dictionary,
7315
7495
  // build hreflang alternates, and derive the language-switcher targets.
@@ -7342,7 +7522,10 @@ const contentLocale =
7342
7522
  const contentDir = i18n
7343
7523
  ? (i18n.locales.find((l) => l.code === contentLocale)?.dir ?? "ltr")
7344
7524
  : "ltr";
7345
- const absolute = (path) => base + (path === "/" ? "" : path);
7525
+ const absolute = (path) => {
7526
+ const p = withBase(path);
7527
+ return base + (p === "/" ? "" : p);
7528
+ };
7346
7529
 
7347
7530
  const localeAlternates =
7348
7531
  i18n && base
@@ -7697,9 +7880,12 @@ import Example from ${JSON.stringify(spec.file)};
7697
7880
  ---
7698
7881
  <Example ${exampleDirective(spec)}{...Astro.props}><slot /></Example>
7699
7882
  `;
7700
- var exampleMapTemplate = (specs) => {
7883
+ var examplesRouteBase = (basePath) => `${basePath}/blume-examples`;
7884
+ var exampleMapTemplate = (specs, basePath) => {
7885
+ const base = `export const examplesBase = ${JSON.stringify(examplesRouteBase(basePath))};`;
7701
7886
  if (specs.length === 0) {
7702
7887
  return `// Generated by Blume. Do not edit.
7888
+ ${base}
7703
7889
  export const examples = {};
7704
7890
  `;
7705
7891
  }
@@ -7709,11 +7895,65 @@ export const examples = {};
7709
7895
  `);
7710
7896
  return `// Generated by Blume. Do not edit.
7711
7897
  ${imports}
7898
+ ${base}
7712
7899
  export const examples = {
7713
7900
  ${entries}
7714
7901
  };
7715
7902
  `;
7716
7903
  };
7904
+ var examplesPageTemplate = () => `---
7905
+ // Generated by Blume. Do not edit.
7906
+ import { examples } from "blume:examples";
7907
+ import "blume:examples-theme";
7908
+
7909
+ // Prerendered even in server output, like docs content.
7910
+ export const prerender = true;
7911
+
7912
+ export const getStaticPaths = () =>
7913
+ Object.keys(examples).map((path) => ({ params: { path } }));
7914
+
7915
+ const { path } = Astro.params;
7916
+ const entry = examples[path];
7917
+ const Example = entry.Component;
7918
+ ---
7919
+
7920
+ <html lang="en">
7921
+ <head>
7922
+ <meta charset="utf-8" />
7923
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
7924
+ <meta name="robots" content="noindex" />
7925
+ <title>{path}</title>
7926
+ <script is:inline>
7927
+ (() => {
7928
+ const root = document.documentElement;
7929
+ const apply = (theme) => {
7930
+ root.dataset.theme = theme;
7931
+ root.classList.toggle("dark", theme === "dark");
7932
+ };
7933
+ const stored = () =>
7934
+ localStorage.getItem("blume-theme") ??
7935
+ (matchMedia("(prefers-color-scheme: dark)").matches
7936
+ ? "dark"
7937
+ : "light");
7938
+ try {
7939
+ const host = window.parent.document.documentElement;
7940
+ apply(host.dataset.theme ?? stored());
7941
+ new MutationObserver(() => {
7942
+ apply(host.dataset.theme ?? stored());
7943
+ }).observe(host, { attributeFilter: ["data-theme"] });
7944
+ } catch {
7945
+ apply(stored());
7946
+ }
7947
+ })();
7948
+ </script>
7949
+ </head>
7950
+ <!-- Flex + margin:auto centers the example and, unlike place-items, keeps
7951
+ the top edge reachable when the example outgrows the frame. -->
7952
+ <body style="display:flex;min-height:100svh;padding:1.5rem">
7953
+ <div style="margin:auto"><Example /></div>
7954
+ </body>
7955
+ </html>
7956
+ `;
7717
7957
  var envTemplate = () => `/// <reference path="../.astro/types.d.ts" />
7718
7958
  /// <reference types="astro/client" />
7719
7959
 
@@ -7862,25 +8102,31 @@ var specConfiguration = async (spec, root) => {
7862
8102
  };
7863
8103
  }
7864
8104
  };
8105
+ var acceptScalarReference = (ref, seen, contentRoutes, warnings) => {
8106
+ if (ref.renderer !== "scalar") {
8107
+ return null;
8108
+ }
8109
+ if (seen.has(ref.route)) {
8110
+ warnings.push(`Two API reference sources resolve to ${ref.route}; keeping the first.`);
8111
+ return null;
8112
+ }
8113
+ if (contentRoutes.has(ref.route)) {
8114
+ warnings.push(`API reference route ${ref.route} collides with a content page; skipping the reference there.`);
8115
+ return null;
8116
+ }
8117
+ seen.add(ref.route);
8118
+ return ref;
8119
+ };
7865
8120
  var buildReferenceFiles = async (options) => {
7866
8121
  const { config, root, contentRoutes } = options;
7867
8122
  const warnings = [];
7868
8123
  const seen = new Set;
7869
8124
  const accepted = [];
7870
8125
  for (const ref of resolveReferences(config)) {
7871
- if (ref.renderer !== "scalar") {
7872
- continue;
7873
- }
7874
- if (seen.has(ref.route)) {
7875
- warnings.push(`Two API reference sources resolve to ${ref.route}; keeping the first.`);
7876
- continue;
8126
+ const next = acceptScalarReference(ref, seen, contentRoutes, warnings);
8127
+ if (next) {
8128
+ accepted.push(next);
7877
8129
  }
7878
- if (contentRoutes.has(ref.route)) {
7879
- warnings.push(`API reference route ${ref.route} collides with a content page; skipping the reference there.`);
7880
- continue;
7881
- }
7882
- seen.add(ref.route);
7883
- accepted.push(ref);
7884
8130
  }
7885
8131
  const built = await Promise.all(accepted.map(async (ref) => ({
7886
8132
  ref,
@@ -7910,18 +8156,9 @@ var buildReferenceFiles = async (options) => {
7910
8156
  };
7911
8157
 
7912
8158
  // src/theme/entry.ts
7913
- var tailwindEntryTemplate = (options) => `/* Generated by Blume. Do not edit. */
7914
- @import "tailwindcss";
7915
- @plugin "@tailwindcss/typography";
7916
-
7917
- /* Scan Blume's components and the user's project for utility classes. */
7918
- ${options.sources.map((source) => `@source "${source}";`).join(`
7919
- `)}
7920
-
7921
- /* Dark mode is driven by data-theme on the <html> element. */
7922
- @custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *));
7923
-
7924
- :root {
8159
+ var DARK_VARIANT = `/* Dark mode is driven by data-theme on the <html> element. */
8160
+ @custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *));`;
8161
+ var TOKEN_DEFAULTS = `:root {
7925
8162
  --blume-background: oklch(1 0 0);
7926
8163
  --blume-background-image: none;
7927
8164
  --blume-background-image-repeat: no-repeat;
@@ -7999,9 +8236,8 @@ ${options.sources.map((source) => `@source "${source}";`).join(`
7999
8236
  --blume-code-remove-border: oklch(0.7 0.2 22 / 0.7);
8000
8237
  --blume-code-word: oklch(0.7 0.14 255 / 0.22);
8001
8238
  --blume-code-word-border: oklch(0.7 0.14 255 / 0.55);
8002
- }
8003
-
8004
- @theme inline {
8239
+ }`;
8240
+ var THEME_MAPPING = `@theme inline {
8005
8241
  --color-background: var(--blume-background);
8006
8242
  --color-foreground: var(--blume-foreground);
8007
8243
  --color-muted: var(--blume-muted);
@@ -8016,7 +8252,20 @@ ${options.sources.map((source) => `@source "${source}";`).join(`
8016
8252
  --font-sans: var(--blume-font-body);
8017
8253
  --font-mono: var(--blume-font-mono);
8018
8254
  --font-display: var(--blume-font-display);
8019
- }
8255
+ }`;
8256
+ var tailwindEntryTemplate = (options) => `/* Generated by Blume. Do not edit. */
8257
+ @import "tailwindcss";
8258
+ @plugin "@tailwindcss/typography";
8259
+
8260
+ /* Scan Blume's components and the user's project for utility classes. */
8261
+ ${options.sources.map((source) => `@source "${source}";`).join(`
8262
+ `)}
8263
+
8264
+ ${DARK_VARIANT}
8265
+
8266
+ ${TOKEN_DEFAULTS}
8267
+
8268
+ ${THEME_MAPPING}
8020
8269
 
8021
8270
  @layer base {
8022
8271
  /* Nothing refuses to shrink below its intrinsic content width. This global
@@ -8495,7 +8744,7 @@ pre:has(.line.focused):hover .line:not(.focused) {
8495
8744
  }
8496
8745
  }
8497
8746
 
8498
- .prose :not(pre) > code {
8747
+ .prose :not(pre) > code:not(:where([class~="not-prose"] *)) {
8499
8748
  background: var(--blume-code-background);
8500
8749
  padding: 0.15em 0.35em;
8501
8750
  border-radius: 0.3rem;
@@ -8561,6 +8810,33 @@ ${options.twoslashCss ?? ""}
8561
8810
  ${options.configTokens}
8562
8811
  ${options.userTheme}
8563
8812
  `;
8813
+ var examplesEntryTemplate = (options) => `/* Generated by Blume. Do not edit. */
8814
+ @import "tailwindcss";
8815
+
8816
+ /* Scan the example files and the project sources they import. */
8817
+ ${options.sources.map((source) => `@source "${source}";`).join(`
8818
+ `)}
8819
+
8820
+ ${DARK_VARIANT}
8821
+
8822
+ ${TOKEN_DEFAULTS}
8823
+
8824
+ ${THEME_MAPPING}
8825
+
8826
+ /* Frame defaults: readable text in both modes, transparent so the docs pane's
8827
+ surface shows through. Base layer, so any user/example CSS wins. */
8828
+ @layer base {
8829
+ body {
8830
+ background: transparent;
8831
+ color: var(--blume-foreground);
8832
+ }
8833
+ }
8834
+
8835
+ /* Token overrides: config first, then the configured examples css (highest
8836
+ priority) — the place for shadcn variables and other component tokens. */
8837
+ ${options.configTokens}
8838
+ ${options.userCss}
8839
+ `;
8564
8840
 
8565
8841
  // src/theme/twoslash.ts
8566
8842
  import { readFileSync as readFileSync6 } from "node:fs";
@@ -8640,6 +8916,7 @@ export const mdxComponents = {};
8640
8916
  export const layoutOverrides = {};
8641
8917
  `;
8642
8918
  var attributeValue = (value) => value.replaceAll(/["\n\r]/gu, " ").trim();
8919
+ var CLIENT_LOAD = "client:load";
8643
8920
  var directiveFor = (override) => {
8644
8921
  const framework = override.source?.framework;
8645
8922
  switch (override.client) {
@@ -8650,13 +8927,13 @@ var directiveFor = (override) => {
8650
8927
  return "client:visible";
8651
8928
  }
8652
8929
  case "media": {
8653
- return override.media ? `client:media="${attributeValue(override.media)}"` : "client:load";
8930
+ return override.media ? `client:media="${attributeValue(override.media)}"` : CLIENT_LOAD;
8654
8931
  }
8655
8932
  case "only": {
8656
- return framework ? `client:only="${attributeValue(framework)}"` : "client:load";
8933
+ return framework ? `client:only="${attributeValue(framework)}"` : CLIENT_LOAD;
8657
8934
  }
8658
8935
  default: {
8659
- return "client:load";
8936
+ return CLIENT_LOAD;
8660
8937
  }
8661
8938
  }
8662
8939
  };
@@ -8779,30 +9056,33 @@ var discoverIslands = async (root) => {
8779
9056
  const islands = [];
8780
9057
  const warnings = [];
8781
9058
  const seen = new Map;
8782
- for (const [index, file] of files.entries()) {
9059
+ const collectIsland = (file, source) => {
8783
9060
  const base = basename(file);
8784
9061
  const ext = base.match(ISLAND_FILE)?.groups?.ext;
8785
9062
  const framework = ext ? FRAMEWORK_BY_EXT2[ext] : undefined;
8786
9063
  if (!framework) {
8787
- continue;
9064
+ return;
8788
9065
  }
8789
9066
  const name = base.replace(ISLAND_FILE, "");
8790
9067
  if (!/^[A-Z][A-Za-z0-9_]*$/u.test(name)) {
8791
9068
  warnings.push(`Island "${file}" must have a PascalCase identifier filename to be used in MDX (letters, digits, and underscores only, e.g. Counter.tsx → <Counter />); skipping it.`);
8792
- continue;
9069
+ return;
8793
9070
  }
8794
9071
  const existing = seen.get(name);
8795
9072
  if (existing) {
8796
9073
  warnings.push(`Two islands both resolve to <${name}> ("${existing}" and "${file}"); ignoring the second. Give them distinct filenames.`);
8797
- continue;
9074
+ return;
8798
9075
  }
8799
9076
  seen.set(name, file);
8800
9077
  islands.push({
8801
- client: readClientMode(sources[index] ?? "", file, warnings),
9078
+ client: readClientMode(source, file, warnings),
8802
9079
  file,
8803
9080
  framework,
8804
9081
  name
8805
9082
  });
9083
+ };
9084
+ for (const [index, file] of files.entries()) {
9085
+ collectIsland(file, sources[index] ?? "");
8806
9086
  }
8807
9087
  return { islands, warnings };
8808
9088
  };
@@ -8821,9 +9101,6 @@ var GLOB_MAGIC = /[!*?[\]{}]/u;
8821
9101
  var splitGlobBase = (pattern) => {
8822
9102
  const segments = pattern.split("/");
8823
9103
  const firstMagic = segments.findIndex((segment) => GLOB_MAGIC.test(segment));
8824
- if (firstMagic === -1) {
8825
- return { base: pattern, rest: "" };
8826
- }
8827
9104
  return {
8828
9105
  base: segments.slice(0, firstMagic).join("/"),
8829
9106
  rest: segments.slice(firstMagic).join("/")
@@ -8842,20 +9119,19 @@ var discoverExamples = async (root, pattern = "examples") => {
8842
9119
  const examples = [];
8843
9120
  const warnings = [];
8844
9121
  const seen = new Map;
8845
- for (const [index, file] of files.entries()) {
9122
+ const collectExample = (file, source) => {
8846
9123
  const ext = file.match(EXAMPLE_FILE)?.groups?.ext;
8847
9124
  const framework = ext ? FRAMEWORK_BY_EXT3[ext] : undefined;
8848
9125
  if (!(ext && framework)) {
8849
- continue;
9126
+ return;
8850
9127
  }
8851
9128
  const path = relative6(dir, file).slice(0, -(ext.length + 1));
8852
9129
  const existing = seen.get(path);
8853
9130
  if (existing) {
8854
9131
  warnings.push(`Two examples both resolve to "${path}" ("${existing}" and "${file}"); ignoring the second. Give them distinct paths.`);
8855
- continue;
9132
+ return;
8856
9133
  }
8857
9134
  seen.set(path, file);
8858
- const source = sources[index] ?? "";
8859
9135
  examples.push({
8860
9136
  client: framework === "astro" ? undefined : readClientMode(source, file, warnings),
8861
9137
  file,
@@ -8864,6 +9140,9 @@ var discoverExamples = async (root, pattern = "examples") => {
8864
9140
  path,
8865
9141
  source
8866
9142
  });
9143
+ };
9144
+ for (const [index, file] of files.entries()) {
9145
+ collectExample(file, sources[index] ?? "");
8867
9146
  }
8868
9147
  return { examples, warnings };
8869
9148
  };
@@ -8895,18 +9174,21 @@ var humanizeSegment = (segment) => segment.split(/[-_]/u).filter(Boolean).map((w
8895
9174
  var customOgRoutes = (pages, siteTitle) => {
8896
9175
  const seen = new Set;
8897
9176
  const routes = [];
8898
- for (const { pattern } of pages) {
9177
+ const collectRoute = (pattern) => {
8899
9178
  const segments = pattern.split("/").filter(Boolean);
8900
9179
  if (segments.some((part) => PRIVATE_SEGMENT.test(part) || part.includes("["))) {
8901
- continue;
9180
+ return;
8902
9181
  }
8903
9182
  const slug = segments.length === 0 ? "index" : segments.join("/");
8904
9183
  if (seen.has(slug)) {
8905
- continue;
9184
+ return;
8906
9185
  }
8907
9186
  seen.add(slug);
8908
9187
  const last = segments.at(-1);
8909
9188
  routes.push({ slug, title: last ? humanizeSegment(last) : siteTitle });
9189
+ };
9190
+ for (const { pattern } of pages) {
9191
+ collectRoute(pattern);
8910
9192
  }
8911
9193
  return routes;
8912
9194
  };
@@ -8915,15 +9197,28 @@ var customOgRoutes = (pages, siteTitle) => {
8915
9197
  var BLUME_SRC = join22(packageRoot(), "src");
8916
9198
  var canResolveFrom = (fromDir, spec) => {
8917
9199
  try {
8918
- createRequire5(pathToFileURL2(join22(fromDir, "_.js")).href).resolve(spec);
9200
+ createRequire5(pathToFileURL3(join22(fromDir, "_.js")).href).resolve(spec);
8919
9201
  return true;
8920
9202
  } catch {
8921
9203
  return false;
8922
9204
  }
8923
9205
  };
9206
+ var resolveReactCompiler = (config, needsReact) => {
9207
+ if (!(needsReact && config.react.compiler)) {
9208
+ return null;
9209
+ }
9210
+ try {
9211
+ return createRequire5(pathToFileURL3(join22(packageRoot(), "_.js")).href).resolve("babel-plugin-react-compiler");
9212
+ } catch {
9213
+ return null;
9214
+ }
9215
+ };
9216
+ var reactCompilerWarnings = (config, needsReact, compilerPath) => needsReact && config.react.compiler && !compilerPath ? [
9217
+ "React Compiler is enabled but `babel-plugin-react-compiler` could not be resolved; falling back to an uncompiled build. Reinstall Blume, or set `react: { compiler: false }` to silence this."
9218
+ ] : [];
8924
9219
  var resolvedAstroPath = (fromDir) => {
8925
9220
  try {
8926
- const pkg = createRequire5(pathToFileURL2(join22(fromDir, "_.js")).href).resolve("astro/package.json");
9221
+ const pkg = createRequire5(pathToFileURL3(join22(fromDir, "_.js")).href).resolve("astro/package.json");
8927
9222
  return realpathSync(pkg);
8928
9223
  } catch {
8929
9224
  return null;
@@ -8934,7 +9229,12 @@ var blumeDepsDir = (pkgDir = packageRoot()) => {
8934
9229
  return candidates.find((dir) => existsSync12(join22(dir, "astro"))) ?? null;
8935
9230
  };
8936
9231
  var linkDepsJunction = async (link, depsDir) => {
8937
- const existing = await lstat(link).catch(() => null);
9232
+ let existing;
9233
+ try {
9234
+ existing = await lstat(link);
9235
+ } catch {
9236
+ existing = null;
9237
+ }
8938
9238
  if (existing) {
8939
9239
  if (!existing.isSymbolicLink()) {
8940
9240
  return;
@@ -8991,6 +9291,17 @@ var islandFrameworkWarnings = (frameworks, root) => {
8991
9291
  }
8992
9292
  return warnings;
8993
9293
  };
9294
+ var examplesCssFile = (root, config) => config.examples.css ? join22(root, config.examples.css) : null;
9295
+ var writeExamplesPreview = async (options) => {
9296
+ const { config, hasExamples, root, srcDir, write } = options;
9297
+ if (hasExamples) {
9298
+ await write(join22(srcDir, "pages", ...config.basePath.split("/").filter(Boolean), "blume-examples", "[...path].astro"), examplesPageTemplate());
9299
+ }
9300
+ const cssFile = examplesCssFile(root, config);
9301
+ return cssFile && !existsSync12(cssFile) ? [
9302
+ `examples.css points at "${config.examples.css}", which doesn't exist; previews render without it.`
9303
+ ] : [];
9304
+ };
8994
9305
  var readOptional = async (path) => {
8995
9306
  if (!path) {
8996
9307
  return "";
@@ -9045,7 +9356,14 @@ var pruneOrphans = async (srcDir, written) => {
9045
9356
  cwd: srcDir,
9046
9357
  onlyFiles: true
9047
9358
  });
9048
- await Promise.all(existing.map((path) => normalize3(path)).filter((path) => !written.has(path)).map((path) => rm2(path, { force: true })));
9359
+ const removals = [];
9360
+ for (const path of existing) {
9361
+ const normalized = normalize3(path);
9362
+ if (!written.has(normalized)) {
9363
+ removals.push(rm2(normalized, { force: true }));
9364
+ }
9365
+ }
9366
+ await Promise.all(removals);
9049
9367
  };
9050
9368
  var collectStaged = (project) => {
9051
9369
  const staged = new Map;
@@ -9165,7 +9483,8 @@ var buildRuntimeData = (project) => {
9165
9483
  return null;
9166
9484
  }
9167
9485
  const rel = relative8(context.root, sourcePath).split("\\").join("/");
9168
- return `${editBase}/${github?.dir ? `${github.dir}/${rel}` : rel}`;
9486
+ const editPath = github?.dir ? `${github.dir}/${rel}` : rel;
9487
+ return `${editBase}/${editPath}`;
9169
9488
  };
9170
9489
  const { i18n } = config;
9171
9490
  const withReferenceTabs = (nav) => ({
@@ -9199,6 +9518,7 @@ var buildRuntimeData = (project) => {
9199
9518
  appleIcon: resolveAppleIcon(project),
9200
9519
  ask: config.ai.ask?.enabled ? { suggestions: config.ai.ask.suggestions } : null,
9201
9520
  banner: resolveBanner(config),
9521
+ codeThemes: config.markdown.codeBlocks.theme,
9202
9522
  codeWrap: config.markdown.code.wrap,
9203
9523
  description: config.description,
9204
9524
  favicon: resolveFavicon(project),
@@ -9352,6 +9672,7 @@ var generateRuntime = async (project) => {
9352
9672
  const themePath = join22(srcDir, "generated", "app.css");
9353
9673
  const searchClientPath = join22(srcDir, "generated", "search-client.ts");
9354
9674
  const examplesPath = join22(srcDir, "generated", "examples.ts");
9675
+ const examplesThemePath = join22(srcDir, "generated", "examples.css");
9355
9676
  const openapiPath = join22(srcDir, "generated", "openapi.json");
9356
9677
  const written = new Set;
9357
9678
  const write = (path, content) => {
@@ -9367,21 +9688,25 @@ var generateRuntime = async (project) => {
9367
9688
  detectedReact,
9368
9689
  usesMath,
9369
9690
  userTheme,
9691
+ userExamplesCss,
9370
9692
  islandDiscovery,
9371
- exampleDiscovery
9693
+ exampleDiscovery,
9694
+ componentSlots
9372
9695
  ] = await Promise.all([
9373
9696
  context.pagesRoot ? discoverPages(context.pagesRoot) : Promise.resolve([]),
9374
9697
  detectNeedsReact(context.root),
9375
9698
  detectUsesMath(context.root),
9376
9699
  readOptional(context.themeFile),
9700
+ readOptional(examplesCssFile(context.root, config)),
9377
9701
  discoverIslands(context.root),
9378
- discoverExamples(context.root, config.examples)
9702
+ discoverExamples(context.root, config.examples.source),
9703
+ buildComponentSlots(context.componentsFile)
9379
9704
  ]);
9380
9705
  const {
9381
9706
  plan: slotPlan,
9382
9707
  tags: overrideTags,
9383
9708
  warnings: overrideWarnings
9384
- } = await buildComponentSlots(context.componentsFile);
9709
+ } = componentSlots;
9385
9710
  const frameworks = new Set([
9386
9711
  ...islandDiscovery.islands.map((island) => island.framework),
9387
9712
  ...exampleDiscovery.examples.map((example) => example.framework),
@@ -9390,63 +9715,73 @@ var generateRuntime = async (project) => {
9390
9715
  const needsReact = detectedReact || askEnabled || frameworks.has("react");
9391
9716
  const needsVue = frameworks.has("vue");
9392
9717
  const needsSvelte = frameworks.has("svelte");
9718
+ const reactCompilerPath = resolveReactCompiler(config, needsReact);
9393
9719
  const ogRoutes = customOgRoutes(pages, config.title);
9394
9720
  const mcp = planMcp(project, srcDir, pages);
9395
9721
  pages.push(...mcp.discoveryPages);
9396
9722
  const staged = collectStaged(project);
9397
9723
  const hasStaged = staged.size > 0;
9398
9724
  const hasFilesystemSource = project.sources.some((source) => !source.staged);
9399
- const structural = await Promise.all([
9400
- write(join22(out, "astro.config.mjs"), astroConfigTemplate({
9401
- aliases: resolveTsconfigAliases(context.root),
9402
- config,
9403
- contentRoutes: project.manifest.routes.map((route) => route.path),
9404
- context,
9405
- dataPath,
9406
- examplesPath,
9407
- needsReact,
9408
- needsSvelte,
9409
- needsVue,
9410
- openapiPath,
9411
- pages,
9412
- searchClientPath,
9413
- themePath
9414
- })),
9415
- write(join22(out, "package.json"), runtimePackageTemplate(runtimeDependencies({ config, needsReact, needsSvelte, needsVue }))),
9416
- write(join22(out, "tsconfig.json"), runtimeTsconfigTemplate()),
9417
- write(join22(srcDir, "env.d.ts"), envTemplate()),
9418
- write(join22(srcDir, "content.config.ts"), contentConfigTemplate({
9419
- collection: resolveDocsCollection(config, context),
9420
- config,
9421
- context,
9422
- filesystem: hasFilesystemSource,
9423
- staged: hasStaged
9424
- })),
9425
- write(join22(srcDir, "pages", "[...slug].astro"), catchAllPageTemplate({
9426
- askEnabled,
9427
- exportEpub,
9428
- exportPdf,
9429
- mathEnabled: usesMath,
9430
- needsReact
9431
- })),
9432
- write(join22(srcDir, "generated", "components.ts"), slotPlan.module),
9433
- write(join22(srcDir, "generated", "islands.ts"), islandMapTemplate(islandDiscovery.islands)),
9434
- write(join22(srcDir, "generated", "examples.ts"), exampleMapTemplate(exampleDiscovery.examples)),
9435
- write(themePath, tailwindEntryTemplate({
9436
- configTokens: `${buildThemeCss(config.theme)}${buildFontsCss(config.theme.fonts)}`,
9437
- sources: [
9438
- `${BLUME_SRC}/**/*.{astro,ts,tsx}`,
9439
- `${context.root}/**/*.{astro,mdx,ts,tsx}`
9440
- ],
9441
- twoslashCss: twoslashCss(),
9442
- userTheme
9443
- }))
9725
+ const [structural] = await Promise.all([
9726
+ Promise.all([
9727
+ write(join22(out, "astro.config.mjs"), astroConfigTemplate({
9728
+ aliases: resolveTsconfigAliases(context.root),
9729
+ config,
9730
+ contentRoutes: project.manifest.routes.map((route) => route.path),
9731
+ context,
9732
+ dataPath,
9733
+ examplesPath,
9734
+ examplesThemePath,
9735
+ needsReact,
9736
+ needsSvelte,
9737
+ needsVue,
9738
+ openapiPath,
9739
+ pages,
9740
+ reactCompilerPath,
9741
+ searchClientPath,
9742
+ themePath
9743
+ })),
9744
+ write(join22(out, "package.json"), runtimePackageTemplate(runtimeDependencies({ config, needsReact, needsSvelte, needsVue }))),
9745
+ write(join22(out, "tsconfig.json"), runtimeTsconfigTemplate()),
9746
+ write(join22(srcDir, "env.d.ts"), envTemplate()),
9747
+ write(join22(srcDir, "content.config.ts"), contentConfigTemplate({
9748
+ collection: resolveDocsCollection(config, context),
9749
+ config,
9750
+ context,
9751
+ filesystem: hasFilesystemSource,
9752
+ staged: hasStaged
9753
+ })),
9754
+ write(join22(srcDir, "pages", "[...slug].astro"), catchAllPageTemplate({
9755
+ askEnabled,
9756
+ exportEpub,
9757
+ exportPdf,
9758
+ mathEnabled: usesMath,
9759
+ needsReact
9760
+ })),
9761
+ write(join22(srcDir, "generated", "components.ts"), slotPlan.module),
9762
+ write(join22(srcDir, "generated", "islands.ts"), islandMapTemplate(islandDiscovery.islands)),
9763
+ write(join22(srcDir, "generated", "examples.ts"), exampleMapTemplate(exampleDiscovery.examples, config.basePath)),
9764
+ write(examplesThemePath, examplesEntryTemplate({
9765
+ configTokens: buildThemeCss(config.theme),
9766
+ sources: [`${context.root}/**/*.{astro,jsx,svelte,ts,tsx,vue}`],
9767
+ userCss: userExamplesCss
9768
+ })),
9769
+ write(themePath, tailwindEntryTemplate({
9770
+ configTokens: `${buildThemeCss(config.theme)}${buildFontsCss(config.theme.fonts)}`,
9771
+ sources: [
9772
+ `${BLUME_SRC}/**/*.{astro,ts,tsx}`,
9773
+ `${context.root}/**/*.{astro,mdx,ts,tsx}`
9774
+ ],
9775
+ twoslashCss: twoslashCss(),
9776
+ userTheme
9777
+ }))
9778
+ ]),
9779
+ Promise.all(islandDiscovery.islands.map((island) => write(join22(srcDir, "generated", "islands", `${island.name}.astro`), islandWrapperTemplate(island)))),
9780
+ Promise.all(slotPlan.wrappers.map((wrapper) => write(join22(srcDir, "generated", "component-slots", `${wrapper.name}.astro`), wrapper.content))),
9781
+ Promise.all(exampleDiscovery.examples.map((example) => write(join22(srcDir, "generated", "examples", `${exampleSlug(example.path)}.astro`), exampleWrapperTemplate(example)))),
9782
+ writeAskFiles(project, srcDir, write),
9783
+ writeMcpFiles(project, mcp, write)
9444
9784
  ]);
9445
- await Promise.all(islandDiscovery.islands.map((island) => write(join22(srcDir, "generated", "islands", `${island.name}.astro`), islandWrapperTemplate(island))));
9446
- await Promise.all(slotPlan.wrappers.map((wrapper) => write(join22(srcDir, "generated", "component-slots", `${wrapper.name}.astro`), wrapper.content)));
9447
- await Promise.all(exampleDiscovery.examples.map((example) => write(join22(srcDir, "generated", "examples", `${exampleSlug(example.path)}.astro`), exampleWrapperTemplate(example))));
9448
- await writeAskFiles(project, srcDir, write);
9449
- await writeMcpFiles(project, mcp, write);
9450
9785
  if (config.seo.og.enabled) {
9451
9786
  await write(join22(srcDir, "pages", "og", "[...slug].png.ts"), ogEndpointTemplate(ogRoutes));
9452
9787
  }
@@ -9459,8 +9794,17 @@ var generateRuntime = async (project) => {
9459
9794
  staged: hasStaged
9460
9795
  }));
9461
9796
  }
9462
- await writeNotFoundPage(write, srcDir, pages, project.graph.pages);
9463
- await write(searchClientPath, searchClientTemplate(config));
9797
+ const [examplesWarnings] = await Promise.all([
9798
+ writeExamplesPreview({
9799
+ config,
9800
+ hasExamples: exampleDiscovery.examples.length > 0,
9801
+ root: context.root,
9802
+ srcDir,
9803
+ write
9804
+ }),
9805
+ writeNotFoundPage(write, srcDir, pages, project.graph.pages),
9806
+ write(searchClientPath, searchClientTemplate(config))
9807
+ ]);
9464
9808
  if (servesStaticIndex(config.search.provider)) {
9465
9809
  const documents = await buildSearchDocuments(project);
9466
9810
  await write(join22(srcDir, "generated", "search.json"), `${JSON.stringify(documents)}
@@ -9488,9 +9832,11 @@ var generateRuntime = async (project) => {
9488
9832
  }
9489
9833
  const warnings = [
9490
9834
  ...depsLinkWarning ? [depsLinkWarning] : [],
9835
+ ...reactCompilerWarnings(config, needsReact, reactCompilerPath),
9491
9836
  ...mcp.warnings,
9492
9837
  ...islandDiscovery.warnings,
9493
9838
  ...exampleDiscovery.warnings,
9839
+ ...examplesWarnings,
9494
9840
  ...overrideWarnings
9495
9841
  ];
9496
9842
  const navTargetRoutes = new Set([
@@ -9522,13 +9868,15 @@ var generateRuntime = async (project) => {
9522
9868
  warnings.push(...references.warnings);
9523
9869
  await Promise.all(references.files.map((file) => write(join22(srcDir, "pages", file.pagePath), file.content)));
9524
9870
  }
9525
- await write(join22(srcDir, "generated", "data.json"), buildRuntimeData(project));
9526
9871
  const openApiSource2 = project.sources.find(isOpenApiSource);
9527
- await write(openapiPath, `${JSON.stringify(openApiSource2 ? openApiSource2.openApiData() : {})}
9528
- `);
9529
- await write(join22(out, "blume.manifest.json"), `${JSON.stringify(project.manifest, null, 2)}
9530
- `);
9531
- await writeStagedContent(out, staged);
9872
+ await Promise.all([
9873
+ write(join22(srcDir, "generated", "data.json"), buildRuntimeData(project)),
9874
+ write(openapiPath, `${JSON.stringify(openApiSource2 ? openApiSource2.openApiData() : {})}
9875
+ `),
9876
+ write(join22(out, "blume.manifest.json"), `${JSON.stringify(project.manifest, null, 2)}
9877
+ `),
9878
+ writeStagedContent(out, staged)
9879
+ ]);
9532
9880
  await pruneOrphans(srcDir, written);
9533
9881
  return { structuralChange: structural.some(Boolean), warnings };
9534
9882
  };
@@ -9620,12 +9968,13 @@ var loadConfig = async (root, options = {}) => {
9620
9968
  message: "Invalid Blume config.",
9621
9969
  severity: "error"
9622
9970
  };
9971
+ const moreIssues = rest.map((d) => ` - ${d.message}`).join(`
9972
+ `);
9623
9973
  throw new BlumeError(rest.length > 0 ? {
9624
9974
  ...primary,
9625
9975
  message: `${primary.message}
9626
9976
  ${rest.length} more config issue(s):
9627
- ${rest.map((d) => ` - ${d.message}`).join(`
9628
- `)}`
9977
+ ${moreIssues}`
9629
9978
  } : primary);
9630
9979
  }
9631
9980
  const config = applyDeploymentEnv(parsed.data);
@@ -9650,7 +9999,7 @@ var WORD_SPLIT2 = /[-_]/u;
9650
9999
  var humanize = (segment) => segment.replace(NUMERIC_PREFIX2, "").split(WORD_SPLIT2).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
9651
10000
  var numericOrder = (segment) => {
9652
10001
  const value = segment.match(NUMERIC_PREFIX2)?.groups?.order;
9653
- return value ? Number.parseInt(value, 10) : Number.POSITIVE_INFINITY;
10002
+ return value ? Math.trunc(Number(value)) : Number.POSITIVE_INFINITY;
9654
10003
  };
9655
10004
  var segmentKey = (raw) => {
9656
10005
  const group = raw.match(GROUP_FOLDER2)?.groups?.label;
@@ -9828,101 +10177,130 @@ var normalizeRef = (ref) => {
9828
10177
  const trimmed = withSlash.endsWith("/index") ? withSlash.slice(0, -"/index".length) : withSlash;
9829
10178
  return trimmed === "" ? "/" : trimmed;
9830
10179
  };
9831
- var routeForRef = (ref, byRoute) => {
10180
+ var routeForRef = (ref, byRoute, basePath) => {
9832
10181
  if (!ref) {
9833
10182
  return;
9834
10183
  }
9835
10184
  const normalized = normalizeRef(ref);
9836
- return byRoute.get(normalized)?.route ?? normalized;
10185
+ return byRoute.get(normalized)?.route ?? withBasePath(basePath, normalized);
9837
10186
  };
9838
- var buildConfigSidebar = (items, byRoute, display) => {
10187
+ var configItemToNode = (item, byRoute, basePath) => {
10188
+ if (typeof item === "string") {
10189
+ const page = byRoute.get(normalizeRef(item));
10190
+ if (!page) {
10191
+ return null;
10192
+ }
10193
+ return {
10194
+ badge: page.meta.sidebar.badge,
10195
+ deprecated: page.meta.deprecated || undefined,
10196
+ description: page.description,
10197
+ icon: page.meta.sidebar.icon,
10198
+ kind: "page",
10199
+ label: page.meta.sidebar.label ?? page.title,
10200
+ pageId: page.id,
10201
+ route: page.route
10202
+ };
10203
+ }
10204
+ if (item.root) {
10205
+ const page = byRoute.get(normalizeRef(item.root));
10206
+ return {
10207
+ badge: item.badge,
10208
+ deprecated: page?.meta.deprecated || undefined,
10209
+ icon: item.icon,
10210
+ kind: "page",
10211
+ label: item.label,
10212
+ pageId: page?.id ?? "",
10213
+ route: page?.route ?? withBasePath(basePath, normalizeRef(item.root))
10214
+ };
10215
+ }
10216
+ if (item.href) {
10217
+ return {
10218
+ badge: item.badge,
10219
+ icon: item.icon,
10220
+ kind: "page",
10221
+ label: item.label,
10222
+ pageId: "",
10223
+ route: withBasePath(basePath, item.href)
10224
+ };
10225
+ }
10226
+ return null;
10227
+ };
10228
+ var buildConfigSidebar = (items, byRoute, display, basePath) => {
9839
10229
  const nodes = [];
9840
10230
  for (const item of items) {
9841
- if (typeof item === "string") {
9842
- const page = byRoute.get(normalizeRef(item));
9843
- if (page) {
9844
- nodes.push({
9845
- badge: page.meta.sidebar.badge,
9846
- deprecated: page.meta.deprecated || undefined,
9847
- description: page.description,
9848
- icon: page.meta.sidebar.icon,
9849
- kind: "page",
9850
- label: page.meta.sidebar.label ?? page.title,
9851
- pageId: page.id,
9852
- route: page.route
9853
- });
9854
- }
9855
- continue;
9856
- }
9857
- if (item.items) {
10231
+ if (typeof item !== "string" && item.items) {
9858
10232
  nodes.push({
9859
10233
  badge: item.badge,
9860
- children: buildConfigSidebar(item.items, byRoute, display),
10234
+ children: buildConfigSidebar(item.items, byRoute, display, basePath),
9861
10235
  collapsed: item.collapsed,
9862
10236
  directory: item.directory,
9863
10237
  display: item.display ?? display,
9864
10238
  icon: item.icon,
9865
10239
  kind: "group",
9866
10240
  label: item.label,
9867
- route: routeForRef(item.root, byRoute)
10241
+ route: routeForRef(item.root, byRoute, basePath)
9868
10242
  });
9869
10243
  continue;
9870
10244
  }
9871
- if (item.root) {
9872
- const page = byRoute.get(normalizeRef(item.root));
9873
- nodes.push({
9874
- badge: item.badge,
9875
- deprecated: page?.meta.deprecated || undefined,
9876
- icon: item.icon,
9877
- kind: "page",
9878
- label: item.label,
9879
- pageId: page?.id ?? "",
9880
- route: page?.route ?? normalizeRef(item.root)
9881
- });
9882
- continue;
9883
- }
9884
- if (item.href) {
9885
- nodes.push({
9886
- badge: item.badge,
9887
- icon: item.icon,
9888
- kind: "page",
9889
- label: item.label,
9890
- pageId: "",
9891
- route: item.href
9892
- });
10245
+ const node = configItemToNode(item, byRoute, basePath);
10246
+ if (node) {
10247
+ nodes.push(node);
9893
10248
  }
9894
10249
  }
9895
10250
  return nodes;
9896
10251
  };
9897
10252
  var buildNavigation = (pages, options) => {
9898
- const featured = options.featured ?? [];
9899
- const selectors = options.selectors ?? [];
9900
- const tabs = options.tabs ?? [];
10253
+ const basePath = options.basePath ?? "";
9901
10254
  const display = options.display ?? "flat";
9902
10255
  const metaPrefix = options.metaPrefix ?? "";
9903
10256
  const sharedFolderMeta = options.sharedFolderMeta ?? new Map;
10257
+ const rebasePath = (item) => ({
10258
+ ...item,
10259
+ path: withBasePath(basePath, item.path)
10260
+ });
10261
+ const featured = basePath ? (options.featured ?? []).map((link) => ({
10262
+ ...link,
10263
+ href: withBasePath(basePath, link.href)
10264
+ })) : options.featured ?? [];
10265
+ const selectors = basePath ? (options.selectors ?? []).map((selector) => ({
10266
+ ...selector,
10267
+ items: selector.items.map(rebasePath)
10268
+ })) : options.selectors ?? [];
10269
+ const tabs = basePath ? (options.tabs ?? []).map((tab) => ({
10270
+ ...tab,
10271
+ items: tab.items?.map(rebasePath),
10272
+ path: withBasePath(basePath, tab.path)
10273
+ })) : options.tabs ?? [];
9904
10274
  const byRoute = new Map(pages.map((page) => [
9905
10275
  options.refByLogical ? page.translationKey : page.route,
9906
10276
  page
9907
10277
  ]));
10278
+ if (basePath && !options.refByLogical) {
10279
+ for (const page of pages) {
10280
+ const bare = stripBasePath(basePath, page.route);
10281
+ if (!byRoute.has(bare)) {
10282
+ byRoute.set(bare, page);
10283
+ }
10284
+ }
10285
+ }
9908
10286
  if (options.sidebar) {
9909
10287
  return {
9910
10288
  featured,
9911
10289
  selectors,
9912
- sidebar: buildConfigSidebar(options.sidebar, byRoute, display),
10290
+ sidebar: buildConfigSidebar(options.sidebar, byRoute, display, basePath),
9913
10291
  tabs
9914
10292
  };
9915
10293
  }
9916
10294
  return {
9917
10295
  featured,
9918
10296
  selectors,
9919
- sidebar: buildFileSystemSidebar(pages, options.folderMeta, sharedFolderMeta, metaPrefix, display, new Set(tabs.map((tab) => tab.path).filter((path) => path !== "/"))),
10297
+ sidebar: buildFileSystemSidebar(pages, options.folderMeta, sharedFolderMeta, metaPrefix, display, new Set(tabs.flatMap((tab) => tab.path === "/" ? [] : [tab.path]))),
9920
10298
  tabs
9921
10299
  };
9922
10300
  };
9923
10301
 
9924
10302
  // src/core/graph.ts
9925
- var buildContentGraph = (pages, options) => {
10303
+ var collectRoutes = (pages) => {
9926
10304
  const routes = new Map;
9927
10305
  const diagnostics = [];
9928
10306
  for (const page of pages) {
@@ -9939,60 +10317,73 @@ var buildContentGraph = (pages, options) => {
9939
10317
  }
9940
10318
  routes.set(page.route, page.id);
9941
10319
  }
9942
- const { i18n } = options;
9943
- const navigationByLocale = {};
9944
- let navigation;
9945
- if (i18n) {
9946
- const fallback = resolveFallbackLocale(i18n);
9947
- const fallbackByKey = new Map;
9948
- if (fallback) {
9949
- for (const page of pages) {
9950
- if (page.locale === fallback) {
9951
- fallbackByKey.set(page.translationKey, page);
9952
- }
9953
- }
10320
+ return { diagnostics, routes };
10321
+ };
10322
+ var localePagesFor = (code, real, fallback, fallbackByKey, i18n, basePath) => {
10323
+ if (!(fallback && code !== fallback)) {
10324
+ return real;
10325
+ }
10326
+ const present = new Set(real.map((page) => page.translationKey));
10327
+ const filled = [];
10328
+ for (const [key, source] of fallbackByKey) {
10329
+ if (!present.has(key)) {
10330
+ filled.push({
10331
+ ...source,
10332
+ locale: code,
10333
+ route: withBasePath(basePath, localizeRoute(key, code, i18n))
10334
+ });
9954
10335
  }
9955
- for (const { code } of i18n.locales) {
9956
- const tabs = options.navigation.tabs?.map((tab) => ({
9957
- ...tab,
9958
- path: tab.path.startsWith("/") ? localizeRoute(tab.path, code, i18n) : tab.path
9959
- }));
9960
- const real = pages.filter((page) => page.locale === code);
9961
- let localePages = real;
9962
- if (fallback && code !== fallback) {
9963
- const present = new Set(real.map((page) => page.translationKey));
9964
- const filled = [];
9965
- for (const [key, source] of fallbackByKey) {
9966
- if (!present.has(key)) {
9967
- filled.push({
9968
- ...source,
9969
- locale: code,
9970
- route: localizeRoute(key, code, i18n)
9971
- });
9972
- }
9973
- }
9974
- localePages = [...real, ...filled];
10336
+ }
10337
+ return [...real, ...filled];
10338
+ };
10339
+ var buildLocaleNavigation = (code, pages, fallback, fallbackByKey, options, i18n) => {
10340
+ const tabs = options.navigation.tabs?.map((tab) => ({
10341
+ ...tab,
10342
+ path: tab.path.startsWith("/") ? localizeRoute(tab.path, code, i18n) : tab.path
10343
+ }));
10344
+ const real = pages.filter((page) => page.locale === code);
10345
+ const localePages = localePagesFor(code, real, fallback, fallbackByKey, i18n, options.basePath ?? "");
10346
+ return buildNavigation(localePages, {
10347
+ basePath: options.basePath ?? "",
10348
+ display: options.navigation.sidebar.display,
10349
+ featured: options.navigation.featured,
10350
+ folderMeta: options.folderMeta,
10351
+ metaPrefix: i18n.parser === "dir" && code !== i18n.defaultLocale ? code : "",
10352
+ refByLogical: true,
10353
+ selectors: options.navigation.selectors,
10354
+ sharedFolderMeta: options.sharedFolderMeta,
10355
+ sidebar: options.navigation.sidebar.items,
10356
+ tabs
10357
+ });
10358
+ };
10359
+ var buildI18nNavigation = (pages, options, i18n) => {
10360
+ const fallback = resolveFallbackLocale(i18n);
10361
+ const fallbackByKey = new Map;
10362
+ if (fallback) {
10363
+ for (const page of pages) {
10364
+ if (page.locale === fallback) {
10365
+ fallbackByKey.set(page.translationKey, page);
9975
10366
  }
9976
- navigationByLocale[code] = buildNavigation(localePages, {
9977
- display: options.navigation.sidebar.display,
9978
- featured: options.navigation.featured,
9979
- folderMeta: options.folderMeta,
9980
- metaPrefix: i18n.parser === "dir" && code !== i18n.defaultLocale ? code : "",
9981
- refByLogical: true,
9982
- selectors: options.navigation.selectors,
9983
- sharedFolderMeta: options.sharedFolderMeta,
9984
- sidebar: options.navigation.sidebar.items,
9985
- tabs
9986
- });
9987
10367
  }
9988
- navigation = navigationByLocale[i18n.defaultLocale] ?? {
9989
- featured: [],
9990
- selectors: [],
9991
- sidebar: [],
9992
- tabs: []
9993
- };
9994
- } else {
9995
- navigation = buildNavigation(pages, {
10368
+ }
10369
+ const navigationByLocale = {};
10370
+ for (const { code } of i18n.locales) {
10371
+ navigationByLocale[code] = buildLocaleNavigation(code, pages, fallback, fallbackByKey, options, i18n);
10372
+ }
10373
+ const navigation = navigationByLocale[i18n.defaultLocale] ?? {
10374
+ featured: [],
10375
+ selectors: [],
10376
+ sidebar: [],
10377
+ tabs: []
10378
+ };
10379
+ return { navigation, navigationByLocale };
10380
+ };
10381
+ var buildContentGraph = (pages, options) => {
10382
+ const { diagnostics, routes } = collectRoutes(pages);
10383
+ const { i18n } = options;
10384
+ const { navigation, navigationByLocale } = i18n ? buildI18nNavigation(pages, options, i18n) : {
10385
+ navigation: buildNavigation(pages, {
10386
+ basePath: options.basePath ?? "",
9996
10387
  display: options.navigation.sidebar.display,
9997
10388
  featured: options.navigation.featured,
9998
10389
  folderMeta: options.folderMeta,
@@ -10000,10 +10391,10 @@ var buildContentGraph = (pages, options) => {
10000
10391
  sharedFolderMeta: options.sharedFolderMeta,
10001
10392
  sidebar: options.navigation.sidebar.items,
10002
10393
  tabs: options.navigation.tabs
10003
- });
10004
- }
10005
- diagnostics.push(...validateNavIcons(navigation));
10006
- diagnostics.push(...validateNavStructure(navigation, pages));
10394
+ }),
10395
+ navigationByLocale: {}
10396
+ };
10397
+ diagnostics.push(...validateNavIcons(navigation), ...validateNavStructure(navigation, pages));
10007
10398
  return {
10008
10399
  diagnostics,
10009
10400
  navigation,
@@ -10200,11 +10591,8 @@ var scanProject = async (root, options = {}) => {
10200
10591
  for (const source of sources) {
10201
10592
  source.validate?.();
10202
10593
  }
10203
- const metaSources = sources.filter((source) => !source.staged && source.contentRoot).map((source) => ({
10204
- prefix: source.prefix,
10205
- root: source.contentRoot ?? ""
10206
- }));
10207
- const localeDirs = config.i18n && config.i18n.parser === "dir" ? config.i18n.locales.map((locale) => locale.code).filter((code) => code !== config.i18n?.defaultLocale) : undefined;
10594
+ const metaSources = sources.flatMap((source) => source.staged || !source.contentRoot ? [] : [{ prefix: source.prefix, root: source.contentRoot }]);
10595
+ const localeDirs = config.i18n && config.i18n.parser === "dir" ? config.i18n.locales.flatMap((locale) => locale.code === config.i18n?.defaultLocale ? [] : [locale.code]) : undefined;
10208
10596
  const [loaded, folderMeta] = await Promise.all([
10209
10597
  Promise.all(sources.map(async (source) => ({ source, ...await source.load() }))),
10210
10598
  discoverFolderMeta(metaSources, { localeDirs })
@@ -10215,6 +10603,7 @@ var scanProject = async (root, options = {}) => {
10215
10603
  contentDiagnostics.push(...diagnostics);
10216
10604
  for (const entry of entries) {
10217
10605
  const normalized = normalizeEntry2(entry, {
10606
+ basePath: config.basePath,
10218
10607
  defaultType: config.content.defaultType,
10219
10608
  i18n: config.i18n,
10220
10609
  source: {
@@ -10239,6 +10628,7 @@ var scanProject = async (root, options = {}) => {
10239
10628
  }
10240
10629
  }
10241
10630
  const graph = buildContentGraph(pages, {
10631
+ basePath: config.basePath,
10242
10632
  folderMeta: folderMeta.meta,
10243
10633
  i18n: config.i18n,
10244
10634
  navigation: config.navigation,
@@ -10358,9 +10748,10 @@ var checkRequiredSecrets = (config) => {
10358
10748
  if (process.env[env]) {
10359
10749
  return;
10360
10750
  }
10751
+ const noteSuffix = note ? ` (${note})` : "";
10361
10752
  diagnostics.push({
10362
10753
  code: "BLUME_MISSING_SECRET",
10363
- message: `${feature} is enabled but ${env} is not set${note ? ` (${note})` : ""}.`,
10754
+ message: `${feature} is enabled but ${env} is not set${noteSuffix}.`,
10364
10755
  severity: "warning",
10365
10756
  suggestion: `Set ${env} in .env.local for local dev, or in your host's environment for production.`
10366
10757
  });
@@ -10432,17 +10823,20 @@ var prepareProject = async (options) => {
10432
10823
 
10433
10824
  // src/cli/commands/build.ts
10434
10825
  var ADAPTERS = ["vercel", "node", "netlify", "cloudflare"];
10826
+ var BUDGET_JS = "budget-js";
10827
+ var BUDGET_CSS = "budget-css";
10435
10828
  var validateBudgetFlags = (args) => {
10436
- for (const flag of ["budget-js", "budget-css"]) {
10829
+ for (const flag of [BUDGET_JS, BUDGET_CSS]) {
10437
10830
  const value = args[flag];
10438
- if (value !== undefined && !(Number(value) > 0)) {
10831
+ const parsed = Number(value);
10832
+ if (value !== undefined && (Number.isNaN(parsed) || parsed <= 0)) {
10439
10833
  logger.error(`Invalid --${flag} "${value}" (expected a positive number of kB).`);
10440
10834
  process.exit(1);
10441
10835
  }
10442
10836
  }
10443
10837
  };
10444
10838
  var emitRedirectFiles = async (config, distDir) => {
10445
- const { redirects } = config;
10839
+ const redirects = applyBaseToRedirects(config.redirects, config.basePath);
10446
10840
  if (redirects.length === 0 || config.deployment.output !== "static") {
10447
10841
  return;
10448
10842
  }
@@ -10454,7 +10848,13 @@ var emitRedirectFiles = async (config, distDir) => {
10454
10848
  await Promise.all(platformFiles.map((file) => existsSync15(join24(distDir, file.name)) ? Promise.resolve() : writeFile7(join24(distDir, file.name), file.content, "utf-8")));
10455
10849
  logger.success(`Emitted redirect files for ${redirects.length} redirect(s)`);
10456
10850
  };
10457
- var formatBytes = (bytes) => bytes < 1024 ? `${bytes} B` : `${(bytes / 1024).toFixed(bytes < 1024 * 100 ? 1 : 0)} kB`;
10851
+ var formatBytes = (bytes) => {
10852
+ if (bytes < 1024) {
10853
+ return `${bytes} B`;
10854
+ }
10855
+ const digits = bytes < 1024 * 100 ? 1 : 0;
10856
+ return `${(bytes / 1024).toFixed(digits)} kB`;
10857
+ };
10458
10858
  var astroAssets = async (distDir, ext) => {
10459
10859
  const astroDir = join24(distDir, "_astro");
10460
10860
  if (!existsSync15(astroDir)) {
@@ -10486,8 +10886,8 @@ var reportBundleSizes = async (distDir) => {
10486
10886
  };
10487
10887
  var enforceBudget = async (distDir, args) => {
10488
10888
  const checks = [
10489
- ...args["budget-js"] ? [{ ext: "js", limitKb: Number(args["budget-js"]), name: "JavaScript" }] : [],
10490
- ...args["budget-css"] ? [{ ext: "css", limitKb: Number(args["budget-css"]), name: "CSS" }] : []
10889
+ ...args[BUDGET_JS] ? [{ ext: "js", limitKb: Number(args[BUDGET_JS]), name: "JavaScript" }] : [],
10890
+ ...args[BUDGET_CSS] ? [{ ext: "css", limitKb: Number(args[BUDGET_CSS]), name: "CSS" }] : []
10491
10891
  ];
10492
10892
  if (checks.length === 0) {
10493
10893
  return "skip";
@@ -10578,11 +10978,11 @@ var buildCommand = defineCommand2({
10578
10978
  description: "Base path the site is served under (e.g. /docs).",
10579
10979
  type: "string"
10580
10980
  },
10581
- "budget-css": {
10981
+ [BUDGET_CSS]: {
10582
10982
  description: "Fail if total client CSS exceeds this many kB.",
10583
10983
  type: "string"
10584
10984
  },
10585
- "budget-js": {
10985
+ [BUDGET_JS]: {
10586
10986
  description: "Fail if total client JavaScript exceeds this many kB.",
10587
10987
  type: "string"
10588
10988
  },
@@ -10718,14 +11118,20 @@ import { defineCommand as defineCommand4 } from "citty";
10718
11118
  var overlayServer = null;
10719
11119
  var overlayChannel = () => overlayServer?.ws ?? overlayServer?.hot;
10720
11120
  var showBlumeErrorOverlay = (diagnostics) => {
10721
- const errors = diagnostics.filter((diagnostic) => diagnostic.severity === "error").map(enrichDiagnostic);
11121
+ const errors = [];
11122
+ for (const diagnostic of diagnostics) {
11123
+ if (diagnostic.severity === "error") {
11124
+ errors.push(enrichDiagnostic(diagnostic));
11125
+ }
11126
+ }
10722
11127
  const channel = overlayChannel();
10723
11128
  if (errors.length === 0 || !channel) {
10724
11129
  return;
10725
11130
  }
10726
11131
  const body = errors.map((diagnostic) => {
11132
+ const lineSuffix = diagnostic.line ? `:${diagnostic.line}` : "";
10727
11133
  const where = diagnostic.file ? `
10728
- at ${diagnostic.file}${diagnostic.line ? `:${diagnostic.line}` : ""}` : "";
11134
+ at ${diagnostic.file}${lineSuffix}` : "";
10729
11135
  const fix = diagnostic.suggestion ? `
10730
11136
  fix: ${diagnostic.suggestion}` : "";
10731
11137
  const docs = diagnostic.docsUrl ? `
@@ -10785,6 +11191,8 @@ var coalescedRunner = (task) => {
10785
11191
  };
10786
11192
 
10787
11193
  // src/cli/commands/dev.ts
11194
+ var routeSignature = (routes) => routes.map((route) => `${route.path} ${route.entryId}`).toSorted().join(`
11195
+ `);
10788
11196
  var devCommand = defineCommand4({
10789
11197
  args: {
10790
11198
  "content-dir": {
@@ -10835,21 +11243,19 @@ var devCommand = defineCommand4({
10835
11243
  root,
10836
11244
  strict: args.strict
10837
11245
  });
10838
- const server = await dev({
11246
+ const createServer = (listenPort, open) => dev({
10839
11247
  logLevel: args.debug ? "debug" : "info",
10840
11248
  root: project.context.outDir,
10841
- server: {
10842
- host: args.host ?? false,
10843
- open: args.open ?? false,
10844
- port: explicitPort
10845
- }
11249
+ server: { host: args.host ?? false, open, port: listenPort }
10846
11250
  });
11251
+ let server = await createServer(explicitPort, args.open ?? false);
10847
11252
  const boundPort = server.address.port;
10848
11253
  if (boundPort !== port) {
10849
11254
  updateDevLockPort(outDir, boundPort);
10850
11255
  devServerUrl = `http://localhost:${boundPort}`;
10851
11256
  }
10852
11257
  showBlumeErrorOverlay(project.diagnostics);
11258
+ let lastSignature = routeSignature(project.manifest.routes);
10853
11259
  const runRegenerate = coalescedRunner(async () => {
10854
11260
  try {
10855
11261
  const next = await scanProject(root, {
@@ -10858,7 +11264,16 @@ var devCommand = defineCommand4({
10858
11264
  overrides,
10859
11265
  preview
10860
11266
  });
10861
- await generateRuntime(next);
11267
+ const nextSignature = routeSignature(next.manifest.routes);
11268
+ const structural = nextSignature !== lastSignature;
11269
+ lastSignature = nextSignature;
11270
+ if (structural) {
11271
+ await server.stop();
11272
+ await generateRuntime(next);
11273
+ server = await createServer(boundPort, false);
11274
+ } else {
11275
+ await generateRuntime(next);
11276
+ }
10862
11277
  showBlumeErrorOverlay(next.diagnostics);
10863
11278
  } catch (error) {
10864
11279
  logger.error(`Regeneration failed: ${error.message}`);
@@ -10916,8 +11331,8 @@ var minSupportedNode = () => {
10916
11331
  }
10917
11332
  };
10918
11333
  var versionBelow = (current, minimum) => {
10919
- const a = current.split(".").map((part) => Number.parseInt(part, 10));
10920
- const b = minimum.split(".").map((part) => Number.parseInt(part, 10));
11334
+ const a = current.split(".").map((part) => Math.trunc(Number(part)));
11335
+ const b = minimum.split(".").map((part) => Math.trunc(Number(part)));
10921
11336
  for (let i = 0;i < 3; i += 1) {
10922
11337
  const delta = (a[i] ?? 0) - (b[i] ?? 0);
10923
11338
  if (delta !== 0) {
@@ -11008,7 +11423,7 @@ import { join as join28, relative as relative13 } from "pathe";
11008
11423
  import { existsSync as existsSync17 } from "node:fs";
11009
11424
  import { cp as cp2, mkdir as mkdir7, readFile as readFile12, rm as rm3, writeFile as writeFile8 } from "node:fs/promises";
11010
11425
  import { join as join27, relative as relative12 } from "pathe";
11011
- var POSIX = (path) => path.split("\\").join("/");
11426
+ var toPosix = (path) => path.split("\\").join("/");
11012
11427
  var ejectOpenApiData = (project) => {
11013
11428
  const source = project.sources.find(isOpenApiSource);
11014
11429
  return source ? source.openApiData() : {};
@@ -11034,6 +11449,13 @@ var askFiles = async (project, srcDir, genDir) => {
11034
11449
  }
11035
11450
  return files;
11036
11451
  };
11452
+ var readExamplesCss = (root, css) => css && existsSync17(join27(root, css)) ? readFile12(join27(root, css), "utf-8") : Promise.resolve("");
11453
+ var examplesPreviewFiles = (srcDir, basePath, hasExamples) => hasExamples ? [
11454
+ {
11455
+ content: examplesPageTemplate(),
11456
+ path: join27(srcDir, "pages", ...basePath.split("/").filter(Boolean), "blume-examples", "[...path].astro")
11457
+ }
11458
+ ] : [];
11037
11459
  var eject = async (root) => {
11038
11460
  const project = await scanProject(root, { mode: "build" });
11039
11461
  const { context, config } = project;
@@ -11047,6 +11469,7 @@ var eject = async (root) => {
11047
11469
  needsReactRaw,
11048
11470
  usesMath,
11049
11471
  userTheme,
11472
+ userExamplesCss,
11050
11473
  rawMarkdown,
11051
11474
  islands,
11052
11475
  examples
@@ -11055,9 +11478,10 @@ var eject = async (root) => {
11055
11478
  detectNeedsReact(root),
11056
11479
  detectUsesMath(root),
11057
11480
  context.themeFile ? readFile12(context.themeFile, "utf-8") : Promise.resolve(""),
11481
+ readExamplesCss(root, config.examples.css),
11058
11482
  buildRawMarkdown(project),
11059
11483
  discoverIslands(root),
11060
- discoverExamples(root, config.examples)
11484
+ discoverExamples(root, config.examples.source)
11061
11485
  ]);
11062
11486
  const frameworks = new Set([
11063
11487
  ...islands.islands.map((island) => island.framework),
@@ -11068,13 +11492,13 @@ var eject = async (root) => {
11068
11492
  const needsSvelte = frameworks.has("svelte");
11069
11493
  const relContext = {
11070
11494
  ...context,
11071
- contentRoot: POSIX(relative12(root, context.contentRoot)),
11495
+ contentRoot: toPosix(relative12(root, context.contentRoot)),
11072
11496
  outDir: ".",
11073
11497
  root: "."
11074
11498
  };
11075
- const componentsImport = context.componentsFile ? `../../${POSIX(relative12(root, context.componentsFile))}` : null;
11499
+ const componentsImport = context.componentsFile ? `../../${toPosix(relative12(root, context.componentsFile))}` : null;
11076
11500
  const relPages = pages.map((page) => ({
11077
- entrypoint: POSIX(relative12(root, page.entrypoint)),
11501
+ entrypoint: toPosix(relative12(root, page.entrypoint)),
11078
11502
  pattern: page.pattern
11079
11503
  }));
11080
11504
  const staged = collectStaged(project);
@@ -11088,6 +11512,7 @@ var eject = async (root) => {
11088
11512
  context: relContext,
11089
11513
  dataPath: "./src/generated/data.json",
11090
11514
  examplesPath: "./src/generated/examples.ts",
11515
+ examplesThemePath: "./src/generated/examples.css",
11091
11516
  needsReact,
11092
11517
  needsSvelte,
11093
11518
  needsVue,
@@ -11132,9 +11557,17 @@ var eject = async (root) => {
11132
11557
  path: join27(genDir, "islands.ts")
11133
11558
  },
11134
11559
  {
11135
- content: exampleMapTemplate(examples.examples),
11560
+ content: exampleMapTemplate(examples.examples, config.basePath),
11136
11561
  path: join27(genDir, "examples.ts")
11137
11562
  },
11563
+ {
11564
+ content: examplesEntryTemplate({
11565
+ configTokens: buildThemeCss(config.theme),
11566
+ sources: ["../../**/*.{astro,jsx,svelte,ts,tsx,vue}"],
11567
+ userCss: userExamplesCss
11568
+ }),
11569
+ path: join27(genDir, "examples.css")
11570
+ },
11138
11571
  {
11139
11572
  content: tailwindEntryTemplate({
11140
11573
  configTokens: buildThemeCss(config.theme),
@@ -11234,7 +11667,7 @@ var eject = async (root) => {
11234
11667
  })), ...examples.examples.map((example) => ({
11235
11668
  content: exampleWrapperTemplate(example),
11236
11669
  path: join27(genDir, "examples", `${exampleSlug(example.path)}.astro`)
11237
- })));
11670
+ })), ...examplesPreviewFiles(srcDir, config.basePath, examples.examples.length > 0));
11238
11671
  for (const [entryId, content] of staged) {
11239
11672
  files.push({ content, path: join27(root, stagedDir, entryId) });
11240
11673
  }
@@ -11621,20 +12054,23 @@ var isIndexPage = (page2) => {
11621
12054
  const ref = page2.source?.ref ?? page2.sourcePath ?? "";
11622
12055
  return /^index\.(?:md|mdx)$/iu.test(basename5(ref));
11623
12056
  };
12057
+ var applyRelativePart = (segments, part) => {
12058
+ if (part === "" || part === ".") {
12059
+ return;
12060
+ }
12061
+ if (part === "..") {
12062
+ segments.pop();
12063
+ return;
12064
+ }
12065
+ segments.push(part);
12066
+ };
11624
12067
  var resolveRelative = (pageRoute, target, isIndex) => {
11625
12068
  const segments = pageRoute.split("/").filter(Boolean);
11626
12069
  if (!isIndex) {
11627
12070
  segments.pop();
11628
12071
  }
11629
12072
  for (const part of target.split("/")) {
11630
- if (part === "" || part === ".") {
11631
- continue;
11632
- }
11633
- if (part === "..") {
11634
- segments.pop();
11635
- continue;
11636
- }
11637
- segments.push(part);
12073
+ applyRelativePart(segments, part);
11638
12074
  }
11639
12075
  return `/${segments.join("/")}`;
11640
12076
  };
@@ -11667,15 +12103,16 @@ var checkAnchor = (route, fragment, site, ctx) => {
11667
12103
  };
11668
12104
  };
11669
12105
  var checkPathLink = (resolved, fragment, target, site, ctx) => {
11670
- const route = toRoute(resolved);
12106
+ const route = toRoute(withBasePath(ctx.basePath, resolved));
11671
12107
  if (ctx.routes.has(route)) {
11672
12108
  return fragment ? checkAnchor(route, fragment, site, ctx) : null;
11673
12109
  }
11674
12110
  if (ctx.redirects.has(route)) {
11675
12111
  return null;
11676
12112
  }
11677
- if (FILE_EXT.test(resolved) && !DOC_EXT.test(resolved)) {
11678
- if (assetIsPresent(resolved, ctx)) {
12113
+ const assetPath = stripBasePath(ctx.basePath, resolved);
12114
+ if (FILE_EXT.test(assetPath) && !DOC_EXT.test(assetPath)) {
12115
+ if (assetIsPresent(assetPath, ctx)) {
11679
12116
  return null;
11680
12117
  }
11681
12118
  if (ctx.publicDir === null) {
@@ -11684,9 +12121,9 @@ var checkPathLink = (resolved, fragment, target, site, ctx) => {
11684
12121
  return {
11685
12122
  ...site,
11686
12123
  code: "BLUME_BROKEN_ASSET",
11687
- message: `Asset ${resolved} was not found in the public directory.`,
12124
+ message: `Asset ${assetPath} was not found in the public directory.`,
11688
12125
  severity: "warning",
11689
- suggestion: `Add the file at public${resolved} or fix the link.`
12126
+ suggestion: `Add the file at public${assetPath} or fix the link.`
11690
12127
  };
11691
12128
  }
11692
12129
  return {
@@ -11721,7 +12158,8 @@ var request = async (url, method) => {
11721
12158
  };
11722
12159
  var probe = async (url) => {
11723
12160
  const head = await request(url, "HEAD");
11724
- const retry = head.status === STATUS_METHOD_NOT_ALLOWED || head.status === STATUS_NOT_IMPLEMENTED || !head.ok && head.status === undefined && !head.timedOut;
12161
+ const unreachable = !head.ok && head.status === undefined && !head.timedOut;
12162
+ const retry = head.status === STATUS_METHOD_NOT_ALLOWED || head.status === STATUS_NOT_IMPLEMENTED || unreachable;
11725
12163
  return retry ? await request(url, "GET") : head;
11726
12164
  };
11727
12165
  var gradeExternal = (result) => {
@@ -11802,10 +12240,12 @@ var classifyLink = (page2, link, ctx, onExternal) => {
11802
12240
  return checkPathLink(resolved, fragment, target, site, ctx);
11803
12241
  };
11804
12242
  var validateLinks = async (graph, options) => {
12243
+ const basePath = options.basePath ?? "";
11805
12244
  const ctx = {
11806
12245
  anchors: buildAnchorIndex(graph.pages),
12246
+ basePath,
11807
12247
  publicDir: options.publicDir,
11808
- redirects: new Set((options.redirects ?? []).map((redirect) => toRoute(redirect.from))),
12248
+ redirects: new Set((options.redirects ?? []).map((redirect) => toRoute(withBasePath(basePath, redirect.from)))),
11809
12249
  routes: new Set(graph.routes.keys())
11810
12250
  };
11811
12251
  const diagnostics = [];
@@ -11862,6 +12302,7 @@ var validateCommand = defineCommand10({
11862
12302
  diagnostics.push(...project.diagnostics);
11863
12303
  const publicDir = join33(root, "public");
11864
12304
  diagnostics.push(...await validateLinks(project.graph, {
12305
+ basePath: project.config.basePath,
11865
12306
  checkExternal: Boolean(args.external),
11866
12307
  publicDir: existsSync21(publicDir) ? publicDir : null,
11867
12308
  redirects: project.config.redirects
@@ -11923,5 +12364,5 @@ process.on("unhandledRejection", (error) => {
11923
12364
  });
11924
12365
  runMain(main);
11925
12366
 
11926
- //# debugId=356EB2263169E1CA64756E2164756E21
12367
+ //# debugId=8E29DA45BB5B37C564756E2164756E21
11927
12368
  //# sourceMappingURL=index.js.map