blume 2.0.0 → 2.0.1

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 (203) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +28 -0
  3. package/README.md +2 -2
  4. package/dist/cli/{chunk-f7t03s3g.js → chunk-27g6wdth.js} +2 -2
  5. package/dist/cli/{chunk-mnqj32sj.js → chunk-2hn4b8z7.js} +13 -13
  6. package/dist/cli/chunk-2hn4b8z7.js.map +12 -0
  7. package/dist/cli/{chunk-by2290sx.js → chunk-5shv93fd.js} +2 -2
  8. package/dist/cli/{chunk-a9kptbw5.js → chunk-6crbhc3x.js} +3 -3
  9. package/dist/cli/{chunk-a9kptbw5.js.map → chunk-6crbhc3x.js.map} +1 -1
  10. package/dist/cli/{chunk-mwt1k8n7.js → chunk-6hsn950k.js} +20 -20
  11. package/dist/cli/chunk-6hsn950k.js.map +10 -0
  12. package/dist/cli/{chunk-11j0384y.js → chunk-6vm74dry.js} +13 -13
  13. package/dist/cli/{chunk-11j0384y.js.map → chunk-6vm74dry.js.map} +3 -3
  14. package/dist/cli/{chunk-j8mw0za6.js → chunk-79jhk4py.js} +8 -8
  15. package/dist/cli/{chunk-j8mw0za6.js.map → chunk-79jhk4py.js.map} +2 -2
  16. package/dist/cli/{chunk-nk3ts2xk.js → chunk-82bbrxdn.js} +2 -2
  17. package/dist/cli/{chunk-2q1dwty4.js → chunk-ah61y8py.js} +8 -8
  18. package/dist/cli/{chunk-2q1dwty4.js.map → chunk-ah61y8py.js.map} +4 -4
  19. package/dist/cli/{chunk-zxccj738.js → chunk-ce574jw2.js} +1 -1
  20. package/dist/cli/{chunk-y3e45rc8.js → chunk-ch6g3ar0.js} +3 -3
  21. package/dist/cli/{chunk-beat36xx.js → chunk-dh8cwk36.js} +5 -5
  22. package/dist/cli/{chunk-beat36xx.js.map → chunk-dh8cwk36.js.map} +2 -2
  23. package/dist/cli/{chunk-1w8dp3qb.js → chunk-epjnccmv.js} +13 -13
  24. package/dist/cli/{chunk-ernrthtr.js → chunk-f2z5v128.js} +13 -13
  25. package/dist/cli/{chunk-zg2gtj10.js → chunk-fs23ddbb.js} +2 -2
  26. package/dist/cli/{chunk-7ez8ny0t.js → chunk-fxypxtvm.js} +2 -2
  27. package/dist/cli/{chunk-tzne8qfq.js → chunk-fz5wtpmh.js} +13 -13
  28. package/dist/cli/{chunk-b5aj94ah.js → chunk-hdpx1tax.js} +4 -4
  29. package/dist/cli/{chunk-d80hr03s.js → chunk-jwyddg7y.js} +9 -9
  30. package/dist/cli/{chunk-d80hr03s.js.map → chunk-jwyddg7y.js.map} +2 -2
  31. package/dist/cli/{chunk-fh5hj5jt.js → chunk-kdp5q7ke.js} +15 -15
  32. package/dist/cli/{chunk-6k8vp3ta.js → chunk-kpf8rrjc.js} +9 -9
  33. package/dist/cli/{chunk-6k8vp3ta.js.map → chunk-kpf8rrjc.js.map} +3 -3
  34. package/dist/cli/{chunk-5a2z0198.js → chunk-m3vmjgmq.js} +9 -9
  35. package/dist/cli/{chunk-5a2z0198.js.map → chunk-m3vmjgmq.js.map} +2 -2
  36. package/dist/cli/{chunk-bctazmbk.js → chunk-mb2919y2.js} +4 -4
  37. package/dist/cli/{chunk-79njf86q.js → chunk-q5163e60.js} +13 -13
  38. package/dist/cli/{chunk-xaz13gwg.js → chunk-qkqwkpte.js} +196 -208
  39. package/dist/cli/{chunk-xaz13gwg.js.map → chunk-qkqwkpte.js.map} +48 -48
  40. package/dist/cli/{chunk-sqn5t4q0.js → chunk-qs4q5p4e.js} +3 -3
  41. package/dist/cli/{chunk-bw22s759.js → chunk-qwsrynx5.js} +1 -1
  42. package/dist/cli/{chunk-z01ze5c1.js → chunk-s1p84fyh.js} +15 -15
  43. package/dist/cli/{chunk-pnnvybbk.js → chunk-s6jhgk0q.js} +5 -5
  44. package/dist/cli/{chunk-pnnvybbk.js.map → chunk-s6jhgk0q.js.map} +2 -2
  45. package/dist/cli/{chunk-f2972sbt.js → chunk-vtk4a6dg.js} +1 -1
  46. package/dist/cli/{chunk-z1f5arsg.js → chunk-wgm7m9qk.js} +21 -21
  47. package/dist/cli/{chunk-z1f5arsg.js.map → chunk-wgm7m9qk.js.map} +4 -4
  48. package/dist/cli/{chunk-bnbmcwfb.js → chunk-wm7js3j9.js} +5 -5
  49. package/dist/cli/{chunk-bnbmcwfb.js.map → chunk-wm7js3j9.js.map} +3 -3
  50. package/dist/cli/{chunk-d1tadaw7.js → chunk-yt5n7ppj.js} +3 -3
  51. package/dist/cli/{chunk-pat2zzwc.js → chunk-yw7dm696.js} +1 -1
  52. package/dist/cli/{chunk-pat2zzwc.js.map → chunk-yw7dm696.js.map} +1 -1
  53. package/dist/cli/{chunk-88cpgt6h.js → chunk-zxcczpyx.js} +1 -1
  54. package/dist/cli/{chunk-41za066z.js → chunk-zxh4d9vy.js} +4 -4
  55. package/dist/cli/index.js +17 -17
  56. package/dist/types/ai/agent-readability.d.ts +1 -1
  57. package/dist/types/ai/api/paths.d.ts +1 -1
  58. package/dist/types/ai/ask-context.d.ts +7 -7
  59. package/dist/types/ai/ask.d.ts +43 -43
  60. package/dist/types/ai/index.d.ts +3 -3
  61. package/dist/types/ai/openapi-components.d.ts +1 -1
  62. package/dist/types/ai/serializers.d.ts +1 -1
  63. package/dist/types/ai/visibility.d.ts +1 -1
  64. package/dist/types/core/config-input.d.ts +17 -17
  65. package/dist/types/core/config.d.ts +3 -3
  66. package/dist/types/core/data.d.ts +3 -3
  67. package/dist/types/core/i18n-ui.d.ts +6 -8
  68. package/dist/types/core/schema.d.ts +5 -5
  69. package/dist/types/core/unrecognized-keys.d.ts +1 -1
  70. package/dist/types/search/documents.d.ts +1 -1
  71. package/dist/types/search/orama-index.d.ts +1 -1
  72. package/docs/02-deployment.mdx +4 -4
  73. package/docs/03-upgrading.mdx +22 -9
  74. package/docs/04-migrating.mdx +4 -4
  75. package/docs/08-faq.mdx +3 -3
  76. package/docs/advanced/custom-pages.mdx +2 -2
  77. package/docs/advanced/skills.mdx +1 -1
  78. package/docs/cli/audit.mdx +3 -3
  79. package/docs/cli/doctor.mdx +3 -3
  80. package/docs/cli/evals.mdx +7 -7
  81. package/docs/cli/index.mdx +2 -2
  82. package/docs/cli/translate.mdx +8 -8
  83. package/docs/configuration/{ask-ai.mdx → assistant.mdx} +19 -19
  84. package/docs/configuration/customization.mdx +2 -2
  85. package/docs/configuration/index.mdx +2 -2
  86. package/docs/configuration/meta.ts +1 -1
  87. package/docs/configuration/search.mdx +1 -1
  88. package/docs/content/i18n.mdx +2 -2
  89. package/docs/content/islands.mdx +1 -1
  90. package/docs/discoverability/agent-discovery.mdx +1 -1
  91. package/docs/discoverability/index.mdx +1 -1
  92. package/docs/index.mdx +2 -2
  93. package/package.json +1 -1
  94. package/skills/blume/SKILL.md +5 -5
  95. package/skills/blume-migrate/SKILL.md +2 -2
  96. package/src/ai/agent-readability.ts +4 -4
  97. package/src/ai/api/paths.ts +1 -1
  98. package/src/ai/ask-context.ts +7 -7
  99. package/src/ai/ask-data.ts +2 -2
  100. package/src/ai/ask.ts +84 -71
  101. package/src/ai/cors.ts +3 -3
  102. package/src/ai/index.ts +16 -16
  103. package/src/ai/openapi-components.ts +1 -1
  104. package/src/ai/serializers.ts +1 -1
  105. package/src/ai/visibility.ts +1 -1
  106. package/src/astro/generate.ts +19 -18
  107. package/src/astro/module-types.ts +1 -1
  108. package/src/astro/runtime-deps.ts +6 -6
  109. package/src/astro/templates.ts +26 -26
  110. package/src/blume-modules.d.ts +2 -2
  111. package/src/cli/commands/audit.ts +1 -1
  112. package/src/cli/commands/doctor.ts +7 -5
  113. package/src/cli/commands/eval.ts +3 -3
  114. package/src/cli/commands/migrate.ts +2 -2
  115. package/src/cli/commands/translate.ts +3 -3
  116. package/src/cli/commands/upgrade.ts +2 -2
  117. package/src/cli/required-secrets.ts +3 -3
  118. package/src/components/copy-feedback.ts +1 -1
  119. package/src/components/islands/{AskAI.astro → Assistant.astro} +10 -10
  120. package/src/components/islands/{ask-ai.tsx → assistant.tsx} +23 -23
  121. package/src/components/islands/hooks.ts +14 -12
  122. package/src/components/layout/Header.astro +10 -10
  123. package/src/components/layout/PageLayout.astro +6 -6
  124. package/src/components/layout/Pagination.astro +7 -7
  125. package/src/components/layout/ReferenceLayout.astro +1 -1
  126. package/src/components/layout/RootLayout.astro +6 -6
  127. package/src/components/layout/Search.astro +13 -13
  128. package/src/components/layout/analytics-client.ts +1 -1
  129. package/src/components/layout/drawer-inert.ts +1 -1
  130. package/src/components/openapi/description.ts +2 -2
  131. package/src/core/code-fences.ts +1 -1
  132. package/src/core/config-input.ts +19 -19
  133. package/src/core/config.ts +3 -3
  134. package/src/core/data.ts +3 -3
  135. package/src/core/i18n-ui.ts +35 -9
  136. package/src/core/request-body.ts +1 -1
  137. package/src/core/schema.ts +25 -20
  138. package/src/core/server-features.ts +2 -2
  139. package/src/core/ui-packs/ar.ts +4 -5
  140. package/src/core/ui-packs/bg.ts +4 -5
  141. package/src/core/ui-packs/bn.ts +4 -5
  142. package/src/core/ui-packs/ca.ts +4 -5
  143. package/src/core/ui-packs/cs.ts +4 -5
  144. package/src/core/ui-packs/da.ts +4 -5
  145. package/src/core/ui-packs/de.ts +4 -5
  146. package/src/core/ui-packs/el.ts +4 -5
  147. package/src/core/ui-packs/es.ts +4 -5
  148. package/src/core/ui-packs/fa.ts +4 -5
  149. package/src/core/ui-packs/fi.ts +4 -5
  150. package/src/core/ui-packs/fr.ts +4 -5
  151. package/src/core/ui-packs/he.ts +4 -5
  152. package/src/core/ui-packs/hi.ts +4 -5
  153. package/src/core/ui-packs/hr.ts +4 -5
  154. package/src/core/ui-packs/hu.ts +4 -5
  155. package/src/core/ui-packs/id.ts +4 -5
  156. package/src/core/ui-packs/it.ts +4 -5
  157. package/src/core/ui-packs/ja.ts +4 -5
  158. package/src/core/ui-packs/ko.ts +4 -5
  159. package/src/core/ui-packs/nl.ts +4 -5
  160. package/src/core/ui-packs/no.ts +4 -5
  161. package/src/core/ui-packs/pl.ts +4 -5
  162. package/src/core/ui-packs/pt-br.ts +4 -5
  163. package/src/core/ui-packs/pt.ts +4 -5
  164. package/src/core/ui-packs/ro.ts +4 -5
  165. package/src/core/ui-packs/ru.ts +4 -5
  166. package/src/core/ui-packs/sk.ts +4 -5
  167. package/src/core/ui-packs/sr.ts +4 -5
  168. package/src/core/ui-packs/sv.ts +4 -5
  169. package/src/core/ui-packs/th.ts +4 -5
  170. package/src/core/ui-packs/tr.ts +4 -5
  171. package/src/core/ui-packs/uk.ts +4 -5
  172. package/src/core/ui-packs/vi.ts +4 -5
  173. package/src/core/ui-packs/zh-tw.ts +4 -5
  174. package/src/core/ui-packs/zh.ts +4 -5
  175. package/src/core/unrecognized-keys.ts +1 -1
  176. package/src/registry/eject.ts +19 -18
  177. package/src/search/documents.ts +2 -2
  178. package/src/search/orama-index.ts +1 -1
  179. package/src/translate/report.ts +1 -1
  180. package/src/upgrade/upgrade.ts +1 -1
  181. package/dist/cli/chunk-mnqj32sj.js.map +0 -12
  182. package/dist/cli/chunk-mwt1k8n7.js.map +0 -10
  183. /package/dist/cli/{chunk-f7t03s3g.js.map → chunk-27g6wdth.js.map} +0 -0
  184. /package/dist/cli/{chunk-by2290sx.js.map → chunk-5shv93fd.js.map} +0 -0
  185. /package/dist/cli/{chunk-nk3ts2xk.js.map → chunk-82bbrxdn.js.map} +0 -0
  186. /package/dist/cli/{chunk-zxccj738.js.map → chunk-ce574jw2.js.map} +0 -0
  187. /package/dist/cli/{chunk-y3e45rc8.js.map → chunk-ch6g3ar0.js.map} +0 -0
  188. /package/dist/cli/{chunk-1w8dp3qb.js.map → chunk-epjnccmv.js.map} +0 -0
  189. /package/dist/cli/{chunk-ernrthtr.js.map → chunk-f2z5v128.js.map} +0 -0
  190. /package/dist/cli/{chunk-zg2gtj10.js.map → chunk-fs23ddbb.js.map} +0 -0
  191. /package/dist/cli/{chunk-7ez8ny0t.js.map → chunk-fxypxtvm.js.map} +0 -0
  192. /package/dist/cli/{chunk-tzne8qfq.js.map → chunk-fz5wtpmh.js.map} +0 -0
  193. /package/dist/cli/{chunk-b5aj94ah.js.map → chunk-hdpx1tax.js.map} +0 -0
  194. /package/dist/cli/{chunk-fh5hj5jt.js.map → chunk-kdp5q7ke.js.map} +0 -0
  195. /package/dist/cli/{chunk-bctazmbk.js.map → chunk-mb2919y2.js.map} +0 -0
  196. /package/dist/cli/{chunk-79njf86q.js.map → chunk-q5163e60.js.map} +0 -0
  197. /package/dist/cli/{chunk-sqn5t4q0.js.map → chunk-qs4q5p4e.js.map} +0 -0
  198. /package/dist/cli/{chunk-bw22s759.js.map → chunk-qwsrynx5.js.map} +0 -0
  199. /package/dist/cli/{chunk-z01ze5c1.js.map → chunk-s1p84fyh.js.map} +0 -0
  200. /package/dist/cli/{chunk-f2972sbt.js.map → chunk-vtk4a6dg.js.map} +0 -0
  201. /package/dist/cli/{chunk-d1tadaw7.js.map → chunk-yt5n7ppj.js.map} +0 -0
  202. /package/dist/cli/{chunk-88cpgt6h.js.map → chunk-zxcczpyx.js.map} +0 -0
  203. /package/dist/cli/{chunk-41za066z.js.map → chunk-zxh4d9vy.js.map} +0 -0
@@ -2,7 +2,7 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/search/documents.ts", "../src/markdown/fence-meta.ts", "../src/markdown/code-title.ts", "../src/search/facets.ts", "../src/ai/mcp/data.ts"],
4
4
  "sourcesContent": [
5
- "import type { Nodes } from \"mdast\";\nimport { markdownToMdast, mdxToMdast } from \"satteri\";\n\nimport {\n componentRegistry,\n downlevelComponentNode,\n downlevelComponents,\n} from \"../ai/component-markdown.ts\";\nimport type {\n ComponentMarkdown,\n DownlevelWalk,\n MdastNode as DownlevelNode,\n} from \"../ai/component-markdown.ts\";\nimport { projectComponentSerializers } from \"../ai/serializers.ts\";\nimport { applyAudienceVisibility } from \"../ai/visibility.ts\";\nimport type { VisibilityAudience } from \"../ai/visibility.ts\";\nimport matter from \"../core/frontmatter.ts\";\nimport { parseHeadingMarkers } from \"../core/heading-markers.ts\";\nimport { contentIndexable } from \"../core/manifest.ts\";\nimport type { BlumeProject } from \"../core/project-graph.ts\";\nimport { HTML_COMMENT } from \"../core/sources/normalize.ts\";\nimport { readExpandedEntryText } from \"../core/sources/read.ts\";\nimport type { NavNode, PageRecord } from \"../core/types.ts\";\nimport { parseCodeTitle } from \"../markdown/code-title.ts\";\nimport { MARKDOWN_FEATURES, MDX_FEATURES } from \"../markdown/features.ts\";\nimport { pageFacets } from \"./facets.ts\";\n\n/** A document indexed by the client-side search providers (Orama, FlexSearch). */\nexport interface SearchDocument {\n route: string;\n title: string;\n description: string;\n content: string;\n /** Ancestor section labels for the result breadcrumb, e.g. `[\"Guides\"]`. */\n breadcrumb: string[];\n /** Top-level section label, used by the search filter pills. */\n section: string;\n /** Locale code, so the dialog can filter results to the active language. */\n locale: string;\n /** Resolved page `type` (`doc`, `blog`, a custom `rfc`…), for type filters. */\n contentType: string;\n /** Docs version (`\"\"` for the current docs), so results scope to the viewed version. */\n version: string;\n /** Frontmatter `search.tags`, surfaced for hosted-provider faceting. */\n tags?: string[];\n /**\n * Declared facet values (`content.types.<type>.facets`), key → value.\n * Filterable through the MCP tools' `filters` input.\n */\n facets?: Record<string, string>;\n}\n\n/**\n * A record uploaded to a hosted search backend (Algolia, Orama Cloud,\n * Typesense, Mixedbread). `_id` is the stable per-page key; each sync adapts it\n * to the backend's own id field (`objectID`, `id`, …).\n */\nexport interface SearchRecord {\n _id: string;\n url: string;\n title: string;\n description: string;\n content: string;\n /** Locale code, carried as a facet for per-language filtering. */\n locale: string;\n /**\n * Docs version, carried as a facet for per-version filtering. The current\n * docs upload as `\"current\"` — hosted backends treat an empty facet value\n * unreliably, so the sentinel stands in for the empty version id.\n */\n version: string;\n /** Single faceting tag (the first frontmatter tag, when present). */\n tag?: string;\n}\n\n// Tag-shaped only: a name (or closing slash/fragment) right after `<`, and no\n// newline inside. Applied *within* html/JSX nodes so their inner prose is\n// kept; the surrounding Markdown is walked as a tree, so a bare `<` in prose\n// (\"costs < 5 credits\") is ordinary text and never at risk.\nconst HTML_OR_JSX = /<\\/?[a-zA-Z][^\\n<>]*>|<\\/?>/gu;\nconst WHITESPACE = /\\s+/gu;\n\n// Parents whose children are inline: no separator is inserted after them, or\n// `re*ally*` would index as `re ally`. Every other parent is block-shaped and\n// ends with a space so adjacent paragraphs/headings/cells don't fuse.\nconst INLINE_PARENTS = new Set([\n \"delete\",\n \"emphasis\",\n \"footnoteReference\",\n \"link\",\n \"linkReference\",\n \"mdxJsxTextElement\",\n \"strong\",\n \"subscript\",\n \"superscript\",\n \"textDirective\",\n]);\n\n// Nodes that never render as prose: image alt text was never indexed, MDX\n// expressions (`{props.x}`) and ESM (`export const meta`) are code, and math\n// is LaTeX source rendered by KaTeX, not searchable words.\nconst NON_PROSE = new Set<Nodes[\"type\"]>([\n \"image\",\n \"imageReference\",\n \"inlineMath\",\n \"math\",\n \"mdxFlowExpression\",\n \"mdxTextExpression\",\n \"mdxjsEsm\",\n]);\n\ntype PageFormat = PageRecord[\"format\"];\n\n/** What the plain-text walk carries: the code-block policy and the downlevel context. */\ninterface Walk {\n includeCodeBlocks: boolean;\n downlevel: DownlevelWalk;\n}\n\n// Front matter is already off (core/frontmatter.ts) by the time a body gets\n// here, so a body that opens with a `---` divider must read as a thematic\n// break rather than a second front matter block.\nconst MD_PARSE = { features: { ...MARKDOWN_FEATURES, frontmatter: false } };\nconst MDX_PARSE = { features: { ...MDX_FEATURES, frontmatter: false } };\n\nconst parseMdx = (markdown: string): Nodes | undefined => {\n try {\n return mdxToMdast(markdown, MDX_PARSE);\n } catch {\n return undefined;\n }\n};\n\n/**\n * Parse a page body the way the renderer reads it. `.mdx` pages go through\n * the MDX grammar with the renderer's feature set, so components become\n * `mdxJsx*` nodes whose children — prose and fences, however they're indented\n * — walk like top-level content, and `:::` directives and `$$` math parse as\n * such; Markdown would instead fold a tight component into one html node and\n * read its indented children as an indented code block. A page the MDX\n * grammar rejects (an HTML comment, an unclosed tag) fails to render too, so\n * it has no body to index: reading it as Markdown instead would put its ESM,\n * expressions, and raw tags in the index as prose.\n */\nconst parseMarkdown = (\n markdown: string,\n format: PageFormat\n): Nodes | undefined =>\n format === \"mdx\" ? parseMdx(markdown) : markdownToMdast(markdown, MD_PARSE);\n\n// Fenced code is excluded from the plain index by default (ranking noise) —\n// the \"markdown\" extraction keeps it for Ask AI grounding. Code-heavy docs opt\n// in via `search.indexing.includeCodeBlocks`, which indexes the fence body and\n// its rendered title (```ts blume.config.ts) but never the fence markers,\n// language, or other meta keywords.\nconst collectCode = (\n node: Extract<Nodes, { type: \"code\" }>,\n out: string[],\n walk: Walk\n): void => {\n if (!walk.includeCodeBlocks) {\n return;\n }\n const title = parseCodeTitle(node.meta ?? undefined);\n if (title) {\n out.push(title, \" \");\n }\n // The trailing space keeps the fence apart from what follows it.\n out.push(node.value, \" \");\n};\n\n// Block boundaries separate words; so does an empty inline element (`<br />`,\n// a self-closing icon), which otherwise fuses its neighbors.\nconst separatesWords = (node: Extract<Nodes, { children: unknown }>): boolean =>\n !INLINE_PARENTS.has(node.type) || node.children.length === 0;\n\n/** Fold one mdast node into the plain-text accumulator. */\nconst collectText = (node: Nodes, out: string[], walk: Walk): void => {\n if (NON_PROSE.has(node.type)) {\n return;\n }\n switch (node.type) {\n case \"code\": {\n collectCode(node, out, walk);\n return;\n }\n // A component with a serializer indexes as the text the page shows —\n // a Card's title and call to action, a TypeTable's descriptions — read\n // from the Markdown the serializer emits (its contract is Markdown, not\n // MDX). One without a serializer walks its children like any block.\n case \"mdxJsxFlowElement\": {\n // SAFETY: DownlevelNode is a structural subset of Satteri's mdast\n // output — the walk checks for offsets before reading them.\n const text = downlevelComponentNode(\n node as DownlevelNode,\n walk.downlevel\n );\n if (text !== null) {\n collectText(markdownToMdast(text, MD_PARSE), out, walk);\n return;\n }\n break;\n }\n // Inline code is kept verbatim — `Array<T>` is a type parameter, not a\n // tag, and its tokens must stay searchable.\n case \"inlineCode\": {\n out.push(node.value);\n return;\n }\n // A raw-HTML run. In a `.md` page (or an `.mdx` page MDX couldn't parse)\n // CommonMark folds a block-level `<Callout>` with no blank lines into ONE\n // html node holding all its inner prose, so the node can't just be\n // dropped — strip the tag-shaped runs and keep the text.\n case \"html\": {\n out.push(\n node.value.replaceAll(HTML_COMMENT, \" \").replaceAll(HTML_OR_JSX, \" \"),\n \" \"\n );\n return;\n }\n case \"break\": {\n out.push(\" \");\n return;\n }\n // Trailing heading markers (`[#custom-id]`, `{#custom-id}`, `[!toc]`,\n // `[toc]`) are anchor\n\n // metadata, not prose — strip them so they never pollute the index. Only\n // a marker that ends the heading's final plain-text child counts,\n // mirroring the renderer: a heading ending in inline code or an image\n // keeps its bracketed text on the page (so it stays searchable), and a\n // heading that is nothing but markers renders them literally.\n case \"heading\": {\n const last = node.children.at(-1);\n const trailing = last?.type === \"text\" ? last : undefined;\n const inner: string[] = [];\n const kept = trailing ? node.children.slice(0, -1) : node.children;\n for (const child of kept) {\n collectText(child, inner, walk);\n }\n if (trailing) {\n const stripped = parseHeadingMarkers(trailing.value).text;\n const literal = stripped === \"\" && node.children.length === 1;\n inner.push(literal ? trailing.value : stripped);\n }\n out.push(inner.join(\"\").trimEnd(), \" \");\n return;\n }\n default: {\n break;\n }\n }\n if (\"value\" in node) {\n out.push(node.value);\n return;\n }\n if (\"children\" in node) {\n for (const child of node.children) {\n collectText(child, out, walk);\n }\n if (separatesWords(node)) {\n out.push(\" \");\n }\n }\n};\n\n/**\n * Reduce Markdown/MDX to plain, searchable text: parse (GFM included) and walk\n * the tree instead of regex-stripping the source, so reference-style links,\n * autolinks, setext headings, tables, and literal `*`/`~`/`>` in prose all\n * reduce correctly. This feeds the client index *and* every hosted-provider\n * record, so anything lost here is a permanent search-quality loss.\n */\nconst toPlainText = (\n markdown: string,\n format: PageFormat,\n walk: Walk\n): string => {\n const tree = parseMarkdown(markdown, format);\n if (!tree) {\n return \"\";\n }\n const out: string[] = [];\n collectText(tree, out, walk);\n return out.join(\"\").replaceAll(WHITESPACE, \" \").trim();\n};\n\ninterface Crumbs {\n breadcrumb: string[];\n section: string;\n}\n\n/**\n * Map each page route to its ancestor section labels by walking the nav sidebar\n * once. The nearest ancestor group — the sidebar section a page appears under —\n * becomes its `section`, the dimension the search filter pills group by, so the\n * pills mirror the visible sidebar (and honor folder-meta renames).\n */\nconst buildCrumbIndex = (sidebar: NavNode[]): Map<string, Crumbs> => {\n const index = new Map<string, Crumbs>();\n // A config-sidebar section's landing page (the group's `root`) lives on the\n // *group* node, not on any page leaf — record it under the group's own label\n // so the section's landing page carries the same facet as its children. A\n // real page leaf for the route (filesystem sidebars emit index pages as\n // leaves) wins, so group routes are merged in only where no leaf claimed one.\n const groupRoutes = new Map<string, Crumbs>();\n const walk = (nodes: NavNode[], trail: string[]): void => {\n for (const node of nodes) {\n if (node.kind === \"group\") {\n if (node.route && !groupRoutes.has(node.route)) {\n groupRoutes.set(node.route, {\n breadcrumb: [...trail, node.label],\n section: node.label,\n });\n }\n walk(node.children, [...trail, node.label]);\n } else if (node.route) {\n index.set(node.route, {\n breadcrumb: trail,\n section: trail.at(-1) ?? \"\",\n });\n }\n }\n };\n walk(sidebar, []);\n for (const [route, crumbs] of groupRoutes) {\n if (!index.has(route)) {\n index.set(route, crumbs);\n }\n }\n return index;\n};\n\ninterface BuildOptions {\n includeWhenDisabled?: boolean;\n content?: \"markdown\" | \"plain\";\n audience?: VisibilityAudience;\n}\n\n/**\n * Read a page's body and reduce it per the extraction options. The\n * `\"markdown\"` body is agent-facing, so its components are downleveled in the\n * source with the serializers every agent surface uses (front matter in scope\n * for prop expressions). The plain body is parsed as written and walked, with\n * the same serializers applied per component on the way, so the index reads a\n * `<Card>` or `<TypeTable>` as the text it shows.\n */\nconst pageBody = async (\n project: BlumeProject,\n page: PageRecord | undefined,\n options: Pick<BuildOptions, \"audience\" | \"content\"> | undefined,\n components: Record<string, ComponentMarkdown>\n): Promise<string> => {\n if (!page) {\n return \"\";\n }\n const parsed = matter(await readExpandedEntryText(project, page));\n const visible = applyAudienceVisibility(\n parsed.content,\n options?.audience ?? \"web\"\n );\n if (options?.content === \"markdown\") {\n return downlevelComponents(visible.trim(), components, parsed.data);\n }\n return toPlainText(visible, page.format, {\n downlevel: {\n frontmatter: parsed.data,\n registry: componentRegistry(components),\n source: visible,\n },\n includeCodeBlocks: project.config.search.indexing.includeCodeBlocks,\n });\n};\n\n/**\n * Build search documents from the content graph. Only indexable pages are\n * included (per the route manifest), and content comes from the source files,\n * so the index is identical in dev and build.\n *\n * Pass `includeWhenDisabled` to index pages on their content merits even when\n * the search provider is `none` — used by the MCP server, which is a separate\n * feature from on-page search.\n *\n * `content` selects the extraction: `\"plain\"` (default) strips Markdown to bare\n * searchable text; `\"markdown\"` keeps the body's Markdown — code blocks, lists,\n * headings — for Ask AI grounding, where fenced examples are often the answer\n * and stripping them makes the model unable to cite content the docs do contain.\n * The `\"markdown\"` body is agent-facing, so components are downleveled with the\n * same serializers the `.md` mirror, llms-full.txt and MCP `get_page` use: a\n * page whose body is a `<CardGroup>` of `<Card>`s is prose to a reader and bare\n * JSX to anything reading the source, and section landing pages are exactly\n * that shape.\n *\n * `audience` resolves `<Visibility>` blocks before extraction: `\"web\"`\n * (default) keeps web-only content and drops agents-only blocks — the site\n * search and hosted syncs must not surface content the page hides — while\n * `\"agents\"` mirrors llms-full.txt/MCP `get_page` (web removed, agents kept).\n *\n * Whether fenced code joins the plain extraction is site-wide policy, read\n * from `search.indexing.includeCodeBlocks` here rather than threaded by each\n * caller, so the client index, hosted syncs, eject, and the MCP `search_docs`\n * index can't drift apart.\n */\nexport const buildSearchDocuments = async (\n project: BlumeProject,\n options?: BuildOptions\n): Promise<SearchDocument[]> => {\n const pageById = new Map(project.graph.pages.map((page) => [page.id, page]));\n\n // Build the crumb index from every locale's sidebar (their nodes carry\n // locale-prefixed routes), so localized pages get the right section/breadcrumb.\n // Falls back to the single default-locale nav when i18n is off.\n const byLocale = Object.values(project.graph.navigationByLocale ?? {});\n // Archived versions' trees contribute too, so snapshot pages get their own\n // section/breadcrumb instead of falling through to the \"Docs\" default.\n const byVersion = Object.values(project.graph.navigationByVersion ?? {})\n .flatMap((locales) => Object.values(locales))\n .map((nav) => nav.sidebar);\n const sidebars = [\n ...(byLocale.length > 0\n ? byLocale.map((nav) => nav.sidebar)\n : [project.graph.navigation?.sidebar ?? []]),\n ...byVersion,\n ];\n const crumbs = new Map<string, Crumbs>();\n for (const sidebar of sidebars) {\n for (const [route, crumb] of buildCrumbIndex(sidebar)) {\n crumbs.set(route, crumb);\n }\n }\n\n const indexable = project.manifest.routes.filter((route) => {\n if (!options?.includeWhenDisabled) {\n return route.indexable;\n }\n // A fallback route renders the fallback locale's page at another locale's\n // URL, so indexing it would only repeat that page as a second hit — the\n // reason site search leaves it out too.\n if (route.fallback) {\n return false;\n }\n const page = pageById.get(route.id);\n return page ? contentIndexable(page, project.config) : false;\n });\n\n // Built once — `downlevelComponents` rebuilds its registry per call\n // otherwise.\n const components = projectComponentSerializers(project);\n\n return await Promise.all(\n indexable.map(async (route) => {\n const page = pageById.get(route.id);\n const body = await pageBody(project, page, options, components);\n const tags = page?.meta?.search?.tags;\n const crumb = crumbs.get(route.path);\n const facets = page ? pageFacets(page, project.config) : undefined;\n const document: SearchDocument = {\n breadcrumb: crumb?.breadcrumb ?? [],\n content: body,\n contentType: route.contentType,\n description: page?.description ?? \"\",\n locale: route.locale,\n route: route.path,\n section: crumb?.section || \"Docs\",\n tags: tags && tags.length > 0 ? tags : undefined,\n title: route.title,\n version: route.version,\n };\n if (facets) {\n document.facets = facets;\n }\n return document;\n })\n );\n};\n\n/**\n * Map per-page search documents to the flat record shape hosted backends\n * ingest. One record per page, keyed by route; the first tag becomes the\n * faceting `tag`.\n */\nexport const toSearchRecords = (documents: SearchDocument[]): SearchRecord[] =>\n documents.map((doc) => ({\n _id: doc.route,\n content: doc.content,\n description: doc.description,\n locale: doc.locale,\n tag: doc.tags?.[0],\n title: doc.title,\n url: doc.route,\n version: doc.version || \"current\",\n }));\n",
5
+ "import type { Nodes } from \"mdast\";\nimport { markdownToMdast, mdxToMdast } from \"satteri\";\n\nimport {\n componentRegistry,\n downlevelComponentNode,\n downlevelComponents,\n} from \"../ai/component-markdown.ts\";\nimport type {\n ComponentMarkdown,\n DownlevelWalk,\n MdastNode as DownlevelNode,\n} from \"../ai/component-markdown.ts\";\nimport { projectComponentSerializers } from \"../ai/serializers.ts\";\nimport { applyAudienceVisibility } from \"../ai/visibility.ts\";\nimport type { VisibilityAudience } from \"../ai/visibility.ts\";\nimport matter from \"../core/frontmatter.ts\";\nimport { parseHeadingMarkers } from \"../core/heading-markers.ts\";\nimport { contentIndexable } from \"../core/manifest.ts\";\nimport type { BlumeProject } from \"../core/project-graph.ts\";\nimport { HTML_COMMENT } from \"../core/sources/normalize.ts\";\nimport { readExpandedEntryText } from \"../core/sources/read.ts\";\nimport type { NavNode, PageRecord } from \"../core/types.ts\";\nimport { parseCodeTitle } from \"../markdown/code-title.ts\";\nimport { MARKDOWN_FEATURES, MDX_FEATURES } from \"../markdown/features.ts\";\nimport { pageFacets } from \"./facets.ts\";\n\n/** A document indexed by the client-side search providers (Orama, FlexSearch). */\nexport interface SearchDocument {\n route: string;\n title: string;\n description: string;\n content: string;\n /** Ancestor section labels for the result breadcrumb, e.g. `[\"Guides\"]`. */\n breadcrumb: string[];\n /** Top-level section label, used by the search filter pills. */\n section: string;\n /** Locale code, so the dialog can filter results to the active language. */\n locale: string;\n /** Resolved page `type` (`doc`, `blog`, a custom `rfc`…), for type filters. */\n contentType: string;\n /** Docs version (`\"\"` for the current docs), so results scope to the viewed version. */\n version: string;\n /** Frontmatter `search.tags`, surfaced for hosted-provider faceting. */\n tags?: string[];\n /**\n * Declared facet values (`content.types.<type>.facets`), key → value.\n * Filterable through the MCP tools' `filters` input.\n */\n facets?: Record<string, string>;\n}\n\n/**\n * A record uploaded to a hosted search backend (Algolia, Orama Cloud,\n * Typesense, Mixedbread). `_id` is the stable per-page key; each sync adapts it\n * to the backend's own id field (`objectID`, `id`, …).\n */\nexport interface SearchRecord {\n _id: string;\n url: string;\n title: string;\n description: string;\n content: string;\n /** Locale code, carried as a facet for per-language filtering. */\n locale: string;\n /**\n * Docs version, carried as a facet for per-version filtering. The current\n * docs upload as `\"current\"` — hosted backends treat an empty facet value\n * unreliably, so the sentinel stands in for the empty version id.\n */\n version: string;\n /** Single faceting tag (the first frontmatter tag, when present). */\n tag?: string;\n}\n\n// Tag-shaped only: a name (or closing slash/fragment) right after `<`, and no\n// newline inside. Applied *within* html/JSX nodes so their inner prose is\n// kept; the surrounding Markdown is walked as a tree, so a bare `<` in prose\n// (\"costs < 5 credits\") is ordinary text and never at risk.\nconst HTML_OR_JSX = /<\\/?[a-zA-Z][^\\n<>]*>|<\\/?>/gu;\nconst WHITESPACE = /\\s+/gu;\n\n// Parents whose children are inline: no separator is inserted after them, or\n// `re*ally*` would index as `re ally`. Every other parent is block-shaped and\n// ends with a space so adjacent paragraphs/headings/cells don't fuse.\nconst INLINE_PARENTS = new Set([\n \"delete\",\n \"emphasis\",\n \"footnoteReference\",\n \"link\",\n \"linkReference\",\n \"mdxJsxTextElement\",\n \"strong\",\n \"subscript\",\n \"superscript\",\n \"textDirective\",\n]);\n\n// Nodes that never render as prose: image alt text was never indexed, MDX\n// expressions (`{props.x}`) and ESM (`export const meta`) are code, and math\n// is LaTeX source rendered by KaTeX, not searchable words.\nconst NON_PROSE = new Set<Nodes[\"type\"]>([\n \"image\",\n \"imageReference\",\n \"inlineMath\",\n \"math\",\n \"mdxFlowExpression\",\n \"mdxTextExpression\",\n \"mdxjsEsm\",\n]);\n\ntype PageFormat = PageRecord[\"format\"];\n\n/** What the plain-text walk carries: the code-block policy and the downlevel context. */\ninterface Walk {\n includeCodeBlocks: boolean;\n downlevel: DownlevelWalk;\n}\n\n// Front matter is already off (core/frontmatter.ts) by the time a body gets\n// here, so a body that opens with a `---` divider must read as a thematic\n// break rather than a second front matter block.\nconst MD_PARSE = { features: { ...MARKDOWN_FEATURES, frontmatter: false } };\nconst MDX_PARSE = { features: { ...MDX_FEATURES, frontmatter: false } };\n\nconst parseMdx = (markdown: string): Nodes | undefined => {\n try {\n return mdxToMdast(markdown, MDX_PARSE);\n } catch {\n return undefined;\n }\n};\n\n/**\n * Parse a page body the way the renderer reads it. `.mdx` pages go through\n * the MDX grammar with the renderer's feature set, so components become\n * `mdxJsx*` nodes whose children — prose and fences, however they're indented\n * — walk like top-level content, and `:::` directives and `$$` math parse as\n * such; Markdown would instead fold a tight component into one html node and\n * read its indented children as an indented code block. A page the MDX\n * grammar rejects (an HTML comment, an unclosed tag) fails to render too, so\n * it has no body to index: reading it as Markdown instead would put its ESM,\n * expressions, and raw tags in the index as prose.\n */\nconst parseMarkdown = (\n markdown: string,\n format: PageFormat\n): Nodes | undefined =>\n format === \"mdx\" ? parseMdx(markdown) : markdownToMdast(markdown, MD_PARSE);\n\n// Fenced code is excluded from the plain index by default (ranking noise) —\n// the \"markdown\" extraction keeps it for assistant grounding. Code-heavy docs opt\n// in via `search.indexing.includeCodeBlocks`, which indexes the fence body and\n// its rendered title (```ts blume.config.ts) but never the fence markers,\n// language, or other meta keywords.\nconst collectCode = (\n node: Extract<Nodes, { type: \"code\" }>,\n out: string[],\n walk: Walk\n): void => {\n if (!walk.includeCodeBlocks) {\n return;\n }\n const title = parseCodeTitle(node.meta ?? undefined);\n if (title) {\n out.push(title, \" \");\n }\n // The trailing space keeps the fence apart from what follows it.\n out.push(node.value, \" \");\n};\n\n// Block boundaries separate words; so does an empty inline element (`<br />`,\n// a self-closing icon), which otherwise fuses its neighbors.\nconst separatesWords = (node: Extract<Nodes, { children: unknown }>): boolean =>\n !INLINE_PARENTS.has(node.type) || node.children.length === 0;\n\n/** Fold one mdast node into the plain-text accumulator. */\nconst collectText = (node: Nodes, out: string[], walk: Walk): void => {\n if (NON_PROSE.has(node.type)) {\n return;\n }\n switch (node.type) {\n case \"code\": {\n collectCode(node, out, walk);\n return;\n }\n // A component with a serializer indexes as the text the page shows —\n // a Card's title and call to action, a TypeTable's descriptions — read\n // from the Markdown the serializer emits (its contract is Markdown, not\n // MDX). One without a serializer walks its children like any block.\n case \"mdxJsxFlowElement\": {\n // SAFETY: DownlevelNode is a structural subset of Satteri's mdast\n // output — the walk checks for offsets before reading them.\n const text = downlevelComponentNode(\n node as DownlevelNode,\n walk.downlevel\n );\n if (text !== null) {\n collectText(markdownToMdast(text, MD_PARSE), out, walk);\n return;\n }\n break;\n }\n // Inline code is kept verbatim — `Array<T>` is a type parameter, not a\n // tag, and its tokens must stay searchable.\n case \"inlineCode\": {\n out.push(node.value);\n return;\n }\n // A raw-HTML run. In a `.md` page (or an `.mdx` page MDX couldn't parse)\n // CommonMark folds a block-level `<Callout>` with no blank lines into ONE\n // html node holding all its inner prose, so the node can't just be\n // dropped — strip the tag-shaped runs and keep the text.\n case \"html\": {\n out.push(\n node.value.replaceAll(HTML_COMMENT, \" \").replaceAll(HTML_OR_JSX, \" \"),\n \" \"\n );\n return;\n }\n case \"break\": {\n out.push(\" \");\n return;\n }\n // Trailing heading markers (`[#custom-id]`, `{#custom-id}`, `[!toc]`,\n // `[toc]`) are anchor\n\n // metadata, not prose — strip them so they never pollute the index. Only\n // a marker that ends the heading's final plain-text child counts,\n // mirroring the renderer: a heading ending in inline code or an image\n // keeps its bracketed text on the page (so it stays searchable), and a\n // heading that is nothing but markers renders them literally.\n case \"heading\": {\n const last = node.children.at(-1);\n const trailing = last?.type === \"text\" ? last : undefined;\n const inner: string[] = [];\n const kept = trailing ? node.children.slice(0, -1) : node.children;\n for (const child of kept) {\n collectText(child, inner, walk);\n }\n if (trailing) {\n const stripped = parseHeadingMarkers(trailing.value).text;\n const literal = stripped === \"\" && node.children.length === 1;\n inner.push(literal ? trailing.value : stripped);\n }\n out.push(inner.join(\"\").trimEnd(), \" \");\n return;\n }\n default: {\n break;\n }\n }\n if (\"value\" in node) {\n out.push(node.value);\n return;\n }\n if (\"children\" in node) {\n for (const child of node.children) {\n collectText(child, out, walk);\n }\n if (separatesWords(node)) {\n out.push(\" \");\n }\n }\n};\n\n/**\n * Reduce Markdown/MDX to plain, searchable text: parse (GFM included) and walk\n * the tree instead of regex-stripping the source, so reference-style links,\n * autolinks, setext headings, tables, and literal `*`/`~`/`>` in prose all\n * reduce correctly. This feeds the client index *and* every hosted-provider\n * record, so anything lost here is a permanent search-quality loss.\n */\nconst toPlainText = (\n markdown: string,\n format: PageFormat,\n walk: Walk\n): string => {\n const tree = parseMarkdown(markdown, format);\n if (!tree) {\n return \"\";\n }\n const out: string[] = [];\n collectText(tree, out, walk);\n return out.join(\"\").replaceAll(WHITESPACE, \" \").trim();\n};\n\ninterface Crumbs {\n breadcrumb: string[];\n section: string;\n}\n\n/**\n * Map each page route to its ancestor section labels by walking the nav sidebar\n * once. The nearest ancestor group — the sidebar section a page appears under —\n * becomes its `section`, the dimension the search filter pills group by, so the\n * pills mirror the visible sidebar (and honor folder-meta renames).\n */\nconst buildCrumbIndex = (sidebar: NavNode[]): Map<string, Crumbs> => {\n const index = new Map<string, Crumbs>();\n // A config-sidebar section's landing page (the group's `root`) lives on the\n // *group* node, not on any page leaf — record it under the group's own label\n // so the section's landing page carries the same facet as its children. A\n // real page leaf for the route (filesystem sidebars emit index pages as\n // leaves) wins, so group routes are merged in only where no leaf claimed one.\n const groupRoutes = new Map<string, Crumbs>();\n const walk = (nodes: NavNode[], trail: string[]): void => {\n for (const node of nodes) {\n if (node.kind === \"group\") {\n if (node.route && !groupRoutes.has(node.route)) {\n groupRoutes.set(node.route, {\n breadcrumb: [...trail, node.label],\n section: node.label,\n });\n }\n walk(node.children, [...trail, node.label]);\n } else if (node.route) {\n index.set(node.route, {\n breadcrumb: trail,\n section: trail.at(-1) ?? \"\",\n });\n }\n }\n };\n walk(sidebar, []);\n for (const [route, crumbs] of groupRoutes) {\n if (!index.has(route)) {\n index.set(route, crumbs);\n }\n }\n return index;\n};\n\ninterface BuildOptions {\n includeWhenDisabled?: boolean;\n content?: \"markdown\" | \"plain\";\n audience?: VisibilityAudience;\n}\n\n/**\n * Read a page's body and reduce it per the extraction options. The\n * `\"markdown\"` body is agent-facing, so its components are downleveled in the\n * source with the serializers every agent surface uses (front matter in scope\n * for prop expressions). The plain body is parsed as written and walked, with\n * the same serializers applied per component on the way, so the index reads a\n * `<Card>` or `<TypeTable>` as the text it shows.\n */\nconst pageBody = async (\n project: BlumeProject,\n page: PageRecord | undefined,\n options: Pick<BuildOptions, \"audience\" | \"content\"> | undefined,\n components: Record<string, ComponentMarkdown>\n): Promise<string> => {\n if (!page) {\n return \"\";\n }\n const parsed = matter(await readExpandedEntryText(project, page));\n const visible = applyAudienceVisibility(\n parsed.content,\n options?.audience ?? \"web\"\n );\n if (options?.content === \"markdown\") {\n return downlevelComponents(visible.trim(), components, parsed.data);\n }\n return toPlainText(visible, page.format, {\n downlevel: {\n frontmatter: parsed.data,\n registry: componentRegistry(components),\n source: visible,\n },\n includeCodeBlocks: project.config.search.indexing.includeCodeBlocks,\n });\n};\n\n/**\n * Build search documents from the content graph. Only indexable pages are\n * included (per the route manifest), and content comes from the source files,\n * so the index is identical in dev and build.\n *\n * Pass `includeWhenDisabled` to index pages on their content merits even when\n * the search provider is `none` — used by the MCP server, which is a separate\n * feature from on-page search.\n *\n * `content` selects the extraction: `\"plain\"` (default) strips Markdown to bare\n * searchable text; `\"markdown\"` keeps the body's Markdown — code blocks, lists,\n * headings — for assistant grounding, where fenced examples are often the answer\n * and stripping them makes the model unable to cite content the docs do contain.\n * The `\"markdown\"` body is agent-facing, so components are downleveled with the\n * same serializers the `.md` mirror, llms-full.txt and MCP `get_page` use: a\n * page whose body is a `<CardGroup>` of `<Card>`s is prose to a reader and bare\n * JSX to anything reading the source, and section landing pages are exactly\n * that shape.\n *\n * `audience` resolves `<Visibility>` blocks before extraction: `\"web\"`\n * (default) keeps web-only content and drops agents-only blocks — the site\n * search and hosted syncs must not surface content the page hides — while\n * `\"agents\"` mirrors llms-full.txt/MCP `get_page` (web removed, agents kept).\n *\n * Whether fenced code joins the plain extraction is site-wide policy, read\n * from `search.indexing.includeCodeBlocks` here rather than threaded by each\n * caller, so the client index, hosted syncs, eject, and the MCP `search_docs`\n * index can't drift apart.\n */\nexport const buildSearchDocuments = async (\n project: BlumeProject,\n options?: BuildOptions\n): Promise<SearchDocument[]> => {\n const pageById = new Map(project.graph.pages.map((page) => [page.id, page]));\n\n // Build the crumb index from every locale's sidebar (their nodes carry\n // locale-prefixed routes), so localized pages get the right section/breadcrumb.\n // Falls back to the single default-locale nav when i18n is off.\n const byLocale = Object.values(project.graph.navigationByLocale ?? {});\n // Archived versions' trees contribute too, so snapshot pages get their own\n // section/breadcrumb instead of falling through to the \"Docs\" default.\n const byVersion = Object.values(project.graph.navigationByVersion ?? {})\n .flatMap((locales) => Object.values(locales))\n .map((nav) => nav.sidebar);\n const sidebars = [\n ...(byLocale.length > 0\n ? byLocale.map((nav) => nav.sidebar)\n : [project.graph.navigation?.sidebar ?? []]),\n ...byVersion,\n ];\n const crumbs = new Map<string, Crumbs>();\n for (const sidebar of sidebars) {\n for (const [route, crumb] of buildCrumbIndex(sidebar)) {\n crumbs.set(route, crumb);\n }\n }\n\n const indexable = project.manifest.routes.filter((route) => {\n if (!options?.includeWhenDisabled) {\n return route.indexable;\n }\n // A fallback route renders the fallback locale's page at another locale's\n // URL, so indexing it would only repeat that page as a second hit — the\n // reason site search leaves it out too.\n if (route.fallback) {\n return false;\n }\n const page = pageById.get(route.id);\n return page ? contentIndexable(page, project.config) : false;\n });\n\n // Built once — `downlevelComponents` rebuilds its registry per call\n // otherwise.\n const components = projectComponentSerializers(project);\n\n return await Promise.all(\n indexable.map(async (route) => {\n const page = pageById.get(route.id);\n const body = await pageBody(project, page, options, components);\n const tags = page?.meta?.search?.tags;\n const crumb = crumbs.get(route.path);\n const facets = page ? pageFacets(page, project.config) : undefined;\n const document: SearchDocument = {\n breadcrumb: crumb?.breadcrumb ?? [],\n content: body,\n contentType: route.contentType,\n description: page?.description ?? \"\",\n locale: route.locale,\n route: route.path,\n section: crumb?.section || \"Docs\",\n tags: tags && tags.length > 0 ? tags : undefined,\n title: route.title,\n version: route.version,\n };\n if (facets) {\n document.facets = facets;\n }\n return document;\n })\n );\n};\n\n/**\n * Map per-page search documents to the flat record shape hosted backends\n * ingest. One record per page, keyed by route; the first tag becomes the\n * faceting `tag`.\n */\nexport const toSearchRecords = (documents: SearchDocument[]): SearchRecord[] =>\n documents.map((doc) => ({\n _id: doc.route,\n content: doc.content,\n description: doc.description,\n locale: doc.locale,\n tag: doc.tags?.[0],\n title: doc.title,\n url: doc.route,\n version: doc.version || \"current\",\n }));\n",
6
6
  "/**\n * The shared fence-meta grammar: how the tokens after a code fence's language\n * (```ts title=\"...\" {1,3-5} lineNumbers) split, and which of them carry\n * reserved meaning. Both the Shiki-side meta reader (`code-title.ts`) and the\n * MDAST plugins that rewrite marked fences (`ts2js.ts`) consume this, so the\n * two layers can't drift on token boundaries or keyword lists.\n */\n\n/**\n * Any quoted `key=\"...\"` attr (spaces allowed inside the quotes). The body\n * excludes only the delimiting quote, so `title=\"foo's file.ts\"` (an\n * apostrophe inside double quotes) still matches. The left boundary stops\n * `subtitle=\"...\"` (or any `*title=` attr) from reading as a title.\n */\nexport const QUOTED_ATTR = /[\\w-]+=(?:\"[^\"]*\"|'[^']*')/gu;\n\n/**\n * A Shiki `{1,3-5}` line-range token, braces included. Shiki tolerates\n * whitespace inside the braces (`{1, 3-5}`), so the range is one token even\n * when it contains spaces — split on whitespace alone, `3-5}` would surface\n * as a bare word and be promoted to the block title.\n */\nconst LINE_RANGE = /\\{[^}]*\\}/u;\n\n/**\n * One fence-meta token: a quoted attribute, a line range, or a bare word, so\n * a keyword inside a quoted value (`title=\"enable ts2js later\"`) never reads\n * as a bare token and a spaced line range never splits.\n */\nconst META_TOKEN = new RegExp(\n `${QUOTED_ATTR.source}|${LINE_RANGE.source}|\\\\S+`,\n \"gu\"\n);\n\n/** Split a raw fence meta string into its tokens. */\nexport const metaTokens = (meta: string | null | undefined): string[] =>\n meta?.match(META_TOKEN) ?? [];\n\n/**\n * Bare keywords with reserved meaning after the language; never promoted to\n * a block title.\n */\nexport const RESERVED_META_KEYWORDS: ReadonlySet<string> = new Set([\n \"lineNumbers\",\n \"ts2js\",\n \"twoslash\",\n]);\n\n/** A Shiki `{1,3-5}` line-range token. */\nexport const isLineRange = (token: string): boolean => token.startsWith(\"{\");\n",
7
7
  "/**\n * Code-fence meta. A Shiki transformer reads the tokens after the language and\n * promotes them to attributes on the rendered `<pre>`:\n *\n * - a title — the first bare token (```ts blume.config.ts) or `title=\"...\"` —\n * becomes `data-title`; the theme's code header shows it, falling back to the\n * language label.\n * - the `lineNumbers` keyword (```ts file.ts lineNumbers) becomes\n * `data-line-numbers`; the theme renders a counter-driven line-number gutter.\n */\n\nimport {\n isLineRange,\n metaTokens,\n QUOTED_ATTR,\n RESERVED_META_KEYWORDS,\n} from \"./fence-meta.ts\";\n\n/** The slice of Shiki's transformer `this` context Blume reads. */\ninterface CodeMetaContext {\n options: { meta?: { __raw?: string } };\n}\n\n/** The `<pre>` hast node a Shiki `pre` hook receives. */\ninterface PreNode {\n properties: Record<string, boolean | number | string | undefined>;\n}\n\n/** A Shiki-compatible transformer, typed structurally to avoid a Shiki dep. */\nexport interface CodeTitleTransformer {\n name: string;\n pre: (this: CodeMetaContext, node: PreNode) => void;\n}\n\nconst TITLE_ATTR = /(?:^|\\s)title=(?:\"(?<dq>[^\"]*)\"|'(?<sq>[^']*)')/u;\nconst LINE_NUMBERS = /(?:^|\\s)lineNumbers(?=\\s|$)/u;\n\n// Quoted attrs are blanked before keyword/bare-token scans so a quoted value\n// can't leak tokens (`title=\"enable lineNumbers later\"`).\nconst withoutQuotedAttrs = (raw: string): string =>\n raw.replace(QUOTED_ATTR, \" \");\n\n// The first bare token is the title (```ts blume.config.ts): a non-empty token\n// that isn't a Shiki line range (`{1,3-5}`), a `key=value` attr, or a reserved\n// keyword.\nconst isTitleToken = (token: string): boolean =>\n token.length > 0 &&\n !isLineRange(token) &&\n !token.includes(\"=\") &&\n !RESERVED_META_KEYWORDS.has(token);\n\n/** The title a fence's meta string promotes to `data-title`, if any. */\nexport const parseCodeTitle = (raw: string | undefined): string | undefined => {\n if (!raw) {\n return undefined;\n }\n // Blank every *other* quoted attr first, so a `title=\"…\"` embedded in\n // another attribute's value (`caption='set title=\"X\" here'`) can't be\n // promoted to the block title.\n const scrubbed = raw.replace(QUOTED_ATTR, (attr) =>\n attr.startsWith(\"title=\") ? attr : \" \"\n );\n const explicit = scrubbed.match(TITLE_ATTR);\n const attrTitle = explicit?.groups?.dq ?? explicit?.groups?.sq;\n if (attrTitle) {\n return attrTitle;\n }\n // The shared tokenizer keeps a quoted attr (rejected below by its `=`) and\n // a spaced line range (`{1, 3-5}`) whole, so neither can shed a fragment\n // that reads as a bare title.\n return metaTokens(raw).find(isTitleToken);\n};\n\nconst hasLineNumbers = (raw: string | undefined): boolean =>\n Boolean(raw && LINE_NUMBERS.test(withoutQuotedAttrs(raw)));\n\n/** Build the transformer. Runs after Shiki's built-in `data-language` hook. */\nexport const codeTitleTransformer = (): CodeTitleTransformer => ({\n name: \"blume:code-meta\",\n pre(node) {\n const raw = this.options.meta?.__raw;\n const title = parseCodeTitle(raw);\n if (title) {\n node.properties.dataTitle = title;\n }\n if (hasLineNumbers(raw)) {\n node.properties.dataLineNumbers = true;\n }\n },\n});\n",
8
8
  "import type { ResolvedConfig } from \"../core/schema.ts\";\nimport type { PageRecord } from \"../core/types.ts\";\n\n/**\n * Resolve a page's facet values: the subset of its validated custom\n * frontmatter named by its content type's `facets` declaration. Strings facet\n * as-is; numbers and booleans are stringified so an enum-like `priority: 1`\n * or `enforced: true` still filters; any other shape (objects, arrays,\n * transformed dates) is not a facet value and is skipped. Returns `undefined`\n * when nothing facets, so the field stays absent from serialized documents\n * rather than shipping as `{}` on every page.\n */\n/** Whether a custom frontmatter value stringifies into a usable facet value. */\nconst isFacetValue = <T>(value: T): value is T & (string | number | boolean) =>\n typeof value === \"string\" ||\n typeof value === \"number\" ||\n typeof value === \"boolean\";\n\nexport const pageFacets = (\n page: Pick<PageRecord, \"contentType\" | \"custom\">,\n config: ResolvedConfig\n): Record<string, string> | undefined => {\n const declared = config.content.types[page.contentType]?.facets;\n if (!declared || declared.length === 0 || !page.custom) {\n return;\n }\n const facets: Record<string, string> = {};\n for (const key of declared) {\n const value = page.custom[key];\n if (isFacetValue(value)) {\n facets[key] = String(value);\n }\n }\n return Object.keys(facets).length > 0 ? facets : undefined;\n};\n",
@@ -14,14 +14,14 @@ import {
14
14
  renderRssFeed,
15
15
  isOpenApiSource,
16
16
  buildRawMarkdown
17
- } from "./chunk-xaz13gwg.js";
17
+ } from "./chunk-qkqwkpte.js";
18
18
  import {
19
19
  packageRoot
20
20
  } from "./index.js";
21
21
  import {
22
22
  buildSearchDocuments,
23
23
  buildMcpData
24
- } from "./chunk-a9kptbw5.js";
24
+ } from "./chunk-6crbhc3x.js";
25
25
  import {
26
26
  planComponentSlots,
27
27
  runtimeDependencies,
@@ -47,10 +47,10 @@ import {
47
47
  exampleMapTemplate,
48
48
  examplesPageTemplate,
49
49
  runtimeTsconfigTemplate
50
- } from "./chunk-mnqj32sj.js";
50
+ } from "./chunk-2hn4b8z7.js";
51
51
  import {
52
52
  discoverIslands
53
- } from "./chunk-bnbmcwfb.js";
53
+ } from "./chunk-wm7js3j9.js";
54
54
  import {
55
55
  buildAskData,
56
56
  buildThemeCss,
@@ -70,10 +70,10 @@ import {
70
70
  analyzeComponentsFile,
71
71
  languageIconCssFor,
72
72
  clientFeaturesFor
73
- } from "./chunk-z1f5arsg.js";
73
+ } from "./chunk-wgm7m9qk.js";
74
74
  import {
75
75
  scanProject
76
- } from "./chunk-zg2gtj10.js";
76
+ } from "./chunk-fs23ddbb.js";
77
77
 
78
78
  // src/registry/eject.ts
79
79
  import { existsSync } from "node:fs";
@@ -98,8 +98,8 @@ var ejectOpenApiData = (project) => {
98
98
  return source ? source.openApiData() : {};
99
99
  };
100
100
  var askFiles = async (project, srcDir, genDir) => {
101
- const { ask } = project.config.ai;
102
- if (!(ask?.enabled && !ask.endpoint)) {
101
+ const { assistant } = project.config.ai;
102
+ if (!(assistant?.enabled && !assistant.endpoint)) {
103
103
  const endpointPath = join(srcDir, "pages", "api", "ask.ts");
104
104
  if (existsSync(endpointPath)) {
105
105
  const content = await readFile(endpointPath, "utf-8");
@@ -110,13 +110,13 @@ var askFiles = async (project, srcDir, genDir) => {
110
110
  await rm(join(genDir, "ask-data.json"), { force: true });
111
111
  return [];
112
112
  }
113
- const backend = resolveAskBackend(ask.provider);
113
+ const backend = resolveAskBackend(assistant.provider);
114
114
  const files = [
115
115
  {
116
116
  content: askEndpointTemplate(backend, {
117
- cors: ask.cors,
118
- instructions: ask.instructions,
119
- retrieval: ask.retrieval
117
+ cors: assistant.cors,
118
+ instructions: assistant.instructions,
119
+ retrieval: assistant.retrieval
120
120
  }),
121
121
  path: join(srcDir, "pages", "api", "ask.ts")
122
122
  }
@@ -201,7 +201,7 @@ var examplesPreviewFiles = (srcDir, basePath, hasExamples) => hasExamples ? [
201
201
  }
202
202
  ] : [];
203
203
  var ejectDependencies = (options) => {
204
- const { ask } = options.config.ai;
204
+ const { assistant } = options.config.ai;
205
205
  return [
206
206
  ...new Set([
207
207
  "astro",
@@ -209,7 +209,7 @@ var ejectDependencies = (options) => {
209
209
  "blume",
210
210
  ...runtimeDependencies(options),
211
211
  ...options.needsReact ? ["react", "react-dom"] : [],
212
- ...ask?.enabled && !ask.endpoint ? ["ai"] : [],
212
+ ...assistant?.enabled && !assistant.endpoint ? ["ai"] : [],
213
213
  ...options.config.export.epub ? ["epub-gen-memory"] : []
214
214
  ])
215
215
  ];
@@ -220,7 +220,7 @@ var eject = async (root) => {
220
220
  const { context, config } = project;
221
221
  const srcDir = join(root, "src");
222
222
  const genDir = join(srcDir, "generated");
223
- const askEnabled = config.ai.ask?.enabled ?? false;
223
+ const assistantEnabled = config.ai.assistant?.enabled ?? false;
224
224
  const exportPdf = config.export.pdf;
225
225
  const exportEpub = config.export.epub;
226
226
  const examples = await discoverExamples(root, config.examples.source);
@@ -252,7 +252,7 @@ var eject = async (root) => {
252
252
  ...examples.examples.map((example) => example.framework),
253
253
  ...slotPlan.frameworks
254
254
  ]);
255
- const needsReact = needsReactRaw || askEnabled || frameworks.has("react");
255
+ const needsReact = needsReactRaw || assistantEnabled || frameworks.has("react");
256
256
  const needsVue = frameworks.has("vue");
257
257
  const needsSvelte = frameworks.has("svelte");
258
258
  const relContext = {
@@ -358,7 +358,7 @@ var eject = async (root) => {
358
358
  },
359
359
  { content: buildRuntimeData(project), path: join(genDir, "data.json") },
360
360
  {
361
- content: askComponentTemplate(askEnabled),
361
+ content: askComponentTemplate(assistantEnabled),
362
362
  path: join(genDir, "Ask.astro")
363
363
  },
364
364
  {
@@ -385,7 +385,7 @@ var eject = async (root) => {
385
385
  path: join(srcDir, "pages", "[...slug].mdx.ts")
386
386
  }
387
387
  ];
388
- if (askEnabled) {
388
+ if (assistantEnabled) {
389
389
  files.push(...await askFiles(project, srcDir, genDir));
390
390
  }
391
391
  if (config.seo.og.enabled) {
@@ -494,5 +494,5 @@ var eject = async (root) => {
494
494
 
495
495
  export { eject };
496
496
 
497
- //# debugId=21FD19BF3F1015A764756E2164756E21
498
- //# sourceMappingURL=chunk-mwt1k8n7.js.map
497
+ //# debugId=038C2E34744E4EC964756E2164756E21
498
+ //# sourceMappingURL=chunk-6hsn950k.js.map
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/registry/eject.ts"],
4
+ "sourcesContent": [
5
+ "import { existsSync } from \"node:fs\";\nimport { cp, mkdir, readFile, rm, writeFile } from \"node:fs/promises\";\n\nimport { join, relative } from \"pathe\";\n\nimport { buildAskData } from \"../ai/ask-data.ts\";\nimport { resolveAskBackend } from \"../ai/ask.ts\";\nimport { buildRawMarkdown } from \"../ai/markdown.ts\";\nimport { buildMcpData } from \"../ai/mcp/data.ts\";\nimport { buildMcpDiscovery, buildMcpServerCard } from \"../ai/mcp/discovery.ts\";\nimport { planComponentSlots } from \"../astro/component-slots.ts\";\nimport {\n EXAMPLE_SCAN_GLOB,\n discoverExamples,\n exampleMarkdownLookup,\n exampleScanRoots,\n} from \"../astro/examples.ts\";\nimport {\n analyzeComponentsFile,\n buildRuntimeData,\n clientFeaturesFor,\n collectStaged,\n detectNeedsReact,\n detectUsesMath,\n languageIconCssFor,\n} from \"../astro/generate.ts\";\nimport { discoverIslands } from \"../astro/islands.ts\";\nimport { customOgRoutes, discoverPages, routeIsTaken } from \"../astro/pages.ts\";\nimport {\n askComponentTemplate,\n askEndpointTemplate,\n astroConfigTemplate,\n catchAllPageTemplate,\n changelogIndexTemplate,\n contentConfigTemplate,\n exampleMapTemplate,\n exampleSlug,\n examplesPageTemplate,\n exampleWrapperTemplate,\n featuresTemplate,\n mcpEndpointTemplate,\n mcpPageFile,\n mixedbreadSearchEndpointTemplate,\n notFoundPageTemplate,\n ogEndpointTemplate,\n rawMarkdownEndpointTemplate,\n rssEndpointTemplate,\n runtimeDependencies,\n runtimeTsconfigTemplate,\n searchClientTemplate,\n searchEndpointTemplate,\n staticJsonEndpointTemplate,\n} from \"../astro/templates.ts\";\nimport { buildIncludeGraph } from \"../core/includes.ts\";\nimport { packageRoot } from \"../core/package-root.ts\";\nimport { scanProject } from \"../core/project-graph.ts\";\nimport type { BlumeProject } from \"../core/project-graph.ts\";\nimport {\n resolveDocsCollection,\n sourcesOfKind,\n} from \"../core/sources/collection.ts\";\nimport type { ProjectContext } from \"../core/types.ts\";\nimport { buildRssFeeds, renderRssFeed } from \"../deploy/rss.ts\";\nimport type { OpenApiData } from \"../openapi/model.ts\";\nimport { hasScalarReferences } from \"../openapi/references.ts\";\nimport { buildReferenceFiles } from \"../openapi/scalar.ts\";\nimport { isOpenApiSource } from \"../openapi/source.ts\";\nimport { buildSearchDocuments } from \"../search/documents.ts\";\nimport {\n examplesEntryTemplate,\n tailwindEntryTemplate,\n} from \"../theme/entry.ts\";\nimport { buildThemeCss } from \"../theme/palette.ts\";\nimport { rebaseSourceDirectives } from \"../theme/sources.ts\";\nimport { twoslashCss } from \"../theme/twoslash.ts\";\n\nconst toPosix = (path: string): string => path.split(\"\\\\\").join(\"/\");\n\n/** The portable `@source` guess: blume in the project's own node_modules. */\nconst LOCAL_BLUME_SOURCE = \"../../node_modules/blume/src/**/*.{astro,ts,tsx}\";\n\n/**\n * The `@source` glob pointing Tailwind at Blume's own source, relative to the\n * ejected `src/generated/app.css`. The project-local `node_modules/blume` is\n * preferred (portable, and under pnpm the symlink survives version bumps), but\n * hoisted installs (npm/yarn workspaces lift blume into the workspace root's\n * node_modules) would make that guess match nothing and silently drop utility\n * classes — so fall back to the package's real installed location, and when\n * even that fails, warn instead of failing silently.\n *\n * Exported for testing.\n */\nexport const blumeSourceGlob = (\n root: string,\n genDir: string,\n resolveBlumeRoot: () => string = packageRoot\n): string => {\n if (existsSync(join(root, \"node_modules\", \"blume\"))) {\n return LOCAL_BLUME_SOURCE;\n }\n try {\n const src = join(resolveBlumeRoot(), \"src\");\n return `${toPosix(relative(genDir, src))}/**/*.{astro,ts,tsx}`;\n } catch {\n console.warn(\n '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.'\n );\n return LOCAL_BLUME_SOURCE;\n }\n};\n\n/** The `blume:openapi` payload for the ejected app (`{}` when none). */\nconst ejectOpenApiData = (project: BlumeProject): OpenApiData => {\n const source = project.sources.find(isOpenApiSource);\n return source ? source.openApiData() : {};\n};\n\n/**\n * The assistant endpoint plus, unless the backend runs its own retrieval (Inkeep),\n * its grounding snapshot. Empty when the assistant is disabled.\n */\nconst askFiles = async (\n project: BlumeProject,\n srcDir: string,\n genDir: string\n): Promise<{ content: string; path: string }[]> => {\n const { assistant } = project.config.ai;\n if (!(assistant?.enabled && !assistant.endpoint)) {\n const endpointPath = join(srcDir, \"pages\", \"api\", \"ask.ts\");\n if (existsSync(endpointPath)) {\n const content = await readFile(endpointPath, \"utf-8\");\n if (content.startsWith(\"// Generated by Blume. Do not edit.\")) {\n await rm(endpointPath, { force: true });\n }\n }\n await rm(join(genDir, \"ask-data.json\"), { force: true });\n return [];\n }\n const backend = resolveAskBackend(assistant.provider);\n const files = [\n {\n content: askEndpointTemplate(backend, {\n cors: assistant.cors,\n instructions: assistant.instructions,\n retrieval: assistant.retrieval,\n }),\n path: join(srcDir, \"pages\", \"api\", \"ask.ts\"),\n },\n ];\n if (backend.grounded) {\n files.push({\n content: `${JSON.stringify(await buildAskData(project))}\\n`,\n path: join(genDir, \"ask-data.json\"),\n });\n }\n return files;\n};\n\n/** Whether the ejected app hosts the MCP server (enabled and route free). */\nconst hostsMcp = (\n project: BlumeProject,\n userPages: { pattern: string }[]\n): boolean =>\n project.config.agents.mcp.enabled &&\n !routeIsTaken(\n userPages,\n project.graph.pages,\n project.config.agents.mcp.route\n );\n\n/**\n * The `.well-known` MCP discovery routes, injected as prerendered pages\n * alongside the user's own so the ejected Astro config wires them in. Empty\n * when the server is disabled or its route is already owned by a page.\n */\nconst mcpDiscoveryPages = (\n project: BlumeProject,\n userPages: { pattern: string }[]\n): { entrypoint: string; pattern: string }[] =>\n hostsMcp(project, userPages)\n ? [\n {\n entrypoint: \"src/blume-mcp/discovery.ts\",\n pattern: \"/.well-known/mcp.json\",\n },\n {\n entrypoint: \"src/blume-mcp/server-card.ts\",\n pattern: \"/.well-known/mcp/server-card.json\",\n },\n ]\n : [];\n\n/**\n * The MCP data snapshot, server endpoint, and `.well-known` discovery\n * documents, mirroring `writeMcpFiles` in generate.ts. Empty when the server\n * is disabled or its route is already owned by a page.\n */\nconst mcpFiles = async (\n project: BlumeProject,\n userPages: { pattern: string }[],\n srcDir: string,\n genDir: string\n): Promise<{ content: string; path: string }[]> => {\n if (!hostsMcp(project, userPages)) {\n return [];\n }\n const { route } = project.config.agents.mcp;\n const data = await buildMcpData(project);\n const discoveryInput = {\n base: data.base,\n name: data.name,\n route,\n site: data.site,\n version: data.version,\n };\n return [\n {\n content: `${JSON.stringify(data)}\\n`,\n path: join(genDir, \"mcp-data.json\"),\n },\n {\n content: mcpEndpointTemplate(),\n path: join(srcDir, \"pages\", mcpPageFile(route)),\n },\n {\n content: staticJsonEndpointTemplate(buildMcpDiscovery(discoveryInput)),\n path: join(srcDir, \"blume-mcp\", \"discovery.ts\"),\n },\n {\n content: staticJsonEndpointTemplate(buildMcpServerCard(discoveryInput)),\n path: join(srcDir, \"blume-mcp\", \"server-card.ts\"),\n },\n ];\n};\n\n/**\n * The `/changelog` index page, mirroring `shouldGenerateChangelog` in\n * generate.ts: emitted when `type: changelog` entries or a release-backed\n * changelog source exist, unless a user page already owns the route.\n */\nconst changelogFiles = (\n project: BlumeProject,\n userPages: { pattern: string }[],\n srcDir: string,\n options: Parameters<typeof changelogIndexTemplate>[0]\n): { content: string; path: string }[] => {\n const hasChangelog = project.graph.pages.some(\n (page) =>\n page.contentType === \"changelog\" &&\n !(page.meta.draft || page.meta.sidebar.hidden)\n );\n const hasChangelogSource =\n sourcesOfKind(project.config, \"github-releases\").length > 0;\n if (\n !(hasChangelog || hasChangelogSource) ||\n routeIsTaken(userPages, project.graph.pages, \"/changelog\")\n ) {\n return [];\n }\n return [\n {\n content: changelogIndexTemplate(options),\n path: join(srcDir, \"pages\", \"changelog.astro\"),\n },\n ];\n};\n\n/** Contents of the configured `examples.css`, or `\"\"` when unset/absent. */\n/**\n * Read a user stylesheet that eject inlines into a generated entry under\n * `genDir`, re-rooting its relative `@source` paths from the user's file.\n * Resolves to an empty string when the file is unset or absent.\n */\nconst readUserCss = async (\n file: string | null,\n genDir: string\n): Promise<string> => {\n if (!(file && existsSync(file))) {\n return \"\";\n }\n const css = await readFile(file, \"utf-8\");\n return rebaseSourceDirectives(css, { from: file, to: genDir });\n};\n\n/**\n * The per-example preview route `<Component />` iframes embed, nested under\n * `basePath` so it stays reachable behind a proxy that only forwards the\n * base. Empty when the project has no examples.\n */\nconst examplesPreviewFiles = (\n srcDir: string,\n basePath: string,\n hasExamples: boolean\n): { content: string; path: string }[] =>\n hasExamples\n ? [\n {\n content: examplesPageTemplate(),\n path: join(\n srcDir,\n \"pages\",\n ...basePath.split(\"/\").filter(Boolean),\n \"blume-examples\",\n \"[...path].astro\"\n ),\n },\n ]\n : [];\n\n/**\n * The packages the ejected app imports by bare name: Astro, Tailwind's Vite\n * plugin, `blume` itself, the integrations the config wires in, whatever the\n * configured adapters declare, and React when islands, examples, or the assistant\n * render with it (Blume ships React, so projects rarely list it). They have\n * to be the project's own dependencies after eject — under a strict linker\n * such as pnpm nothing else makes them resolvable, and `astro build` fails.\n * The hidden runtime reaches the same packages through its `node_modules`\n * junction into Blume's own, so only eject declares the ones its generated\n * files import directly: the AI SDK the assistant route streams through (unless\n * `ai.assistant.endpoint` points elsewhere, when no route is written) and the EPUB\n * generator's browser bundle `features.ts` loads.\n */\nconst ejectDependencies = (\n options: Parameters<typeof runtimeDependencies>[0]\n): string[] => {\n const { assistant } = options.config.ai;\n return [\n ...new Set([\n \"astro\",\n \"@tailwindcss/vite\",\n \"blume\",\n ...runtimeDependencies(options),\n ...(options.needsReact ? [\"react\", \"react-dom\"] : []),\n ...(assistant?.enabled && !assistant.endpoint ? [\"ai\"] : []),\n ...(options.config.export.epub ? [\"epub-gen-memory\"] : []),\n ]),\n ];\n};\n\nconst ejectIntegrationBridge = (\n config: BlumeProject[\"config\"],\n root: string,\n configFile: string | null\n): Parameters<typeof astroConfigTemplate>[0][\"integrationBridge\"] =>\n config.integrations.length > 0 && configFile\n ? { configFile: toPosix(relative(root, configFile)) }\n : undefined;\n\n/**\n * Promote the generated runtime into the project as an owned Astro app. After\n * eject the project has a normal `astro.config.mjs` and `src/`, the `blume` CLI\n * is no longer required, and the `blume` package remains importable.\n *\n * Returns the written files plus non-fatal warnings, mirroring the generated\n * runtime (e.g. a Scalar reference spec that wasn't found, or a reference\n * route colliding with a content page), and the packages the ejected app\n * imports by bare name, for the caller to add to package.json.\n */\nexport const eject = async (\n root: string\n): Promise<{ dependencies: string[]; files: string[]; warnings: string[] }> => {\n const project = await scanProject(root, { mode: \"build\" });\n const { context, config } = project;\n\n const srcDir = join(root, \"src\");\n const genDir = join(srcDir, \"generated\");\n const assistantEnabled = config.ai.assistant?.enabled ?? false;\n const exportPdf = config.export.pdf;\n const exportEpub = config.export.epub;\n\n // Discover examples first and expose them on the project, so the agent-facing\n // Markdown built below (raw `.md`, MCP) downlevels `<Component>` to its source.\n const examples = await discoverExamples(root, config.examples.source);\n project.examples = exampleMarkdownLookup(examples.examples);\n\n const [\n pages,\n needsReactRaw,\n usesMath,\n userTheme,\n userExamplesCss,\n rawMarkdown,\n islands,\n overrideAnalysis,\n ] = await Promise.all([\n context.pagesRoot ? discoverPages(context.pagesRoot) : Promise.resolve([]),\n detectNeedsReact(root),\n detectUsesMath(root),\n readUserCss(context.themeFile, genDir),\n readUserCss(\n config.examples.css ? join(root, config.examples.css) : null,\n genDir\n ),\n buildRawMarkdown(project),\n discoverIslands(root),\n analyzeComponentsFile(context.componentsFile),\n ]);\n // The `islands/` convention and `components.ts` share the same static plan\n // the CLI uses, with every import written relative to its generated file so\n // the ejected app builds from any checkout (like the example wrappers below).\n const slotPlan = planComponentSlots(islands.islands, overrideAnalysis, {\n relativeTo: genDir,\n });\n // Island/example/override frameworks drive which Astro renderers the ejected\n // config wires in; React also switches on for project `.tsx`/`.jsx` and the assistant.\n const frameworks = new Set<string>([\n ...islands.islands.map((island) => island.framework),\n ...examples.examples.map((example) => example.framework),\n ...slotPlan.frameworks,\n ]);\n const needsReact =\n needsReactRaw || assistantEnabled || frameworks.has(\"react\");\n const needsVue = frameworks.has(\"vue\");\n const needsSvelte = frameworks.has(\"svelte\");\n\n // A project-relative context so generated files use portable paths.\n const relContext: ProjectContext = {\n ...context,\n contentRoot: toPosix(relative(root, context.contentRoot)),\n distDir: \"./dist\",\n outDir: \".\",\n pagesRoot: context.pagesRoot\n ? `./${toPosix(relative(root, context.pagesRoot))}`\n : null,\n root: \".\",\n };\n // The `docs` collection resolves its base against the real project root and\n // is then relativized like everything else, so the ejected content config\n // never bakes in the absolute path `eject` happened to run from.\n const docsCollection = resolveDocsCollection(config, root);\n const relCollection = {\n ...docsCollection,\n base: toPosix(relative(root, docsCollection.base)),\n };\n\n const relPages = [\n ...pages.map((page) => ({\n entrypoint: toPosix(relative(root, page.entrypoint)),\n pattern: page.pattern,\n })),\n ...mcpDiscoveryPages(project, pages),\n ];\n\n // Non-filesystem sources eject their materialized MDX into `<root>/blume-staged`\n // (a dedicated dir so it never clashes with a content root literally named\n // `content`; the relative `staged` collection points there).\n const staged = collectStaged(project);\n const hasStaged = staged.size > 0;\n const stagedDir = \"blume-staged\";\n const features = await clientFeaturesFor(project);\n const languageIcons = await languageIconCssFor(project);\n\n const files: {\n path: string;\n content: string;\n /** Don't overwrite a file the user already owns (e.g. a tuned tsconfig). */\n skipIfExists?: boolean;\n }[] = [\n {\n content: astroConfigTemplate({\n askPath: \"./src/generated/Ask.astro\",\n config,\n contentRoot: relContext.contentRoot,\n contentRoutes: project.manifest.routes.map((route) => route.path),\n context: relContext,\n examplesPath: \"./src/generated/examples.ts\",\n examplesThemePath: \"./src/generated/examples.css\",\n features,\n featuresPath: \"./src/generated/features.ts\",\n // No CLI publishes the runtime data modules in memory after eject, so\n // the config aliases each to the JSON snapshot written below.\n generatedModulesDir: \"./src/generated\",\n integrationBridge: ejectIntegrationBridge(\n config,\n root,\n context.configFile\n ),\n needsReact,\n needsSvelte,\n needsVue,\n pages: relPages,\n searchClientPath: \"./src/generated/search-client.ts\",\n themePath: \"./src/generated/app.css\",\n }),\n path: join(root, \"astro.config.mjs\"),\n },\n {\n content: runtimeTsconfigTemplate(),\n path: join(root, \"tsconfig.json\"),\n // Never clobber a hand-tuned tsconfig; only write ours if none exists.\n skipIfExists: true,\n },\n {\n content: contentConfigTemplate({\n collection: relCollection,\n config,\n context: relContext,\n staged: hasStaged,\n stagedBase: stagedDir,\n }),\n path: join(srcDir, \"content.config.ts\"),\n },\n {\n content: catchAllPageTemplate({\n exportEpub,\n exportPdf,\n mathEnabled: usesMath,\n needsReact,\n }),\n path: join(srcDir, \"pages\", \"[...slug].astro\"),\n },\n {\n content: slotPlan.module,\n path: join(genDir, \"components.ts\"),\n },\n // The example map the catch-all imports; written even when empty so the\n // `blume:examples` alias always resolves.\n {\n content: exampleMapTemplate(examples.examples, config.basePath),\n path: join(genDir, \"examples.ts\"),\n },\n {\n // The isolated Tailwind entry for `<Component />` preview frames.\n // Relative sources keep the ejected app portable.\n content: examplesEntryTemplate({\n configTokens: buildThemeCss(config.theme),\n sources: exampleScanRoots(root, examples.dir).map(\n (dir) => `${relative(genDir, dir)}/${EXAMPLE_SCAN_GLOB}`\n ),\n userCss: userExamplesCss,\n }),\n path: join(genDir, \"examples.css\"),\n },\n {\n content: tailwindEntryTemplate({\n configTokens: buildThemeCss(config.theme),\n languageIcons,\n // Relative paths from src/generated/app.css keep the ejected app\n // portable; the blume glob resolves the real install location when\n // the package is hoisted out of the project's own node_modules.\n sources: [\n blumeSourceGlob(root, genDir),\n \"../../**/*.{astro,mdx,ts,tsx}\",\n ],\n twoslashCss: twoslashCss(),\n userTheme,\n }),\n path: join(genDir, \"app.css\"),\n },\n { content: buildRuntimeData(project), path: join(genDir, \"data.json\") },\n // The header's Ask trigger behind the `blume:ask` alias. Always written — it\n // renders nothing when Ask is off — so the alias always resolves.\n {\n content: askComponentTemplate(assistantEnabled),\n path: join(genDir, \"Ask.astro\"),\n },\n {\n content: `${JSON.stringify(ejectOpenApiData(project))}\\n`,\n path: join(genDir, \"openapi.json\"),\n },\n {\n content: `${JSON.stringify(rawMarkdown)}\\n`,\n path: join(genDir, \"raw-markdown.json\"),\n },\n {\n // The partial → including-pages map behind `includeHmrPlugin`, which the\n // ejected astro.config wires at this exact path — without the file every\n // hot update's read would silently no-op and partial edits would serve\n // stale pages. A snapshot like the rest of `src/generated`: the ejected\n // app owns (and may regenerate or prune) it.\n content: `${JSON.stringify(buildIncludeGraph(project.graph.pages))}\\n`,\n path: join(genDir, \"includes.json\"),\n },\n {\n content: rawMarkdownEndpointTemplate(\"md\"),\n path: join(srcDir, \"pages\", \"[...slug].md.ts\"),\n },\n {\n content: rawMarkdownEndpointTemplate(\"mdx\"),\n path: join(srcDir, \"pages\", \"[...slug].mdx.ts\"),\n },\n ];\n\n if (assistantEnabled) {\n files.push(...(await askFiles(project, srcDir, genDir)));\n }\n\n if (config.seo.og.enabled) {\n files.push({\n content: ogEndpointTemplate(\n customOgRoutes(pages, config.title, config.seo.og.titles),\n { pageDescriptions: config.seo.og.description !== false }\n ),\n path: join(srcDir, \"pages\", \"og\", \"[...slug].png.ts\"),\n });\n }\n\n // The hosted MCP server and the `/changelog` index, mirrored from the\n // generated runtime (each helper returns `[]` when its feature is off).\n files.push(\n ...(await mcpFiles(project, pages, srcDir, genDir)),\n ...changelogFiles(project, pages, srcDir, {\n exportEpub,\n exportPdf,\n needsReact,\n staged: hasStaged,\n })\n );\n\n // Default 404 page, unless the project already owns `/404` (a custom\n // `pages/404.astro` or a `404.md` content page). The ejected project owns the\n // file afterwards and can edit or remove it.\n if (!routeIsTaken(pages, project.graph.pages, \"/404\")) {\n files.push({\n content: notFoundPageTemplate(),\n path: join(srcDir, \"pages\", \"404.astro\"),\n });\n }\n\n // The client-feature loaders behind the `blume:features` alias, and the\n // provider-specific client loader behind `blume:search-client`.\n files.push(\n {\n content: featuresTemplate(features),\n path: join(genDir, \"features.ts\"),\n },\n {\n content: searchClientTemplate(config),\n path: join(genDir, \"search-client.ts\"),\n }\n );\n\n const searchAdapter = config.search.provider;\n if (searchAdapter.mode === \"static\") {\n const documents = await buildSearchDocuments(project);\n files.push(\n {\n content: `${JSON.stringify(documents)}\\n`,\n path: join(genDir, \"search.json\"),\n },\n {\n content: searchEndpointTemplate(),\n path: join(srcDir, \"pages\", \"blume-search.json.ts\"),\n }\n );\n }\n\n if (searchAdapter.kind === \"mixedbread\") {\n files.push({\n content: mixedbreadSearchEndpointTemplate(searchAdapter.options),\n path: join(srcDir, \"pages\", \"api\", \"search.ts\"),\n });\n }\n\n const feeds = buildRssFeeds(project);\n if (feeds.length > 0) {\n const feedXml = Object.fromEntries(\n feeds.map((feed) => [feed.type, renderRssFeed(feed)])\n );\n files.push(\n {\n content: `${JSON.stringify(feedXml)}\\n`,\n path: join(genDir, \"rss.json\"),\n },\n {\n content: rssEndpointTemplate(),\n path: join(srcDir, \"pages\", \"[section]\", \"rss.xml.ts\"),\n }\n );\n }\n\n // Scalar API/AsyncAPI reference pages, mirrored from the generated runtime so\n // the ejected app keeps its reference routes — including the warnings (a\n // missing spec file, a route collision), which the caller surfaces exactly\n // like the generated-runtime path does.\n const warnings: string[] = [];\n if (hasScalarReferences(config)) {\n const references = await buildReferenceFiles({\n config,\n contentRoutes: new Set(project.graph.pages.map((page) => page.route)),\n root,\n });\n warnings.push(...references.warnings);\n for (const file of references.files) {\n files.push({\n content: file.content,\n path: join(srcDir, \"pages\", file.pagePath),\n });\n }\n }\n\n // Per-override and per-example live wrappers referenced by the maps above.\n files.push(\n ...slotPlan.wrappers.map((wrapper) => ({\n content: wrapper.content,\n path: join(genDir, \"component-slots\", `${wrapper.name}.astro`),\n })),\n ...examples.examples.map((example) => ({\n content: exampleWrapperTemplate(example, join(genDir, \"examples\")),\n path: join(genDir, \"examples\", `${exampleSlug(example.path)}.astro`),\n })),\n ...examplesPreviewFiles(\n srcDir,\n config.basePath,\n examples.examples.length > 0\n )\n );\n\n // Materialize staged source bodies under `<root>/blume-staged/<source>/<ref>`,\n // matching the relative `staged` collection base in the ejected config.\n for (const [entryId, content] of staged) {\n files.push({ content, path: join(root, stagedDir, entryId) });\n }\n\n const written = files.filter(\n (file) => !(file.skipIfExists && existsSync(file.path))\n );\n await Promise.all(\n written.map(async (file) => {\n await mkdir(join(file.path, \"..\"), { recursive: true });\n await writeFile(file.path, file.content, \"utf-8\");\n })\n );\n\n // Materialized source assets (e.g. downloaded Notion images) live under the\n // hidden runtime's public dir; copy them into the owned project's `public/`\n // so the staged content's `/blume-assets/…` references still resolve.\n const assetsSrc = join(context.outDir, \"public\", \"blume-assets\");\n if (existsSync(assetsSrc)) {\n await cp(assetsSrc, join(root, \"public\", \"blume-assets\"), {\n recursive: true,\n });\n }\n\n // The hidden runtime is no longer the source of truth.\n await rm(context.outDir, { force: true, recursive: true });\n\n return {\n dependencies: ejectDependencies({\n config,\n needsReact,\n needsSvelte,\n needsVue,\n }),\n files: written.map((file) => file.path),\n warnings,\n };\n};\n"
6
+ ],
7
+ "mappings": "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",
8
+ "debugId": "038C2E34744E4EC964756E2164756E21",
9
+ "names": []
10
+ }
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  discoverIslands,
4
4
  missingDependencyDiagnostic
5
- } from "./chunk-bnbmcwfb.js";
5
+ } from "./chunk-wm7js3j9.js";
6
6
  import {
7
7
  packageRoot,
8
8
  commandMeta,
@@ -14,23 +14,23 @@ import {
14
14
  reportDiagnosticsJson,
15
15
  reportDiagnostics
16
16
  } from "./index.js";
17
- import"./chunk-xaz13gwg.js";
17
+ import"./chunk-qkqwkpte.js";
18
18
  import {
19
19
  ComponentOverridesError,
20
20
  analyzeComponentOverrides
21
- } from "./chunk-f2972sbt.js";
21
+ } from "./chunk-vtk4a6dg.js";
22
22
  import"./chunk-jts8mvcz.js";
23
23
  import {
24
24
  scanProject
25
- } from "./chunk-zg2gtj10.js";
25
+ } from "./chunk-fs23ddbb.js";
26
26
  import"./chunk-jq5n4avg.js";
27
27
  import {
28
28
  unregisteredSnapshotDiagnostics
29
- } from "./chunk-sqn5t4q0.js";
29
+ } from "./chunk-qs4q5p4e.js";
30
30
  import {
31
31
  serverFeatures,
32
32
  checkRequiredSecrets
33
- } from "./chunk-2q1dwty4.js";
33
+ } from "./chunk-ah61y8py.js";
34
34
 
35
35
  // src/cli/commands/doctor.ts
36
36
  import { readFileSync } from "node:fs";
@@ -109,12 +109,12 @@ var doctorCommand = defineCommand({
109
109
  logger.info(`References: ${config.reference.map((adapter) => adapter.kind).join(", ") || "none"}`);
110
110
  logger.info(`Analytics: ${config.analytics.map((adapter) => adapter.kind).join(", ") || "none"}`);
111
111
  logger.info(`Sources: ${config.content.sources.map((source) => source.kind).join(", ")}`);
112
- const { ask } = config.ai;
113
- let askSummary = "off";
114
- if (ask?.enabled) {
115
- askSummary = ask.endpoint ? "external endpoint" : ask.provider.kind;
112
+ const { assistant } = config.ai;
113
+ let assistantSummary = "off";
114
+ if (assistant?.enabled) {
115
+ assistantSummary = assistant.endpoint ? "external endpoint" : assistant.provider.kind;
116
116
  }
117
- logger.info(`Ask AI: ${askSummary}`);
117
+ logger.info(`Assistant: ${assistantSummary}`);
118
118
  }
119
119
  } catch (error) {
120
120
  if (error instanceof BlumeError) {
@@ -144,5 +144,5 @@ export {
144
144
  doctorCommand
145
145
  };
146
146
 
147
- //# debugId=C33A5318E923BC8364756E2164756E21
148
- //# sourceMappingURL=chunk-11j0384y.js.map
147
+ //# debugId=57C3E0688A0BB3FA64756E2164756E21
148
+ //# sourceMappingURL=chunk-6vm74dry.js.map
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/cli/commands/doctor.ts"],
4
4
  "sourcesContent": [
5
- "import { readFileSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\n\nimport { defineCommand } from \"citty\";\nimport { join } from \"pathe\";\nimport { satisfies } from \"semver\";\n\nimport { discoverIslands } from \"../../astro/islands.ts\";\nimport { missingDependencyDiagnostic } from \"../../astro/runtime-deps.ts\";\nimport {\n analyzeComponentOverrides,\n ComponentOverridesError,\n} from \"../../core/component-overrides.ts\";\nimport { BlumeError } from \"../../core/diagnostics.ts\";\nimport { packageRoot } from \"../../core/package-root.ts\";\nimport { scanProject } from \"../../core/project-graph.ts\";\nimport { serverFeatures } from \"../../core/server-features.ts\";\nimport type { Diagnostic } from \"../../core/types.ts\";\nimport { unregisteredSnapshotDiagnostics } from \"../../core/version-cut.ts\";\nimport { commandMeta } from \"../command-meta.ts\";\nimport { loadEnvFiles } from \"../env.ts\";\nimport { reportInternalError } from \"../internal-error.ts\";\nimport {\n flushStdout,\n logger,\n reportDiagnostics,\n reportDiagnosticsJson,\n} from \"../log.ts\";\nimport { checkRequiredSecrets } from \"../required-secrets.ts\";\n\nconst FALLBACK_NODE_RANGE = \">=22.12.0\";\n\n/**\n * Plan `components.ts` the way `blume dev`/`build` do, reporting each override\n * that can't be planned — a non-static form, or an import of a file that\n * doesn't exist — with its own line. The scan alone never reads the file, so\n * without this doctor would pass a project whose build fails.\n */\nconst componentsDiagnostics = async (\n componentsFile: string | null\n): Promise<Diagnostic[]> => {\n if (!componentsFile) {\n return [];\n }\n try {\n analyzeComponentOverrides(\n await readFile(componentsFile, \"utf-8\"),\n componentsFile\n );\n return [];\n } catch (error) {\n if (error instanceof ComponentOverridesError) {\n return error.issues;\n }\n throw error;\n }\n};\n\n/** The supported Node range, read from the package's own `engines` field so\n * doctor can never drift from what the package actually declares. */\nconst supportedNodeRange = (): string => {\n try {\n // SAFETY: this parses blume's own package.json; the optional fields cover\n // an `engines` block going missing, and a bad read falls to the catch.\n const pkg = JSON.parse(\n readFileSync(join(packageRoot(), \"package.json\"), \"utf-8\")\n ) as { engines?: { node?: string } };\n return pkg.engines?.node || FALLBACK_NODE_RANGE;\n } catch {\n return FALLBACK_NODE_RANGE;\n }\n};\n\nexport const doctorCommand = defineCommand({\n args: {\n json: {\n description: \"Emit diagnostics as JSON on stdout (for CI/editors).\",\n type: \"boolean\",\n },\n },\n meta: commandMeta.doctor,\n async run({ args }) {\n const root = process.cwd();\n const diagnostics: Diagnostic[] = [];\n\n const nodeRange = supportedNodeRange();\n if (!satisfies(process.versions.node, nodeRange)) {\n diagnostics.push({\n code: \"BLUME_NODE_VERSION\",\n message: `Node ${process.versions.node} is outside the supported range (${nodeRange}).`,\n severity: \"warning\",\n });\n }\n\n // Read `.env` files first, as `blume dev`/`build` do: remote sources read\n // their tokens during the scan, and the secrets check below reads them too.\n loadEnvFiles(root);\n\n try {\n const project = await scanProject(root, { mode: \"build\" });\n diagnostics.push(\n ...project.diagnostics,\n ...unregisteredSnapshotDiagnostics(project)\n );\n\n const { config } = project;\n // The packages and secrets a build would need: an adapter's missing SDK\n // fails the build, and a missing secret fails at the first request.\n const { islands } = await discoverIslands(root);\n const dependencies = await missingDependencyDiagnostic(\n config,\n root,\n \"error\",\n islands.map((island) => island.framework)\n );\n if (dependencies) {\n diagnostics.push(dependencies);\n }\n diagnostics.push(\n ...(await componentsDiagnostics(project.context.componentsFile)),\n ...checkRequiredSecrets(config)\n );\n const features = serverFeatures(config);\n if (\n features.length > 0 &&\n config.deployment.options.output === \"static\"\n ) {\n // A host adapter already names the target, so only its\n // `output: \"static\"` stands in the way; otherwise name one.\n const { kind } = config.deployment;\n diagnostics.push({\n code: \"BLUME_SERVER_FEATURE_REQUIRED\",\n message: `${features.join(\", \")} ${features.length === 1 ? \"requires\" : \"require\"} server output.`,\n severity: \"error\",\n suggestion:\n kind === \"static\"\n ? 'Set deployment to a host adapter from \"blume/deploy\" (e.g. `deployment: vercel()`).'\n : `Drop \\`output: \"static\"\\` from \\`deployment: ${kind}()\\` to build for the server.`,\n });\n }\n\n if (!args.json) {\n logger.info(`Pages: ${project.graph.pages.length}`);\n logger.info(`Output: ${config.deployment.options.output}`);\n logger.info(`Adapter: ${config.deployment.kind}`);\n logger.info(`Search: ${config.search.provider.kind}`);\n logger.info(\n `References: ${config.reference.map((adapter) => adapter.kind).join(\", \") || \"none\"}`\n );\n logger.info(\n `Analytics: ${config.analytics.map((adapter) => adapter.kind).join(\", \") || \"none\"}`\n );\n logger.info(\n `Sources: ${config.content.sources.map((source) => source.kind).join(\", \")}`\n );\n const { ask } = config.ai;\n let askSummary = \"off\";\n if (ask?.enabled) {\n askSummary = ask.endpoint ? \"external endpoint\" : ask.provider.kind;\n }\n logger.info(`Ask AI: ${askSummary}`);\n }\n } catch (error) {\n if (error instanceof BlumeError) {\n diagnostics.push(error.diagnostic);\n } else {\n reportInternalError(error);\n process.exit(1);\n }\n }\n\n if (args.json) {\n // Drain stdout before exiting non-zero: `process.exit` would otherwise\n // truncate the JSON payload mid-write when stdout is a pipe — exactly how\n // `--json` is consumed in CI/editors.\n if (reportDiagnosticsJson(diagnostics, root)) {\n await flushStdout();\n process.exit(1);\n }\n return;\n }\n\n const hadErrors = reportDiagnostics(diagnostics, root);\n if (diagnostics.length === 0) {\n logger.success(\"No problems found.\");\n }\n if (hadErrors) {\n process.exit(1);\n }\n },\n});\n"
5
+ "import { readFileSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\n\nimport { defineCommand } from \"citty\";\nimport { join } from \"pathe\";\nimport { satisfies } from \"semver\";\n\nimport { discoverIslands } from \"../../astro/islands.ts\";\nimport { missingDependencyDiagnostic } from \"../../astro/runtime-deps.ts\";\nimport {\n analyzeComponentOverrides,\n ComponentOverridesError,\n} from \"../../core/component-overrides.ts\";\nimport { BlumeError } from \"../../core/diagnostics.ts\";\nimport { packageRoot } from \"../../core/package-root.ts\";\nimport { scanProject } from \"../../core/project-graph.ts\";\nimport { serverFeatures } from \"../../core/server-features.ts\";\nimport type { Diagnostic } from \"../../core/types.ts\";\nimport { unregisteredSnapshotDiagnostics } from \"../../core/version-cut.ts\";\nimport { commandMeta } from \"../command-meta.ts\";\nimport { loadEnvFiles } from \"../env.ts\";\nimport { reportInternalError } from \"../internal-error.ts\";\nimport {\n flushStdout,\n logger,\n reportDiagnostics,\n reportDiagnosticsJson,\n} from \"../log.ts\";\nimport { checkRequiredSecrets } from \"../required-secrets.ts\";\n\nconst FALLBACK_NODE_RANGE = \">=22.12.0\";\n\n/**\n * Plan `components.ts` the way `blume dev`/`build` do, reporting each override\n * that can't be planned — a non-static form, or an import of a file that\n * doesn't exist — with its own line. The scan alone never reads the file, so\n * without this doctor would pass a project whose build fails.\n */\nconst componentsDiagnostics = async (\n componentsFile: string | null\n): Promise<Diagnostic[]> => {\n if (!componentsFile) {\n return [];\n }\n try {\n analyzeComponentOverrides(\n await readFile(componentsFile, \"utf-8\"),\n componentsFile\n );\n return [];\n } catch (error) {\n if (error instanceof ComponentOverridesError) {\n return error.issues;\n }\n throw error;\n }\n};\n\n/** The supported Node range, read from the package's own `engines` field so\n * doctor can never drift from what the package actually declares. */\nconst supportedNodeRange = (): string => {\n try {\n // SAFETY: this parses blume's own package.json; the optional fields cover\n // an `engines` block going missing, and a bad read falls to the catch.\n const pkg = JSON.parse(\n readFileSync(join(packageRoot(), \"package.json\"), \"utf-8\")\n ) as { engines?: { node?: string } };\n return pkg.engines?.node || FALLBACK_NODE_RANGE;\n } catch {\n return FALLBACK_NODE_RANGE;\n }\n};\n\nexport const doctorCommand = defineCommand({\n args: {\n json: {\n description: \"Emit diagnostics as JSON on stdout (for CI/editors).\",\n type: \"boolean\",\n },\n },\n meta: commandMeta.doctor,\n async run({ args }) {\n const root = process.cwd();\n const diagnostics: Diagnostic[] = [];\n\n const nodeRange = supportedNodeRange();\n if (!satisfies(process.versions.node, nodeRange)) {\n diagnostics.push({\n code: \"BLUME_NODE_VERSION\",\n message: `Node ${process.versions.node} is outside the supported range (${nodeRange}).`,\n severity: \"warning\",\n });\n }\n\n // Read `.env` files first, as `blume dev`/`build` do: remote sources read\n // their tokens during the scan, and the secrets check below reads them too.\n loadEnvFiles(root);\n\n try {\n const project = await scanProject(root, { mode: \"build\" });\n diagnostics.push(\n ...project.diagnostics,\n ...unregisteredSnapshotDiagnostics(project)\n );\n\n const { config } = project;\n // The packages and secrets a build would need: an adapter's missing SDK\n // fails the build, and a missing secret fails at the first request.\n const { islands } = await discoverIslands(root);\n const dependencies = await missingDependencyDiagnostic(\n config,\n root,\n \"error\",\n islands.map((island) => island.framework)\n );\n if (dependencies) {\n diagnostics.push(dependencies);\n }\n diagnostics.push(\n ...(await componentsDiagnostics(project.context.componentsFile)),\n ...checkRequiredSecrets(config)\n );\n const features = serverFeatures(config);\n if (\n features.length > 0 &&\n config.deployment.options.output === \"static\"\n ) {\n // A host adapter already names the target, so only its\n // `output: \"static\"` stands in the way; otherwise name one.\n const { kind } = config.deployment;\n diagnostics.push({\n code: \"BLUME_SERVER_FEATURE_REQUIRED\",\n message: `${features.join(\", \")} ${features.length === 1 ? \"requires\" : \"require\"} server output.`,\n severity: \"error\",\n suggestion:\n kind === \"static\"\n ? 'Set deployment to a host adapter from \"blume/deploy\" (e.g. `deployment: vercel()`).'\n : `Drop \\`output: \"static\"\\` from \\`deployment: ${kind}()\\` to build for the server.`,\n });\n }\n\n if (!args.json) {\n logger.info(`Pages: ${project.graph.pages.length}`);\n logger.info(`Output: ${config.deployment.options.output}`);\n logger.info(`Adapter: ${config.deployment.kind}`);\n logger.info(`Search: ${config.search.provider.kind}`);\n logger.info(\n `References: ${config.reference.map((adapter) => adapter.kind).join(\", \") || \"none\"}`\n );\n logger.info(\n `Analytics: ${config.analytics.map((adapter) => adapter.kind).join(\", \") || \"none\"}`\n );\n logger.info(\n `Sources: ${config.content.sources.map((source) => source.kind).join(\", \")}`\n );\n const { assistant } = config.ai;\n let assistantSummary = \"off\";\n if (assistant?.enabled) {\n assistantSummary = assistant.endpoint\n ? \"external endpoint\"\n : assistant.provider.kind;\n }\n logger.info(`Assistant: ${assistantSummary}`);\n }\n } catch (error) {\n if (error instanceof BlumeError) {\n diagnostics.push(error.diagnostic);\n } else {\n reportInternalError(error);\n process.exit(1);\n }\n }\n\n if (args.json) {\n // Drain stdout before exiting non-zero: `process.exit` would otherwise\n // truncate the JSON payload mid-write when stdout is a pipe — exactly how\n // `--json` is consumed in CI/editors.\n if (reportDiagnosticsJson(diagnostics, root)) {\n await flushStdout();\n process.exit(1);\n }\n return;\n }\n\n const hadErrors = reportDiagnostics(diagnostics, root);\n if (diagnostics.length === 0) {\n logger.success(\"No problems found.\");\n }\n if (hadErrors) {\n process.exit(1);\n }\n },\n});\n"
6
6
  ],
7
- "mappings": "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",
8
- "debugId": "C33A5318E923BC8364756E2164756E21",
7
+ "mappings": "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",
8
+ "debugId": "57C3E0688A0BB3FA64756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -19,13 +19,13 @@ import {
19
19
  writeAgentReport,
20
20
  fixPrompt,
21
21
  launchInstalledAgent
22
- } from "./chunk-bw22s759.js";
22
+ } from "./chunk-qwsrynx5.js";
23
23
  import {
24
24
  frontmatter_default,
25
25
  gradeExternal,
26
26
  probeAll,
27
27
  deployPlatform
28
- } from "./chunk-xaz13gwg.js";
28
+ } from "./chunk-qkqwkpte.js";
29
29
  import {
30
30
  normalizeBasePath,
31
31
  normalizePath,
@@ -34,13 +34,13 @@ import {
34
34
  } from "./chunk-jts8mvcz.js";
35
35
  import {
36
36
  deployStaticDir
37
- } from "./chunk-nk3ts2xk.js";
37
+ } from "./chunk-82bbrxdn.js";
38
38
  import {
39
39
  examplesRouteBase
40
- } from "./chunk-mnqj32sj.js";
40
+ } from "./chunk-2hn4b8z7.js";
41
41
  import {
42
42
  scanProject
43
- } from "./chunk-zg2gtj10.js";
43
+ } from "./chunk-fs23ddbb.js";
44
44
 
45
45
  // src/cli/commands/audit.ts
46
46
  import { defineCommand } from "citty";
@@ -1916,7 +1916,7 @@ var auditCommand = defineCommand({
1916
1916
  }
1917
1917
  const agents = Object.keys(AGENTS).filter((kind) => args[kind]);
1918
1918
  if (agents.length > 1) {
1919
- logger.error("Pass at most one of --claude or --codex.");
1919
+ logger.error("Pass at most one of --codex or --claude.");
1920
1920
  process.exit(1);
1921
1921
  }
1922
1922
  const [agent] = agents;
@@ -1985,5 +1985,5 @@ export {
1985
1985
  shouldFail
1986
1986
  };
1987
1987
 
1988
- //# debugId=938D6EDE48FC146464756E2164756E21
1989
- //# sourceMappingURL=chunk-j8mw0za6.js.map
1988
+ //# debugId=5DFBDEAD3AD54A7664756E2164756E21
1989
+ //# sourceMappingURL=chunk-79jhk4py.js.map