blume 0.7.0 → 1.0.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 (185) hide show
  1. package/CHANGELOG.md +666 -0
  2. package/LICENSE +21 -0
  3. package/README.md +107 -0
  4. package/dist/cli/index.js +1852 -380
  5. package/dist/cli/index.js.map +98 -91
  6. package/dist/types/ai/component-markdown.d.ts +34 -0
  7. package/dist/types/components/content/youtube.d.ts +18 -0
  8. package/dist/types/core/base-path.d.ts +9 -0
  9. package/dist/types/core/config-input.d.ts +47 -2
  10. package/dist/types/core/config.d.ts +3 -2
  11. package/dist/types/core/data.d.ts +7 -0
  12. package/dist/types/core/i18n-ui.d.ts +526 -132
  13. package/dist/types/core/schema.d.ts +293 -146
  14. package/dist/types/index.d.ts +1 -0
  15. package/dist/types/openapi/references.d.ts +60 -0
  16. package/dist/types/seo/x-handle.d.ts +12 -0
  17. package/docs/01-quickstart.mdx +5 -2
  18. package/docs/02-deployment.mdx +8 -8
  19. package/docs/03-faq.mdx +46 -16
  20. package/docs/advanced/api-reference.mdx +1 -1
  21. package/docs/advanced/changelog.mdx +1 -1
  22. package/docs/advanced/custom-pages.mdx +1 -1
  23. package/docs/advanced/skills.mdx +1 -1
  24. package/docs/configuration/ai.mdx +49 -10
  25. package/docs/configuration/customization.mdx +11 -0
  26. package/docs/configuration/export.mdx +1 -1
  27. package/docs/configuration/index.mdx +27 -3
  28. package/docs/configuration/seo.mdx +35 -5
  29. package/docs/content/components.mdx +2 -2
  30. package/docs/content/i18n.mdx +1 -1
  31. package/docs/content/navigation.mdx +3 -3
  32. package/docs/content/sources.mdx +1 -1
  33. package/docs/content/syntax.mdx +6 -4
  34. package/docs/index.mdx +2 -2
  35. package/docs/reference/cli.mdx +9 -7
  36. package/docs/reference/frontmatter.mdx +1 -1
  37. package/package.json +22 -4
  38. package/skills/blume/SKILL.md +5 -3
  39. package/skills/blume-update-docs/SKILL.md +3 -2
  40. package/src/ai/agent-readability.ts +9 -8
  41. package/src/ai/ask-context.ts +7 -2
  42. package/src/ai/ask-data.ts +3 -0
  43. package/src/ai/component-markdown.ts +461 -0
  44. package/src/ai/llms.ts +135 -26
  45. package/src/ai/markdown.ts +35 -6
  46. package/src/ai/mcp/data.ts +25 -4
  47. package/src/ai/mcp/discovery.ts +10 -3
  48. package/src/ai/mcp/server.ts +21 -7
  49. package/src/ai/mcp/tools.ts +1 -1
  50. package/src/ai/visibility.ts +74 -0
  51. package/src/astro/component-slots.ts +11 -1
  52. package/src/astro/generate.ts +77 -45
  53. package/src/astro/integration.ts +1 -1
  54. package/src/astro/markdown-negotiation.ts +1 -1
  55. package/src/astro/pages.ts +81 -19
  56. package/src/astro/templates.ts +150 -19
  57. package/src/blume-modules.d.ts +8 -0
  58. package/src/cli/commands/build.ts +120 -23
  59. package/src/cli/commands/check.ts +1 -1
  60. package/src/cli/commands/dev.ts +26 -5
  61. package/src/cli/commands/eject.ts +47 -19
  62. package/src/cli/commands/init.ts +120 -180
  63. package/src/cli/commands/preview.ts +4 -1
  64. package/src/cli/commands/validate.ts +43 -2
  65. package/src/cli/dev-lock.ts +8 -4
  66. package/src/cli/eject-scripts.ts +72 -0
  67. package/src/cli/env.ts +15 -5
  68. package/src/cli/init/questions.ts +158 -0
  69. package/src/cli/init/scaffold.ts +380 -0
  70. package/src/cli/internal-error.ts +9 -4
  71. package/src/cli/prepare.ts +3 -2
  72. package/src/components/Icon.astro +2 -1
  73. package/src/components/content/AccordionItem.astro +23 -4
  74. package/src/components/content/Badge.astro +3 -1
  75. package/src/components/content/Card.astro +4 -2
  76. package/src/components/content/Step.astro +10 -1
  77. package/src/components/content/Tabs.astro +15 -3
  78. package/src/components/content/Tile.astro +2 -1
  79. package/src/components/content/Tooltip.astro +3 -1
  80. package/src/components/content/Update.astro +9 -2
  81. package/src/components/content/auto-type-table.ts +7 -1
  82. package/src/components/content/base-href.ts +33 -0
  83. package/src/components/content/changelog-element.ts +9 -2
  84. package/src/components/content/mermaid-element.ts +7 -2
  85. package/src/components/islands/AskAI.astro +5 -2
  86. package/src/components/islands/ask-ai.tsx +86 -11
  87. package/src/components/islands/hooks.ts +28 -8
  88. package/src/components/layout/Banner.astro +10 -2
  89. package/src/components/layout/Breadcrumbs.astro +11 -2
  90. package/src/components/layout/Header.astro +13 -4
  91. package/src/components/layout/Logo.astro +11 -3
  92. package/src/components/layout/NavTree.astro +19 -5
  93. package/src/components/layout/PageActions.astro +25 -10
  94. package/src/components/layout/PageLayout.astro +85 -9
  95. package/src/components/layout/Pagination.astro +10 -4
  96. package/src/components/layout/ReferenceLayout.astro +20 -2
  97. package/src/components/layout/RootLayout.astro +142 -12
  98. package/src/components/layout/Search.astro +117 -27
  99. package/src/components/layout/search/algolia.ts +11 -2
  100. package/src/components/layout/search/endpoint.ts +11 -5
  101. package/src/components/layout/search/orama-cloud.ts +8 -2
  102. package/src/components/layout/search/types.ts +5 -1
  103. package/src/components/layout/search/typesense.ts +4 -1
  104. package/src/components/layout/toc-element.ts +1 -1
  105. package/src/components/openapi/ApiTagOperations.astro +2 -1
  106. package/src/components/openapi/Operation.astro +47 -40
  107. package/src/components/openapi/RequestPanel.astro +1 -1
  108. package/src/components/openapi/helpers.ts +71 -3
  109. package/src/components/openapi/panel.ts +1 -1
  110. package/src/core/base-path.ts +24 -0
  111. package/src/core/builtin-tags.ts +2 -0
  112. package/src/core/config-input.ts +48 -2
  113. package/src/core/config.ts +3 -2
  114. package/src/core/data.ts +4 -0
  115. package/src/core/frontmatter.ts +7 -0
  116. package/src/core/graph.ts +15 -5
  117. package/src/core/i18n-ui.ts +54 -0
  118. package/src/core/i18n.ts +16 -8
  119. package/src/core/last-modified.ts +13 -6
  120. package/src/core/links.ts +32 -8
  121. package/src/core/navigation.ts +29 -4
  122. package/src/core/package-json.ts +17 -2
  123. package/src/core/project-graph.ts +15 -6
  124. package/src/core/schema.ts +71 -2
  125. package/src/core/sources/assets.ts +6 -1
  126. package/src/core/sources/filesystem.ts +4 -0
  127. package/src/core/sources/mdx-remote.ts +23 -14
  128. package/src/core/sources/normalize.ts +152 -50
  129. package/src/core/sources/notion.ts +8 -8
  130. package/src/core/ui-packs/ar.ts +8 -0
  131. package/src/core/ui-packs/bg.ts +8 -0
  132. package/src/core/ui-packs/bn.ts +8 -0
  133. package/src/core/ui-packs/ca.ts +8 -0
  134. package/src/core/ui-packs/cs.ts +8 -0
  135. package/src/core/ui-packs/da.ts +8 -0
  136. package/src/core/ui-packs/de.ts +8 -0
  137. package/src/core/ui-packs/el.ts +8 -0
  138. package/src/core/ui-packs/es.ts +8 -0
  139. package/src/core/ui-packs/fa.ts +8 -0
  140. package/src/core/ui-packs/fi.ts +8 -0
  141. package/src/core/ui-packs/fr.ts +9 -1
  142. package/src/core/ui-packs/he.ts +8 -0
  143. package/src/core/ui-packs/hi.ts +8 -0
  144. package/src/core/ui-packs/hr.ts +8 -0
  145. package/src/core/ui-packs/hu.ts +8 -0
  146. package/src/core/ui-packs/id.ts +8 -0
  147. package/src/core/ui-packs/it.ts +8 -0
  148. package/src/core/ui-packs/ja.ts +8 -0
  149. package/src/core/ui-packs/ko.ts +8 -0
  150. package/src/core/ui-packs/nl.ts +8 -0
  151. package/src/core/ui-packs/no.ts +8 -0
  152. package/src/core/ui-packs/pl.ts +8 -0
  153. package/src/core/ui-packs/pt-br.ts +8 -0
  154. package/src/core/ui-packs/pt.ts +8 -0
  155. package/src/core/ui-packs/ro.ts +8 -0
  156. package/src/core/ui-packs/ru.ts +8 -0
  157. package/src/core/ui-packs/sk.ts +8 -0
  158. package/src/core/ui-packs/sr.ts +8 -0
  159. package/src/core/ui-packs/sv.ts +8 -0
  160. package/src/core/ui-packs/th.ts +8 -0
  161. package/src/core/ui-packs/tr.ts +8 -0
  162. package/src/core/ui-packs/uk.ts +8 -0
  163. package/src/core/ui-packs/vi.ts +8 -0
  164. package/src/core/ui-packs/zh-tw.ts +8 -0
  165. package/src/core/ui-packs/zh.ts +8 -0
  166. package/src/deploy/adapter-output.ts +18 -8
  167. package/src/deploy/redirects.ts +7 -2
  168. package/src/deploy/sitemap.ts +53 -11
  169. package/src/index.ts +5 -0
  170. package/src/markdown/base-links.ts +10 -8
  171. package/src/markdown/index.ts +15 -3
  172. package/src/markdown/inline-code.ts +7 -2
  173. package/src/markdown/package-commands.ts +10 -4
  174. package/src/og/card.ts +4 -2
  175. package/src/og/dimensions.ts +12 -0
  176. package/src/openapi/model.ts +12 -4
  177. package/src/openapi/parse.ts +21 -0
  178. package/src/openapi/references.ts +38 -8
  179. package/src/openapi/render-mdx.ts +62 -1
  180. package/src/openapi/source.ts +59 -10
  181. package/src/registry/eject.ts +184 -12
  182. package/src/registry/registry.ts +0 -3
  183. package/src/search/documents.ts +34 -2
  184. package/src/seo/jsonld.ts +20 -13
  185. package/src/seo/x-handle.ts +18 -0
package/dist/cli/index.js CHANGED
@@ -48,7 +48,6 @@ import { dirname as dirname3, join as join4 } from "pathe";
48
48
  // src/registry/registry.ts
49
49
  import { join as join3 } from "pathe";
50
50
  var packageSrc = join3(packageRoot(), "src");
51
- var itemsRoot = join3(packageRoot(), "src", "registry", "items");
52
51
  var layoutComponent = (config) => {
53
52
  const target = `components/blume/${config.file}`;
54
53
  return {
@@ -663,14 +662,17 @@ var buildAgentReadability = (project) => {
663
662
  }
664
663
  const site = config.deployment.site ?? null;
665
664
  const deployBase = normalizeBasePath(config.deployment.base);
666
- const abs = (path) => site ? `${site.replace(/\/+$/u, "")}${withBasePath(deployBase, path)}` : path;
665
+ const abs = (path) => {
666
+ const based = withBasePath(deployBase, path);
667
+ return site ? `${site.replace(/\/+$/u, "")}${based}` : based;
668
+ };
667
669
  const artifacts = {
668
670
  markdown: {
669
671
  contentNegotiation: "text/markdown",
670
672
  pattern: abs("/{route}.md")
671
673
  }
672
674
  };
673
- if (config.ai.llmsTxt) {
675
+ if (config.ai.llmsTxt.enabled) {
674
676
  artifacts.llmsFullTxt = abs("/llms-full.txt");
675
677
  artifacts.llmsTxt = abs("/llms.txt");
676
678
  }
@@ -723,6 +725,7 @@ var withYamlEngine = (options) => ({
723
725
  }
724
726
  });
725
727
  var matter = Object.assign((input, options) => baseMatter(input, withYamlEngine(options)), baseMatter, {
728
+ read: (filepath, options) => baseMatter.read(filepath, withYamlEngine(options)),
726
729
  stringify: (file, data, options) => baseMatter.stringify(file, data, withYamlEngine(options))
727
730
  });
728
731
  var frontmatter_default = matter;
@@ -744,37 +747,376 @@ var readEntryText = async (ctx, page) => {
744
747
  return "";
745
748
  };
746
749
 
750
+ // src/ai/component-markdown.ts
751
+ import { mdxToMdast } from "satteri";
752
+
753
+ // src/components/content/youtube.ts
754
+ var BARE_ID = /^[\w-]{11}$/u;
755
+ var URL_ID = /(?:youtu\.be\/|\/embed\/|\/shorts\/|\/live\/|[?&]v=)(?<id>[\w-]{11})/u;
756
+ var parseYouTubeId = (input) => {
757
+ const value = input.trim();
758
+ if (!value) {
759
+ return null;
760
+ }
761
+ if (BARE_ID.test(value)) {
762
+ return value;
763
+ }
764
+ return URL_ID.exec(value)?.groups?.id ?? null;
765
+ };
766
+
767
+ // src/ai/component-markdown.ts
768
+ var evaluateExpression = (raw) => {
769
+ try {
770
+ const value = new Function(`"use strict"; return (${raw});`)();
771
+ return { ok: true, value };
772
+ } catch {
773
+ return { ok: false, value: undefined };
774
+ }
775
+ };
776
+ var readProps = (node) => {
777
+ const props = {};
778
+ let lossy = false;
779
+ for (const attribute of node.attributes ?? []) {
780
+ if (attribute.type !== "mdxJsxAttribute" || !attribute.name) {
781
+ lossy = true;
782
+ continue;
783
+ }
784
+ if (attribute.value === null || attribute.value === undefined) {
785
+ props[attribute.name] = true;
786
+ } else if (typeof attribute.value === "string") {
787
+ props[attribute.name] = attribute.value;
788
+ } else {
789
+ const result = evaluateExpression(attribute.value.value);
790
+ if (result.ok) {
791
+ props[attribute.name] = result.value;
792
+ } else {
793
+ lossy = true;
794
+ }
795
+ }
796
+ }
797
+ return { lossy, props };
798
+ };
799
+ var hasOffsets = (node) => typeof node.position?.start?.offset === "number" && typeof node.position?.end?.offset === "number";
800
+ var applySplices = (text, splices) => {
801
+ let result = text;
802
+ for (const splice of [...splices].toSorted((a, b) => b.start - a.start)) {
803
+ const lineStart = result.lastIndexOf(`
804
+ `, splice.start - 1) + 1;
805
+ const prefix = result.slice(lineStart, splice.start);
806
+ const indent = /^[\t ]+$/u.test(prefix) ? prefix : "";
807
+ const replacement = indent ? splice.text.split(`
808
+ `).map((line, index) => index === 0 || line === "" ? line : `${indent}${line}`).join(`
809
+ `) : splice.text;
810
+ result = result.slice(0, splice.start) + replacement + result.slice(splice.end);
811
+ }
812
+ return result;
813
+ };
814
+ var dedent = (text) => {
815
+ const lines = text.split(`
816
+ `);
817
+ const rest = lines.slice(1).filter((line) => line.trim() !== "");
818
+ if (rest.length === 0) {
819
+ return text;
820
+ }
821
+ const indent = Math.min(...rest.map((line) => line.length - line.trimStart().length));
822
+ if (indent === 0) {
823
+ return text;
824
+ }
825
+ return [
826
+ lines[0],
827
+ ...lines.slice(1).map((line) => line.trim() === "" ? "" : line.slice(indent))
828
+ ].join(`
829
+ `);
830
+ };
831
+ var isJsxElement = (node) => node.type === "mdxJsxFlowElement" || node.type === "mdxJsxTextElement";
832
+ var cellText = (value) => String(value ?? "").replaceAll(/\s*\n\s*/gu, " ").replaceAll("|", "\\|").trim();
833
+ var cellCode = (value) => {
834
+ const text = cellText(value);
835
+ return text && !text.includes("`") ? `\`${text}\`` : text;
836
+ };
837
+ var typeTable = ({ children, props }) => {
838
+ const { type } = props;
839
+ if (type === null || typeof type !== "object") {
840
+ return null;
841
+ }
842
+ const entries = Object.entries(type);
843
+ const rows = entries.map(([name, info]) => {
844
+ const prop = cellCode(`${name}${info.required ? "" : "?"}`);
845
+ const typeCell = info.typeDescriptionLink ? `[${cellCode(info.type)}](${cellText(info.typeDescriptionLink)})` : cellCode(info.type);
846
+ const defaultCell = info.default === undefined ? "-" : cellCode(info.default);
847
+ const description = cellText([info.description, info.typeDescription].filter((part) => typeof part === "string" && part !== "").join(" "));
848
+ return `| ${prop} | ${typeCell} | ${defaultCell} | ${description} |`;
849
+ });
850
+ const table = rows.length > 0 ? [
851
+ "| Prop | Type | Default | Description |",
852
+ "| --- | --- | --- | --- |",
853
+ ...rows
854
+ ].join(`
855
+ `) : "";
856
+ return [table, children].filter(Boolean).join(`
857
+
858
+ `);
859
+ };
860
+ var callout = ({ children, props }) => {
861
+ const type = typeof props.type === "string" ? props.type : "info";
862
+ const label = typeof props.title === "string" && props.title !== "" ? props.title : type.charAt(0).toUpperCase() + type.slice(1);
863
+ if (!children) {
864
+ return `> **${label}**`;
865
+ }
866
+ const body = children.split(`
867
+ `).map((line) => line.trim() === "" ? ">" : `> ${line}`).join(`
868
+ `);
869
+ return `> **${label}**
870
+ >
871
+ ${body}`;
872
+ };
873
+ var listItem = (index, content) => {
874
+ const marker = `${index}. `;
875
+ return content.split(`
876
+ `).map((line, lineIndex) => {
877
+ if (lineIndex === 0) {
878
+ return `${marker}${line}`;
879
+ }
880
+ return line === "" ? "" : ` ${line}`;
881
+ }).join(`
882
+ `);
883
+ };
884
+ var steps = ({ childComponents, children }) => {
885
+ const items = childComponents("Step");
886
+ if (items.length === 0) {
887
+ return children;
888
+ }
889
+ return items.map((step, index) => {
890
+ const title = typeof step.props.title === "string" && step.props.title !== "" ? `**${step.props.title}**` : "";
891
+ const content = [title, step.children].filter(Boolean).join(`
892
+
893
+ `);
894
+ return listItem(index + 1, content);
895
+ }).join(`
896
+
897
+ `);
898
+ };
899
+ var tabs = ({ childComponents, children }) => {
900
+ const items = childComponents("Tab");
901
+ if (items.length === 0) {
902
+ return children;
903
+ }
904
+ return items.map((tab, index) => {
905
+ const title = typeof tab.props.title === "string" && tab.props.title !== "" ? tab.props.title : `Tab ${index + 1}`;
906
+ return tab.children ? `**${title}**
907
+
908
+ ${tab.children}` : `**${title}**`;
909
+ }).join(`
910
+
911
+ `);
912
+ };
913
+ var youtube = ({ props }) => {
914
+ let input = "";
915
+ if (typeof props.id === "string") {
916
+ input = props.id;
917
+ } else if (typeof props.url === "string") {
918
+ input = props.url;
919
+ }
920
+ const videoId = parseYouTubeId(input);
921
+ if (!videoId) {
922
+ return null;
923
+ }
924
+ const start = typeof props.start === "number" && props.start > 0 ? `&t=${Math.floor(props.start)}s` : "";
925
+ const title = typeof props.title === "string" && props.title !== "" ? props.title : "Watch on YouTube";
926
+ return `[${title}](https://www.youtube.com/watch?v=${videoId}${start})`;
927
+ };
928
+ var SERIALIZERS = {
929
+ Callout: callout,
930
+ Steps: steps,
931
+ Tabs: tabs,
932
+ TypeTable: typeTable,
933
+ YouTube: youtube
934
+ };
935
+ var escapeRegExp2 = (value) => value.replaceAll(/[$()*+.?[\\\]^{|}]/gu, String.raw`\$&`);
936
+ var componentHint = (registry2) => new RegExp(`<(?:${Object.keys(registry2).map(escapeRegExp2).join("|")})[\\s/>]`, "u");
937
+ var BUILT_IN_HINT = componentHint(SERIALIZERS);
938
+ var renderChildren = (walk, node) => {
939
+ const children = (node.children ?? []).filter(hasOffsets);
940
+ const [first] = children;
941
+ if (!first) {
942
+ return "";
943
+ }
944
+ const start = first.position.start.offset;
945
+ const end = children.at(-1)?.position.end.offset ?? start;
946
+ const splices = [];
947
+ collectSplices(walk, children, splices);
948
+ const spliced = applySplices(walk.source.slice(start, end), splices.map((splice) => ({
949
+ ...splice,
950
+ end: splice.end - start,
951
+ start: splice.start - start
952
+ })));
953
+ return dedent(spliced).trim();
954
+ };
955
+ var serializeElement = (serializer, walk, node) => serializer({
956
+ ...readProps(node),
957
+ childComponents: (name) => (node.children ?? []).filter((child) => isJsxElement(child) && child.name === name).map((child) => ({
958
+ ...readProps(child),
959
+ children: renderChildren(walk, child)
960
+ })),
961
+ children: renderChildren(walk, node)
962
+ });
963
+ var collectSplices = (walk, nodes, out) => {
964
+ for (const node of nodes) {
965
+ const serializer = node.type === "mdxJsxFlowElement" && node.name ? walk.registry[node.name] : undefined;
966
+ if (serializer && hasOffsets(node)) {
967
+ const text = serializeElement(serializer, walk, node);
968
+ if (text !== null) {
969
+ out.push({
970
+ end: node.position.end.offset,
971
+ start: node.position.start.offset,
972
+ text
973
+ });
974
+ continue;
975
+ }
976
+ }
977
+ collectSplices(walk, node.children ?? [], out);
978
+ }
979
+ };
980
+ var downlevelComponents = (source, components) => {
981
+ const custom = components && Object.keys(components).length > 0;
982
+ const registry2 = custom ? { ...SERIALIZERS, ...components } : SERIALIZERS;
983
+ const hint = custom ? componentHint(registry2) : BUILT_IN_HINT;
984
+ if (!hint.test(source)) {
985
+ return source;
986
+ }
987
+ let tree;
988
+ try {
989
+ tree = mdxToMdast(source);
990
+ } catch {
991
+ return source;
992
+ }
993
+ const splices = [];
994
+ collectSplices({ registry: registry2, source }, tree.children ?? [], splices);
995
+ return splices.length > 0 ? applySplices(source, splices) : source;
996
+ };
997
+
998
+ // src/ai/visibility.ts
999
+ var CODE_FENCE_BLOCK = /^(?<fence>`{3,}|~{3,})[^\n]*\n[\s\S]*?^\k<fence>[^\n]*(?=\n|$)/gmu;
1000
+ var FENCE_TOKEN = /\u0000blume-fence-(?<index>\d+)\u0000/gu;
1001
+ var visibilityBlock = (audience) => new RegExp(`<Visibility\\s+for\\s*=\\s*(?:"${audience}"|'${audience}')\\s*>(?<inner>[\\s\\S]*?)</Visibility\\s*>`, "gu");
1002
+ var BLOCKS = {
1003
+ agents: visibilityBlock("agents"),
1004
+ web: visibilityBlock("web")
1005
+ };
1006
+ var applyAudienceVisibility = (markdown, audience) => {
1007
+ const fences = [];
1008
+ const masked = markdown.replace(CODE_FENCE_BLOCK, (block) => {
1009
+ fences.push(block);
1010
+ return `\x00blume-fence-${fences.length - 1}\x00`;
1011
+ });
1012
+ let touched = false;
1013
+ const filtered = masked.replaceAll(BLOCKS[audience === "agents" ? "web" : "agents"], () => {
1014
+ touched = true;
1015
+ return "";
1016
+ }).replaceAll(BLOCKS[audience], (_match, inner) => {
1017
+ touched = true;
1018
+ return inner;
1019
+ });
1020
+ const tidied = touched ? filtered.replaceAll(/\n{3,}/gu, `
1021
+
1022
+ `) : filtered;
1023
+ return tidied.replaceAll(FENCE_TOKEN, (token, index) => fences[Number(index)] ?? token);
1024
+ };
1025
+ var applyAgentVisibility = (markdown) => applyAudienceVisibility(markdown, "agents");
1026
+
747
1027
  // src/ai/llms.ts
748
1028
  var pageUrl = (route, site, base = "") => {
749
- if (!site) {
750
- return route;
1029
+ const path = withBasePath(base, route);
1030
+ return encodeURI(site ? `${site.replace(/\/$/u, "")}${path}` : path);
1031
+ };
1032
+ var eligiblePages = (project) => project.graph.pages.filter((page) => !(page.meta.draft || page.meta.sidebar.hidden || page.meta.seo.noindex) && (project.config.ai.llmsTxt.openapi || page.source.name !== "openapi"));
1033
+ var indexedNavigations = (project) => {
1034
+ const { i18n } = project.config;
1035
+ if (i18n) {
1036
+ return i18n.locales.flatMap(({ code, label }) => {
1037
+ const nav = project.graph.navigationByLocale[code];
1038
+ if (!nav) {
1039
+ return [];
1040
+ }
1041
+ return [{ label: code === i18n.defaultLocale ? undefined : label, nav }];
1042
+ });
751
1043
  }
752
- return `${site.replace(/\/$/u, "")}${withBasePath(base, route)}`;
1044
+ return [{ nav: project.graph.navigation }];
753
1045
  };
754
- var orderedPages = (project) => [...project.graph.pages].filter((page) => !page.meta.draft).sort((a, b) => a.route.localeCompare(b.route));
755
1046
  var buildIndex = (project) => {
756
1047
  const { config } = project;
757
1048
  const { site } = config.deployment;
758
- const lines = [`# ${config.title}`];
759
- if (config.description) {
760
- lines.push("", `> ${config.description}`);
761
- }
762
- lines.push("", "## Docs", "");
763
- for (const page of orderedPages(project)) {
764
- const url = pageUrl(page.route, site, normalizeBasePath(config.deployment.base));
1049
+ const base = normalizeBasePath(config.deployment.base);
1050
+ const eligible = eligiblePages(project);
1051
+ const byRoute = new Map(eligible.map((page) => [page.route, page]));
1052
+ const seen = new Set;
1053
+ const line = (page) => {
1054
+ seen.add(page.route);
765
1055
  const summary = page.description ? `: ${page.description}` : "";
766
- lines.push(`- [${page.title}](${url})${summary}`);
1056
+ return `- [${page.title}](${pageUrl(page.route, site, base)})${summary}`;
1057
+ };
1058
+ const renderLevel = (nodes, depth) => {
1059
+ const list = [];
1060
+ const groupBlocks = [];
1061
+ for (const node of nodes) {
1062
+ if (node.kind === "page") {
1063
+ const page = byRoute.get(node.route);
1064
+ if (page && !seen.has(page.route)) {
1065
+ list.push(line(page));
1066
+ }
1067
+ continue;
1068
+ }
1069
+ const rootPage = node.route ? byRoute.get(node.route) : undefined;
1070
+ const blocks2 = renderLevel(node.children, depth + 1);
1071
+ if (rootPage && !seen.has(rootPage.route)) {
1072
+ blocks2.unshift(line(rootPage));
1073
+ }
1074
+ if (blocks2.length > 0) {
1075
+ groupBlocks.push(`${"#".repeat(Math.min(depth, 6))} ${node.label}`, ...blocks2);
1076
+ }
1077
+ }
1078
+ return list.length > 0 ? [list.join(`
1079
+ `), ...groupBlocks] : groupBlocks;
1080
+ };
1081
+ const renderNav = (nav, depth) => {
1082
+ const loose = nav.sidebar.filter((node) => node.kind === "page");
1083
+ const groups = nav.sidebar.filter((node) => node.kind === "group");
1084
+ const looseBlocks = renderLevel(loose, depth + 1);
1085
+ return [
1086
+ ...looseBlocks.length > 0 ? [`${"#".repeat(depth)} Docs`, ...looseBlocks] : [],
1087
+ ...renderLevel(groups, depth)
1088
+ ];
1089
+ };
1090
+ const blocks = [];
1091
+ for (const { label, nav } of indexedNavigations(project)) {
1092
+ if (label) {
1093
+ const localized = renderNav(nav, 3);
1094
+ if (localized.length > 0) {
1095
+ blocks.push(`## ${label}`, ...localized);
1096
+ }
1097
+ continue;
1098
+ }
1099
+ blocks.push(...renderNav(nav, 2));
767
1100
  }
768
- return `${lines.join(`
1101
+ const leftover = eligible.filter((page) => !seen.has(page.route)).toSorted((a, b) => a.route.localeCompare(b.route));
1102
+ if (leftover.length > 0) {
1103
+ blocks.push(blocks.length > 0 ? "## Other" : "## Docs", leftover.map(line).join(`
1104
+ `));
1105
+ }
1106
+ const header = config.description ? `# ${config.title}
1107
+
1108
+ > ${config.description}` : `# ${config.title}`;
1109
+ return `${[header, ...blocks].join(`
1110
+
769
1111
  `)}
770
1112
  `;
771
1113
  };
772
1114
  var buildFull = async (project) => {
773
1115
  const { config } = project;
774
- const pages = orderedPages(project);
1116
+ const pages = eligiblePages(project).toSorted((a, b) => a.route.localeCompare(b.route));
775
1117
  const sections = await Promise.all(pages.map(async (page) => {
776
1118
  const raw = await readEntryText(project, page);
777
- const body = frontmatter_default(raw).content.trim();
1119
+ const body = downlevelComponents(applyAgentVisibility(frontmatter_default(raw).content), config.ai.markdownComponents).trim();
778
1120
  const url = pageUrl(page.route, config.deployment.site, normalizeBasePath(config.deployment.base));
779
1121
  return [`# ${page.title}`, `Source: ${url}`, "", body].join(`
780
1122
  `);
@@ -897,7 +1239,7 @@ import { existsSync as existsSync4 } from "node:fs";
897
1239
  import { cp, mkdir as mkdir2, rm } from "node:fs/promises";
898
1240
  import { dirname as dirname4, join as join6 } from "pathe";
899
1241
  var ADAPTER_OUTPUT_PATHS = {
900
- netlify: ".netlify",
1242
+ netlify: ".netlify/v1",
901
1243
  vercel: ".vercel/output"
902
1244
  };
903
1245
  var deployStaticDir = (config, context) => {
@@ -905,7 +1247,11 @@ var deployStaticDir = (config, context) => {
905
1247
  if (output === "server" && adapter === "vercel") {
906
1248
  return join6(context.root, ".vercel", "output", "static");
907
1249
  }
908
- return context.distDir ?? join6(context.root, "dist");
1250
+ const dist = context.distDir ?? join6(context.root, "dist");
1251
+ if (output === "server" && adapter === "node") {
1252
+ return join6(dist, "client");
1253
+ }
1254
+ return dist;
909
1255
  };
910
1256
  var surfaceAdapterOutput = async (config, context) => {
911
1257
  const { adapter, output } = config.deployment;
@@ -940,8 +1286,8 @@ var buildNetlifyRedirects = (redirects) => `${redirects.map((redirect) => `${red
940
1286
  var buildVercelConfig = (redirects) => `${JSON.stringify({
941
1287
  redirects: redirects.map((redirect) => ({
942
1288
  destination: redirect.to,
943
- permanent: redirect.status === 301 || redirect.status === 308,
944
- source: redirect.from
1289
+ source: redirect.from,
1290
+ statusCode: redirect.status
945
1291
  }))
946
1292
  }, null, 2)}
947
1293
  `;
@@ -986,7 +1332,71 @@ var buildRobots = (project) => {
986
1332
  `;
987
1333
  };
988
1334
 
1335
+ // src/astro/pages.ts
1336
+ import { extname, relative as relative4 } from "pathe";
1337
+ import { glob, globSync } from "tinyglobby";
1338
+ var PAGE_GLOB = ["**/*.astro"];
1339
+ var toPageRoutes = (pagesRoot, files) => {
1340
+ files.sort();
1341
+ return files.map((file) => {
1342
+ const rel = relative4(pagesRoot, file);
1343
+ const withoutExt = rel.slice(0, rel.length - extname(rel).length);
1344
+ const parts = withoutExt.split("/");
1345
+ if (parts.at(-1) === "index") {
1346
+ parts.pop();
1347
+ }
1348
+ const pattern = parts.length === 0 ? "/" : `/${parts.join("/")}`;
1349
+ return { entrypoint: file, pattern };
1350
+ });
1351
+ };
1352
+ var discoverPages = async (pagesRoot) => toPageRoutes(pagesRoot, await glob(PAGE_GLOB, { absolute: true, cwd: pagesRoot, onlyFiles: true }));
1353
+ var discoverPagesSync = (pagesRoot) => toPageRoutes(pagesRoot, globSync(PAGE_GLOB, { absolute: true, cwd: pagesRoot, onlyFiles: true }));
1354
+ var routeIsTaken = (pages, contentPages, route) => pages.some((page) => page.pattern === route) || contentPages.some((page) => page.route === route);
1355
+ var PRIVATE_SEGMENT = /^[._]/u;
1356
+ var staticSegments = (pattern) => {
1357
+ const segments = pattern.split("/").filter(Boolean);
1358
+ return segments.some((part) => PRIVATE_SEGMENT.test(part) || part.includes("[")) ? null : segments;
1359
+ };
1360
+ var customStaticRoutes = (pages) => {
1361
+ const routes = new Set;
1362
+ for (const { pattern } of pages) {
1363
+ const segments = staticSegments(pattern);
1364
+ if (segments !== null) {
1365
+ routes.add(segments.length === 0 ? "/" : `/${segments.join("/")}`);
1366
+ }
1367
+ }
1368
+ return [...routes];
1369
+ };
1370
+ var hasGeneratedChangelog = (project, userPages) => {
1371
+ const hasChangelog = project.graph.pages.some((page) => page.contentType === "changelog" && !(page.meta.draft || page.meta.sidebar.hidden));
1372
+ const hasChangelogSource = (project.config.content.sources ?? []).some((source) => source.type === "github-releases");
1373
+ return (hasChangelog || hasChangelogSource) && !routeIsTaken(userPages, project.graph.pages, "/changelog");
1374
+ };
1375
+ var humanizeSegment = (segment) => segment.split(/[-_]/u).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
1376
+ var customOgRoutes = (pages, siteTitle) => {
1377
+ const seen = new Set;
1378
+ const routes = [];
1379
+ const collectRoute = (pattern) => {
1380
+ const segments = staticSegments(pattern);
1381
+ if (segments === null) {
1382
+ return;
1383
+ }
1384
+ const slug = segments.length === 0 ? "index" : segments.join("/");
1385
+ if (seen.has(slug)) {
1386
+ return;
1387
+ }
1388
+ seen.add(slug);
1389
+ const last = segments.at(-1);
1390
+ routes.push({ slug, title: last ? humanizeSegment(last) : siteTitle });
1391
+ };
1392
+ for (const { pattern } of pages) {
1393
+ collectRoute(pattern);
1394
+ }
1395
+ return routes;
1396
+ };
1397
+
989
1398
  // src/deploy/sitemap.ts
1399
+ var ERROR_ROUTES = new Set(["/404", "/500"]);
990
1400
  var lastmodTag = (value) => {
991
1401
  if (!value) {
992
1402
  return "";
@@ -1001,13 +1411,29 @@ var buildSitemap = (project) => {
1001
1411
  }
1002
1412
  const base = site.replace(/\/$/u, "");
1003
1413
  const deployBase = normalizeBasePath(project.config.deployment.base);
1414
+ const seen = new Set;
1004
1415
  const urls = [];
1416
+ const pushUrl = (route, lastModified) => {
1417
+ const loc = escapeXml(encodeURI(`${base}${route}`));
1418
+ if (seen.has(loc)) {
1419
+ return;
1420
+ }
1421
+ seen.add(loc);
1422
+ urls.push(` <url><loc>${loc}</loc>${lastmodTag(lastModified)}</url>`);
1423
+ };
1005
1424
  for (const page of project.graph.pages) {
1006
- if (page.meta.draft || page.meta.sidebar.hidden || page.meta.seo.noindex) {
1425
+ if (page.meta.draft || page.meta.sidebar.hidden || page.meta.seo.noindex || ERROR_ROUTES.has(page.route)) {
1007
1426
  continue;
1008
1427
  }
1009
- const loc = escapeXml(encodeURI(`${base}${withBasePath(deployBase, page.route)}`));
1010
- urls.push(` <url><loc>${loc}</loc>${lastmodTag(page.lastModified)}</url>`);
1428
+ pushUrl(withBasePath(deployBase, page.route), page.lastModified);
1429
+ }
1430
+ const userPages = project.context.pagesRoot ? discoverPagesSync(project.context.pagesRoot) : [];
1431
+ const extraRoutes = customStaticRoutes(userPages).filter((route) => !ERROR_ROUTES.has(route));
1432
+ if (hasGeneratedChangelog(project, userPages)) {
1433
+ extraRoutes.push("/changelog");
1434
+ }
1435
+ for (const route of extraRoutes) {
1436
+ pushUrl(withBasePath(deployBase, route));
1011
1437
  }
1012
1438
  urls.sort();
1013
1439
  return `<?xml version="1.0" encoding="UTF-8"?>
@@ -1055,6 +1481,11 @@ var ar = {
1055
1481
  send: "إرسال",
1056
1482
  title: "اسأل الذكاء الاصطناعي"
1057
1483
  },
1484
+ banner: { dismiss: "إغلاق الإعلان" },
1485
+ changelog: {
1486
+ description: "تحديثات المنتج وملاحظات الإصدارات.",
1487
+ title: "سجل التغييرات"
1488
+ },
1058
1489
  feedback: {
1059
1490
  no: "لا",
1060
1491
  question: "هل كانت هذه الصفحة مفيدة؟",
@@ -1062,13 +1493,16 @@ var ar = {
1062
1493
  yes: "نعم"
1063
1494
  },
1064
1495
  languageSwitcher: { label: "اللغة", untranslated: "غير مترجم" },
1496
+ nav: { breadcrumb: "مسار التنقل" },
1065
1497
  page: {
1066
1498
  lastUpdated: "آخر تحديث في",
1067
1499
  next: "التالي",
1500
+ pagination: "ترقيم الصفحات",
1068
1501
  previous: "السابق",
1069
1502
  skipToContent: "الانتقال إلى المحتوى"
1070
1503
  },
1071
1504
  search: {
1505
+ all: "الكل",
1072
1506
  button: "بحث",
1073
1507
  devOnly: "البحث متاح في إصدار الإنتاج.",
1074
1508
  label: "البحث في الوثائق",
@@ -1101,6 +1535,11 @@ var bg = {
1101
1535
  send: "Изпрати",
1102
1536
  title: "Попитай ИИ"
1103
1537
  },
1538
+ banner: { dismiss: "Затваряне на съобщението" },
1539
+ changelog: {
1540
+ description: "Актуализации на продукта и бележки към изданията.",
1541
+ title: "Дневник на промените"
1542
+ },
1104
1543
  feedback: {
1105
1544
  no: "Не",
1106
1545
  question: "Беше ли полезна тази страница?",
@@ -1108,13 +1547,16 @@ var bg = {
1108
1547
  yes: "Да"
1109
1548
  },
1110
1549
  languageSwitcher: { label: "Език", untranslated: "Непреведено" },
1550
+ nav: { breadcrumb: "Навигационна пътека" },
1111
1551
  page: {
1112
1552
  lastUpdated: "Последна актуализация",
1113
1553
  next: "Напред",
1554
+ pagination: "Пагинация",
1114
1555
  previous: "Назад",
1115
1556
  skipToContent: "Към съдържанието"
1116
1557
  },
1117
1558
  search: {
1559
+ all: "Всички",
1118
1560
  button: "Търсене",
1119
1561
  devOnly: "Търсенето е достъпно в производствената компилация.",
1120
1562
  label: "Търсене в документацията",
@@ -1147,6 +1589,11 @@ var bn = {
1147
1589
  send: "পাঠান",
1148
1590
  title: "AI-কে জিজ্ঞাসা করুন"
1149
1591
  },
1592
+ banner: { dismiss: "ঘোষণা বন্ধ করুন" },
1593
+ changelog: {
1594
+ description: "পণ্য আপডেট এবং রিলিজ নোট।",
1595
+ title: "পরিবর্তন লগ"
1596
+ },
1150
1597
  feedback: {
1151
1598
  no: "না",
1152
1599
  question: "এই পৃষ্ঠাটি কি সহায়ক ছিল?",
@@ -1154,13 +1601,16 @@ var bn = {
1154
1601
  yes: "হ্যাঁ"
1155
1602
  },
1156
1603
  languageSwitcher: { label: "ভাষা", untranslated: "অনূদিত নয়" },
1604
+ nav: { breadcrumb: "ব্রেডক্রাম্ব" },
1157
1605
  page: {
1158
1606
  lastUpdated: "সর্বশেষ আপডেট",
1159
1607
  next: "পরবর্তী",
1608
+ pagination: "পেজিনেশন",
1160
1609
  previous: "পূর্ববর্তী",
1161
1610
  skipToContent: "বিষয়বস্তুতে যান"
1162
1611
  },
1163
1612
  search: {
1613
+ all: "সব",
1164
1614
  button: "অনুসন্ধান",
1165
1615
  devOnly: "অনুসন্ধান প্রোডাকশন বিল্ডে উপলব্ধ।",
1166
1616
  label: "ডকুমেন্টেশন অনুসন্ধান করুন",
@@ -1193,6 +1643,11 @@ var ca = {
1193
1643
  send: "Envia",
1194
1644
  title: "Pregunta a la IA"
1195
1645
  },
1646
+ banner: { dismiss: "Tanca l'anunci" },
1647
+ changelog: {
1648
+ description: "Actualitzacions del producte i notes de la versió.",
1649
+ title: "Registre de canvis"
1650
+ },
1196
1651
  feedback: {
1197
1652
  no: "No",
1198
1653
  question: "Aquesta pàgina t'ha estat útil?",
@@ -1200,13 +1655,16 @@ var ca = {
1200
1655
  yes: "Sí"
1201
1656
  },
1202
1657
  languageSwitcher: { label: "Idioma", untranslated: "Sense traduir" },
1658
+ nav: { breadcrumb: "Ruta de navegació" },
1203
1659
  page: {
1204
1660
  lastUpdated: "Última actualització el",
1205
1661
  next: "Següent",
1662
+ pagination: "Paginació",
1206
1663
  previous: "Anterior",
1207
1664
  skipToContent: "Vés al contingut"
1208
1665
  },
1209
1666
  search: {
1667
+ all: "Tots",
1210
1668
  button: "Cerca",
1211
1669
  devOnly: "La cerca està disponible a la compilació de producció.",
1212
1670
  label: "Cerca a la documentació",
@@ -1239,6 +1697,11 @@ var cs = {
1239
1697
  send: "Odeslat",
1240
1698
  title: "Zeptat se AI"
1241
1699
  },
1700
+ banner: { dismiss: "Zavřít oznámení" },
1701
+ changelog: {
1702
+ description: "Novinky produktu a poznámky k vydání.",
1703
+ title: "Seznam změn"
1704
+ },
1242
1705
  feedback: {
1243
1706
  no: "Ne",
1244
1707
  question: "Byla tato stránka užitečná?",
@@ -1246,13 +1709,16 @@ var cs = {
1246
1709
  yes: "Ano"
1247
1710
  },
1248
1711
  languageSwitcher: { label: "Jazyk", untranslated: "Nepřeloženo" },
1712
+ nav: { breadcrumb: "Drobečková navigace" },
1249
1713
  page: {
1250
1714
  lastUpdated: "Naposledy aktualizováno",
1251
1715
  next: "Další",
1716
+ pagination: "Stránkování",
1252
1717
  previous: "Předchozí",
1253
1718
  skipToContent: "Přejít k obsahu"
1254
1719
  },
1255
1720
  search: {
1721
+ all: "Vše",
1256
1722
  button: "Hledat",
1257
1723
  devOnly: "Vyhledávání je dostupné v produkčním buildu.",
1258
1724
  label: "Prohledat dokumentaci",
@@ -1285,6 +1751,11 @@ var da = {
1285
1751
  send: "Send",
1286
1752
  title: "Spørg AI"
1287
1753
  },
1754
+ banner: { dismiss: "Luk meddelelsen" },
1755
+ changelog: {
1756
+ description: "Produktopdateringer og udgivelsesnoter.",
1757
+ title: "Ændringslog"
1758
+ },
1288
1759
  feedback: {
1289
1760
  no: "Nej",
1290
1761
  question: "Var denne side nyttig?",
@@ -1292,13 +1763,16 @@ var da = {
1292
1763
  yes: "Ja"
1293
1764
  },
1294
1765
  languageSwitcher: { label: "Sprog", untranslated: "Ikke oversat" },
1766
+ nav: { breadcrumb: "Brødkrummesti" },
1295
1767
  page: {
1296
1768
  lastUpdated: "Senest opdateret den",
1297
1769
  next: "Næste",
1770
+ pagination: "Paginering",
1298
1771
  previous: "Forrige",
1299
1772
  skipToContent: "Spring til indhold"
1300
1773
  },
1301
1774
  search: {
1775
+ all: "Alle",
1302
1776
  button: "Søg",
1303
1777
  devOnly: "Søgning er tilgængelig i produktionsbygningen.",
1304
1778
  label: "Søg i dokumentationen",
@@ -1331,6 +1805,11 @@ var de = {
1331
1805
  send: "Senden",
1332
1806
  title: "KI fragen"
1333
1807
  },
1808
+ banner: { dismiss: "Ankündigung schließen" },
1809
+ changelog: {
1810
+ description: "Produkt-Updates und Versionshinweise.",
1811
+ title: "Änderungsprotokoll"
1812
+ },
1334
1813
  feedback: {
1335
1814
  no: "Nein",
1336
1815
  question: "War diese Seite hilfreich?",
@@ -1338,13 +1817,16 @@ var de = {
1338
1817
  yes: "Ja"
1339
1818
  },
1340
1819
  languageSwitcher: { label: "Sprache", untranslated: "Nicht übersetzt" },
1820
+ nav: { breadcrumb: "Brotkrümelnavigation" },
1341
1821
  page: {
1342
1822
  lastUpdated: "Zuletzt aktualisiert am",
1343
1823
  next: "Weiter",
1824
+ pagination: "Seitennummerierung",
1344
1825
  previous: "Zurück",
1345
1826
  skipToContent: "Zum Inhalt springen"
1346
1827
  },
1347
1828
  search: {
1829
+ all: "Alle",
1348
1830
  button: "Suchen",
1349
1831
  devOnly: "Die Suche ist im Produktions-Build verfügbar.",
1350
1832
  label: "Dokumentation durchsuchen",
@@ -1377,6 +1859,11 @@ var el = {
1377
1859
  send: "Αποστολή",
1378
1860
  title: "Ρωτήστε την AI"
1379
1861
  },
1862
+ banner: { dismiss: "Κλείσιμο ανακοίνωσης" },
1863
+ changelog: {
1864
+ description: "Ενημερώσεις προϊόντος και σημειώσεις έκδοσης.",
1865
+ title: "Ιστορικό αλλαγών"
1866
+ },
1380
1867
  feedback: {
1381
1868
  no: "Όχι",
1382
1869
  question: "Σας φάνηκε χρήσιμη αυτή η σελίδα;",
@@ -1384,13 +1871,16 @@ var el = {
1384
1871
  yes: "Ναι"
1385
1872
  },
1386
1873
  languageSwitcher: { label: "Γλώσσα", untranslated: "Δεν έχει μεταφραστεί" },
1874
+ nav: { breadcrumb: "Διαδρομή πλοήγησης" },
1387
1875
  page: {
1388
1876
  lastUpdated: "Τελευταία ενημέρωση",
1389
1877
  next: "Επόμενο",
1878
+ pagination: "Σελιδοποίηση",
1390
1879
  previous: "Προηγούμενο",
1391
1880
  skipToContent: "Μετάβαση στο περιεχόμενο"
1392
1881
  },
1393
1882
  search: {
1883
+ all: "Όλα",
1394
1884
  button: "Αναζήτηση",
1395
1885
  devOnly: "Η αναζήτηση είναι διαθέσιμη στην έκδοση παραγωγής.",
1396
1886
  label: "Αναζήτηση στην τεκμηρίωση",
@@ -1423,6 +1913,11 @@ var es = {
1423
1913
  send: "Enviar",
1424
1914
  title: "Preguntar a la IA"
1425
1915
  },
1916
+ banner: { dismiss: "Cerrar el anuncio" },
1917
+ changelog: {
1918
+ description: "Novedades del producto y notas de la versión.",
1919
+ title: "Registro de cambios"
1920
+ },
1426
1921
  feedback: {
1427
1922
  no: "No",
1428
1923
  question: "¿Te ha resultado útil esta página?",
@@ -1430,13 +1925,16 @@ var es = {
1430
1925
  yes: "Sí"
1431
1926
  },
1432
1927
  languageSwitcher: { label: "Idioma", untranslated: "Sin traducir" },
1928
+ nav: { breadcrumb: "Ruta de navegación" },
1433
1929
  page: {
1434
1930
  lastUpdated: "Última actualización el",
1435
1931
  next: "Siguiente",
1932
+ pagination: "Paginación",
1436
1933
  previous: "Anterior",
1437
1934
  skipToContent: "Saltar al contenido"
1438
1935
  },
1439
1936
  search: {
1937
+ all: "Todos",
1440
1938
  button: "Buscar",
1441
1939
  devOnly: "La búsqueda está disponible en la compilación de producción.",
1442
1940
  label: "Buscar en la documentación",
@@ -1469,6 +1967,11 @@ var fa = {
1469
1967
  send: "ارسال",
1470
1968
  title: "از هوش مصنوعی بپرسید"
1471
1969
  },
1970
+ banner: { dismiss: "بستن اطلاعیه" },
1971
+ changelog: {
1972
+ description: "به‌روزرسانی‌های محصول و یادداشت‌های انتشار.",
1973
+ title: "گزارش تغییرات"
1974
+ },
1472
1975
  feedback: {
1473
1976
  no: "خیر",
1474
1977
  question: "آیا این صفحه مفید بود؟",
@@ -1476,13 +1979,16 @@ var fa = {
1476
1979
  yes: "بله"
1477
1980
  },
1478
1981
  languageSwitcher: { label: "زبان", untranslated: "ترجمه‌نشده" },
1982
+ nav: { breadcrumb: "مسیر ناوبری" },
1479
1983
  page: {
1480
1984
  lastUpdated: "آخرین به‌روزرسانی",
1481
1985
  next: "بعدی",
1986
+ pagination: "صفحه‌بندی",
1482
1987
  previous: "قبلی",
1483
1988
  skipToContent: "پرش به محتوا"
1484
1989
  },
1485
1990
  search: {
1991
+ all: "همه",
1486
1992
  button: "جستجو",
1487
1993
  devOnly: "جستجو در نسخه تولید در دسترس است.",
1488
1994
  label: "جستجو در مستندات",
@@ -1515,6 +2021,11 @@ var fi = {
1515
2021
  send: "Lähetä",
1516
2022
  title: "Kysy tekoälyltä"
1517
2023
  },
2024
+ banner: { dismiss: "Sulje ilmoitus" },
2025
+ changelog: {
2026
+ description: "Tuotepäivitykset ja julkaisutiedot.",
2027
+ title: "Muutosloki"
2028
+ },
1518
2029
  feedback: {
1519
2030
  no: "Ei",
1520
2031
  question: "Oliko tästä sivusta apua?",
@@ -1522,13 +2033,16 @@ var fi = {
1522
2033
  yes: "Kyllä"
1523
2034
  },
1524
2035
  languageSwitcher: { label: "Kieli", untranslated: "Ei käännetty" },
2036
+ nav: { breadcrumb: "Murupolku" },
1525
2037
  page: {
1526
2038
  lastUpdated: "Viimeksi päivitetty",
1527
2039
  next: "Seuraava",
2040
+ pagination: "Sivutus",
1528
2041
  previous: "Edellinen",
1529
2042
  skipToContent: "Siirry sisältöön"
1530
2043
  },
1531
2044
  search: {
2045
+ all: "Kaikki",
1532
2046
  button: "Hae",
1533
2047
  devOnly: "Haku on käytettävissä tuotantokäännöksessä.",
1534
2048
  label: "Hae dokumentaatiosta",
@@ -1543,7 +2057,7 @@ var fr = {
1543
2057
  actions: {
1544
2058
  addToCursor: "Ajouter à Cursor",
1545
2059
  addToVscode: "Ajouter à VS Code",
1546
- askAI: "Demander à l'IA",
2060
+ askAI: "Demander à l'IA à propos de cette page",
1547
2061
  connectMcp: "Se connecter à MCP",
1548
2062
  copied: "Copié !",
1549
2063
  copyClaudeCode: "Copier la commande Claude Code",
@@ -1561,6 +2075,11 @@ var fr = {
1561
2075
  send: "Envoyer",
1562
2076
  title: "Demander à l'IA"
1563
2077
  },
2078
+ banner: { dismiss: "Fermer l'annonce" },
2079
+ changelog: {
2080
+ description: "Nouveautés du produit et notes de version.",
2081
+ title: "Journal des modifications"
2082
+ },
1564
2083
  feedback: {
1565
2084
  no: "Non",
1566
2085
  question: "Cette page vous a-t-elle été utile ?",
@@ -1568,13 +2087,16 @@ var fr = {
1568
2087
  yes: "Oui"
1569
2088
  },
1570
2089
  languageSwitcher: { label: "Langue", untranslated: "Non traduit" },
2090
+ nav: { breadcrumb: "Fil d'Ariane" },
1571
2091
  page: {
1572
2092
  lastUpdated: "Dernière mise à jour le",
1573
2093
  next: "Suivant",
2094
+ pagination: "Pagination",
1574
2095
  previous: "Précédent",
1575
2096
  skipToContent: "Aller au contenu"
1576
2097
  },
1577
2098
  search: {
2099
+ all: "Tous",
1578
2100
  button: "Rechercher",
1579
2101
  devOnly: "La recherche est disponible dans la version de production.",
1580
2102
  label: "Rechercher dans la doc",
@@ -1607,6 +2129,11 @@ var he = {
1607
2129
  send: "שלח",
1608
2130
  title: "שאל את ה-AI"
1609
2131
  },
2132
+ banner: { dismiss: "סגירת ההודעה" },
2133
+ changelog: {
2134
+ description: "עדכוני מוצר והערות גרסה.",
2135
+ title: "יומן שינויים"
2136
+ },
1610
2137
  feedback: {
1611
2138
  no: "לא",
1612
2139
  question: "האם העמוד הזה היה מועיל?",
@@ -1614,13 +2141,16 @@ var he = {
1614
2141
  yes: "כן"
1615
2142
  },
1616
2143
  languageSwitcher: { label: "שפה", untranslated: "לא מתורגם" },
2144
+ nav: { breadcrumb: "פירורי לחם" },
1617
2145
  page: {
1618
2146
  lastUpdated: "עודכן לאחרונה",
1619
2147
  next: "הבא",
2148
+ pagination: "עימוד",
1620
2149
  previous: "הקודם",
1621
2150
  skipToContent: "דלג לתוכן"
1622
2151
  },
1623
2152
  search: {
2153
+ all: "הכל",
1624
2154
  button: "חיפוש",
1625
2155
  devOnly: "החיפוש זמין בבנייה לייצור.",
1626
2156
  label: "חפש בתיעוד",
@@ -1653,6 +2183,11 @@ var hi = {
1653
2183
  send: "भेजें",
1654
2184
  title: "AI से पूछें"
1655
2185
  },
2186
+ banner: { dismiss: "घोषणा बंद करें" },
2187
+ changelog: {
2188
+ description: "उत्पाद अपडेट और रिलीज़ नोट्स।",
2189
+ title: "परिवर्तन लॉग"
2190
+ },
1656
2191
  feedback: {
1657
2192
  no: "नहीं",
1658
2193
  question: "क्या यह पेज सहायक था?",
@@ -1660,13 +2195,16 @@ var hi = {
1660
2195
  yes: "हाँ"
1661
2196
  },
1662
2197
  languageSwitcher: { label: "भाषा", untranslated: "अनुवादित नहीं" },
2198
+ nav: { breadcrumb: "ब्रेडक्रम" },
1663
2199
  page: {
1664
2200
  lastUpdated: "अंतिम अपडेट",
1665
2201
  next: "अगला",
2202
+ pagination: "पृष्ठांकन",
1666
2203
  previous: "पिछला",
1667
2204
  skipToContent: "सामग्री पर जाएँ"
1668
2205
  },
1669
2206
  search: {
2207
+ all: "सभी",
1670
2208
  button: "खोजें",
1671
2209
  devOnly: "खोज प्रोडक्शन बिल्ड में उपलब्ध है।",
1672
2210
  label: "दस्तावेज़ खोजें",
@@ -1699,6 +2237,11 @@ var hr = {
1699
2237
  send: "Pošalji",
1700
2238
  title: "Pitaj AI"
1701
2239
  },
2240
+ banner: { dismiss: "Zatvori obavijest" },
2241
+ changelog: {
2242
+ description: "Ažuriranja proizvoda i napomene o izdanjima.",
2243
+ title: "Popis promjena"
2244
+ },
1702
2245
  feedback: {
1703
2246
  no: "Ne",
1704
2247
  question: "Je li vam ova stranica bila korisna?",
@@ -1706,13 +2249,16 @@ var hr = {
1706
2249
  yes: "Da"
1707
2250
  },
1708
2251
  languageSwitcher: { label: "Jezik", untranslated: "Nije prevedeno" },
2252
+ nav: { breadcrumb: "Navigacijski put" },
1709
2253
  page: {
1710
2254
  lastUpdated: "Posljednje ažuriranje",
1711
2255
  next: "Sljedeće",
2256
+ pagination: "Straničenje",
1712
2257
  previous: "Prethodno",
1713
2258
  skipToContent: "Prijeđi na sadržaj"
1714
2259
  },
1715
2260
  search: {
2261
+ all: "Sve",
1716
2262
  button: "Pretraži",
1717
2263
  devOnly: "Pretraživanje je dostupno u produkcijskoj verziji.",
1718
2264
  label: "Pretraži dokumentaciju",
@@ -1745,6 +2291,11 @@ var hu = {
1745
2291
  send: "Küldés",
1746
2292
  title: "Kérdezd az AI-t"
1747
2293
  },
2294
+ banner: { dismiss: "Közlemény bezárása" },
2295
+ changelog: {
2296
+ description: "Termékfrissítések és kiadási megjegyzések.",
2297
+ title: "Változásnapló"
2298
+ },
1748
2299
  feedback: {
1749
2300
  no: "Nem",
1750
2301
  question: "Hasznos volt ez az oldal?",
@@ -1752,13 +2303,16 @@ var hu = {
1752
2303
  yes: "Igen"
1753
2304
  },
1754
2305
  languageSwitcher: { label: "Nyelv", untranslated: "Nincs lefordítva" },
2306
+ nav: { breadcrumb: "Morzsamenü" },
1755
2307
  page: {
1756
2308
  lastUpdated: "Utoljára frissítve",
1757
2309
  next: "Következő",
2310
+ pagination: "Lapozás",
1758
2311
  previous: "Előző",
1759
2312
  skipToContent: "Ugrás a tartalomra"
1760
2313
  },
1761
2314
  search: {
2315
+ all: "Összes",
1762
2316
  button: "Keresés",
1763
2317
  devOnly: "A keresés az éles buildben érhető el.",
1764
2318
  label: "Keresés a dokumentációban",
@@ -1791,6 +2345,11 @@ var id = {
1791
2345
  send: "Kirim",
1792
2346
  title: "Tanya AI"
1793
2347
  },
2348
+ banner: { dismiss: "Tutup pengumuman" },
2349
+ changelog: {
2350
+ description: "Pembaruan produk dan catatan rilis.",
2351
+ title: "Log perubahan"
2352
+ },
1794
2353
  feedback: {
1795
2354
  no: "Tidak",
1796
2355
  question: "Apakah halaman ini membantu?",
@@ -1798,13 +2357,16 @@ var id = {
1798
2357
  yes: "Ya"
1799
2358
  },
1800
2359
  languageSwitcher: { label: "Bahasa", untranslated: "Belum diterjemahkan" },
2360
+ nav: { breadcrumb: "Remah roti" },
1801
2361
  page: {
1802
2362
  lastUpdated: "Terakhir diperbarui",
1803
2363
  next: "Berikutnya",
2364
+ pagination: "Penomoran halaman",
1804
2365
  previous: "Sebelumnya",
1805
2366
  skipToContent: "Lewati ke konten"
1806
2367
  },
1807
2368
  search: {
2369
+ all: "Semua",
1808
2370
  button: "Cari",
1809
2371
  devOnly: "Pencarian tersedia di build produksi.",
1810
2372
  label: "Cari dokumentasi",
@@ -1837,6 +2399,11 @@ var it = {
1837
2399
  send: "Invia",
1838
2400
  title: "Chiedi all'IA"
1839
2401
  },
2402
+ banner: { dismiss: "Chiudi l'annuncio" },
2403
+ changelog: {
2404
+ description: "Aggiornamenti del prodotto e note di rilascio.",
2405
+ title: "Registro delle modifiche"
2406
+ },
1840
2407
  feedback: {
1841
2408
  no: "No",
1842
2409
  question: "Questa pagina ti è stata utile?",
@@ -1844,13 +2411,16 @@ var it = {
1844
2411
  yes: "Sì"
1845
2412
  },
1846
2413
  languageSwitcher: { label: "Lingua", untranslated: "Non tradotto" },
2414
+ nav: { breadcrumb: "Percorso di navigazione" },
1847
2415
  page: {
1848
2416
  lastUpdated: "Ultimo aggiornamento il",
1849
2417
  next: "Successivo",
2418
+ pagination: "Paginazione",
1850
2419
  previous: "Precedente",
1851
2420
  skipToContent: "Vai al contenuto"
1852
2421
  },
1853
2422
  search: {
2423
+ all: "Tutti",
1854
2424
  button: "Cerca",
1855
2425
  devOnly: "La ricerca è disponibile nella build di produzione.",
1856
2426
  label: "Cerca nella documentazione",
@@ -1883,6 +2453,11 @@ var ja = {
1883
2453
  send: "送信",
1884
2454
  title: "AI に質問"
1885
2455
  },
2456
+ banner: { dismiss: "お知らせを閉じる" },
2457
+ changelog: {
2458
+ description: "製品のアップデートとリリースノート。",
2459
+ title: "変更履歴"
2460
+ },
1886
2461
  feedback: {
1887
2462
  no: "いいえ",
1888
2463
  question: "このページは役に立ちましたか?",
@@ -1890,13 +2465,16 @@ var ja = {
1890
2465
  yes: "はい"
1891
2466
  },
1892
2467
  languageSwitcher: { label: "言語", untranslated: "未翻訳" },
2468
+ nav: { breadcrumb: "パンくずリスト" },
1893
2469
  page: {
1894
2470
  lastUpdated: "最終更新",
1895
2471
  next: "次へ",
2472
+ pagination: "ページネーション",
1896
2473
  previous: "前へ",
1897
2474
  skipToContent: "コンテンツにスキップ"
1898
2475
  },
1899
2476
  search: {
2477
+ all: "すべて",
1900
2478
  button: "検索",
1901
2479
  devOnly: "検索は本番ビルドで利用できます。",
1902
2480
  label: "ドキュメントを検索",
@@ -1929,6 +2507,11 @@ var ko = {
1929
2507
  send: "보내기",
1930
2508
  title: "AI에게 질문"
1931
2509
  },
2510
+ banner: { dismiss: "공지 닫기" },
2511
+ changelog: {
2512
+ description: "제품 업데이트 및 릴리스 노트.",
2513
+ title: "변경 로그"
2514
+ },
1932
2515
  feedback: {
1933
2516
  no: "아니요",
1934
2517
  question: "이 페이지가 도움이 되었나요?",
@@ -1936,13 +2519,16 @@ var ko = {
1936
2519
  yes: "예"
1937
2520
  },
1938
2521
  languageSwitcher: { label: "언어", untranslated: "번역되지 않음" },
2522
+ nav: { breadcrumb: "탐색 경로" },
1939
2523
  page: {
1940
2524
  lastUpdated: "마지막 업데이트",
1941
2525
  next: "다음",
2526
+ pagination: "페이지네이션",
1942
2527
  previous: "이전",
1943
2528
  skipToContent: "본문으로 건너뛰기"
1944
2529
  },
1945
2530
  search: {
2531
+ all: "전체",
1946
2532
  button: "검색",
1947
2533
  devOnly: "검색은 프로덕션 빌드에서 사용할 수 있습니다.",
1948
2534
  label: "문서 검색",
@@ -1975,6 +2561,11 @@ var nl = {
1975
2561
  send: "Verzenden",
1976
2562
  title: "AI vragen"
1977
2563
  },
2564
+ banner: { dismiss: "Aankondiging sluiten" },
2565
+ changelog: {
2566
+ description: "Productupdates en releaseopmerkingen.",
2567
+ title: "Wijzigingslogboek"
2568
+ },
1978
2569
  feedback: {
1979
2570
  no: "Nee",
1980
2571
  question: "Was deze pagina nuttig?",
@@ -1982,13 +2573,16 @@ var nl = {
1982
2573
  yes: "Ja"
1983
2574
  },
1984
2575
  languageSwitcher: { label: "Taal", untranslated: "Niet vertaald" },
2576
+ nav: { breadcrumb: "Kruimelpad" },
1985
2577
  page: {
1986
2578
  lastUpdated: "Laatst bijgewerkt op",
1987
2579
  next: "Volgende",
2580
+ pagination: "Paginering",
1988
2581
  previous: "Vorige",
1989
2582
  skipToContent: "Naar inhoud springen"
1990
2583
  },
1991
2584
  search: {
2585
+ all: "Alle",
1992
2586
  button: "Zoeken",
1993
2587
  devOnly: "Zoeken is beschikbaar in de productiebuild.",
1994
2588
  label: "Documentatie doorzoeken",
@@ -2021,6 +2615,11 @@ var no = {
2021
2615
  send: "Send",
2022
2616
  title: "Spør AI"
2023
2617
  },
2618
+ banner: { dismiss: "Lukk kunngjøringen" },
2619
+ changelog: {
2620
+ description: "Produktoppdateringer og utgivelsesnotater.",
2621
+ title: "Endringslogg"
2622
+ },
2024
2623
  feedback: {
2025
2624
  no: "Nei",
2026
2625
  question: "Var denne siden nyttig?",
@@ -2028,13 +2627,16 @@ var no = {
2028
2627
  yes: "Ja"
2029
2628
  },
2030
2629
  languageSwitcher: { label: "Språk", untranslated: "Ikke oversatt" },
2630
+ nav: { breadcrumb: "Brødsmulesti" },
2031
2631
  page: {
2032
2632
  lastUpdated: "Sist oppdatert",
2033
2633
  next: "Neste",
2634
+ pagination: "Paginering",
2034
2635
  previous: "Forrige",
2035
2636
  skipToContent: "Hopp til innhold"
2036
2637
  },
2037
2638
  search: {
2639
+ all: "Alle",
2038
2640
  button: "Søk",
2039
2641
  devOnly: "Søk er tilgjengelig i produksjonsbygget.",
2040
2642
  label: "Søk i dokumentasjonen",
@@ -2067,6 +2669,11 @@ var pl = {
2067
2669
  send: "Wyślij",
2068
2670
  title: "Zapytaj AI"
2069
2671
  },
2672
+ banner: { dismiss: "Zamknij ogłoszenie" },
2673
+ changelog: {
2674
+ description: "Aktualizacje produktu i informacje o wydaniach.",
2675
+ title: "Dziennik zmian"
2676
+ },
2070
2677
  feedback: {
2071
2678
  no: "Nie",
2072
2679
  question: "Czy ta strona była pomocna?",
@@ -2074,13 +2681,16 @@ var pl = {
2074
2681
  yes: "Tak"
2075
2682
  },
2076
2683
  languageSwitcher: { label: "Język", untranslated: "Nieprzetłumaczone" },
2684
+ nav: { breadcrumb: "Ścieżka nawigacyjna" },
2077
2685
  page: {
2078
2686
  lastUpdated: "Ostatnia aktualizacja",
2079
2687
  next: "Następny",
2688
+ pagination: "Paginacja",
2080
2689
  previous: "Poprzedni",
2081
2690
  skipToContent: "Przejdź do treści"
2082
2691
  },
2083
2692
  search: {
2693
+ all: "Wszystkie",
2084
2694
  button: "Szukaj",
2085
2695
  devOnly: "Wyszukiwanie jest dostępne w kompilacji produkcyjnej.",
2086
2696
  label: "Przeszukaj dokumentację",
@@ -2113,6 +2723,11 @@ var ptBR = {
2113
2723
  send: "Enviar",
2114
2724
  title: "Perguntar à IA"
2115
2725
  },
2726
+ banner: { dismiss: "Fechar o anúncio" },
2727
+ changelog: {
2728
+ description: "Atualizações do produto e notas de lançamento.",
2729
+ title: "Registro de alterações"
2730
+ },
2116
2731
  feedback: {
2117
2732
  no: "Não",
2118
2733
  question: "Esta página foi útil?",
@@ -2120,13 +2735,16 @@ var ptBR = {
2120
2735
  yes: "Sim"
2121
2736
  },
2122
2737
  languageSwitcher: { label: "Idioma", untranslated: "Não traduzido" },
2738
+ nav: { breadcrumb: "Trilha de navegação" },
2123
2739
  page: {
2124
2740
  lastUpdated: "Última atualização em",
2125
2741
  next: "Próximo",
2742
+ pagination: "Paginação",
2126
2743
  previous: "Anterior",
2127
2744
  skipToContent: "Pular para o conteúdo"
2128
2745
  },
2129
2746
  search: {
2747
+ all: "Todos",
2130
2748
  button: "Pesquisar",
2131
2749
  devOnly: "A busca está disponível na build de produção.",
2132
2750
  label: "Pesquisar na documentação",
@@ -2159,6 +2777,11 @@ var pt = {
2159
2777
  send: "Enviar",
2160
2778
  title: "Perguntar à IA"
2161
2779
  },
2780
+ banner: { dismiss: "Fechar o anúncio" },
2781
+ changelog: {
2782
+ description: "Atualizações do produto e notas de lançamento.",
2783
+ title: "Registo de alterações"
2784
+ },
2162
2785
  feedback: {
2163
2786
  no: "Não",
2164
2787
  question: "Esta página foi útil?",
@@ -2166,13 +2789,16 @@ var pt = {
2166
2789
  yes: "Sim"
2167
2790
  },
2168
2791
  languageSwitcher: { label: "Idioma", untranslated: "Não traduzido" },
2792
+ nav: { breadcrumb: "Caminho de navegação" },
2169
2793
  page: {
2170
2794
  lastUpdated: "Última atualização a",
2171
2795
  next: "Seguinte",
2796
+ pagination: "Paginação",
2172
2797
  previous: "Anterior",
2173
2798
  skipToContent: "Saltar para o conteúdo"
2174
2799
  },
2175
2800
  search: {
2801
+ all: "Todos",
2176
2802
  button: "Pesquisar",
2177
2803
  devOnly: "A pesquisa está disponível na compilação de produção.",
2178
2804
  label: "Pesquisar na documentação",
@@ -2205,6 +2831,11 @@ var ro = {
2205
2831
  send: "Trimite",
2206
2832
  title: "Întreabă AI"
2207
2833
  },
2834
+ banner: { dismiss: "Închide anunțul" },
2835
+ changelog: {
2836
+ description: "Actualizări ale produsului și note de lansare.",
2837
+ title: "Jurnal de modificări"
2838
+ },
2208
2839
  feedback: {
2209
2840
  no: "Nu",
2210
2841
  question: "Ți-a fost utilă această pagină?",
@@ -2212,13 +2843,16 @@ var ro = {
2212
2843
  yes: "Da"
2213
2844
  },
2214
2845
  languageSwitcher: { label: "Limbă", untranslated: "Netradus" },
2846
+ nav: { breadcrumb: "Cale de navigare" },
2215
2847
  page: {
2216
2848
  lastUpdated: "Ultima actualizare",
2217
2849
  next: "Următorul",
2850
+ pagination: "Paginare",
2218
2851
  previous: "Anteriorul",
2219
2852
  skipToContent: "Sari la conținut"
2220
2853
  },
2221
2854
  search: {
2855
+ all: "Toate",
2222
2856
  button: "Caută",
2223
2857
  devOnly: "Căutarea este disponibilă în versiunea de producție.",
2224
2858
  label: "Caută în documentație",
@@ -2251,6 +2885,11 @@ var ru = {
2251
2885
  send: "Отправить",
2252
2886
  title: "Спросить ИИ"
2253
2887
  },
2888
+ banner: { dismiss: "Закрыть объявление" },
2889
+ changelog: {
2890
+ description: "Обновления продукта и примечания к выпускам.",
2891
+ title: "Журнал изменений"
2892
+ },
2254
2893
  feedback: {
2255
2894
  no: "Нет",
2256
2895
  question: "Эта страница была полезной?",
@@ -2258,13 +2897,16 @@ var ru = {
2258
2897
  yes: "Да"
2259
2898
  },
2260
2899
  languageSwitcher: { label: "Язык", untranslated: "Не переведено" },
2900
+ nav: { breadcrumb: "Навигационная цепочка" },
2261
2901
  page: {
2262
2902
  lastUpdated: "Последнее обновление",
2263
2903
  next: "Далее",
2904
+ pagination: "Пагинация",
2264
2905
  previous: "Назад",
2265
2906
  skipToContent: "Перейти к содержимому"
2266
2907
  },
2267
2908
  search: {
2909
+ all: "Все",
2268
2910
  button: "Поиск",
2269
2911
  devOnly: "Поиск доступен в production-сборке.",
2270
2912
  label: "Поиск по документации",
@@ -2297,6 +2939,11 @@ var sk = {
2297
2939
  send: "Odoslať",
2298
2940
  title: "Opýtať sa AI"
2299
2941
  },
2942
+ banner: { dismiss: "Zavrieť oznámenie" },
2943
+ changelog: {
2944
+ description: "Novinky produktu a poznámky k vydaniu.",
2945
+ title: "Zoznam zmien"
2946
+ },
2300
2947
  feedback: {
2301
2948
  no: "Nie",
2302
2949
  question: "Bola táto stránka užitočná?",
@@ -2304,13 +2951,16 @@ var sk = {
2304
2951
  yes: "Áno"
2305
2952
  },
2306
2953
  languageSwitcher: { label: "Jazyk", untranslated: "Nepreložené" },
2954
+ nav: { breadcrumb: "Omrvinková navigácia" },
2307
2955
  page: {
2308
2956
  lastUpdated: "Naposledy aktualizované",
2309
2957
  next: "Ďalej",
2958
+ pagination: "Stránkovanie",
2310
2959
  previous: "Predchádzajúce",
2311
2960
  skipToContent: "Prejsť na obsah"
2312
2961
  },
2313
2962
  search: {
2963
+ all: "Všetko",
2314
2964
  button: "Hľadať",
2315
2965
  devOnly: "Vyhľadávanie je dostupné v produkčnom builde.",
2316
2966
  label: "Prehľadať dokumentáciu",
@@ -2343,6 +2993,11 @@ var sr = {
2343
2993
  send: "Пошаљи",
2344
2994
  title: "Питајте AI"
2345
2995
  },
2996
+ banner: { dismiss: "Затвори обавештење" },
2997
+ changelog: {
2998
+ description: "Ажурирања производа и белешке о издањима.",
2999
+ title: "Дневник измена"
3000
+ },
2346
3001
  feedback: {
2347
3002
  no: "Не",
2348
3003
  question: "Да ли вам је ова страница помогла?",
@@ -2350,13 +3005,16 @@ var sr = {
2350
3005
  yes: "Да"
2351
3006
  },
2352
3007
  languageSwitcher: { label: "Језик", untranslated: "Није преведено" },
3008
+ nav: { breadcrumb: "Путања навигације" },
2353
3009
  page: {
2354
3010
  lastUpdated: "Последње ажурирање",
2355
3011
  next: "Следеће",
3012
+ pagination: "Пагинација",
2356
3013
  previous: "Претходно",
2357
3014
  skipToContent: "Пређи на садржај"
2358
3015
  },
2359
3016
  search: {
3017
+ all: "Све",
2360
3018
  button: "Претражи",
2361
3019
  devOnly: "Претрага је доступна у продукцијској верзији.",
2362
3020
  label: "Претражи документацију",
@@ -2389,6 +3047,11 @@ var sv = {
2389
3047
  send: "Skicka",
2390
3048
  title: "Fråga AI"
2391
3049
  },
3050
+ banner: { dismiss: "Stäng meddelandet" },
3051
+ changelog: {
3052
+ description: "Produktuppdateringar och versionsinformation.",
3053
+ title: "Ändringslogg"
3054
+ },
2392
3055
  feedback: {
2393
3056
  no: "Nej",
2394
3057
  question: "Var den här sidan till hjälp?",
@@ -2396,13 +3059,16 @@ var sv = {
2396
3059
  yes: "Ja"
2397
3060
  },
2398
3061
  languageSwitcher: { label: "Språk", untranslated: "Inte översatt" },
3062
+ nav: { breadcrumb: "Brödsmulor" },
2399
3063
  page: {
2400
3064
  lastUpdated: "Senast uppdaterad",
2401
3065
  next: "Nästa",
3066
+ pagination: "Paginering",
2402
3067
  previous: "Föregående",
2403
3068
  skipToContent: "Hoppa till innehåll"
2404
3069
  },
2405
3070
  search: {
3071
+ all: "Alla",
2406
3072
  button: "Sök",
2407
3073
  devOnly: "Sökning är tillgänglig i produktionsbygget.",
2408
3074
  label: "Sök i dokumentationen",
@@ -2435,6 +3101,11 @@ var th = {
2435
3101
  send: "ส่ง",
2436
3102
  title: "ถาม AI"
2437
3103
  },
3104
+ banner: { dismiss: "ปิดประกาศ" },
3105
+ changelog: {
3106
+ description: "อัปเดตผลิตภัณฑ์และบันทึกประจำรุ่น",
3107
+ title: "บันทึกการเปลี่ยนแปลง"
3108
+ },
2438
3109
  feedback: {
2439
3110
  no: "ไม่",
2440
3111
  question: "หน้านี้มีประโยชน์หรือไม่?",
@@ -2442,13 +3113,16 @@ var th = {
2442
3113
  yes: "ใช่"
2443
3114
  },
2444
3115
  languageSwitcher: { label: "ภาษา", untranslated: "ยังไม่ได้แปล" },
3116
+ nav: { breadcrumb: "เส้นทางนำทาง" },
2445
3117
  page: {
2446
3118
  lastUpdated: "อัปเดตล่าสุดเมื่อ",
2447
3119
  next: "ถัดไป",
3120
+ pagination: "การแบ่งหน้า",
2448
3121
  previous: "ก่อนหน้า",
2449
3122
  skipToContent: "ข้ามไปยังเนื้อหา"
2450
3123
  },
2451
3124
  search: {
3125
+ all: "ทั้งหมด",
2452
3126
  button: "ค้นหา",
2453
3127
  devOnly: "การค้นหาพร้อมใช้งานในบิลด์โปรดักชัน",
2454
3128
  label: "ค้นหาเอกสาร",
@@ -2481,6 +3155,11 @@ var tr = {
2481
3155
  send: "Gönder",
2482
3156
  title: "Yapay zekâya sor"
2483
3157
  },
3158
+ banner: { dismiss: "Duyuruyu kapat" },
3159
+ changelog: {
3160
+ description: "Ürün güncellemeleri ve sürüm notları.",
3161
+ title: "Değişiklik günlüğü"
3162
+ },
2484
3163
  feedback: {
2485
3164
  no: "Hayır",
2486
3165
  question: "Bu sayfa yardımcı oldu mu?",
@@ -2488,13 +3167,16 @@ var tr = {
2488
3167
  yes: "Evet"
2489
3168
  },
2490
3169
  languageSwitcher: { label: "Dil", untranslated: "Çevrilmemiş" },
3170
+ nav: { breadcrumb: "Gezinme yolu" },
2491
3171
  page: {
2492
3172
  lastUpdated: "Son güncelleme",
2493
3173
  next: "Sonraki",
3174
+ pagination: "Sayfalama",
2494
3175
  previous: "Önceki",
2495
3176
  skipToContent: "İçeriğe geç"
2496
3177
  },
2497
3178
  search: {
3179
+ all: "Tümü",
2498
3180
  button: "Ara",
2499
3181
  devOnly: "Arama, üretim derlemesinde kullanılabilir.",
2500
3182
  label: "Belgelerde ara",
@@ -2527,6 +3209,11 @@ var uk = {
2527
3209
  send: "Надіслати",
2528
3210
  title: "Запитати ШІ"
2529
3211
  },
3212
+ banner: { dismiss: "Закрити оголошення" },
3213
+ changelog: {
3214
+ description: "Оновлення продукту та примітки до випусків.",
3215
+ title: "Журнал змін"
3216
+ },
2530
3217
  feedback: {
2531
3218
  no: "Ні",
2532
3219
  question: "Чи була ця сторінка корисною?",
@@ -2534,13 +3221,16 @@ var uk = {
2534
3221
  yes: "Так"
2535
3222
  },
2536
3223
  languageSwitcher: { label: "Мова", untranslated: "Не перекладено" },
3224
+ nav: { breadcrumb: "Навігаційний ланцюжок" },
2537
3225
  page: {
2538
3226
  lastUpdated: "Останнє оновлення",
2539
3227
  next: "Далі",
3228
+ pagination: "Пагінація",
2540
3229
  previous: "Назад",
2541
3230
  skipToContent: "Перейти до вмісту"
2542
3231
  },
2543
3232
  search: {
3233
+ all: "Усі",
2544
3234
  button: "Пошук",
2545
3235
  devOnly: "Пошук доступний у production-збірці.",
2546
3236
  label: "Пошук у документації",
@@ -2573,6 +3263,11 @@ var vi = {
2573
3263
  send: "Gửi",
2574
3264
  title: "Hỏi AI"
2575
3265
  },
3266
+ banner: { dismiss: "Đóng thông báo" },
3267
+ changelog: {
3268
+ description: "Cập nhật sản phẩm và ghi chú phát hành.",
3269
+ title: "Nhật ký thay đổi"
3270
+ },
2576
3271
  feedback: {
2577
3272
  no: "Không",
2578
3273
  question: "Trang này có hữu ích không?",
@@ -2580,13 +3275,16 @@ var vi = {
2580
3275
  yes: "Có"
2581
3276
  },
2582
3277
  languageSwitcher: { label: "Ngôn ngữ", untranslated: "Chưa dịch" },
3278
+ nav: { breadcrumb: "Đường dẫn điều hướng" },
2583
3279
  page: {
2584
3280
  lastUpdated: "Cập nhật lần cuối",
2585
3281
  next: "Tiếp theo",
3282
+ pagination: "Phân trang",
2586
3283
  previous: "Trước",
2587
3284
  skipToContent: "Chuyển đến nội dung"
2588
3285
  },
2589
3286
  search: {
3287
+ all: "Tất cả",
2590
3288
  button: "Tìm kiếm",
2591
3289
  devOnly: "Tìm kiếm có sẵn trong bản dựng production.",
2592
3290
  label: "Tìm kiếm tài liệu",
@@ -2619,6 +3317,11 @@ var zhTW = {
2619
3317
  send: "傳送",
2620
3318
  title: "向 AI 提問"
2621
3319
  },
3320
+ banner: { dismiss: "關閉公告" },
3321
+ changelog: {
3322
+ description: "產品更新與版本說明。",
3323
+ title: "更新日誌"
3324
+ },
2622
3325
  feedback: {
2623
3326
  no: "沒有幫助",
2624
3327
  question: "這個頁面有幫助嗎?",
@@ -2626,13 +3329,16 @@ var zhTW = {
2626
3329
  yes: "有幫助"
2627
3330
  },
2628
3331
  languageSwitcher: { label: "語言", untranslated: "未翻譯" },
3332
+ nav: { breadcrumb: "麵包屑導覽" },
2629
3333
  page: {
2630
3334
  lastUpdated: "最後更新於",
2631
3335
  next: "下一頁",
3336
+ pagination: "分頁",
2632
3337
  previous: "上一頁",
2633
3338
  skipToContent: "跳至內容"
2634
3339
  },
2635
3340
  search: {
3341
+ all: "全部",
2636
3342
  button: "搜尋",
2637
3343
  devOnly: "搜尋在正式版組建中可用。",
2638
3344
  label: "搜尋文件",
@@ -2665,6 +3371,11 @@ var zh = {
2665
3371
  send: "发送",
2666
3372
  title: "向 AI 提问"
2667
3373
  },
3374
+ banner: { dismiss: "关闭公告" },
3375
+ changelog: {
3376
+ description: "产品更新与版本说明。",
3377
+ title: "更新日志"
3378
+ },
2668
3379
  feedback: {
2669
3380
  no: "没有帮助",
2670
3381
  question: "这个页面有帮助吗?",
@@ -2672,13 +3383,16 @@ var zh = {
2672
3383
  yes: "有帮助"
2673
3384
  },
2674
3385
  languageSwitcher: { label: "语言", untranslated: "未翻译" },
3386
+ nav: { breadcrumb: "面包屑导航" },
2675
3387
  page: {
2676
3388
  lastUpdated: "最后更新于",
2677
3389
  next: "下一页",
3390
+ pagination: "分页",
2678
3391
  previous: "上一页",
2679
3392
  skipToContent: "跳到内容"
2680
3393
  },
2681
3394
  search: {
3395
+ all: "全部",
2682
3396
  button: "搜索",
2683
3397
  devOnly: "搜索在生产构建中可用。",
2684
3398
  label: "搜索文档",
@@ -2745,10 +3459,10 @@ var localizeRoute = (logicalRoute, code, i18n) => {
2745
3459
  return logicalRoute === "/" ? prefix : `${prefix}${logicalRoute}`;
2746
3460
  };
2747
3461
  var detectLocale = (parts, i18n) => {
2748
- const [first] = parts;
2749
- const isNonDefault = i18n.locales.some((locale) => locale.code !== i18n.defaultLocale && locale.code === first);
2750
- if (first !== undefined && isNonDefault) {
2751
- return { locale: first, rest: parts.slice(1) };
3462
+ const first = parts[0]?.toLowerCase();
3463
+ const matched = i18n.locales.find((locale) => locale.code !== i18n.defaultLocale && locale.code.toLowerCase() === first);
3464
+ if (matched) {
3465
+ return { locale: matched.code, rest: parts.slice(1) };
2752
3466
  }
2753
3467
  return { locale: i18n.defaultLocale, rest: parts };
2754
3468
  };
@@ -2763,11 +3477,11 @@ var localePlacement = (rel, ext, i18n) => {
2763
3477
  if (i18n.parser === "dot") {
2764
3478
  const lastDot = base.lastIndexOf(".");
2765
3479
  if (lastDot > base.lastIndexOf("/")) {
2766
- const suffix = base.slice(lastDot + 1);
2767
- const matched = i18n.locales.some((locale2) => locale2.code === suffix);
3480
+ const suffix = base.slice(lastDot + 1).toLowerCase();
3481
+ const matched = i18n.locales.find((locale2) => locale2.code.toLowerCase() === suffix);
2768
3482
  if (matched) {
2769
3483
  return {
2770
- locales: [suffix],
3484
+ locales: [matched.code],
2771
3485
  navPath: `${base.slice(0, lastDot)}${ext}`
2772
3486
  };
2773
3487
  }
@@ -2903,9 +3617,16 @@ var toPlainText = (markdown) => {
2903
3617
  };
2904
3618
  var buildCrumbIndex = (sidebar) => {
2905
3619
  const index = new Map;
3620
+ const groupRoutes = new Map;
2906
3621
  const walk = (nodes, trail) => {
2907
3622
  for (const node of nodes) {
2908
3623
  if (node.kind === "group") {
3624
+ if (node.route && !groupRoutes.has(node.route)) {
3625
+ groupRoutes.set(node.route, {
3626
+ breadcrumb: [...trail, node.label],
3627
+ section: node.label
3628
+ });
3629
+ }
2909
3630
  walk(node.children, [...trail, node.label]);
2910
3631
  } else if (node.route) {
2911
3632
  index.set(node.route, {
@@ -2916,6 +3637,11 @@ var buildCrumbIndex = (sidebar) => {
2916
3637
  }
2917
3638
  };
2918
3639
  walk(sidebar, []);
3640
+ for (const [route, crumbs] of groupRoutes) {
3641
+ if (!index.has(route)) {
3642
+ index.set(route, crumbs);
3643
+ }
3644
+ }
2919
3645
  return index;
2920
3646
  };
2921
3647
  var buildSearchDocuments = async (project, options) => {
@@ -2939,7 +3665,8 @@ var buildSearchDocuments = async (project, options) => {
2939
3665
  const page = pageById.get(route.id);
2940
3666
  const raw = page ? await readEntryText(project, page) : "";
2941
3667
  const source = raw ? frontmatter_default(raw).content : "";
2942
- const body = options?.content === "markdown" ? source.trim() : toPlainText(source);
3668
+ const visible = applyAudienceVisibility(source, options?.audience ?? "web");
3669
+ const body = options?.content === "markdown" ? visible.trim() : toPlainText(visible);
2943
3670
  const tags = page?.meta?.search?.tags;
2944
3671
  const crumb = crumbs.get(route.path);
2945
3672
  return {
@@ -3235,10 +3962,11 @@ var updateDevLockPort = (outDir, port) => {
3235
3962
  }
3236
3963
  };
3237
3964
  var describeDevLock = (lock) => lock.port === undefined ? "" : ` at http://localhost:${lock.port}`;
3238
- var refuseIfDevRunning = (root, action, runtimeDir) => {
3239
- const lock = readDevLock(resolveRuntimeDir(root, runtimeDir));
3965
+ var refuseIfDevRunning = (root, action, options = {}) => {
3966
+ const lock = readDevLock(resolveRuntimeDir(root, options.runtimeDir));
3240
3967
  if (lock) {
3241
- logger.error(`A \`blume dev\` server is running${describeDevLock(lock)}; ${action} would corrupt its .blume runtime. Reuse that server, stop it first, or re-run with --isolated to build/verify against .blume-verify without touching it.`);
3968
+ const remedies = options.isolatedHint ? "Reuse that server, stop it first, or re-run with --isolated to build/verify against .blume-verify without touching it." : "Reuse that server or stop it first.";
3969
+ logger.error(`A \`blume dev\` server is running${describeDevLock(lock)}; ${action} would corrupt its .blume runtime. ${remedies}`);
3242
3970
  process.exit(1);
3243
3971
  }
3244
3972
  };
@@ -3262,6 +3990,7 @@ import { glob as glob5 } from "tinyglobby";
3262
3990
  // src/ai/ask-data.ts
3263
3991
  var buildAskData = async (project) => {
3264
3992
  const documents = await buildSearchDocuments(project, {
3993
+ audience: "agents",
3265
3994
  content: "markdown",
3266
3995
  includeWhenDisabled: true
3267
3996
  });
@@ -3332,6 +4061,7 @@ var askBackendRuntimeDep = (ask) => {
3332
4061
 
3333
4062
  // src/ai/markdown.ts
3334
4063
  import { readFile as readFile4 } from "node:fs/promises";
4064
+ var agentMarkdown = (entry) => entry.md ?? entry.mdx;
3335
4065
  var buildRawMarkdown = async (project) => {
3336
4066
  const pageById = new Map(project.graph.pages.map((page) => [page.id, page]));
3337
4067
  const readRoute = async (route) => {
@@ -3341,17 +4071,29 @@ var buildRawMarkdown = async (project) => {
3341
4071
  }
3342
4072
  return route.sourcePath ? await readFile4(route.sourcePath, "utf-8") : "";
3343
4073
  };
3344
- const entries = await Promise.all(project.manifest.routes.map(async (route) => [route.path, await readRoute(route)]));
4074
+ const entries = await Promise.all(project.manifest.routes.map(async (route) => {
4075
+ const source = applyAgentVisibility(await readRoute(route));
4076
+ const md = downlevelComponents(source, project.config.ai.markdownComponents);
4077
+ const entry = md === source ? { mdx: source } : { md, mdx: source };
4078
+ return [route.path, entry];
4079
+ }));
3345
4080
  return Object.fromEntries(entries);
3346
4081
  };
3347
4082
 
3348
4083
  // src/ai/mcp/data.ts
3349
4084
  var buildMcpData = async (project) => {
3350
4085
  const { config, graph, manifest } = project;
3351
- const [documents, pages] = await Promise.all([
3352
- buildSearchDocuments(project, { includeWhenDisabled: true }),
4086
+ const [documents, rawMarkdown] = await Promise.all([
4087
+ buildSearchDocuments(project, {
4088
+ audience: "agents",
4089
+ includeWhenDisabled: true
4090
+ }),
3353
4091
  buildRawMarkdown(project)
3354
4092
  ]);
4093
+ const pages = Object.fromEntries(Object.entries(rawMarkdown).map(([route, entry]) => [
4094
+ route,
4095
+ agentMarkdown(entry)
4096
+ ]));
3355
4097
  const descriptionById = new Map(graph.pages.map((page) => [page.id, page.description]));
3356
4098
  const routes = [];
3357
4099
  for (const route of manifest.routes) {
@@ -3368,6 +4110,7 @@ var buildMcpData = async (project) => {
3368
4110
  });
3369
4111
  }
3370
4112
  return {
4113
+ base: normalizeBasePath(config.deployment.base),
3371
4114
  documents: documents.map((doc) => ({
3372
4115
  content: doc.content,
3373
4116
  description: doc.description,
@@ -3395,7 +4138,7 @@ var MCP_TOOLS = [
3395
4138
  },
3396
4139
  {
3397
4140
  annotations: READ_ONLY,
3398
- description: "Fetch a single documentation page as its original Markdown source (frontmatter included). Pass a route from `search_docs` or `list_pages`, e.g. `/guides/install`.",
4141
+ description: "Fetch a single documentation page as agent-optimized Markdown (frontmatter included, components downleveled to plain Markdown). Pass a route from `search_docs` or `list_pages`, e.g. `/guides/install`.",
3399
4142
  name: "get_page",
3400
4143
  title: "Get page Markdown"
3401
4144
  },
@@ -3414,7 +4157,10 @@ var MCP_TOOLS = [
3414
4157
  ];
3415
4158
 
3416
4159
  // src/ai/mcp/discovery.ts
3417
- var serverUrl = (input) => input.site ? `${input.site.replace(/\/+$/u, "")}${input.route}` : input.route;
4160
+ var serverUrl = (input) => {
4161
+ const path = withBasePath(input.base, input.route);
4162
+ return input.site ? `${input.site.replace(/\/+$/u, "")}${path}` : path;
4163
+ };
3418
4164
  var buildMcpDiscovery = (input) => ({
3419
4165
  servers: [
3420
4166
  {
@@ -3504,7 +4250,7 @@ var validateUsedComponents = (pages, extraTags, registryNames) => {
3504
4250
 
3505
4251
  // src/core/component-overrides.ts
3506
4252
  import { existsSync as existsSync7 } from "node:fs";
3507
- import { dirname as dirname5, extname, isAbsolute as isAbsolute2, resolve as resolve3 } from "pathe";
4253
+ import { dirname as dirname5, extname as extname2, isAbsolute as isAbsolute2, resolve as resolve3 } from "pathe";
3508
4254
  import ts from "typescript";
3509
4255
  var GROUPS = ["mdx", "layout", "islands"];
3510
4256
  var GROUP_SET = new Set(GROUPS);
@@ -3603,17 +4349,17 @@ var probeExtension = (base) => {
3603
4349
  return null;
3604
4350
  };
3605
4351
  var toImport = (specifier, imported, dir) => {
3606
- const relative4 = specifier.startsWith(".") || isAbsolute2(specifier);
4352
+ const relative5 = specifier.startsWith(".") || isAbsolute2(specifier);
3607
4353
  let path = specifier;
3608
- let extension = extname(specifier).slice(1).toLowerCase();
3609
- if (relative4) {
4354
+ let extension = extname2(specifier).slice(1).toLowerCase();
4355
+ if (relative5) {
3610
4356
  const absolute = isAbsolute2(specifier) ? specifier : resolve3(dir, specifier);
3611
4357
  if (extension) {
3612
4358
  path = absolute;
3613
4359
  } else {
3614
4360
  const probed = probeExtension(absolute);
3615
4361
  path = probed ?? absolute;
3616
- extension = probed ? extname(probed).slice(1).toLowerCase() : "";
4362
+ extension = probed ? extname2(probed).slice(1).toLowerCase() : "";
3617
4363
  }
3618
4364
  }
3619
4365
  return {
@@ -3779,14 +4525,20 @@ var uiStringsObject = z.object({
3779
4525
  connectMcp: z.string().default("Connect to MCP"),
3780
4526
  copied: z.string().default("Copied!"),
3781
4527
  copyClaudeCode: z.string().default("Copy Claude Code command"),
4528
+ copyCode: z.string().default("Copy code"),
3782
4529
  copyCodex: z.string().default("Copy Codex command"),
3783
4530
  copyMarkdown: z.string().default("Copy as Markdown"),
3784
4531
  copyServerUrl: z.string().default("Copy server URL"),
3785
4532
  edit: z.string().default("Edit on GitHub"),
4533
+ export: z.string().default("Export"),
4534
+ exportEpub: z.string().default("Export to EPUB"),
4535
+ exportPdf: z.string().default("Export to PDF"),
4536
+ generating: z.string().default("Generating…"),
3786
4537
  openInChat: z.string().default("Open in chat"),
3787
4538
  scrollToTop: z.string().default("Scroll to top")
3788
4539
  }).default({}),
3789
4540
  ask: z.object({
4541
+ ai: z.string().default("AI"),
3790
4542
  clear: z.string().default("Clear conversation"),
3791
4543
  close: z.string().default("Close"),
3792
4544
  copy: z.string().default("Copy conversation"),
@@ -3796,7 +4548,19 @@ var uiStringsObject = z.object({
3796
4548
  placeholder: z.string().default("Ask a question…"),
3797
4549
  send: z.string().default("Send"),
3798
4550
  tip: z.string().default("Tip: You can open and close chat with"),
3799
- title: z.string().default("Ask AI")
4551
+ title: z.string().default("Ask AI"),
4552
+ you: z.string().default("You")
4553
+ }).default({}),
4554
+ banner: z.object({
4555
+ dismiss: z.string().default("Dismiss announcement")
4556
+ }).default({}),
4557
+ changelog: z.object({
4558
+ description: z.string().default("Product updates, new features, and fixes from every release."),
4559
+ showReleases: z.string().default("Show {version} releases"),
4560
+ title: z.string().default("Changelog")
4561
+ }).default({}),
4562
+ content: z.object({
4563
+ diagramError: z.string().default("Could not render this diagram.")
3800
4564
  }).default({}),
3801
4565
  feedback: z.object({
3802
4566
  no: z.string().default("No"),
@@ -3808,6 +4572,19 @@ var uiStringsObject = z.object({
3808
4572
  label: z.string().default("Language"),
3809
4573
  untranslated: z.string().default("Not translated")
3810
4574
  }).default({}),
4575
+ nav: z.object({
4576
+ back: z.string().default("Back"),
4577
+ breadcrumb: z.string().default("Breadcrumb"),
4578
+ closeNavigation: z.string().default("Close navigation"),
4579
+ deprecated: z.string().default("deprecated"),
4580
+ featured: z.string().default("Featured"),
4581
+ githubRepository: z.string().default("GitHub repository"),
4582
+ navigation: z.string().default("Navigation"),
4583
+ primary: z.string().default("Primary"),
4584
+ sections: z.string().default("Sections"),
4585
+ toggleNavigation: z.string().default("Toggle navigation"),
4586
+ toggleTheme: z.string().default("Toggle color theme")
4587
+ }).default({}),
3811
4588
  notFound: z.object({
3812
4589
  description: z.string().default("We couldn't find the page you're looking for."),
3813
4590
  home: z.string().default("Back to home"),
@@ -3816,16 +4593,26 @@ var uiStringsObject = z.object({
3816
4593
  page: z.object({
3817
4594
  lastUpdated: z.string().default("Last updated on"),
3818
4595
  next: z.string().default("Next"),
4596
+ pagination: z.string().default("Pagination"),
3819
4597
  previous: z.string().default("Previous"),
3820
4598
  skipToContent: z.string().default("Skip to content")
3821
4599
  }).default({}),
3822
4600
  search: z.object({
4601
+ all: z.string().default("All"),
3823
4602
  allLanguages: z.string().default("All languages"),
4603
+ askAi: z.string().default("Ask AI"),
4604
+ askAiHint: z.string().default("Get an instant answer from AI"),
3824
4605
  button: z.string().default("Search"),
3825
4606
  devOnly: z.string().default("Search is available in the production build."),
4607
+ error: z.string().default("Something went wrong. Please try again."),
3826
4608
  label: z.string().default("Search docs"),
4609
+ navigate: z.string().default("navigate"),
3827
4610
  noResults: z.string().default("No results found."),
3828
- placeholder: z.string().default("Search documentation…")
4611
+ open: z.string().default("open"),
4612
+ placeholder: z.string().default("Search documentation…"),
4613
+ popular: z.string().default("Popular"),
4614
+ preview: z.string().default("preview"),
4615
+ results: z.string().default("Results")
3829
4616
  }).default({}),
3830
4617
  toc: z.object({
3831
4618
  title: z.string().default("On this page")
@@ -4060,7 +4847,7 @@ var sourcesOf = (block) => {
4060
4847
  }
4061
4848
  return sources;
4062
4849
  };
4063
- var referencesFor = (kind, block, defaultLabel, renderer, display) => {
4850
+ var referencesFor = (kind, block, defaultLabel, renderer, display, basePath) => {
4064
4851
  if (!block.enabled) {
4065
4852
  return [];
4066
4853
  }
@@ -4078,6 +4865,7 @@ var referencesFor = (kind, block, defaultLabel, renderer, display) => {
4078
4865
  route = normalizeRoute(`${base}/${suffix || index + 1}`);
4079
4866
  }
4080
4867
  return {
4868
+ basePath,
4081
4869
  display,
4082
4870
  kind,
4083
4871
  label,
@@ -4094,8 +4882,8 @@ var resolveReferences = (config) => [
4094
4882
  ...referencesFor("openapi", config.openapi, "API Reference", config.openapi.renderer, {
4095
4883
  codeSamples: config.openapi.codeSamples,
4096
4884
  expandSchemas: config.openapi.expandSchemas
4097
- }),
4098
- ...referencesFor("asyncapi", config.asyncapi, "Events", "scalar", NO_DISPLAY)
4885
+ }, config.basePath),
4886
+ ...referencesFor("asyncapi", config.asyncapi, "Events", "scalar", NO_DISPLAY, config.basePath)
4099
4887
  ];
4100
4888
  var referenceTabs = (config) => resolveReferences(config).map((ref) => ({
4101
4889
  label: ref.label,
@@ -4105,10 +4893,11 @@ var blumeReferenceOf = (ref, seen, usedSlugs) => {
4105
4893
  if (ref.kind !== "openapi" || ref.renderer !== "blume") {
4106
4894
  return null;
4107
4895
  }
4108
- if (seen.has(ref.route)) {
4896
+ const kept = seen.get(ref.route);
4897
+ if (kept) {
4898
+ (kept.collisions ??= []).push(`Two API reference sources resolve to ${ref.route}; keeping the first.`);
4109
4899
  return null;
4110
4900
  }
4111
- seen.add(ref.route);
4112
4901
  let { slug } = ref;
4113
4902
  let n = 2;
4114
4903
  while (usedSlugs.has(slug)) {
@@ -4116,10 +4905,12 @@ var blumeReferenceOf = (ref, seen, usedSlugs) => {
4116
4905
  n += 1;
4117
4906
  }
4118
4907
  usedSlugs.add(slug);
4119
- return slug === ref.slug ? ref : { ...ref, slug };
4908
+ const accepted = slug === ref.slug ? ref : { ...ref, slug };
4909
+ seen.set(ref.route, accepted);
4910
+ return accepted;
4120
4911
  };
4121
4912
  var blumeReferences = (config) => {
4122
- const seen = new Set;
4913
+ const seen = new Map;
4123
4914
  const usedSlugs = new Set;
4124
4915
  const result = [];
4125
4916
  for (const ref of resolveReferences(config)) {
@@ -4243,9 +5034,14 @@ var extractOperations = (document, baseRoute) => {
4243
5034
  const tagsSeen = new Set;
4244
5035
  const tagMeta = new Map((document.tags ?? []).map((tag) => [tag.name, tag.description ?? ""]));
4245
5036
  const seen = new Set;
5037
+ const warnings = [];
4246
5038
  for (const [path, rawItem] of Object.entries(document.paths ?? {})) {
4247
5039
  const item = rawItem;
4248
- if (!item || "$ref" in item) {
5040
+ if (!item) {
5041
+ continue;
5042
+ }
5043
+ if ("$ref" in item) {
5044
+ warnings.push(`Path "${path}" is a $ref to a shared path item; referenced path items are not resolved, so its operations are missing from the reference. Inline the path item under "paths" to render it.`);
4249
5045
  continue;
4250
5046
  }
4251
5047
  for (const method of HTTP_METHODS) {
@@ -4283,7 +5079,7 @@ var extractOperations = (document, baseRoute) => {
4283
5079
  name,
4284
5080
  slug: slugify2(name) || "operations"
4285
5081
  }));
4286
- return { operations, tags };
5082
+ return { operations, tags, warnings };
4287
5083
  };
4288
5084
 
4289
5085
  // src/openapi/parse.ts
@@ -4307,6 +5103,13 @@ var PROXY_ENV_VARS = [
4307
5103
  "ALL_PROXY",
4308
5104
  "all_proxy"
4309
5105
  ];
5106
+
5107
+ class InvalidSpecError extends Error {
5108
+ constructor(message) {
5109
+ super(message);
5110
+ this.name = "InvalidSpecError";
5111
+ }
5112
+ }
4310
5113
  var proxyInstalled = false;
4311
5114
  var ensureProxyDispatcher = async () => {
4312
5115
  if (proxyInstalled || !PROXY_ENV_VARS.some((name) => process.env[name])) {
@@ -4417,6 +5220,9 @@ var parseSpec = async (spec, root, options = {}) => {
4417
5220
  const { text, warnings } = await readSpecText(spec, root, options);
4418
5221
  const normalized = normalize2(text);
4419
5222
  const { specification } = upgrade(normalized);
5223
+ if (specification === null || typeof specification !== "object") {
5224
+ throw new InvalidSpecError(`${spec} is not a valid OpenAPI document (expected a YAML or JSON object).`);
5225
+ }
4420
5226
  return { document: specification, warnings };
4421
5227
  };
4422
5228
 
@@ -4442,6 +5248,31 @@ var mdxSafe = (text) => {
4442
5248
  }
4443
5249
  return out + escapeProse(text.slice(cursor));
4444
5250
  };
5251
+ var META_DESCRIPTION_MAX = 160;
5252
+ var PARAGRAPH_BREAK = /\n\s*\n/u;
5253
+ var MARKDOWN_LINK = /\[(?<text>[^\]]*)\]\([^)]*\)/gu;
5254
+ var MARKDOWN_MARKS = /[*_`#>]/gu;
5255
+ var WHITESPACE2 = /\s+/gu;
5256
+ var TRAILING_WORD = /\s+\S*$/u;
5257
+ var plainProse = (markdown) => (markdown.trim().split(PARAGRAPH_BREAK).at(0) ?? "").replace(MARKDOWN_LINK, "$<text>").replace(MARKDOWN_MARKS, "").replace(WHITESPACE2, " ").trim();
5258
+ var clip = (text, max) => {
5259
+ if (max <= 0) {
5260
+ return "";
5261
+ }
5262
+ if (text.length <= max) {
5263
+ return text;
5264
+ }
5265
+ const head = text.slice(0, max - 1);
5266
+ const onWordBoundary = head.replace(TRAILING_WORD, "");
5267
+ return `${onWordBoundary.length >= max / 2 ? onWordBoundary : head}…`;
5268
+ };
5269
+ var apiName = (spec) => spec.title || spec.label;
5270
+ var operationDescription = (spec, operation) => {
5271
+ const endpoint = `${operation.method.toUpperCase()} ${operation.path}`;
5272
+ const suffix = `Reference for the ${endpoint} endpoint in the ${apiName(spec)} API.`;
5273
+ const prose = clip(plainProse(operation.description || operation.summary), META_DESCRIPTION_MAX - suffix.length - 1);
5274
+ return clip([prose, suffix].filter(Boolean).join(" "), META_DESCRIPTION_MAX);
5275
+ };
4445
5276
  var withDescription = (description, component) => description.trim() ? `${mdxSafe(description.trim())}
4446
5277
 
4447
5278
  ${component}` : component;
@@ -4454,6 +5285,7 @@ var operationMdx = (spec, operation) => {
4454
5285
  data: {
4455
5286
  ...operation.deprecated ? { deprecated: true } : {},
4456
5287
  search: { tags: [operation.tag, method] },
5288
+ seo: { description: operationDescription(spec, operation) },
4457
5289
  sidebar: { badge: method, label: operation.summary || operation.path },
4458
5290
  title,
4459
5291
  type: "openapi-operation"
@@ -4502,8 +5334,11 @@ var overviewMdx = (spec) => {
4502
5334
 
4503
5335
  `),
4504
5336
  data: {
5337
+ seo: {
5338
+ description: clip(plainProse(spec.description), META_DESCRIPTION_MAX) || `${apiName(spec)} API reference.`
5339
+ },
4505
5340
  sidebar: { label: "Overview" },
4506
- title: spec.title || spec.label
5341
+ title: apiName(spec)
4507
5342
  }
4508
5343
  };
4509
5344
  };
@@ -4533,7 +5368,11 @@ var openApiSource = (references, ctx) => {
4533
5368
  const loadReference = async (reference) => {
4534
5369
  try {
4535
5370
  const { document, warnings } = await parseSpec(reference.spec, ctx.projectRoot, { cacheDir: ctx.cacheDir, refresh: ctx.refresh });
4536
- const { operations, tags } = extractOperations(document, reference.route);
5371
+ const {
5372
+ operations,
5373
+ tags,
5374
+ warnings: extractWarnings
5375
+ } = extractOperations(document, reference.route);
4537
5376
  const info = document.info ?? { title: reference.label, version: "" };
4538
5377
  const spec = {
4539
5378
  codeSamples: reference.display.codeSamples,
@@ -4541,7 +5380,13 @@ var openApiSource = (references, ctx) => {
4541
5380
  document,
4542
5381
  expandSchemas: reference.display.expandSchemas,
4543
5382
  label: reference.label,
4544
- operations: Object.fromEntries(operations.map((operation) => [operation.key, operation])),
5383
+ operations: Object.fromEntries(operations.map((operation) => [
5384
+ operation.key,
5385
+ {
5386
+ ...operation,
5387
+ route: withBasePath(reference.basePath, operation.route)
5388
+ }
5389
+ ])),
4545
5390
  route: reference.route,
4546
5391
  slug: reference.slug,
4547
5392
  tags,
@@ -4549,11 +5394,26 @@ var openApiSource = (references, ctx) => {
4549
5394
  version: info.version ?? ""
4550
5395
  };
4551
5396
  return {
4552
- diagnostics: warnings.map((message) => ({
4553
- code: "BLUME_OPENAPI_STALE",
4554
- message,
4555
- severity: "warning"
4556
- })),
5397
+ diagnostics: [
5398
+ ...warnings.map((message) => ({
5399
+ code: "BLUME_OPENAPI_STALE",
5400
+ message,
5401
+ severity: "warning"
5402
+ })),
5403
+ ...extractWarnings.map((message) => ({
5404
+ code: "BLUME_OPENAPI_REF_PATH_ITEM",
5405
+ message: `In OpenAPI spec "${reference.spec}": ${message}`,
5406
+ severity: "warning"
5407
+ })),
5408
+ ...operations.length === 0 ? [
5409
+ {
5410
+ code: "BLUME_OPENAPI_EMPTY",
5411
+ message: `OpenAPI spec "${reference.spec}" for ${reference.route} declares no operations; its API reference is empty.`,
5412
+ severity: "warning",
5413
+ suggestion: "Check the spec points at an OpenAPI document with operations under `paths`."
5414
+ }
5415
+ ] : []
5416
+ ],
4557
5417
  entries: specEntries(spec, operations),
4558
5418
  slug: reference.slug,
4559
5419
  spec
@@ -4563,14 +5423,18 @@ var openApiSource = (references, ctx) => {
4563
5423
  code: "BLUME_OPENAPI_UNAVAILABLE",
4564
5424
  message: `Could not load OpenAPI spec "${reference.spec}" for ${reference.route} (${error.message}); its reference pages were skipped.`,
4565
5425
  severity: ctx.mode === "build" ? "error" : "warning",
4566
- suggestion: "Check the spec URL/path is reachable from the build environment; behind a proxy, set HTTP(S)_PROXY."
5426
+ suggestion: error instanceof InvalidSpecError ? "Point the spec at an OpenAPI document (a YAML or JSON file with an object at the top level)." : "Check the spec URL/path is reachable from the build environment; behind a proxy, set HTTP(S)_PROXY."
4567
5427
  };
4568
5428
  }
4569
5429
  };
4570
5430
  const load2 = async () => {
4571
5431
  const results = await Promise.all(references.map(loadReference));
4572
5432
  const entries = [];
4573
- const diagnostics = [];
5433
+ const diagnostics = references.flatMap((reference) => (reference.collisions ?? []).map((message) => ({
5434
+ code: "BLUME_OPENAPI_ROUTE_COLLISION",
5435
+ message,
5436
+ severity: "warning"
5437
+ })));
4574
5438
  const data = {};
4575
5439
  for (const result of results) {
4576
5440
  if ("severity" in result) {
@@ -4596,8 +5460,8 @@ var openApiSource = (references, ctx) => {
4596
5460
  // src/core/sources/filesystem.ts
4597
5461
  import { existsSync as existsSync8, watch as fsWatch } from "node:fs";
4598
5462
  import { readFile as readFile7 } from "node:fs/promises";
4599
- import { extname as extname2, isAbsolute as isAbsolute4, join as join12, relative as relative4, resolve as resolve4 } from "pathe";
4600
- import { glob } from "tinyglobby";
5463
+ import { extname as extname3, isAbsolute as isAbsolute4, join as join12, relative as relative5, resolve as resolve4 } from "pathe";
5464
+ import { glob as glob2 } from "tinyglobby";
4601
5465
 
4602
5466
  // src/core/sources/watch.ts
4603
5467
  var BLUME_IGNORE_DIRS = [
@@ -4627,7 +5491,7 @@ var ignoringWatchListener = (onChange, ignoreDirs = BLUME_WATCH_IGNORE_DIRS) =>
4627
5491
  var filesystemSource = (options) => {
4628
5492
  const contentRoot = isAbsolute4(options.root) ? options.root : join12(resolve4(options.projectRoot), options.root);
4629
5493
  const load2 = async () => {
4630
- const files = await glob(options.include, {
5494
+ const files = await glob2(options.include, {
4631
5495
  absolute: true,
4632
5496
  cwd: contentRoot,
4633
5497
  ignore: [...options.exclude, ...baselineScanIgnore()],
@@ -4636,13 +5500,14 @@ var filesystemSource = (options) => {
4636
5500
  files.sort();
4637
5501
  const entries = await Promise.all(files.map(async (file) => {
4638
5502
  const source = await readFile7(file, "utf-8");
4639
- const ext = extname2(file).toLowerCase();
5503
+ const ext = extname3(file).toLowerCase();
4640
5504
  const format = ext === ".mdx" ? "mdx" : "md";
4641
5505
  const parsed = frontmatter_default(source);
4642
5506
  return {
4643
5507
  body: { format, text: parsed.content },
4644
5508
  data: parsed.data,
4645
- ref: relative4(contentRoot, file),
5509
+ raw: source,
5510
+ ref: relative5(contentRoot, file),
4646
5511
  sourcePath: file
4647
5512
  };
4648
5513
  }));
@@ -4878,14 +5743,9 @@ var mdxRemoteSource = (options, ctx) => {
4878
5743
  const doFetch = options.fetchImpl ?? globalThis.fetch;
4879
5744
  const cache = snapshotCache(ctx.cacheDir);
4880
5745
  let snapshot = new Map;
4881
- const enumerate = async () => {
4882
- if (options.github) {
4883
- return await enumerateGithub(options.github, options.include, doFetch);
4884
- }
4885
- if (options.files && options.url) {
4886
- const base = options.url.replace(/\/$/u, "");
4887
- const refs = options.files.flatMap((ref) => matchesInclude(ref, options.include) ? [{ editUrl: `${base}/${ref}`, fetchUrl: `${base}/${ref}`, ref }] : []);
4888
- return { refs, truncated: false };
5746
+ const assertConfigured = () => {
5747
+ if (options.github || options.files && options.url) {
5748
+ return;
4889
5749
  }
4890
5750
  throw new BlumeError({
4891
5751
  code: "BLUME_SOURCE_MISCONFIGURED",
@@ -4893,6 +5753,14 @@ var mdxRemoteSource = (options, ctx) => {
4893
5753
  severity: "error"
4894
5754
  });
4895
5755
  };
5756
+ const enumerate = async () => {
5757
+ if (options.github) {
5758
+ return await enumerateGithub(options.github, options.include, doFetch);
5759
+ }
5760
+ const base = (options.url ?? "").replace(/\/$/u, "");
5761
+ const refs = (options.files ?? []).flatMap((ref) => matchesInclude(ref, options.include) ? [{ editUrl: `${base}/${ref}`, fetchUrl: `${base}/${ref}`, ref }] : []);
5762
+ return { refs, truncated: false };
5763
+ };
4896
5764
  const fetchEntry = async (item) => {
4897
5765
  const res = await doFetch(item.fetchUrl, {
4898
5766
  headers: githubHeaders2(item.fetchUrl)
@@ -4913,6 +5781,7 @@ var mdxRemoteSource = (options, ctx) => {
4913
5781
  };
4914
5782
  };
4915
5783
  const load2 = async (refresh = ctx.refresh ?? true) => {
5784
+ assertConfigured();
4916
5785
  const skipped = [];
4917
5786
  const result = await loadWithCache(options.name, cache, async () => {
4918
5787
  const { refs, truncated } = await enumerate();
@@ -4973,22 +5842,22 @@ import { join as join14 } from "pathe";
4973
5842
 
4974
5843
  // src/core/sources/assets.ts
4975
5844
  import { mkdir as mkdir5, writeFile as writeFile5 } from "node:fs/promises";
4976
- import { extname as extname3, join as join13 } from "pathe";
5845
+ import { extname as extname4, join as join13 } from "pathe";
4977
5846
  var MD_IMAGE = /!\[(?<alt>[^\]]*)\]\((?<url>[^)\s]+)\)/gu;
4978
5847
  var REMOTE = /^https?:\/\//u;
4979
5848
  var SAFE_EXT = /^\.[a-z0-9]+$/iu;
4980
- var CODE_FENCE_BLOCK = /^(?<fence>`{3,}|~{3,})[^\n]*\n[\s\S]*?^\k<fence>[^\n]*(?=\n|$)/gmu;
4981
- var FENCE_TOKEN = /\u0000blume-fence-(?<index>\d+)\u0000/gu;
5849
+ var CODE_FENCE_BLOCK2 = /^(?<fence>`{3,}|~{3,})[^\n]*\n[\s\S]*?^\k<fence>[^\n]*(?=\n|$)/gmu;
5850
+ var FENCE_TOKEN2 = /\u0000blume-fence-(?<index>\d+)\u0000/gu;
4982
5851
  var extFor = (url) => {
4983
5852
  const clean = url.split("?")[0] ?? url;
4984
- const ext = extname3(clean);
5853
+ const ext = extname4(clean);
4985
5854
  return SAFE_EXT.test(ext) ? ext.toLowerCase() : ".png";
4986
5855
  };
4987
5856
  var materializeAssets = async (markdown, ctx) => {
4988
5857
  const doFetch = ctx.fetchImpl ?? globalThis.fetch;
4989
5858
  const diagnostics = [];
4990
5859
  const fences = [];
4991
- const masked = markdown.replace(CODE_FENCE_BLOCK, (block) => {
5860
+ const masked = markdown.replace(CODE_FENCE_BLOCK2, (block) => {
4992
5861
  fences.push(block);
4993
5862
  return `\x00blume-fence-${fences.length - 1}\x00`;
4994
5863
  });
@@ -5007,7 +5876,7 @@ var materializeAssets = async (markdown, ctx) => {
5007
5876
  throw new Error(`${res.status}`);
5008
5877
  }
5009
5878
  const bytes = new Uint8Array(await res.arrayBuffer());
5010
- const file = `${hashText(url)}${extFor(url)}`;
5879
+ const file = `${hashText(url.split("?")[0] ?? url)}${extFor(url)}`;
5011
5880
  await mkdir5(ctx.assetsDir, { recursive: true });
5012
5881
  await writeFile5(join13(ctx.assetsDir, file), bytes);
5013
5882
  rewrites.set(url, `${ctx.assetsBaseUrl}/${file}`);
@@ -5022,18 +5891,27 @@ var materializeAssets = async (markdown, ctx) => {
5022
5891
  const rewritten = masked.replaceAll(MD_IMAGE, (match, alt, url) => {
5023
5892
  const local = rewrites.get(url);
5024
5893
  return local ? `![${alt}](${local})` : match;
5025
- }).replaceAll(FENCE_TOKEN, (token, index) => fences[Number(index)] ?? token);
5894
+ }).replaceAll(FENCE_TOKEN2, (token, index) => fences[Number(index)] ?? token);
5026
5895
  return { diagnostics, markdown: rewritten };
5027
5896
  };
5028
5897
 
5029
5898
  // src/core/sources/normalize.ts
5030
5899
  import { existsSync as existsSync9, readFileSync as readFileSync3 } from "node:fs";
5031
5900
  import GithubSlugger from "github-slugger";
5032
- import { extname as extname4 } from "pathe";
5901
+ import { extname as extname5 } from "pathe";
5033
5902
 
5034
5903
  // src/core/schema.ts
5035
5904
  import { z as z2 } from "zod";
5036
5905
 
5906
+ // src/seo/x-handle.ts
5907
+ var normalizeXHandle = (value) => {
5908
+ if (typeof value !== "string") {
5909
+ return;
5910
+ }
5911
+ const handle = value.trim().replace(/^@+/u, "");
5912
+ return handle ? `@${handle}` : undefined;
5913
+ };
5914
+
5037
5915
  // src/theme/fonts.ts
5038
5916
  var FALLBACKS = {
5039
5917
  mono: ["ui-monospace", "SF Mono", "Menlo", "monospace"],
@@ -5191,12 +6069,14 @@ var sidebarMetaSchema = z2.strictObject({
5191
6069
  label: z2.string().optional(),
5192
6070
  order: z2.number().optional()
5193
6071
  });
6072
+ var xHandleSchema = z2.string().transform(normalizeXHandle).optional();
5194
6073
  var seoMetaSchema = z2.strictObject({
5195
6074
  canonical: z2.string().url().optional(),
5196
6075
  description: z2.string().optional(),
5197
6076
  image: z2.string().optional(),
5198
6077
  noindex: z2.boolean().default(false),
5199
- title: z2.string().optional()
6078
+ title: z2.string().optional(),
6079
+ x: z2.strictObject({ creator: xHandleSchema }).optional()
5200
6080
  });
5201
6081
  var searchMetaSchema = z2.strictObject({
5202
6082
  boost: z2.number().optional(),
@@ -5503,7 +6383,16 @@ var aiConfigSchema = z2.strictObject({
5503
6383
  });
5504
6384
  }
5505
6385
  }).optional(),
5506
- llmsTxt: z2.boolean().default(true)
6386
+ llmsTxt: z2.union([
6387
+ z2.boolean(),
6388
+ z2.strictObject({
6389
+ enabled: z2.boolean().default(true),
6390
+ openapi: z2.boolean().default(true)
6391
+ })
6392
+ ]).default(true).transform((value) => typeof value === "boolean" ? { enabled: value, openapi: true } : value),
6393
+ markdownComponents: z2.record(z2.string(), z2.custom((value) => typeof value === "function", {
6394
+ message: "Expected a serializer function."
6395
+ })).default({})
5507
6396
  });
5508
6397
  var featuredLinkSchema = z2.strictObject({
5509
6398
  href: z2.string(),
@@ -5534,7 +6423,7 @@ var mcpConfigSchema = z2.strictObject({
5534
6423
  enabled: z2.boolean().default(false),
5535
6424
  instructions: z2.string().optional(),
5536
6425
  name: z2.string().optional(),
5537
- route: z2.string().default("/mcp")
6426
+ route: z2.string().default("/mcp").transform(normalizeRoute)
5538
6427
  });
5539
6428
  var localeSchema = z2.strictObject({
5540
6429
  code: z2.string().min(1),
@@ -5592,6 +6481,10 @@ var redirectSchema = z2.strictObject({
5592
6481
  status: z2.union([z2.literal(301), z2.literal(302), z2.literal(307), z2.literal(308)]).default(301),
5593
6482
  to: z2.string()
5594
6483
  });
6484
+ var xConfigSchema = z2.strictObject({
6485
+ creator: xHandleSchema,
6486
+ handle: xHandleSchema
6487
+ });
5595
6488
  var ogConfigSchema = z2.strictObject({
5596
6489
  enabled: z2.boolean().optional()
5597
6490
  });
@@ -5621,7 +6514,8 @@ var seoConfigSchema = z2.strictObject({
5621
6514
  robots: z2.boolean().default(true),
5622
6515
  rss: rssConfigSchema.default({}),
5623
6516
  sitemap: z2.boolean().default(true),
5624
- structuredData: z2.boolean().default(true)
6517
+ structuredData: z2.boolean().default(true),
6518
+ x: xConfigSchema.default({})
5625
6519
  });
5626
6520
  var githubConfigSchema = z2.strictObject({
5627
6521
  branch: z2.string().default("main"),
@@ -5697,6 +6591,8 @@ var tocConfigSchema = z2.union([
5697
6591
  maxLevel: value.maxHeadingLevel ?? 3,
5698
6592
  minLevel: value.minHeadingLevel ?? 2
5699
6593
  };
6594
+ }).refine((value) => value.minLevel <= value.maxLevel, {
6595
+ message: "toc.minHeadingLevel must be less than or equal to toc.maxHeadingLevel."
5700
6596
  });
5701
6597
  var blumeConfigSchema = z2.strictObject({
5702
6598
  ai: aiConfigSchema.default({}),
@@ -5751,7 +6647,7 @@ var addRouteSegment = (part, segments, groups) => {
5751
6647
  segments.push(clean);
5752
6648
  };
5753
6649
  var mapRoute = (relativePath) => {
5754
- const withoutExt = relativePath.slice(0, relativePath.length - extname4(relativePath).length);
6650
+ const withoutExt = relativePath.slice(0, relativePath.length - extname5(relativePath).length);
5755
6651
  const rawParts = withoutExt.split("/");
5756
6652
  const segments = [];
5757
6653
  const groups = [];
@@ -5761,41 +6657,78 @@ var mapRoute = (relativePath) => {
5761
6657
  const route = segments.length === 0 ? "/" : `/${segments.join("/")}`;
5762
6658
  return { groups, route, segments };
5763
6659
  };
5764
- var CODE_FENCE2 = /^```/u;
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;
6660
+ var CODE_FENCE2 = /^(?<delimiter>```|~~~)/u;
6661
+ var nextFenceState = (line, fence) => {
6662
+ const delimiter = line.trimStart().match(CODE_FENCE2)?.groups?.delimiter;
6663
+ if (delimiter === undefined) {
6664
+ return fence;
6665
+ }
6666
+ if (fence === null) {
6667
+ return delimiter;
6668
+ }
6669
+ return fence === delimiter ? null : fence;
6670
+ };
6671
+ var ATX_HEADING = /^ {0,3}(?<hashes>#{1,6})\s+(?<text>.+?)(?:\s+#+)?\s*$/u;
6672
+ var SETEXT_UNDERLINE = /^ {0,3}(?<marker>=+|-+)\s*$/u;
6673
+ var PARAGRAPH_INTERRUPT = /^ {0,3}(?:[-+*][ \t]|\d{1,9}[.)][ \t]|>)/u;
6674
+ var THEMATIC_BREAK = /^ {0,3}(?:(?:-[ \t]*){3,}|(?:\*[ \t]*){3,}|(?:_[ \t]*){3,})$/u;
6675
+ var FRONT_MATTER_CLOSE = /^(?:-{3}|\.{3})\s*$/u;
6676
+ var linesWithoutFrontMatter = (body) => {
6677
+ const lines = body.split(`
6678
+ `);
6679
+ if (!/^-{3}\s*$/u.test(lines[0] ?? "")) {
6680
+ return lines;
5769
6681
  }
5770
- if (inFence) {
5771
- return inFence;
6682
+ const close = lines.findIndex((line, index) => index > 0 && FRONT_MATTER_CLOSE.test(line));
6683
+ return close === -1 ? lines : lines.slice(close + 1);
6684
+ };
6685
+ var scanHeadingLine = (line, state, slugger, headings) => {
6686
+ const next = nextFenceState(line, state.fence);
6687
+ if (state.fence !== null || next !== null) {
6688
+ state.fence = next;
6689
+ state.paragraph = [];
6690
+ return;
5772
6691
  }
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();
6692
+ const atx = line.match(ATX_HEADING);
6693
+ if (atx?.groups) {
6694
+ const depth = atx.groups.hashes?.length ?? 1;
6695
+ const text = (atx.groups.text ?? "").trim();
5777
6696
  headings.push({ depth, slug: slugger.slug(text), text });
6697
+ state.paragraph = [];
6698
+ return;
5778
6699
  }
5779
- return inFence;
6700
+ const setext = line.match(SETEXT_UNDERLINE);
6701
+ if (setext?.groups && state.paragraph.length > 0) {
6702
+ const text = state.paragraph.join(" ").trim();
6703
+ headings.push({
6704
+ depth: setext.groups.marker?.startsWith("=") ? 1 : 2,
6705
+ slug: slugger.slug(text),
6706
+ text
6707
+ });
6708
+ state.paragraph = [];
6709
+ return;
6710
+ }
6711
+ if (line.trim() === "" || THEMATIC_BREAK.test(line) || PARAGRAPH_INTERRUPT.test(line)) {
6712
+ state.paragraph = [];
6713
+ return;
6714
+ }
6715
+ state.paragraph.push(line.trim());
5780
6716
  };
5781
6717
  var extractHeadings = (body) => {
5782
6718
  const headings = [];
5783
6719
  const slugger = new GithubSlugger;
5784
- let inFence = false;
5785
- for (const line of body.split(`
5786
- `)) {
5787
- inFence = scanHeadingLine(line, inFence, slugger, headings);
6720
+ const state = { fence: null, paragraph: [] };
6721
+ for (const line of linesWithoutFrontMatter(body)) {
6722
+ scanHeadingLine(line, state, slugger, headings);
5788
6723
  }
5789
6724
  return headings;
5790
6725
  };
5791
6726
  var MD_LINK = /\[[^\]]*\]\((?<target>[^)\s]+)(?:\s+"[^"]*")?\)/gu;
5792
6727
  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;
6728
+ var scanLinkLine = (line, lineNumber, fence, links) => {
6729
+ const next = nextFenceState(line, fence);
6730
+ if (fence !== null || next !== null) {
6731
+ return next;
5799
6732
  }
5800
6733
  const masked = line.replaceAll(INLINE_CODE2, (span) => " ".repeat(span.length));
5801
6734
  for (const match of masked.matchAll(MD_LINK)) {
@@ -5810,27 +6743,25 @@ var scanLinkLine = (line, lineNumber, inFence, links) => {
5810
6743
  target
5811
6744
  });
5812
6745
  }
5813
- return inFence;
6746
+ return next;
5814
6747
  };
5815
- var extractLinks = (body) => {
6748
+ var extractLinks = (body, lineOffset = 0) => {
5816
6749
  const links = [];
5817
- let inFence = false;
5818
- let lineNumber = 0;
6750
+ let fence = null;
6751
+ let lineNumber = lineOffset;
5819
6752
  for (const line of body.split(`
5820
6753
  `)) {
5821
6754
  lineNumber += 1;
5822
- inFence = scanLinkLine(line, lineNumber, inFence, links);
6755
+ fence = scanLinkLine(line, lineNumber, fence, links);
5823
6756
  }
5824
6757
  return links;
5825
6758
  };
5826
6759
  var DOUBLE_QUOTED = /"[^"]*"/gu;
5827
6760
  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;
6761
+ var scanTagLine = (line, fence, tags) => {
6762
+ const next = nextFenceState(line, fence);
6763
+ if (fence !== null || next !== null) {
6764
+ return next;
5834
6765
  }
5835
6766
  const clean = line.replaceAll(INLINE_CODE2, "").replaceAll(DOUBLE_QUOTED, "");
5836
6767
  for (const match of clean.matchAll(JSX_OPEN)) {
@@ -5839,17 +6770,20 @@ var scanTagLine = (line, inFence, tags) => {
5839
6770
  tags.add(tag);
5840
6771
  }
5841
6772
  }
5842
- return inFence;
6773
+ return next;
5843
6774
  };
5844
6775
  var extractComponentTags = (body) => {
5845
6776
  const tags = new Set;
5846
- let inFence = false;
6777
+ let fence = null;
5847
6778
  for (const line of body.split(`
5848
6779
  `)) {
5849
- inFence = scanTagLine(line, inFence, tags);
6780
+ fence = scanTagLine(line, fence, tags);
5850
6781
  }
5851
6782
  return [...tags];
5852
6783
  };
6784
+ var strippedLineOffset = (raw, body) => raw ? Math.max(0, raw.split(`
6785
+ `).length - body.split(`
6786
+ `).length) : 0;
5853
6787
  var deriveTitle = (meta, headings, id2) => {
5854
6788
  if (meta.title) {
5855
6789
  return meta.title;
@@ -5859,7 +6793,7 @@ var deriveTitle = (meta, headings, id2) => {
5859
6793
  return firstHeading.text;
5860
6794
  }
5861
6795
  const base = id2.split("/").pop() ?? id2;
5862
- return titleCase(stripNumericPrefix(base.replace(extname4(base), "")));
6796
+ return titleCase(stripNumericPrefix(base.replace(extname5(base), "")));
5863
6797
  };
5864
6798
  var trimSlashes = (value) => value.replaceAll(/^\/+|\/+$/gu, "");
5865
6799
  var withPrefix = (prefix, path) => {
@@ -5910,7 +6844,7 @@ var normalizeEntry2 = (entry, ctx) => {
5910
6844
  headings,
5911
6845
  id: `${ctx.source.name}:${entry.ref}`,
5912
6846
  lastModified: meta.lastModified ?? entry.lastModified,
5913
- links: extractLinks(entry.body.text),
6847
+ links: extractLinks(entry.body.text, strippedLineOffset(entry.raw, entry.body.text)),
5914
6848
  meta,
5915
6849
  navPath,
5916
6850
  segments,
@@ -6129,12 +7063,9 @@ ${nested}`;
6129
7063
  return Object.values(page.properties).find((p) => p.type === "title");
6130
7064
  };
6131
7065
  const isDraft = (page) => {
6132
- if (!options.publishedValue) {
6133
- return false;
6134
- }
6135
7066
  const prop = page.properties[props.status ?? "Status"];
6136
7067
  const status = prop?.status?.name ?? prop?.select?.name;
6137
- return Boolean(status && status !== options.publishedValue);
7068
+ return Boolean(status && status !== (options.publishedValue ?? "Published"));
6138
7069
  };
6139
7070
  const orderOf = (page) => {
6140
7071
  const order = page.properties[props.order ?? "Order"]?.number;
@@ -6267,7 +7198,7 @@ var renderSpan = (span, defs) => {
6267
7198
  }
6268
7199
  return link?.href ? `[${text}](${link.href})` : text;
6269
7200
  };
6270
- var renderChildren = (block) => {
7201
+ var renderChildren2 = (block) => {
6271
7202
  const defs = new Map((block.markDefs ?? []).map((def) => [def._key, def]));
6272
7203
  return (block.children ?? []).map((span) => renderSpan(span, defs)).join("");
6273
7204
  };
@@ -6284,7 +7215,7 @@ var renderBlock = (block, options) => {
6284
7215
  if (block._type !== "block") {
6285
7216
  return `<!-- unsupported Portable Text block: ${block._type} -->`;
6286
7217
  }
6287
- const inline = renderChildren(block);
7218
+ const inline = renderChildren2(block);
6288
7219
  if (block.listItem) {
6289
7220
  const indent = " ".repeat(Math.max(0, (block.level ?? 1) - 1));
6290
7221
  const marker = block.listItem === "number" ? "1." : "-";
@@ -6708,7 +7639,7 @@ import { isAbsolute as isAbsolute8, join as join19 } from "pathe";
6708
7639
  // src/astro/templates.ts
6709
7640
  import { existsSync as existsSync11, readFileSync as readFileSync5 } from "node:fs";
6710
7641
  import { pathToFileURL as pathToFileURL2 } from "node:url";
6711
- import { dirname as dirname7, isAbsolute as isAbsolute7, join as join18, relative as relative5 } from "pathe";
7642
+ import { dirname as dirname7, isAbsolute as isAbsolute7, join as join18, relative as relative6 } from "pathe";
6712
7643
  var WORKSPACE_MARKERS = [
6713
7644
  ".git",
6714
7645
  "bun.lock",
@@ -6798,7 +7729,8 @@ var RENDER_EXTERNAL_DEPS = [
6798
7729
  var renderUserAliases = (aliases) => Object.entries(aliases ?? {}).toSorted(([a], [b]) => b.length - a.length).map(([find, replacement]) => `
6799
7730
  ${JSON.stringify(find)}: ${JSON.stringify(replacement)},`).join("");
6800
7731
  var astroOutDir = (context) => context.distDir ?? `${context.root}/dist`;
6801
- var reactIntegration = (compilerPath) => compilerPath ? `react({ babel: { plugins: [[${JSON.stringify(compilerPath)}, { target: "19" }]] } })` : "react()";
7732
+ var REACT_EXCLUDE = String.raw`exclude: [/\/node_modules\/\.vite\//]`;
7733
+ var reactIntegration = (compilerPath) => compilerPath ? `react({ babel: { plugins: [[${JSON.stringify(compilerPath)}, { target: "19" }]] }, ${REACT_EXCLUDE} })` : `react({ ${REACT_EXCLUDE} })`;
6802
7734
  var astroConfigTemplate = (options) => {
6803
7735
  const { context, config, needsReact, pages, dataPath, themePath } = options;
6804
7736
  const {
@@ -6852,11 +7784,12 @@ var astroConfigTemplate = (options) => {
6852
7784
  const twoslashImport = `import { transformerTwoslash } from "@shikijs/twoslash";
6853
7785
  `;
6854
7786
  const twoslashTransformer = "transformerTwoslash({ explicitTrigger: true }), ";
6855
- const contentLinkBase = normalizeBasePath(deployment.base) + config.basePath;
7787
+ const deployBase = normalizeBasePath(deployment.base);
6856
7788
  const integrations = [
6857
7789
  `mdx({ processor: blumeMdxProcessor(${JSON.stringify({
6858
- basePath: contentLinkBase,
7790
+ basePath: config.basePath,
6859
7791
  codeThemes: config.markdown.codeBlocks.theme,
7792
+ deployBase,
6860
7793
  headingAnchors: config.markdown.headingAnchors
6861
7794
  })}) })`
6862
7795
  ];
@@ -6885,8 +7818,9 @@ export default defineConfig({
6885
7818
  integrations: [${integrations.join(", ")}],
6886
7819
  markdown: {
6887
7820
  processor: blumeMarkdownProcessor(${JSON.stringify({
6888
- basePath: contentLinkBase,
7821
+ basePath: config.basePath,
6889
7822
  codeThemes: config.markdown.codeBlocks.theme,
7823
+ deployBase,
6890
7824
  headingAnchors: config.markdown.headingAnchors
6891
7825
  })}),
6892
7826
  shikiConfig: {
@@ -6953,7 +7887,7 @@ var contentConfigTemplate = (options) => {
6953
7887
  const collectionBase = options.collection?.base ?? context.contentRoot;
6954
7888
  const includeGlobs = options.collection?.include ?? config.content.include;
6955
7889
  const excludeGlobs = options.collection?.exclude ?? config.content.exclude;
6956
- const outDirRel = relative5(collectionBase, context.outDir);
7890
+ const outDirRel = relative6(collectionBase, context.outDir);
6957
7891
  const outDirIgnore = outDirRel && !outDirRel.startsWith("..") && !isAbsolute7(outDirRel) ? [`!${outDirRel}/**`] : [];
6958
7892
  const filesystem = options.filesystem ?? true;
6959
7893
  const docsPattern = filesystem ? [
@@ -7041,6 +7975,21 @@ const ground = createAskContext(askData);
7041
7975
  content: m.content,
7042
7976
  role: m.role,
7043
7977
  }));`;
7978
+ const keyCheck = backend.kind === "gateway" ? ` // The AI Gateway authenticates with an API key or Vercel's OIDC token.
7979
+ if (!(process.env.AI_GATEWAY_API_KEY || process.env.VERCEL_OIDC_TOKEN)) {
7980
+ return new Response(
7981
+ "Ask AI is not configured: set AI_GATEWAY_API_KEY (or deploy on Vercel with OIDC).",
7982
+ { status: 500 }
7983
+ );
7984
+ }` : ` if (!process.env[${JSON.stringify(backend.apiKeyEnv)}]) {
7985
+ return new Response(
7986
+ ${JSON.stringify(`Ask AI is not configured: set ${backend.apiKeyEnv}.`)},
7987
+ { status: 500 }
7988
+ );
7989
+ }`;
7990
+ const onError = ` onError({ error }) {
7991
+ console.error("Ask AI provider error:", error);
7992
+ },`;
7044
7993
  const stream = grounded ? ` const system =
7045
7994
  (await ground(messages, body.page)) ??
7046
7995
  "You are a helpful documentation assistant. Answer using the project's documentation.";
@@ -7048,14 +7997,17 @@ const ground = createAskContext(askData);
7048
7997
  model: ${modelExpr},
7049
7998
  system,
7050
7999
  messages,
8000
+ ${onError}
7051
8001
  });` : ` const result = streamText({
7052
8002
  model: ${modelExpr},
7053
8003
  system:
7054
8004
  "You are a helpful documentation assistant. Answer using the project's documentation.",
7055
8005
  messages,
8006
+ ${onError}
7056
8007
  });`;
7057
8008
  const handler = `export const POST: APIRoute = async ({ request }) => {
7058
8009
  ${validate}
8010
+ ${keyCheck}
7059
8011
  try {
7060
8012
  ${stream}
7061
8013
  return result.toTextStreamResponse();
@@ -7182,7 +8134,7 @@ export const POST: APIRoute = async ({ request }) => {
7182
8134
  });
7183
8135
  };
7184
8136
  `;
7185
- var rawMarkdownEndpointTemplate = () => `// Generated by Blume. Do not edit.
8137
+ var rawMarkdownEndpointTemplate = (kind) => `// Generated by Blume. Do not edit.
7186
8138
  import raw from "../generated/raw-markdown.json";
7187
8139
 
7188
8140
  export const prerender = true;
@@ -7195,7 +8147,8 @@ export function getStaticPaths() {
7195
8147
  }
7196
8148
 
7197
8149
  export function GET({ props }) {
7198
- return new Response(raw[props.route] ?? "", {
8150
+ const entry = raw[props.route];
8151
+ return new Response(entry ? ${kind === "md" ? "(entry.md ?? entry.mdx)" : "entry.mdx"} : "", {
7199
8152
  headers: { "Content-Type": "text/markdown; charset=utf-8" },
7200
8153
  });
7201
8154
  }
@@ -7320,12 +8273,24 @@ import data from ${JSON.stringify(options.dataImport)};
7320
8273
  export const prerender = true;
7321
8274
 
7322
8275
  const configuration = ${JSON.stringify(options.configuration, null, 2)};
8276
+
8277
+ // The reference is an unlocalized route, so its chrome renders in the default
8278
+ // locale's language and direction (\`data.ui\` is the default locale's resolved
8279
+ // dictionary), mirroring the changelog index's locale wiring.
8280
+ const i18n = data.config.i18n;
8281
+ const localeMeta = i18n
8282
+ ? i18n.locales.find((l) => l.code === i18n.defaultLocale)
8283
+ : null;
8284
+ const dir = localeMeta?.dir ?? "ltr";
8285
+ const htmlLang = i18n ? i18n.defaultLocale : "en";
7323
8286
  ---
7324
8287
 
7325
8288
  <ReferenceLayout
7326
8289
  analytics={data.config.analytics}
7327
8290
  banner={data.config.banner}
8291
+ dir={dir}
7328
8292
  fontCssVars={data.fontCssVars}
8293
+ locale={htmlLang}
7329
8294
  logo={data.config.logo}
7330
8295
  favicon={data.config.favicon}
7331
8296
  appleIcon={data.config.appleIcon}
@@ -7335,6 +8300,7 @@ const configuration = ${JSON.stringify(options.configuration, null, 2)};
7335
8300
  searchEnabled={data.config.search.enabled}
7336
8301
  site={{ title: data.config.title, description: data.config.description }}
7337
8302
  themeMode={data.config.theme.mode}
8303
+ ui={data.ui}
7338
8304
  >
7339
8305
  <ScalarComponent configuration={configuration} renderMode="client" />
7340
8306
  </ReferenceLayout>
@@ -7485,6 +8451,13 @@ const ogRel = seo.image ?? ogPath;
7485
8451
  // an external URL, which passes through verbatim (mirrors PageLayout).
7486
8452
  const ogImage =
7487
8453
  ogRel && base && ogRel.startsWith("/") ? \`\${base}\${withBase(ogRel)}\` : ogRel;
8454
+ // Blume's generated card has known dimensions the layout can declare; a user's
8455
+ // \`seo.image\` could be any size or format, so it gets none.
8456
+ const ogGenerated = !seo.image && Boolean(ogPath);
8457
+
8458
+ // X attribution: the site's account, plus a creator the page can claim for
8459
+ // itself (a guest post crediting its own author) over the configured default.
8460
+ const x = { ...data.config.x, ...(seo.x?.creator ? { creator: seo.x.creator } : {}) };
7488
8461
 
7489
8462
  const basedRoute = withBase(route);
7490
8463
  const canonical =
@@ -7581,6 +8554,8 @@ const LayoutComponent = resolveSlot(layoutOverrides.Layout, RootLayout);
7581
8554
  searchEnabled={data.config.search.enabled}
7582
8555
  indexable={indexable}
7583
8556
  ogImage={ogImage}
8557
+ ogGenerated={ogGenerated}
8558
+ x={x}
7584
8559
  canonical={canonical}
7585
8560
  editUrl={editUrl}
7586
8561
  feedback={data.config.feedback}
@@ -7607,7 +8582,7 @@ var changelogIndexTemplate = (options) => {
7607
8582
  const askSlot = options.askEnabled ? `
7608
8583
  <AskAI slot="ask" strings={data.ui.ask} suggestions={data.config.ask?.suggestions ?? []} />` : "";
7609
8584
  const clientData = options.needsReact ? `
7610
- clientData={{ config: data.config, navigation: data.navigation, page: { route: "/changelog", title: data.config.title + " changelog" } }}` : "";
8585
+ clientData={{ config: data.config, navigation: data.navigation, page: { route: "/changelog", title: pageTitle } }}` : "";
7611
8586
  const stagedSpread = options.staged ? `
7612
8587
  ...(await getCollection("staged")),` : "";
7613
8588
  return `---
@@ -7615,6 +8590,7 @@ var changelogIndexTemplate = (options) => {
7615
8590
  import { getCollection, render } from "astro:content";
7616
8591
  import RootLayout from "blume/components/layout/RootLayout.astro";
7617
8592
  import Update from "blume/components/content/Update.astro";
8593
+ import { withBase } from "blume/components/islands/base-path.ts";
7618
8594
  import { resolveSlot } from "blume/components/layout/overrides.ts";
7619
8595
  import { layoutOverrides } from "../generated/components.ts";
7620
8596
  ${askImport}import data from "../generated/data.json";
@@ -7695,6 +8671,20 @@ const items = await Promise.all(
7695
8671
  })
7696
8672
  );
7697
8673
 
8674
+ // Repeated labels slug to the same id (e.g. two entries with neither a title
8675
+ // nor a version both falling back to "update"); suffix the later ones -2, -3,
8676
+ // ... so every heading deep-links to its own entry. The first keeps the plain
8677
+ // slug, and the rendered ids stay in lockstep with the \`headings\` list below.
8678
+ const seenIds = new Set();
8679
+ for (const item of items) {
8680
+ let uniqueId = item.id;
8681
+ for (let n = 2; seenIds.has(uniqueId); n += 1) {
8682
+ uniqueId = item.id + "-" + n;
8683
+ }
8684
+ seenIds.add(uniqueId);
8685
+ item.id = uniqueId;
8686
+ }
8687
+
7698
8688
  // A changelog is semver-paginated only when every visible release parses as
7699
8689
  // semver and they span more than one major line. Older majors then collapse
7700
8690
  // into groups the reader reveals one at a time; otherwise the timeline is flat.
@@ -7715,7 +8705,29 @@ const headings = items.map((item) => ({
7715
8705
  }));
7716
8706
 
7717
8707
  const base = data.config.site ? data.config.site.replace(/\\/$/, "") : null;
7718
- const canonical = base ? base + "/changelog" : null;
8708
+ // The canonical URL carries the deployment base (the page is served under it),
8709
+ // matching how the catch-all canonicalizes via \`withBase(route)\`.
8710
+ const basedRoute = withBase("/changelog");
8711
+ const canonical = base ? base + basedRoute : null;
8712
+
8713
+ // The changelog is an unlocalized route, so its chrome renders in the default
8714
+ // locale's dictionary and direction (\`data.ui\` is the default locale's resolved
8715
+ // dictionary), mirroring the catch-all's locale wiring.
8716
+ const i18n = data.config.i18n;
8717
+ const localeMeta = i18n
8718
+ ? i18n.locales.find((l) => l.code === i18n.defaultLocale)
8719
+ : null;
8720
+ const dir = localeMeta?.dir ?? "ltr";
8721
+ const htmlLang = i18n ? i18n.defaultLocale : "en";
8722
+
8723
+ // The page chrome (h1, title, description) comes from the same translatable
8724
+ // \`changelog\` group as the reveal button; optional chaining tolerates a
8725
+ // not-yet-regenerated data snapshot from before these keys existed.
8726
+ const changelogTitle = data.ui.changelog?.title ?? "Changelog";
8727
+ const changelogDescription =
8728
+ data.ui.changelog?.description ??
8729
+ "Product updates, new features, and fixes from every release.";
8730
+ const pageTitle = data.config.title + " " + changelogTitle;
7719
8731
 
7720
8732
  const LayoutComponent = resolveSlot(layoutOverrides.Layout, RootLayout);
7721
8733
  ---
@@ -7732,9 +8744,12 @@ const LayoutComponent = resolveSlot(layoutOverrides.Layout, RootLayout);
7732
8744
  imageZoom={data.config.imageZoom}
7733
8745
  codeWrap={data.config.codeWrap}
7734
8746
  navigation={data.navigation}
8747
+ locale={htmlLang}
8748
+ dir={dir}
8749
+ ui={data.ui}
7735
8750
  page={{
7736
- title: data.config.title + " changelog",
7737
- description: "Product updates and release notes.",
8751
+ title: pageTitle,
8752
+ description: changelogDescription,
7738
8753
  route: "/changelog",
7739
8754
  }}
7740
8755
  headings={headings}
@@ -7745,6 +8760,7 @@ const LayoutComponent = resolveSlot(layoutOverrides.Layout, RootLayout);
7745
8760
  searchEnabled={data.config.search.enabled}
7746
8761
  indexable={true}
7747
8762
  ogImage={null}
8763
+ x={data.config.x}
7748
8764
  canonical={canonical}
7749
8765
  askEnabled={${options.askEnabled}}
7750
8766
  exportPdf={${options.exportPdf}}
@@ -7754,12 +8770,15 @@ const LayoutComponent = resolveSlot(layoutOverrides.Layout, RootLayout);
7754
8770
  noindex={false}
7755
8771
  structuredDataEnabled={data.config.structuredData}
7756
8772
  >${askSlot}
7757
- <h1>Changelog</h1>
8773
+ <h1>{changelogTitle}</h1>
7758
8774
  {
7759
8775
  items.length === 0 ? (
7760
8776
  <p>No changelog entries yet.</p>
7761
8777
  ) : paginate ? (
7762
- <blume-changelog class="not-prose mt-8 block">
8778
+ <blume-changelog
8779
+ class="not-prose mt-8 block"
8780
+ data-i18n-more={data.ui.changelog?.showReleases}
8781
+ >
7763
8782
  {majorGroups[0].items.map(({ Content, href, id, label, date, tags }) => (
7764
8783
  <Update description={date} href={href} id={id} label={label} tags={tags}>
7765
8784
  <Content />
@@ -7808,11 +8827,22 @@ const LayoutComponent = resolveSlot(layoutOverrides.Layout, RootLayout);
7808
8827
  var notFoundPageTemplate = () => `---
7809
8828
  // Generated by Blume. Do not edit. Override by adding \`pages/404.astro\`.
7810
8829
  import PageLayout from "blume/components/layout/PageLayout.astro";
8830
+ import { withBase } from "blume/components/islands/base-path.ts";
7811
8831
  import data from "../generated/data.json";
7812
8832
 
7813
8833
  export const prerender = true;
7814
8834
 
7815
8835
  const nf = data.ui.notFound;
8836
+
8837
+ // The 404 page is an unlocalized route, so its chrome renders in the default
8838
+ // locale's dictionary and direction (\`data.ui\` is the default locale's resolved
8839
+ // dictionary), mirroring the catch-all's locale wiring.
8840
+ const i18n = data.config.i18n;
8841
+ const localeMeta = i18n
8842
+ ? i18n.locales.find((l) => l.code === i18n.defaultLocale)
8843
+ : null;
8844
+ const dir = localeMeta?.dir ?? "ltr";
8845
+ const htmlLang = i18n ? i18n.defaultLocale : "en";
7816
8846
  ---
7817
8847
 
7818
8848
  <PageLayout
@@ -7827,6 +8857,8 @@ const nf = data.ui.notFound;
7827
8857
  themeMode={data.config.theme.mode}
7828
8858
  fontCssVars={data.fontCssVars}
7829
8859
  searchEnabled={data.config.search.enabled}
8860
+ locale={htmlLang}
8861
+ dir={dir}
7830
8862
  ui={data.ui}
7831
8863
  noindex={true}
7832
8864
  >
@@ -7838,7 +8870,7 @@ const nf = data.ui.notFound;
7838
8870
  <p class="text-muted-foreground">{nf.description}</p>
7839
8871
  <a
7840
8872
  class="mt-2 rounded-md bg-accent px-4 py-2 text-sm font-medium text-accent-foreground"
7841
- href="/">{nf.home}</a
8873
+ href={withBase("/")}>{nf.home}</a
7842
8874
  >
7843
8875
  </div>
7844
8876
  </PageLayout>
@@ -8948,7 +9980,7 @@ ${clause}
8948
9980
  <Component ${directiveFor(override)} {...Astro.props}><slot /></Component>
8949
9981
  `;
8950
9982
  };
8951
- var sanitize = (value) => value.replaceAll(/[^A-Za-z0-9]/gu, "_");
9983
+ var sanitize = (value) => value.replaceAll(/[^A-Za-z0-9]/gu, (char) => `_${(char.codePointAt(0) ?? 0).toString(16)}_`);
8952
9984
  var planComponentSlots = (componentsFile, analysis) => {
8953
9985
  const frameworks = new Set;
8954
9986
  if (!componentsFile) {
@@ -9011,13 +10043,13 @@ export const layoutOverrides = { ...(overrides.layout ?? {})${layoutEntries.leng
9011
10043
 
9012
10044
  // src/astro/examples.ts
9013
10045
  import { readFile as readFile10 } from "node:fs/promises";
9014
- import { join as join21, relative as relative6 } from "pathe";
9015
- import { glob as glob3 } from "tinyglobby";
10046
+ import { join as join21, relative as relative7 } from "pathe";
10047
+ import { glob as glob4 } from "tinyglobby";
9016
10048
 
9017
10049
  // src/astro/islands.ts
9018
10050
  import { readFile as readFile9 } from "node:fs/promises";
9019
10051
  import { basename, join as join20 } from "pathe";
9020
- import { glob as glob2 } from "tinyglobby";
10052
+ import { glob as glob3 } from "tinyglobby";
9021
10053
  var DEFAULT_CLIENT = "visible";
9022
10054
  var VALID_MODES = new Set([
9023
10055
  "idle",
@@ -9046,7 +10078,7 @@ var readClientMode = (source, file, warnings) => {
9046
10078
  };
9047
10079
  var discoverIslands = async (root) => {
9048
10080
  const dir = join20(root, "islands");
9049
- const matches = await glob2(["**/*.{jsx,svelte,tsx,vue}"], {
10081
+ const matches = await glob3(["**/*.{jsx,svelte,tsx,vue}"], {
9050
10082
  absolute: true,
9051
10083
  cwd: dir,
9052
10084
  onlyFiles: true
@@ -9109,7 +10141,7 @@ var splitGlobBase = (pattern) => {
9109
10141
  var discoverExamples = async (root, pattern = "examples") => {
9110
10142
  const { base, rest } = GLOB_MAGIC.test(pattern) ? splitGlobBase(pattern) : { base: pattern, rest: DEFAULT_EXAMPLE_GLOB };
9111
10143
  const dir = join21(root, base);
9112
- const matches = await glob3([rest], {
10144
+ const matches = await glob4([rest], {
9113
10145
  absolute: true,
9114
10146
  cwd: dir,
9115
10147
  onlyFiles: true
@@ -9125,7 +10157,7 @@ var discoverExamples = async (root, pattern = "examples") => {
9125
10157
  if (!(ext && framework)) {
9126
10158
  return;
9127
10159
  }
9128
- const path = relative6(dir, file).slice(0, -(ext.length + 1));
10160
+ const path = relative7(dir, file).slice(0, -(ext.length + 1));
9129
10161
  const existing = seen.get(path);
9130
10162
  if (existing) {
9131
10163
  warnings.push(`Two examples both resolve to "${path}" ("${existing}" and "${file}"); ignoring the second. Give them distinct paths.`);
@@ -9147,52 +10179,6 @@ var discoverExamples = async (root, pattern = "examples") => {
9147
10179
  return { examples, warnings };
9148
10180
  };
9149
10181
 
9150
- // src/astro/pages.ts
9151
- import { extname as extname5, relative as relative7 } from "pathe";
9152
- import { glob as glob4 } from "tinyglobby";
9153
- var discoverPages = async (pagesRoot) => {
9154
- const files = await glob4(["**/*.astro"], {
9155
- absolute: true,
9156
- cwd: pagesRoot,
9157
- onlyFiles: true
9158
- });
9159
- files.sort();
9160
- return files.map((file) => {
9161
- const rel = relative7(pagesRoot, file);
9162
- const withoutExt = rel.slice(0, rel.length - extname5(rel).length);
9163
- const parts = withoutExt.split("/");
9164
- if (parts.at(-1) === "index") {
9165
- parts.pop();
9166
- }
9167
- const pattern = parts.length === 0 ? "/" : `/${parts.join("/")}`;
9168
- return { entrypoint: file, pattern };
9169
- });
9170
- };
9171
- var routeIsTaken = (pages, contentPages, route) => pages.some((page) => page.pattern === route) || contentPages.some((page) => page.route === route);
9172
- var PRIVATE_SEGMENT = /^[._]/u;
9173
- var humanizeSegment = (segment) => segment.split(/[-_]/u).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
9174
- var customOgRoutes = (pages, siteTitle) => {
9175
- const seen = new Set;
9176
- const routes = [];
9177
- const collectRoute = (pattern) => {
9178
- const segments = pattern.split("/").filter(Boolean);
9179
- if (segments.some((part) => PRIVATE_SEGMENT.test(part) || part.includes("["))) {
9180
- return;
9181
- }
9182
- const slug = segments.length === 0 ? "index" : segments.join("/");
9183
- if (seen.has(slug)) {
9184
- return;
9185
- }
9186
- seen.add(slug);
9187
- const last = segments.at(-1);
9188
- routes.push({ slug, title: last ? humanizeSegment(last) : siteTitle });
9189
- };
9190
- for (const { pattern } of pages) {
9191
- collectRoute(pattern);
9192
- }
9193
- return routes;
9194
- };
9195
-
9196
10182
  // src/astro/generate.ts
9197
10183
  var BLUME_SRC = join22(packageRoot(), "src");
9198
10184
  var canResolveFrom = (fromDir, spec) => {
@@ -9281,6 +10267,10 @@ var ISLAND_FRAMEWORK_DEPS = {
9281
10267
  svelte: "@astrojs/svelte",
9282
10268
  vue: "@astrojs/vue"
9283
10269
  };
10270
+ var DEPLOYMENT_ADAPTER_DEPS = {
10271
+ cloudflare: "@astrojs/cloudflare",
10272
+ netlify: "@astrojs/netlify"
10273
+ };
9284
10274
  var islandFrameworkWarnings = (frameworks, root) => {
9285
10275
  const warnings = [];
9286
10276
  for (const framework of frameworks) {
@@ -9291,6 +10281,15 @@ var islandFrameworkWarnings = (frameworks, root) => {
9291
10281
  }
9292
10282
  return warnings;
9293
10283
  };
10284
+ var deploymentAdapterWarnings = (deployment, root) => {
10285
+ const dep = deployment.output === "server" && deployment.adapter ? DEPLOYMENT_ADAPTER_DEPS[deployment.adapter] : undefined;
10286
+ if (dep && !(canResolveFrom(root, dep) || canResolveFrom(packageRoot(), dep))) {
10287
+ return [
10288
+ `Deployment adapter "${deployment.adapter}" needs "${dep}", which isn't installed. Run \`npm install ${dep}\` (or your package manager's equivalent).`
10289
+ ];
10290
+ }
10291
+ return [];
10292
+ };
9294
10293
  var examplesCssFile = (root, config) => config.examples.css ? join22(root, config.examples.css) : null;
9295
10294
  var writeExamplesPreview = async (options) => {
9296
10295
  const { config, hasExamples, root, srcDir, write } = options;
@@ -9320,14 +10319,15 @@ var detectNeedsReact = async (root) => {
9320
10319
  });
9321
10320
  return matches.length > 0;
9322
10321
  };
9323
- var detectUsesMath = async (root) => {
9324
- const files = await glob5(["**/*.mdx"], {
10322
+ var containsMath = (content) => content.includes("$$") || content.includes("<Math");
10323
+ var detectUsesMath = async (root, staged = []) => {
10324
+ const files = await glob5(["**/*.{md,mdx}"], {
9325
10325
  cwd: root,
9326
10326
  ignore: ["**/node_modules/**", "**/.blume/**", "**/dist/**"],
9327
10327
  onlyFiles: true
9328
10328
  });
9329
10329
  const contents = await Promise.all(files.map((file) => readOptional(join22(root, file))));
9330
- return contents.some((content) => content.includes("$$"));
10330
+ return [...contents, ...staged].some(containsMath);
9331
10331
  };
9332
10332
  var writeIfChanged = async (path, content) => {
9333
10333
  let existing = null;
@@ -9518,6 +10518,7 @@ var buildRuntimeData = (project) => {
9518
10518
  appleIcon: resolveAppleIcon(project),
9519
10519
  ask: config.ai.ask?.enabled ? { suggestions: config.ai.ask.suggestions } : null,
9520
10520
  banner: resolveBanner(config),
10521
+ basePath: config.basePath,
9521
10522
  codeThemes: config.markdown.codeBlocks.theme,
9522
10523
  codeWrap: config.markdown.code.wrap,
9523
10524
  description: config.description,
@@ -9546,7 +10547,8 @@ var buildRuntimeData = (project) => {
9546
10547
  structuredData: config.seo.structuredData,
9547
10548
  theme: config.theme,
9548
10549
  title: config.title,
9549
- toc: config.toc
10550
+ toc: config.toc,
10551
+ x: config.seo.x
9550
10552
  },
9551
10553
  feeds: buildRssFeeds(project).map((feed) => ({
9552
10554
  href: feed.path,
@@ -9620,6 +10622,7 @@ var writeMcpFiles = async (project, plan, write) => {
9620
10622
  }
9621
10623
  const data = await buildMcpData(project);
9622
10624
  const discoveryInput = {
10625
+ base: data.base,
9623
10626
  name: data.name,
9624
10627
  route: plan.route,
9625
10628
  site: data.site,
@@ -9651,11 +10654,6 @@ var writeNotFoundPage = async (write, srcDir, pages, contentPages) => {
9651
10654
  }
9652
10655
  await write(join22(srcDir, "pages", "404.astro"), notFoundPageTemplate());
9653
10656
  };
9654
- var shouldGenerateChangelog = (project, userPages) => {
9655
- const hasChangelog = project.graph.pages.some((page) => page.contentType === "changelog" && !(page.meta.draft || page.meta.sidebar.hidden));
9656
- const hasChangelogSource = (project.config.content.sources ?? []).some((source) => source.type === "github-releases");
9657
- return (hasChangelog || hasChangelogSource) && !routeIsTaken(userPages, project.graph.pages, "/changelog");
9658
- };
9659
10657
  var buildComponentSlots = async (componentsFile) => {
9660
10658
  const analysis = componentsFile ? analyzeComponentOverrides(await readFile11(componentsFile, "utf-8"), componentsFile) : null;
9661
10659
  return {
@@ -9683,6 +10681,7 @@ var generateRuntime = async (project) => {
9683
10681
  const askEnabled = config.ai.ask?.enabled ?? false;
9684
10682
  const exportPdf = config.export.pdf;
9685
10683
  const exportEpub = config.export.epub;
10684
+ const staged = collectStaged(project);
9686
10685
  const [
9687
10686
  pages,
9688
10687
  detectedReact,
@@ -9695,7 +10694,7 @@ var generateRuntime = async (project) => {
9695
10694
  ] = await Promise.all([
9696
10695
  context.pagesRoot ? discoverPages(context.pagesRoot) : Promise.resolve([]),
9697
10696
  detectNeedsReact(context.root),
9698
- detectUsesMath(context.root),
10697
+ detectUsesMath(context.root, staged.values()),
9699
10698
  readOptional(context.themeFile),
9700
10699
  readOptional(examplesCssFile(context.root, config)),
9701
10700
  discoverIslands(context.root),
@@ -9719,7 +10718,6 @@ var generateRuntime = async (project) => {
9719
10718
  const ogRoutes = customOgRoutes(pages, config.title);
9720
10719
  const mcp = planMcp(project, srcDir, pages);
9721
10720
  pages.push(...mcp.discoveryPages);
9722
- const staged = collectStaged(project);
9723
10721
  const hasStaged = staged.size > 0;
9724
10722
  const hasFilesystemSource = project.sources.some((source) => !source.staged);
9725
10723
  const [structural] = await Promise.all([
@@ -9785,7 +10783,7 @@ var generateRuntime = async (project) => {
9785
10783
  if (config.seo.og.enabled) {
9786
10784
  await write(join22(srcDir, "pages", "og", "[...slug].png.ts"), ogEndpointTemplate(ogRoutes));
9787
10785
  }
9788
- if (shouldGenerateChangelog(project, pages)) {
10786
+ if (hasGeneratedChangelog(project, pages)) {
9789
10787
  await write(join22(srcDir, "pages", "changelog.astro"), changelogIndexTemplate({
9790
10788
  askEnabled,
9791
10789
  exportEpub,
@@ -9818,8 +10816,8 @@ var generateRuntime = async (project) => {
9818
10816
  await Promise.all([
9819
10817
  write(join22(srcDir, "generated", "raw-markdown.json"), `${JSON.stringify(rawMarkdown)}
9820
10818
  `),
9821
- write(join22(srcDir, "pages", "[...slug].md.ts"), rawMarkdownEndpointTemplate()),
9822
- write(join22(srcDir, "pages", "[...slug].mdx.ts"), rawMarkdownEndpointTemplate())
10819
+ write(join22(srcDir, "pages", "[...slug].md.ts"), rawMarkdownEndpointTemplate("md")),
10820
+ write(join22(srcDir, "pages", "[...slug].mdx.ts"), rawMarkdownEndpointTemplate("mdx"))
9823
10821
  ]);
9824
10822
  const feeds = buildRssFeeds(project);
9825
10823
  if (feeds.length > 0) {
@@ -9844,7 +10842,7 @@ var generateRuntime = async (project) => {
9844
10842
  ...pages.map((page) => page.pattern),
9845
10843
  ...referenceTabs(config).map((tab) => tab.path)
9846
10844
  ]);
9847
- if (shouldGenerateChangelog(project, pages)) {
10845
+ if (hasGeneratedChangelog(project, pages)) {
9848
10846
  navTargetRoutes.add("/changelog");
9849
10847
  }
9850
10848
  warnings.push(...validateNavTargets(project.graph.navigation, navTargetRoutes).map((diagnostic) => diagnostic.suggestion ? `${diagnostic.message} ${diagnostic.suggestion}` : diagnostic.message));
@@ -9858,7 +10856,7 @@ var generateRuntime = async (project) => {
9858
10856
  warnings.push(`Search provider "${config.search.provider}" needs "${dep}", which isn't installed. Run \`npm install ${dep}\` (or your package manager's equivalent).`);
9859
10857
  }
9860
10858
  }
9861
- warnings.push(...islandFrameworkWarnings(frameworks, context.root));
10859
+ warnings.push(...deploymentAdapterWarnings(config.deployment, context.root), ...islandFrameworkWarnings(frameworks, context.root));
9862
10860
  if (hasScalarReferences(config)) {
9863
10861
  const references = await buildReferenceFiles({
9864
10862
  config,
@@ -10005,6 +11003,7 @@ var segmentKey = (raw) => {
10005
11003
  const group = raw.match(GROUP_FOLDER2)?.groups?.label;
10006
11004
  return (group ?? raw).replace(NUMERIC_PREFIX2, "");
10007
11005
  };
11006
+ var isIndexStem = (stem) => stem.replace(NUMERIC_PREFIX2, "") === "index";
10008
11007
  var createGroup = (key, path, label, order) => ({
10009
11008
  children: [],
10010
11009
  index: new Map,
@@ -10029,7 +11028,7 @@ var pageOrder = (page, filename) => {
10029
11028
  if (page.meta.sidebar.order !== undefined) {
10030
11029
  return page.meta.sidebar.order;
10031
11030
  }
10032
- if (filename.replace(extname6(filename), "") === "index") {
11031
+ if (isIndexStem(filename.replace(extname6(filename), ""))) {
10033
11032
  return Number.NEGATIVE_INFINITY;
10034
11033
  }
10035
11034
  if (page.contentType === "changelog") {
@@ -10138,7 +11137,7 @@ var buildFileSystemSidebar = (pages, folderMeta, sharedMeta, metaPrefix, display
10138
11137
  const stem = filename.replace(extname6(filename), "");
10139
11138
  const dirs = parts.slice(0, -1);
10140
11139
  const routeSegments = page.route.split("/").filter(Boolean);
10141
- const folderParts = stem === "index" ? routeSegments : routeSegments.slice(0, -1);
11140
+ const folderParts = isIndexStem(stem) ? routeSegments : routeSegments.slice(0, -1);
10142
11141
  const routeDirCount = dirs.filter((dir) => !GROUP_FOLDER2.test(dir)).length;
10143
11142
  const offset = Math.max(0, folderParts.length - routeDirCount);
10144
11143
  let parent = root;
@@ -10266,7 +11265,7 @@ var buildNavigation = (pages, options) => {
10266
11265
  ...selector,
10267
11266
  items: selector.items.map(rebasePath)
10268
11267
  })) : options.selectors ?? [];
10269
- const tabs = basePath ? (options.tabs ?? []).map((tab) => ({
11268
+ const tabs2 = basePath ? (options.tabs ?? []).map((tab) => ({
10270
11269
  ...tab,
10271
11270
  items: tab.items?.map(rebasePath),
10272
11271
  path: withBasePath(basePath, tab.path)
@@ -10288,14 +11287,15 @@ var buildNavigation = (pages, options) => {
10288
11287
  featured,
10289
11288
  selectors,
10290
11289
  sidebar: buildConfigSidebar(options.sidebar, byRoute, display, basePath),
10291
- tabs
11290
+ tabs: tabs2
10292
11291
  };
10293
11292
  }
11293
+ const rootTabPath = withBasePath(basePath, options.localizedRoot ?? "/");
10294
11294
  return {
10295
11295
  featured,
10296
11296
  selectors,
10297
- sidebar: buildFileSystemSidebar(pages, options.folderMeta, sharedFolderMeta, metaPrefix, display, new Set(tabs.flatMap((tab) => tab.path === "/" ? [] : [tab.path]))),
10298
- tabs
11297
+ sidebar: buildFileSystemSidebar(pages, options.folderMeta, sharedFolderMeta, metaPrefix, display, new Set(tabs2.flatMap((tab) => tab.path === rootTabPath ? [] : [tab.path]))),
11298
+ tabs: tabs2
10299
11299
  };
10300
11300
  };
10301
11301
 
@@ -10337,9 +11337,14 @@ var localePagesFor = (code, real, fallback, fallbackByKey, i18n, basePath) => {
10337
11337
  return [...real, ...filled];
10338
11338
  };
10339
11339
  var buildLocaleNavigation = (code, pages, fallback, fallbackByKey, options, i18n) => {
10340
- const tabs = options.navigation.tabs?.map((tab) => ({
11340
+ const localizePath = (path) => path.startsWith("/") ? localizeRoute(path, code, i18n) : path;
11341
+ const tabs2 = options.navigation.tabs?.map((tab) => ({
10341
11342
  ...tab,
10342
- path: tab.path.startsWith("/") ? localizeRoute(tab.path, code, i18n) : tab.path
11343
+ items: tab.items?.map((item) => ({
11344
+ ...item,
11345
+ path: localizePath(item.path)
11346
+ })),
11347
+ path: localizePath(tab.path)
10343
11348
  }));
10344
11349
  const real = pages.filter((page) => page.locale === code);
10345
11350
  const localePages = localePagesFor(code, real, fallback, fallbackByKey, i18n, options.basePath ?? "");
@@ -10348,12 +11353,13 @@ var buildLocaleNavigation = (code, pages, fallback, fallbackByKey, options, i18n
10348
11353
  display: options.navigation.sidebar.display,
10349
11354
  featured: options.navigation.featured,
10350
11355
  folderMeta: options.folderMeta,
11356
+ localizedRoot: localizeRoute("/", code, i18n),
10351
11357
  metaPrefix: i18n.parser === "dir" && code !== i18n.defaultLocale ? code : "",
10352
11358
  refByLogical: true,
10353
11359
  selectors: options.navigation.selectors,
10354
11360
  sharedFolderMeta: options.sharedFolderMeta,
10355
11361
  sidebar: options.navigation.sidebar.items,
10356
- tabs
11362
+ tabs: tabs2
10357
11363
  });
10358
11364
  };
10359
11365
  var buildI18nNavigation = (pages, options, i18n) => {
@@ -10429,7 +11435,10 @@ var parseGitLog = (output) => {
10429
11435
  }
10430
11436
  return times;
10431
11437
  };
10432
- var gitLastModifiedTimes = (root, contentRoot, sourcePaths) => {
11438
+ var gitLastModifiedTimes = (root, contentRoots, sourcePaths) => {
11439
+ if (sourcePaths.length === 0) {
11440
+ return new Map;
11441
+ }
10433
11442
  try {
10434
11443
  const gitRoot = execFileSync("git", ["-C", root, "rev-parse", "--show-toplevel"], { encoding: "utf-8" }).trim();
10435
11444
  const output = execFileSync("git", [
@@ -10441,7 +11450,7 @@ var gitLastModifiedTimes = (root, contentRoot, sourcePaths) => {
10441
11450
  "--format=%x00%cI",
10442
11451
  "--name-only",
10443
11452
  "--",
10444
- contentRoot
11453
+ ...contentRoots
10445
11454
  ], { encoding: "utf-8", maxBuffer: 256 * 1024 * 1024 });
10446
11455
  const byRepoPath = parseGitLog(output);
10447
11456
  const result = new Map;
@@ -10567,7 +11576,7 @@ var entryIdDiagnostics = (pages, collectionBase) => {
10567
11576
  file: page.sourcePath,
10568
11577
  message: `Content source "${page.source.name}" is rooted outside the docs collection base, so ${page.route} resolves entry id "${entryId}" but the collection would generate "${expected}" — the page would 404 at runtime.`,
10569
11578
  severity: "error",
10570
- suggestion: "Give each filesystem source a root under content.root, or use a single filesystem source so the collection can root at it."
11579
+ suggestion: "Use a single filesystem source (the docs collection roots at it), or root every filesystem source at content.root and partition them with include globs — a root at a subdirectory of content.root still mismatches."
10571
11580
  });
10572
11581
  }
10573
11582
  }
@@ -10579,7 +11588,7 @@ var scanProject = async (root, options = {}) => {
10579
11588
  const configResult = await loadConfig(root, {
10580
11589
  devServerUrl: options.devServerUrl
10581
11590
  });
10582
- const config = applyConfigOverrides(configResult.config, options.overrides);
11591
+ const config = applyDeploymentEnv(applyConfigOverrides(configResult.config, options.overrides));
10583
11592
  const context = resolveProjectContext(root, config, {
10584
11593
  runtimeDir: options.runtimeDir
10585
11594
  });
@@ -10620,7 +11629,8 @@ var scanProject = async (root, options = {}) => {
10620
11629
  const lastModified = resolveLastModifiedConfig(config.lastModified);
10621
11630
  if (lastModified.enabled && lastModified.source === "git") {
10622
11631
  const fsPaths = pages.map((page) => page.sourcePath).filter((path) => path !== undefined);
10623
- const gitTimes = gitLastModifiedTimes(context.root, context.contentRoot, fsPaths);
11632
+ const contentRoots = sources.flatMap((source) => source.staged || !source.contentRoot ? [] : [source.contentRoot]);
11633
+ const gitTimes = gitLastModifiedTimes(context.root, contentRoots, fsPaths);
10624
11634
  for (const page of pages) {
10625
11635
  if (!page.lastModified && page.sourcePath) {
10626
11636
  page.lastModified = gitTimes.get(page.sourcePath);
@@ -10659,11 +11669,18 @@ import { dirname as dirname10, join as join23, resolve as resolve7 } from "pathe
10659
11669
  var ENV_LINE = /^\s*(?:export\s+)?(?<key>[A-Za-z_][A-Za-z0-9_]*)\s*=\s*(?<value>.*?)\s*$/u;
10660
11670
  var DOUBLE_QUOTED2 = /^"(?<body>[\s\S]*)"$/u;
10661
11671
  var SINGLE_QUOTED = /^'(?<body>[\s\S]*)'$/u;
11672
+ var ESCAPE = /\\(?<char>[\\nt"])/gu;
11673
+ var UNESCAPED = {
11674
+ '"': '"',
11675
+ "\\": "\\",
11676
+ n: `
11677
+ `,
11678
+ t: "\t"
11679
+ };
10662
11680
  var unquote = (raw) => {
10663
11681
  const double = raw.match(DOUBLE_QUOTED2)?.groups?.body;
10664
11682
  if (double !== undefined) {
10665
- return double.replaceAll("\\n", `
10666
- `).replaceAll("\\t", "\t").replaceAll("\\\"", '"').replaceAll("\\\\", "\\");
11683
+ return double.replaceAll(ESCAPE, (match, char) => UNESCAPED[char] ?? match);
10667
11684
  }
10668
11685
  const single = raw.match(SINGLE_QUOTED)?.groups?.body;
10669
11686
  if (single !== undefined) {
@@ -10718,9 +11735,10 @@ var RED = `${ESC2}[31m`;
10718
11735
  var BOLD = `${ESC2}[1m`;
10719
11736
  var RESET = `${ESC2}[0m`;
10720
11737
  var ISSUES_URL = "https://github.com/haydenbleasel/blume/issues";
10721
- var BLUME_FRAME = /(?<abs>\/[^\s()]*\/\.blume\/[^\s()]*)/gu;
11738
+ var BLUME_FRAME = /(?<abs>(?:\/[^\s()]*\/|[A-Za-z]:\\[^\s()]*\\)\.blume[/\\][^\s()]*)/gu;
11739
+ var BLUME_MARKER = /[/\\]\.blume[/\\]/u;
10722
11740
  var remapBlumeStack = (stack) => stack.replaceAll(BLUME_FRAME, (match) => {
10723
- const marker = match.indexOf("/.blume/");
11741
+ const marker = match.search(BLUME_MARKER);
10724
11742
  return `${match.slice(marker + 1)} (generated)`;
10725
11743
  });
10726
11744
  var reportInternalError = (error) => {
@@ -10905,6 +11923,54 @@ var enforceBudget = async (distDir, args) => {
10905
11923
  }
10906
11924
  return passed ? "pass" : "fail";
10907
11925
  };
11926
+ var runClientAssetChecks = async (staticDir, args) => {
11927
+ if (args.analyze) {
11928
+ await reportBundleSizes(staticDir);
11929
+ }
11930
+ if (await enforceBudget(staticDir, args) === "fail") {
11931
+ process.exit(1);
11932
+ }
11933
+ };
11934
+ var isolatedOutputDir = (config, context) => {
11935
+ const { adapter, output } = config.deployment;
11936
+ if (output === "server" && adapter === "vercel") {
11937
+ return join24(context.outDir, ".vercel", "output");
11938
+ }
11939
+ return context.distDir ?? join24(context.outDir, "dist");
11940
+ };
11941
+ var isolatedStaticDir = (config, context) => {
11942
+ const { adapter, output } = config.deployment;
11943
+ const outputDir = isolatedOutputDir(config, context);
11944
+ if (output === "server" && adapter === "vercel") {
11945
+ return join24(outputDir, "static");
11946
+ }
11947
+ if (output === "server" && adapter === "node") {
11948
+ return join24(outputDir, "client");
11949
+ }
11950
+ return outputDir;
11951
+ };
11952
+ var publishLlmsFiles = async (project, distDir) => {
11953
+ const indexPath = join24(distDir, "llms.txt");
11954
+ const fullPath = join24(distDir, "llms-full.txt");
11955
+ const writeIndex = !existsSync15(indexPath);
11956
+ const writeFull = !existsSync15(fullPath);
11957
+ if (!(writeIndex || writeFull)) {
11958
+ return;
11959
+ }
11960
+ const { index, full } = await buildLlmsFiles(project);
11961
+ const writes = [];
11962
+ if (writeIndex) {
11963
+ writes.push(writeFile7(indexPath, index, "utf-8"));
11964
+ }
11965
+ if (writeFull) {
11966
+ writes.push(writeFile7(fullPath, full, "utf-8"));
11967
+ }
11968
+ await Promise.all(writes);
11969
+ logger.success(`Generated ${[
11970
+ writeIndex ? "llms.txt" : null,
11971
+ writeFull ? "llms-full.txt" : null
11972
+ ].filter(Boolean).join(" and ")}`);
11973
+ };
10908
11974
  var publishBuildArtifacts = async (project, distDir, args) => {
10909
11975
  if (project.config.search.provider === "pagefind") {
10910
11976
  logger.start("Building search index");
@@ -10916,13 +11982,8 @@ var publishBuildArtifacts = async (project, distDir, args) => {
10916
11982
  success: (message) => logger.success(message),
10917
11983
  warn: (message) => logger.warn(message)
10918
11984
  });
10919
- if (project.config.ai.llmsTxt) {
10920
- const { index, full } = await buildLlmsFiles(project);
10921
- await Promise.all([
10922
- writeFile7(join24(distDir, "llms.txt"), index, "utf-8"),
10923
- writeFile7(join24(distDir, "llms-full.txt"), full, "utf-8")
10924
- ]);
10925
- logger.success("Generated llms.txt and llms-full.txt");
11985
+ if (project.config.ai.llmsTxt.enabled) {
11986
+ await publishLlmsFiles(project, distDir);
10926
11987
  }
10927
11988
  const sitemap = buildSitemap(project);
10928
11989
  if (sitemap && !existsSync15(join24(distDir, "sitemap.xml"))) {
@@ -10943,25 +12004,21 @@ var publishBuildArtifacts = async (project, distDir, args) => {
10943
12004
  await emitRedirectFiles(project.config, distDir);
10944
12005
  const { config } = project;
10945
12006
  const features = serverFeatures(config);
12007
+ const sitemapNote = config.seo.sitemap ? "no (set deployment.site)" : "no (seo.sitemap is false)";
10946
12008
  logger.box([
10947
12009
  `Output ${config.deployment.output}`,
10948
12010
  `Adapter ${config.deployment.adapter ?? "none"}`,
10949
12011
  `Site ${config.deployment.site ?? "not set"}`,
10950
12012
  `Search ${config.search.provider}`,
10951
12013
  `Redirects ${config.redirects.length}`,
10952
- `Sitemap ${sitemap ? "yes" : "no (set deployment.site)"}`,
12014
+ `Sitemap ${sitemap ? "yes" : sitemapNote}`,
10953
12015
  `Robots ${robots ? "yes" : "no"}`,
10954
12016
  `Agent JSON ${agentReadability ? "yes" : "no"}`,
10955
- `LLM files ${config.ai.llmsTxt ? "yes" : "no"}`,
12017
+ `LLM files ${config.ai.llmsTxt.enabled ? "yes" : "no"}`,
10956
12018
  `Server features ${features.length > 0 ? features.join(", ") : "none"}`
10957
12019
  ].join(`
10958
12020
  `));
10959
- if (args.analyze) {
10960
- await reportBundleSizes(distDir);
10961
- }
10962
- if (await enforceBudget(distDir, args) === "fail") {
10963
- process.exit(1);
10964
- }
12021
+ await runClientAssetChecks(distDir, args);
10965
12022
  logger.success(`Built to ${distDir}`);
10966
12023
  };
10967
12024
  var buildCommand = defineCommand2({
@@ -11007,7 +12064,7 @@ var buildCommand = defineCommand2({
11007
12064
  async run({ args }) {
11008
12065
  const root = process.cwd();
11009
12066
  const runtimeDir = args.isolated ? ".blume-verify" : process.env.BLUME_RUNTIME_DIR;
11010
- refuseIfDevRunning(root, "building", runtimeDir);
12067
+ refuseIfDevRunning(root, "building", { isolatedHint: true, runtimeDir });
11011
12068
  if (args.isolated) {
11012
12069
  await ensureGitignore(root, [".blume-verify/"]);
11013
12070
  }
@@ -11037,9 +12094,9 @@ var buildCommand = defineCommand2({
11037
12094
  logLevel: "info",
11038
12095
  root: project.context.outDir
11039
12096
  });
11040
- const distDir = project.context.distDir ?? join24(root, "dist");
11041
12097
  if (runtimeDir) {
11042
- logger.success(`Isolated build OK — output at ${distDir} (not published).`);
12098
+ await runClientAssetChecks(isolatedStaticDir(project.config, project.context), args);
12099
+ logger.success(`Isolated build OK — output at ${isolatedOutputDir(project.config, project.context)} (not published).`);
11043
12100
  return;
11044
12101
  }
11045
12102
  const surfaced = await surfaceAdapterOutput(project.config, project.context);
@@ -11079,7 +12136,7 @@ var checkCommand = defineCommand3({
11079
12136
  async run({ args }) {
11080
12137
  const root = process.cwd();
11081
12138
  const runtimeDir = args.isolated ? ".blume-verify" : process.env.BLUME_RUNTIME_DIR;
11082
- refuseIfDevRunning(root, "checking", runtimeDir);
12139
+ refuseIfDevRunning(root, "checking", { isolatedHint: true, runtimeDir });
11083
12140
  if (args.isolated) {
11084
12141
  await ensureGitignore(root, [".blume-verify/"]);
11085
12142
  }
@@ -11191,6 +12248,7 @@ var coalescedRunner = (task) => {
11191
12248
  };
11192
12249
 
11193
12250
  // src/cli/commands/dev.ts
12251
+ var normalizeHost = (host) => host === "" ? true : host ?? false;
11194
12252
  var routeSignature = (routes) => routes.map((route) => `${route.path} ${route.entryId}`).toSorted().join(`
11195
12253
  `);
11196
12254
  var devCommand = defineCommand4({
@@ -11246,7 +12304,7 @@ var devCommand = defineCommand4({
11246
12304
  const createServer = (listenPort, open) => dev({
11247
12305
  logLevel: args.debug ? "debug" : "info",
11248
12306
  root: project.context.outDir,
11249
- server: { host: args.host ?? false, open, port: listenPort }
12307
+ server: { host: normalizeHost(args.host), open, port: listenPort }
11250
12308
  });
11251
12309
  let server = await createServer(explicitPort, args.open ?? false);
11252
12310
  const boundPort = server.address.port;
@@ -11266,7 +12324,6 @@ var devCommand = defineCommand4({
11266
12324
  });
11267
12325
  const nextSignature = routeSignature(next.manifest.routes);
11268
12326
  const structural = nextSignature !== lastSignature;
11269
- lastSignature = nextSignature;
11270
12327
  if (structural) {
11271
12328
  await server.stop();
11272
12329
  await generateRuntime(next);
@@ -11274,6 +12331,8 @@ var devCommand = defineCommand4({
11274
12331
  } else {
11275
12332
  await generateRuntime(next);
11276
12333
  }
12334
+ lastSignature = nextSignature;
12335
+ reportDiagnostics(next.diagnostics, root);
11277
12336
  showBlumeErrorOverlay(next.diagnostics);
11278
12337
  } catch (error) {
11279
12338
  logger.error(`Regeneration failed: ${error.message}`);
@@ -11415,15 +12474,27 @@ var doctorCommand = defineCommand5({
11415
12474
  });
11416
12475
 
11417
12476
  // src/cli/commands/eject.ts
11418
- import { readFile as readFile13, writeFile as writeFile9 } from "node:fs/promises";
11419
12477
  import { defineCommand as defineCommand6 } from "citty";
11420
- import { join as join28, relative as relative13 } from "pathe";
12478
+ import { relative as relative14 } from "pathe";
11421
12479
 
11422
12480
  // src/registry/eject.ts
11423
12481
  import { existsSync as existsSync17 } from "node:fs";
11424
12482
  import { cp as cp2, mkdir as mkdir7, readFile as readFile12, rm as rm3, writeFile as writeFile8 } from "node:fs/promises";
11425
12483
  import { join as join27, relative as relative12 } from "pathe";
11426
12484
  var toPosix = (path) => path.split("\\").join("/");
12485
+ var LOCAL_BLUME_SOURCE = "../../node_modules/blume/src/**/*.{astro,ts,tsx}";
12486
+ var blumeSourceGlob = (root, genDir, resolveBlumeRoot = packageRoot) => {
12487
+ if (existsSync17(join27(root, "node_modules", "blume"))) {
12488
+ return LOCAL_BLUME_SOURCE;
12489
+ }
12490
+ try {
12491
+ const src = join27(resolveBlumeRoot(), "src");
12492
+ return `${toPosix(relative12(genDir, src))}/**/*.{astro,ts,tsx}`;
12493
+ } catch {
12494
+ console.warn('blume: could not locate the installed blume package; src/generated/app.css keeps its default `@source "../../node_modules/blume/..."` glob. If blume is hoisted elsewhere, point that glob at its install location or Blume\'s utility classes will be missing.');
12495
+ return LOCAL_BLUME_SOURCE;
12496
+ }
12497
+ };
11427
12498
  var ejectOpenApiData = (project) => {
11428
12499
  const source = project.sources.find(isOpenApiSource);
11429
12500
  return source ? source.openApiData() : {};
@@ -11449,6 +12520,63 @@ var askFiles = async (project, srcDir, genDir) => {
11449
12520
  }
11450
12521
  return files;
11451
12522
  };
12523
+ var hostsMcp = (project, userPages) => project.config.mcp.enabled && !routeIsTaken(userPages, project.graph.pages, project.config.mcp.route);
12524
+ var mcpDiscoveryPages = (project, userPages) => hostsMcp(project, userPages) ? [
12525
+ {
12526
+ entrypoint: "src/blume-mcp/discovery.ts",
12527
+ pattern: "/.well-known/mcp.json"
12528
+ },
12529
+ {
12530
+ entrypoint: "src/blume-mcp/server-card.ts",
12531
+ pattern: "/.well-known/mcp/server-card.json"
12532
+ }
12533
+ ] : [];
12534
+ var mcpFiles = async (project, userPages, srcDir, genDir) => {
12535
+ if (!hostsMcp(project, userPages)) {
12536
+ return [];
12537
+ }
12538
+ const { route } = project.config.mcp;
12539
+ const data = await buildMcpData(project);
12540
+ const discoveryInput = {
12541
+ base: data.base,
12542
+ name: data.name,
12543
+ route,
12544
+ site: data.site,
12545
+ version: data.version
12546
+ };
12547
+ return [
12548
+ {
12549
+ content: `${JSON.stringify(data)}
12550
+ `,
12551
+ path: join27(genDir, "mcp-data.json")
12552
+ },
12553
+ {
12554
+ content: mcpEndpointTemplate(route),
12555
+ path: join27(srcDir, "pages", mcpPageFile(route))
12556
+ },
12557
+ {
12558
+ content: staticJsonEndpointTemplate(buildMcpDiscovery(discoveryInput)),
12559
+ path: join27(srcDir, "blume-mcp", "discovery.ts")
12560
+ },
12561
+ {
12562
+ content: staticJsonEndpointTemplate(buildMcpServerCard(discoveryInput)),
12563
+ path: join27(srcDir, "blume-mcp", "server-card.ts")
12564
+ }
12565
+ ];
12566
+ };
12567
+ var changelogFiles = (project, userPages, srcDir, options) => {
12568
+ const hasChangelog = project.graph.pages.some((page) => page.contentType === "changelog" && !(page.meta.draft || page.meta.sidebar.hidden));
12569
+ const hasChangelogSource = (project.config.content.sources ?? []).some((source) => source.type === "github-releases");
12570
+ if (!(hasChangelog || hasChangelogSource) || routeIsTaken(userPages, project.graph.pages, "/changelog")) {
12571
+ return [];
12572
+ }
12573
+ return [
12574
+ {
12575
+ content: changelogIndexTemplate(options),
12576
+ path: join27(srcDir, "pages", "changelog.astro")
12577
+ }
12578
+ ];
12579
+ };
11452
12580
  var readExamplesCss = (root, css) => css && existsSync17(join27(root, css)) ? readFile12(join27(root, css), "utf-8") : Promise.resolve("");
11453
12581
  var examplesPreviewFiles = (srcDir, basePath, hasExamples) => hasExamples ? [
11454
12582
  {
@@ -11497,10 +12625,13 @@ var eject = async (root) => {
11497
12625
  root: "."
11498
12626
  };
11499
12627
  const componentsImport = context.componentsFile ? `../../${toPosix(relative12(root, context.componentsFile))}` : null;
11500
- const relPages = pages.map((page) => ({
11501
- entrypoint: toPosix(relative12(root, page.entrypoint)),
11502
- pattern: page.pattern
11503
- }));
12628
+ const relPages = [
12629
+ ...pages.map((page) => ({
12630
+ entrypoint: toPosix(relative12(root, page.entrypoint)),
12631
+ pattern: page.pattern
12632
+ })),
12633
+ ...mcpDiscoveryPages(project, pages)
12634
+ ];
11504
12635
  const staged = collectStaged(project);
11505
12636
  const hasStaged = staged.size > 0;
11506
12637
  const stagedDir = "blume-staged";
@@ -11572,7 +12703,7 @@ var eject = async (root) => {
11572
12703
  content: tailwindEntryTemplate({
11573
12704
  configTokens: buildThemeCss(config.theme),
11574
12705
  sources: [
11575
- "../../node_modules/blume/src/**/*.{astro,ts,tsx}",
12706
+ blumeSourceGlob(root, genDir),
11576
12707
  "../../**/*.{astro,mdx,ts,tsx}"
11577
12708
  ],
11578
12709
  twoslashCss: twoslashCss(),
@@ -11592,11 +12723,11 @@ var eject = async (root) => {
11592
12723
  path: join27(genDir, "raw-markdown.json")
11593
12724
  },
11594
12725
  {
11595
- content: rawMarkdownEndpointTemplate(),
12726
+ content: rawMarkdownEndpointTemplate("md"),
11596
12727
  path: join27(srcDir, "pages", "[...slug].md.ts")
11597
12728
  },
11598
12729
  {
11599
- content: rawMarkdownEndpointTemplate(),
12730
+ content: rawMarkdownEndpointTemplate("mdx"),
11600
12731
  path: join27(srcDir, "pages", "[...slug].mdx.ts")
11601
12732
  }
11602
12733
  ];
@@ -11609,6 +12740,13 @@ var eject = async (root) => {
11609
12740
  path: join27(srcDir, "pages", "og", "[...slug].png.ts")
11610
12741
  });
11611
12742
  }
12743
+ files.push(...await mcpFiles(project, pages, srcDir, genDir), ...changelogFiles(project, pages, srcDir, {
12744
+ askEnabled,
12745
+ exportEpub,
12746
+ exportPdf,
12747
+ needsReact,
12748
+ staged: hasStaged
12749
+ }));
11612
12750
  if (!routeIsTaken(pages, project.graph.pages, "/404")) {
11613
12751
  files.push({
11614
12752
  content: notFoundPageTemplate(),
@@ -11648,12 +12786,14 @@ var eject = async (root) => {
11648
12786
  path: join27(srcDir, "pages", "[section]", "rss.xml.ts")
11649
12787
  });
11650
12788
  }
12789
+ const warnings = [];
11651
12790
  if (hasScalarReferences(config)) {
11652
12791
  const references = await buildReferenceFiles({
11653
12792
  config,
11654
12793
  contentRoutes: new Set(project.graph.pages.map((page) => page.route)),
11655
12794
  root
11656
12795
  });
12796
+ warnings.push(...references.warnings);
11657
12797
  for (const file of references.files) {
11658
12798
  files.push({
11659
12799
  content: file.content,
@@ -11683,10 +12823,37 @@ var eject = async (root) => {
11683
12823
  });
11684
12824
  }
11685
12825
  await rm3(context.outDir, { force: true, recursive: true });
11686
- return written.map((file) => file.path);
12826
+ return { files: written.map((file) => file.path), warnings };
11687
12827
  };
11688
12828
 
11689
- // src/cli/commands/eject.ts
12829
+ // src/cli/eject-scripts.ts
12830
+ import { readFile as readFile13, writeFile as writeFile9 } from "node:fs/promises";
12831
+ import { join as join28 } from "pathe";
12832
+ var droppedArtifactNotices = (config) => {
12833
+ const notices = [];
12834
+ if (config.search.provider === "pagefind") {
12835
+ notices.push('the Pagefind search index — the search UI loads it from the built site, so search will break in production. Add a post-build step: `"build": "astro build && pagefind --site dist"` (with `pagefind` installed as a devDependency).');
12836
+ }
12837
+ if (searchProviderMeta(config.search.provider).syncs) {
12838
+ notices.push(`the hosted ${config.search.provider} index sync — new and updated pages stop being pushed; re-upload your search records after each build with the provider's API or CLI.`);
12839
+ }
12840
+ if (config.ai.llmsTxt.enabled) {
12841
+ notices.push("llms.txt and llms-full.txt");
12842
+ }
12843
+ if (config.deployment.site && config.seo.sitemap) {
12844
+ notices.push("sitemap.xml — recreate it with the @astrojs/sitemap integration.");
12845
+ }
12846
+ if (config.seo.robots) {
12847
+ notices.push("robots.txt — recreate it as a public/robots.txt file.");
12848
+ }
12849
+ if (config.seo.agentReadability) {
12850
+ notices.push("agent-readability.json");
12851
+ }
12852
+ if (config.redirects.length > 0 && config.deployment.output === "static") {
12853
+ notices.push("the platform redirect files (_redirects, vercel.json) — your redirects still work as Astro-generated meta-refresh pages.");
12854
+ }
12855
+ return notices;
12856
+ };
11690
12857
  var updatePackageScripts = async (root) => {
11691
12858
  const pkgPath = join28(root, "package.json");
11692
12859
  let pkg;
@@ -11705,47 +12872,21 @@ var updatePackageScripts = async (root) => {
11705
12872
  await writeFile9(pkgPath, `${JSON.stringify(pkg, null, 2)}
11706
12873
  `, "utf-8");
11707
12874
  };
11708
- var ejectCommand = defineCommand6({
11709
- args: {
11710
- yes: { description: "Skip the confirmation prompt.", type: "boolean" }
11711
- },
11712
- meta: {
11713
- description: "Promote the generated runtime into an owned Astro project.",
11714
- name: "eject"
11715
- },
11716
- async run({ args }) {
11717
- const root = process.cwd();
11718
- refuseIfDevRunning(root, "ejecting");
11719
- if (!args.yes) {
11720
- logger.warn("Eject is one-way: it writes astro.config.mjs, src/, and (if absent) tsconfig.json, rewrites your package.json scripts, and removes .blume. An existing tsconfig.json is left untouched.");
11721
- logger.info("Re-run with --yes to proceed.");
11722
- return;
11723
- }
11724
- const files = await eject(root);
11725
- await updatePackageScripts(root);
11726
- logger.success(`Ejected ${files.length} file(s):`);
11727
- for (const file of files) {
11728
- process.stdout.write(` ${relative13(root, file)}
11729
- `);
11730
- }
11731
- logger.box(`Your project is now a standalone Astro app.
11732
-
11733
- bun run dev
11734
- bun run build
11735
-
11736
- The blume package remains importable.`);
11737
- }
11738
- });
11739
12875
 
11740
- // src/cli/commands/init.ts
12876
+ // src/cli/init/scaffold.ts
11741
12877
  import { existsSync as existsSync18 } from "node:fs";
11742
12878
  import { mkdir as mkdir8, writeFile as writeFile10 } from "node:fs/promises";
11743
- import { defineCommand as defineCommand7 } from "citty";
11744
- import { basename as basename4, dirname as dirname11, isAbsolute as isAbsolute9, join as join29, relative as relative14 } from "pathe";
12879
+ import { basename as basename4, dirname as dirname11, isAbsolute as isAbsolute9, join as join29, relative as relative13 } from "pathe";
11745
12880
 
11746
12881
  // src/core/package-json.ts
11747
12882
  var toPackageName = (raw) => raw.toLowerCase().replaceAll(/[^a-z0-9._-]+/gu, "-").replaceAll(/^[-_.]+|[-_.]+$/gu, "") || "docs";
11748
- var blumePackageJson = (name) => `{
12883
+ var blumePackageJson = (name, extraDeps = {}) => {
12884
+ const dependencies = Object.entries({
12885
+ blume: `^${getBlumeVersion()}`,
12886
+ ...extraDeps
12887
+ }).toSorted(([a], [b]) => a < b ? -1 : 1).map(([dep, range]) => ` ${JSON.stringify(dep)}: ${JSON.stringify(range)}`).join(`,
12888
+ `);
12889
+ return `{
11749
12890
  "name": ${JSON.stringify(name)},
11750
12891
  "private": true,
11751
12892
  "type": "module",
@@ -11755,21 +12896,22 @@ var blumePackageJson = (name) => `{
11755
12896
  "doctor": "blume doctor"
11756
12897
  },
11757
12898
  "dependencies": {
11758
- "blume": "^${getBlumeVersion()}"
12899
+ ${dependencies}
11759
12900
  }
11760
12901
  }
11761
12902
  `;
12903
+ };
11762
12904
 
11763
- // src/cli/commands/init.ts
12905
+ // src/cli/init/scaffold.ts
11764
12906
  var TEMPLATES = ["docs", "api", "sdk", "changelog"];
11765
12907
  var PACKAGE_MANAGERS = ["npm", "pnpm", "yarn", "bun"];
11766
- var configFor = (extra) => `import { defineConfig } from "blume";
11767
-
11768
- export default defineConfig({
11769
- title: "My Docs",
11770
- description: "Documentation powered by Blume.",${extra}
11771
- });
11772
- `;
12908
+ var SOURCE_KINDS = [
12909
+ "filesystem",
12910
+ "github-releases",
12911
+ "notion",
12912
+ "sanity",
12913
+ "mdx-remote"
12914
+ ];
11773
12915
  var page = (title, description, body) => `---
11774
12916
  title: ${title}
11775
12917
  description: ${description}
@@ -11779,7 +12921,7 @@ ${body}
11779
12921
  `;
11780
12922
  var STARTERS = {
11781
12923
  api: {
11782
- config: configFor(`
12924
+ configExtra: `
11783
12925
  openapi: {
11784
12926
  enabled: true,
11785
12927
  route: "/api",
@@ -11789,7 +12931,7 @@ var STARTERS = {
11789
12931
  spec: "https://petstore3.swagger.io/api/v3/openapi.json",
11790
12932
  },
11791
12933
  ],
11792
- },`),
12934
+ },`,
11793
12935
  files: (dir) => [
11794
12936
  {
11795
12937
  content: page("API Reference", "Explore the API.", "# API Reference\n\nYour OpenAPI spec renders at [`/api`](/api). Point `openapi.sources` at your own spec in `blume.config.ts`."),
@@ -11798,13 +12940,13 @@ var STARTERS = {
11798
12940
  ]
11799
12941
  },
11800
12942
  changelog: {
11801
- config: configFor(`
12943
+ configExtra: `
11802
12944
  navigation: {
11803
12945
  tabs: [
11804
12946
  { label: "Docs", path: "/" },
11805
12947
  { label: "Changelog", path: "/changelog" },
11806
12948
  ],
11807
- },`),
12949
+ },`,
11808
12950
  files: (dir) => [
11809
12951
  {
11810
12952
  content: page("Introduction", "Welcome to your new Blume docs.", "# Introduction\n\nWrite your docs here, and log releases under `changelog/`."),
@@ -11824,7 +12966,7 @@ The first release. Edit \`${dir}/changelog/v1-0-0.mdx\` or add new entries besid
11824
12966
  ]
11825
12967
  },
11826
12968
  docs: {
11827
- config: configFor(""),
12969
+ configExtra: "",
11828
12970
  files: (dir) => [
11829
12971
  {
11830
12972
  content: page("Introduction", "Welcome to your new Blume docs.", `# Introduction
@@ -11837,7 +12979,7 @@ Edit \`${dir}/index.mdx\` to get started, then run \`blume dev\`.`),
11837
12979
  ]
11838
12980
  },
11839
12981
  sdk: {
11840
- config: configFor(""),
12982
+ configExtra: "",
11841
12983
  files: (dir) => [
11842
12984
  {
11843
12985
  content: page("Introduction", "Get started with the SDK.", `# Introduction
@@ -11853,26 +12995,331 @@ Install the SDK and make your first call. See [Installation](/installation).`),
11853
12995
  }
11854
12996
  };
11855
12997
  var commandsFor = (pm) => ({
12998
+ build: pm === "npm" || pm === "bun" ? `${pm} run build` : `${pm} build`,
11856
12999
  dev: pm === "npm" ? "npm run dev" : `${pm} dev`,
13000
+ exec: { bun: "bunx", npm: "npx", pnpm: "pnpm exec", yarn: "yarn" }[pm],
11857
13001
  install: `${pm} install`
11858
13002
  });
11859
- var writeFileSafe = async (path, content) => {
11860
- if (existsSync18(path)) {
11861
- logger.info(`Skipped existing ${path}`);
13003
+ var detectPackageManager = (userAgent) => {
13004
+ const name = userAgent?.split("/")[0];
13005
+ return name !== undefined && PACKAGE_MANAGERS.includes(name) ? name : "npm";
13006
+ };
13007
+ var validateContentDir = (root, dir) => isAbsolute9(dir) || relative13(root, join29(root, dir)).startsWith("..") ? "Must be a relative path inside the project." : undefined;
13008
+ var titleize = (raw) => {
13009
+ const words = raw.replaceAll(/[-_.]+/gu, " ").split(/\s+/u).filter(Boolean);
13010
+ return words.length === 0 ? "My Docs" : words.map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
13011
+ };
13012
+ var hasRemoteSource = (sources) => sources.some((source) => source !== "filesystem");
13013
+ var sourceSnippetFor = (kind) => {
13014
+ switch (kind) {
13015
+ case "github-releases": {
13016
+ return ` // Changelog entries from GitHub Releases. Private repos read
13017
+ // GITHUB_TOKEN from the environment.
13018
+ {
13019
+ type: "github-releases",
13020
+ owner: "your-org",
13021
+ repo: "your-repo",
13022
+ prefix: "changelog",
13023
+ },`;
13024
+ }
13025
+ case "notion": {
13026
+ return ` // Pages from a Notion database. Reads NOTION_TOKEN from the environment.
13027
+ {
13028
+ type: "notion",
13029
+ database: "your-database-id",
13030
+ prefix: "notion",
13031
+ },`;
13032
+ }
13033
+ case "sanity": {
13034
+ return ` // Documents from a Sanity dataset. Private datasets read SANITY_TOKEN
13035
+ // from the environment.
13036
+ {
13037
+ type: "sanity",
13038
+ projectId: "your-project-id",
13039
+ dataset: "production",
13040
+ query: \`*[_type == "doc"]\`,
13041
+ prefix: "sanity",
13042
+ },`;
13043
+ }
13044
+ case "mdx-remote": {
13045
+ return ` // MDX fetched from a GitHub repo. Private repos read GITHUB_TOKEN
13046
+ // from the environment.
13047
+ {
13048
+ type: "mdx-remote",
13049
+ github: { owner: "your-org", repo: "your-repo", path: "docs" },
13050
+ prefix: "remote",
13051
+ },`;
13052
+ }
13053
+ default: {
13054
+ return kind;
13055
+ }
13056
+ }
13057
+ };
13058
+ var contentBlockFor = (answers) => {
13059
+ const sources = answers.sources.length === 0 ? ["filesystem"] : answers.sources;
13060
+ if (!hasRemoteSource(sources)) {
13061
+ return answers.contentDir === "docs" ? "" : `
13062
+ content: {
13063
+ root: ${JSON.stringify(answers.contentDir)},
13064
+ },`;
13065
+ }
13066
+ const entries = SOURCE_KINDS.filter((kind) => sources.includes(kind)).map((kind) => kind === "filesystem" ? ` { type: "filesystem", root: ${JSON.stringify(answers.contentDir)} },` : sourceSnippetFor(kind));
13067
+ return `
13068
+ content: {
13069
+ sources: [
13070
+ ${entries.join(`
13071
+ `)}
13072
+ ],
13073
+ },`;
13074
+ };
13075
+ var buildConfig = (answers) => `import { defineConfig } from "blume";
13076
+
13077
+ export default defineConfig({
13078
+ title: ${JSON.stringify(answers.title)},
13079
+ description: "Documentation powered by Blume.",${STARTERS[answers.template].configExtra}${contentBlockFor(answers)}
13080
+ });
13081
+ `;
13082
+ var extraDepsFor = (sources) => ({
13083
+ ...sources.includes("notion") && { "@notionhq/client": "^2.2.15" },
13084
+ ...sources.includes("sanity") && { "@sanity/client": "^6.21.0" }
13085
+ });
13086
+ var buildPlan = (root, answers) => {
13087
+ const files = [
13088
+ {
13089
+ content: blumePackageJson(toPackageName(basename4(root)), extraDepsFor(answers.sources)),
13090
+ path: join29(root, "package.json")
13091
+ },
13092
+ { content: buildConfig(answers), path: join29(root, "blume.config.ts") }
13093
+ ];
13094
+ if (answers.sources.length === 0 || answers.sources.includes("filesystem")) {
13095
+ files.push(...STARTERS[answers.template].files(answers.contentDir).map((file) => ({ ...file, path: join29(root, file.path) })));
13096
+ }
13097
+ return files;
13098
+ };
13099
+ var writeFileSafe = async (file, log) => {
13100
+ if (existsSync18(file.path)) {
13101
+ log.info(`Skipped existing ${file.path}`);
11862
13102
  return false;
11863
13103
  }
11864
- await mkdir8(dirname11(path), { recursive: true });
11865
- await writeFile10(path, content, "utf-8");
11866
- logger.success(`Created ${path}`);
13104
+ await mkdir8(dirname11(file.path), { recursive: true });
13105
+ await writeFile10(file.path, file.content, "utf-8");
13106
+ log.success(`Created ${file.path}`);
11867
13107
  return true;
11868
13108
  };
13109
+ var applyPlan = async (files, log) => {
13110
+ const created = await Promise.all(files.map((file) => writeFileSafe(file, log)));
13111
+ const createdPackage = files.some((file, index) => created[index] && basename4(file.path) === "package.json");
13112
+ return { createdPackage };
13113
+ };
13114
+ var envVarsFor = (sources) => [
13115
+ ["GITHUB_TOKEN", ["github-releases", "mdx-remote"]],
13116
+ ["NOTION_TOKEN", ["notion"]],
13117
+ ["SANITY_TOKEN", ["sanity"]]
13118
+ ].filter(([, kinds]) => kinds.some((kind) => sources.includes(kind))).map(([envVar]) => envVar);
13119
+ var nextSteps = (answers, createdPackage) => {
13120
+ const commands = commandsFor(answers.packageManager);
13121
+ const lines = [];
13122
+ if (answers.directory !== ".") {
13123
+ lines.push(`cd ${answers.directory}`);
13124
+ }
13125
+ if (createdPackage) {
13126
+ lines.push(commands.install);
13127
+ }
13128
+ lines.push(commands.dev);
13129
+ const envVars = envVarsFor(answers.sources);
13130
+ const auth = envVars.length > 0 ? `
13131
+ Set ${envVars.join(" and ")} in .env.local so your sources can authenticate.
13132
+ ` : "";
13133
+ return `Next steps:
13134
+
13135
+ ${lines.join(`
13136
+ `)}
13137
+ ${auth}`;
13138
+ };
13139
+
13140
+ // src/cli/commands/eject.ts
13141
+ var reportDroppedArtifacts = (notices) => {
13142
+ if (notices.length === 0) {
13143
+ return;
13144
+ }
13145
+ logger.warn([
13146
+ "The ejected build script runs plain `astro build`, which stops producing these `blume build` artifacts:",
13147
+ ...notices.map((notice) => ` - ${notice}`)
13148
+ ].join(`
13149
+ `));
13150
+ };
13151
+ var ejectCommand = defineCommand6({
13152
+ args: {
13153
+ yes: { description: "Skip the confirmation prompt.", type: "boolean" }
13154
+ },
13155
+ meta: {
13156
+ description: "Promote the generated runtime into an owned Astro project.",
13157
+ name: "eject"
13158
+ },
13159
+ async run({ args }) {
13160
+ const root = process.cwd();
13161
+ refuseIfDevRunning(root, "ejecting");
13162
+ let notices = [];
13163
+ try {
13164
+ const { config } = await loadConfig(root);
13165
+ notices = droppedArtifactNotices(config);
13166
+ } catch {}
13167
+ if (!args.yes) {
13168
+ logger.warn("Eject is one-way: it writes astro.config.mjs, src/, and (if absent) tsconfig.json, rewrites your package.json scripts, and removes .blume. An existing tsconfig.json is left untouched.");
13169
+ reportDroppedArtifacts(notices);
13170
+ logger.info("Re-run with --yes to proceed.");
13171
+ return;
13172
+ }
13173
+ const { files, warnings } = await eject(root);
13174
+ await updatePackageScripts(root);
13175
+ for (const warning of warnings) {
13176
+ logger.warn(warning);
13177
+ }
13178
+ logger.success(`Ejected ${files.length} file(s):`);
13179
+ for (const file of files) {
13180
+ process.stdout.write(` ${relative14(root, file)}
13181
+ `);
13182
+ }
13183
+ reportDroppedArtifacts(notices);
13184
+ const pm = detectPackageManager(process.env.npm_config_user_agent);
13185
+ const { build: build2, dev: dev2 } = commandsFor(pm);
13186
+ logger.box(`Your project is now a standalone Astro app.
13187
+
13188
+ ${dev2}
13189
+ ${build2}
13190
+
13191
+ The blume package remains importable.`);
13192
+ }
13193
+ });
13194
+
13195
+ // src/cli/commands/init.ts
13196
+ import * as clack from "@clack/prompts";
13197
+ import { defineCommand as defineCommand7 } from "citty";
13198
+ import { resolve as resolve9 } from "pathe";
13199
+
13200
+ // src/cli/init/questions.ts
13201
+ import { basename as basename5, resolve as resolve8 } from "pathe";
13202
+ var cancelled = (value) => typeof value === "symbol";
13203
+ var collectAnswers = async (prompter, flags, defaults) => {
13204
+ const directory = flags.directory ?? await prompter.text({
13205
+ defaultValue: ".",
13206
+ message: "Where should we create your project?",
13207
+ placeholder: "./my-docs"
13208
+ });
13209
+ if (cancelled(directory)) {
13210
+ return null;
13211
+ }
13212
+ const root = resolve8(defaults.cwd, directory);
13213
+ const title = await prompter.text({
13214
+ initialValue: titleize(basename5(root)),
13215
+ message: "What's your docs site called?",
13216
+ validate: (value) => value?.trim() ? undefined : "Give your docs site a name."
13217
+ });
13218
+ if (cancelled(title)) {
13219
+ return null;
13220
+ }
13221
+ const template = flags.template ?? await prompter.select({
13222
+ message: "Which template?",
13223
+ options: [
13224
+ { hint: "Markdown docs site", label: "docs", value: "docs" },
13225
+ { hint: "OpenAPI reference at /api", label: "api", value: "api" },
13226
+ { hint: "SDK docs with an install page", label: "sdk", value: "sdk" },
13227
+ {
13228
+ hint: "Docs plus a changelog tab",
13229
+ label: "changelog",
13230
+ value: "changelog"
13231
+ }
13232
+ ]
13233
+ });
13234
+ if (cancelled(template)) {
13235
+ return null;
13236
+ }
13237
+ const picked = await prompter.multiselect({
13238
+ initialValues: ["filesystem"],
13239
+ message: "Where does your content live?",
13240
+ options: [
13241
+ { hint: "Local .mdx files", label: "filesystem", value: "filesystem" },
13242
+ {
13243
+ hint: "Changelog from GitHub Releases",
13244
+ label: "github-releases",
13245
+ value: "github-releases"
13246
+ },
13247
+ { hint: "A Notion database", label: "notion", value: "notion" },
13248
+ { hint: "A Sanity dataset", label: "sanity", value: "sanity" },
13249
+ {
13250
+ hint: "MDX fetched from a GitHub repo",
13251
+ label: "mdx-remote",
13252
+ value: "mdx-remote"
13253
+ }
13254
+ ],
13255
+ required: false
13256
+ });
13257
+ if (cancelled(picked)) {
13258
+ return null;
13259
+ }
13260
+ const sources = picked.length === 0 ? ["filesystem"] : picked;
13261
+ let contentDir = flags.contentDir ?? "docs";
13262
+ if (flags.contentDir === undefined && sources.includes("filesystem")) {
13263
+ const answer = await prompter.text({
13264
+ defaultValue: "docs",
13265
+ message: "Content directory?",
13266
+ placeholder: "docs",
13267
+ validate: (value) => validateContentDir(root, value || "docs")
13268
+ });
13269
+ if (cancelled(answer)) {
13270
+ return null;
13271
+ }
13272
+ contentDir = answer;
13273
+ }
13274
+ return {
13275
+ contentDir,
13276
+ directory,
13277
+ packageManager: flags.packageManager ?? detectPackageManager(defaults.userAgent),
13278
+ sources,
13279
+ template,
13280
+ title
13281
+ };
13282
+ };
13283
+
13284
+ // src/cli/commands/init.ts
13285
+ var ejectScaffold = async (root, answers) => {
13286
+ const commands = commandsFor(answers.packageManager);
13287
+ const cd = answers.directory === "." ? [] : [`cd ${answers.directory}`];
13288
+ try {
13289
+ await eject(root);
13290
+ await updatePackageScripts(root);
13291
+ logger.success("Ejected to a standalone Astro project.");
13292
+ const steps2 = [...cd, commands.install, commands.dev];
13293
+ logger.box(`Next steps:
13294
+
13295
+ ${steps2.join(`
13296
+ `)}
13297
+ `);
13298
+ } catch (error) {
13299
+ logger.warn(`Scaffolded, but eject needs the project's dependencies installed to load blume.config.ts: ${error.message}`);
13300
+ const steps2 = [
13301
+ ...cd,
13302
+ commands.install,
13303
+ `${commands.exec} blume eject --yes`
13304
+ ];
13305
+ logger.box(`Next steps:
13306
+
13307
+ ${steps2.join(`
13308
+ `)}
13309
+ `);
13310
+ }
13311
+ };
11869
13312
  var initCommand = defineCommand7({
11870
13313
  args: {
11871
13314
  "content-dir": {
11872
- default: "docs",
11873
13315
  description: "Content directory.",
11874
13316
  type: "string"
11875
13317
  },
13318
+ dir: {
13319
+ description: "Directory to scaffold into (default: current directory).",
13320
+ required: false,
13321
+ type: "positional"
13322
+ },
11876
13323
  eject: {
11877
13324
  description: "Eject to a standalone Astro project after scaffolding.",
11878
13325
  type: "boolean"
@@ -11885,66 +13332,74 @@ var initCommand = defineCommand7({
11885
13332
  description: "Starter template: docs | api | sdk | changelog.",
11886
13333
  type: "string"
11887
13334
  },
11888
- yes: { description: "Skip prompts.", type: "boolean" }
13335
+ yes: {
13336
+ description: "Skip prompts and scaffold with defaults.",
13337
+ type: "boolean"
13338
+ }
11889
13339
  },
11890
13340
  meta: {
11891
13341
  description: "Scaffold a minimal Blume project.",
11892
13342
  name: "init"
11893
13343
  },
11894
13344
  async run({ args }) {
11895
- const root = process.cwd();
11896
- const contentDir = args["content-dir"] ?? "docs";
11897
- if (isAbsolute9(contentDir) || relative14(root, join29(root, contentDir)).startsWith("..")) {
11898
- logger.error(`Invalid --content-dir "${contentDir}" (must be a path inside the project).`);
11899
- process.exit(1);
11900
- }
11901
- const template = args.template ?? "docs";
11902
- if (!TEMPLATES.includes(template)) {
13345
+ const cwd = process.cwd();
13346
+ const template = args.template;
13347
+ if (template !== undefined && !TEMPLATES.includes(template)) {
11903
13348
  logger.error(`Unknown template "${args.template}" (use ${TEMPLATES.join(" | ")}).`);
11904
13349
  process.exit(1);
11905
13350
  }
11906
- const pm = args["package-manager"] ?? "npm";
11907
- if (!PACKAGE_MANAGERS.includes(pm)) {
13351
+ const pm = args["package-manager"];
13352
+ if (pm !== undefined && !PACKAGE_MANAGERS.includes(pm)) {
11908
13353
  logger.error(`Unknown package manager "${args["package-manager"]}" (use ${PACKAGE_MANAGERS.join(" | ")}).`);
11909
13354
  process.exit(1);
11910
13355
  }
11911
- const starter = STARTERS[template];
11912
- const createdPackage = await writeFileSafe(join29(root, "package.json"), blumePackageJson(toPackageName(basename4(root))));
11913
- await writeFileSafe(join29(root, "blume.config.ts"), starter.config);
11914
- await Promise.all(starter.files(contentDir).map((file) => writeFileSafe(join29(root, file.path), file.content)));
13356
+ const interactive = !args.yes && process.stdin.isTTY === true && clack.isTTY(process.stdout) && !clack.isCI();
13357
+ let answers;
13358
+ if (interactive) {
13359
+ clack.intro("blume init");
13360
+ const collected = await collectAnswers(clack, {
13361
+ contentDir: args["content-dir"],
13362
+ directory: args.dir,
13363
+ packageManager: pm,
13364
+ template
13365
+ }, { cwd, userAgent: process.env.npm_config_user_agent });
13366
+ if (collected === null) {
13367
+ clack.cancel("Cancelled — nothing was written.");
13368
+ process.exit(0);
13369
+ }
13370
+ answers = collected;
13371
+ } else {
13372
+ answers = {
13373
+ contentDir: args["content-dir"] ?? "docs",
13374
+ directory: args.dir ?? ".",
13375
+ packageManager: pm ?? detectPackageManager(process.env.npm_config_user_agent),
13376
+ sources: ["filesystem"],
13377
+ template: template ?? "docs",
13378
+ title: "My Docs"
13379
+ };
13380
+ }
13381
+ const root = resolve9(cwd, answers.directory);
13382
+ if (validateContentDir(root, answers.contentDir) !== undefined) {
13383
+ logger.error(`Invalid --content-dir "${answers.contentDir}" (must be a path inside the project).`);
13384
+ process.exit(1);
13385
+ }
13386
+ const sink = interactive ? clack.log : logger;
13387
+ const { createdPackage } = await applyPlan(buildPlan(root, answers), sink);
11915
13388
  const ignored = await ensureGitignore(root, [".blume/", "dist/"]);
11916
13389
  if (ignored.length > 0) {
11917
- logger.success(`Added ${ignored.join(", ")} to .gitignore`);
13390
+ sink.success(`Added ${ignored.join(", ")} to .gitignore`);
11918
13391
  }
11919
- const commands = commandsFor(pm);
11920
13392
  if (args.eject) {
11921
- try {
11922
- await eject(root);
11923
- logger.success("Ejected to a standalone Astro project.");
11924
- logger.box(`Next steps:
11925
-
11926
- ${commands.install}
11927
- npx astro dev
11928
- `);
11929
- } catch (error) {
11930
- logger.warn(`Scaffolded, but eject failed: ${error.message}`);
11931
- logger.box(`Next steps:
11932
-
11933
- ${commands.install}
11934
- blume eject --yes
11935
- `);
11936
- }
13393
+ await ejectScaffold(root, answers);
11937
13394
  return;
11938
13395
  }
11939
- const nextSteps = createdPackage ? `Next steps:
11940
-
11941
- ${commands.install}
11942
- ${commands.dev}
11943
- ` : `Next steps:
11944
-
11945
- ${commands.dev}
11946
- `;
11947
- logger.box(nextSteps);
13396
+ const steps2 = nextSteps(answers, createdPackage);
13397
+ if (interactive) {
13398
+ clack.note(steps2.trimEnd());
13399
+ clack.outro("You're all set.");
13400
+ } else {
13401
+ logger.box(steps2);
13402
+ }
11948
13403
  }
11949
13404
  });
11950
13405
 
@@ -11974,7 +13429,7 @@ var previewCommand = defineCommand8({
11974
13429
  logLevel: "info",
11975
13430
  root: context.outDir,
11976
13431
  server: {
11977
- host: args.host ?? false,
13432
+ host: normalizeHost(args.host),
11978
13433
  port: parsePort(args.port)
11979
13434
  }
11980
13435
  });
@@ -12030,7 +13485,7 @@ import { join as join33 } from "pathe";
12030
13485
 
12031
13486
  // src/core/links.ts
12032
13487
  import { existsSync as existsSync20 } from "node:fs";
12033
- import { basename as basename5, join as join32 } from "pathe";
13488
+ import { basename as basename6, join as join32 } from "pathe";
12034
13489
  var HTTP = /^https?:\/\//iu;
12035
13490
  var PROTOCOL_RELATIVE = /^\/\//u;
12036
13491
  var SCHEME = /^[a-z][a-z0-9+.-]*:/iu;
@@ -12050,10 +13505,8 @@ var STATUS_GONE = 410;
12050
13505
  var STATUS_METHOD_NOT_ALLOWED = 405;
12051
13506
  var STATUS_NOT_IMPLEMENTED = 501;
12052
13507
  var assetIsPresent = (resolved, ctx) => ctx.publicDir !== null && existsSync20(join32(ctx.publicDir, resolved));
12053
- var isIndexPage = (page2) => {
12054
- const ref = page2.source?.ref ?? page2.sourcePath ?? "";
12055
- return /^index\.(?:md|mdx)$/iu.test(basename5(ref));
12056
- };
13508
+ var NUMERIC_PREFIX3 = /^\d+[-_.]/u;
13509
+ var isIndexPage = (page2) => /^index\.(?:md|mdx)$/iu.test(basename6(page2.navPath).replace(NUMERIC_PREFIX3, ""));
12057
13510
  var applyRelativePart = (segments, part) => {
12058
13511
  if (part === "" || part === ".") {
12059
13512
  return;
@@ -12107,6 +13560,9 @@ var checkPathLink = (resolved, fragment, target, site, ctx) => {
12107
13560
  if (ctx.routes.has(route)) {
12108
13561
  return fragment ? checkAnchor(route, fragment, site, ctx) : null;
12109
13562
  }
13563
+ if (ctx.extraRoutes.has(route)) {
13564
+ return null;
13565
+ }
12110
13566
  if (ctx.redirects.has(route)) {
12111
13567
  return null;
12112
13568
  }
@@ -12244,6 +13700,7 @@ var validateLinks = async (graph, options) => {
12244
13700
  const ctx = {
12245
13701
  anchors: buildAnchorIndex(graph.pages),
12246
13702
  basePath,
13703
+ extraRoutes: new Set((options.extraRoutes ?? []).map(toRoute)),
12247
13704
  publicDir: options.publicDir,
12248
13705
  redirects: new Set((options.redirects ?? []).map((redirect) => toRoute(withBasePath(basePath, redirect.from)))),
12249
13706
  routes: new Set(graph.routes.keys())
@@ -12300,10 +13757,24 @@ var validateCommand = defineCommand10({
12300
13757
  try {
12301
13758
  const project = await scanProject(root, { mode: "build" });
12302
13759
  diagnostics.push(...project.diagnostics);
13760
+ const userPages = project.context.pagesRoot ? await discoverPages(project.context.pagesRoot) : [];
13761
+ const extraRoutes = customStaticRoutes(userPages);
13762
+ if (hasGeneratedChangelog(project, userPages)) {
13763
+ extraRoutes.push("/changelog");
13764
+ }
13765
+ if (project.config.i18n) {
13766
+ const manifest = buildManifest({
13767
+ config: project.config,
13768
+ context: project.context,
13769
+ graph: project.graph
13770
+ });
13771
+ extraRoutes.push(...manifest.routes.flatMap((route) => route.fallback ? [route.path] : []));
13772
+ }
12303
13773
  const publicDir = join33(root, "public");
12304
13774
  diagnostics.push(...await validateLinks(project.graph, {
12305
13775
  basePath: project.config.basePath,
12306
13776
  checkExternal: Boolean(args.external),
13777
+ extraRoutes,
12307
13778
  publicDir: existsSync21(publicDir) ? publicDir : null,
12308
13779
  redirects: project.config.redirects
12309
13780
  }));
@@ -12315,9 +13786,10 @@ var validateCommand = defineCommand10({
12315
13786
  process.exit(1);
12316
13787
  }
12317
13788
  }
13789
+ const strictFailure = Boolean(args.strict) && diagnostics.some((diagnostic) => diagnostic.severity !== "info");
12318
13790
  if (args.json) {
12319
13791
  const hadErrors2 = reportDiagnosticsJson(diagnostics, root);
12320
- if (hadErrors2 || Boolean(args.strict) && diagnostics.length > 0) {
13792
+ if (hadErrors2 || strictFailure) {
12321
13793
  await flushStdout();
12322
13794
  process.exit(1);
12323
13795
  }
@@ -12327,7 +13799,7 @@ var validateCommand = defineCommand10({
12327
13799
  if (diagnostics.length === 0) {
12328
13800
  logger.success("No broken links found.");
12329
13801
  }
12330
- if (hadErrors || Boolean(args.strict) && diagnostics.length > 0) {
13802
+ if (hadErrors || strictFailure) {
12331
13803
  process.exit(1);
12332
13804
  }
12333
13805
  }
@@ -12364,5 +13836,5 @@ process.on("unhandledRejection", (error) => {
12364
13836
  });
12365
13837
  runMain(main);
12366
13838
 
12367
- //# debugId=8E29DA45BB5B37C564756E2164756E21
13839
+ //# debugId=066FB7264FFC708364756E2164756E21
12368
13840
  //# sourceMappingURL=index.js.map