blume 0.0.0 → 0.1.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 (330) hide show
  1. package/bin/blume.mjs +18 -0
  2. package/dist/cli/index.js +12696 -0
  3. package/dist/cli/index.js.map +145 -0
  4. package/dist/types/core/bridge.d.ts +24 -0
  5. package/dist/types/core/config.d.ts +35 -0
  6. package/dist/types/core/data.d.ts +129 -0
  7. package/dist/types/core/define-components.d.ts +27 -0
  8. package/dist/types/core/define-meta.d.ts +16 -0
  9. package/dist/types/core/deployment-env.d.ts +9 -0
  10. package/dist/types/core/diagnostics.d.ts +17 -0
  11. package/dist/types/core/i18n-ui.d.ts +500 -0
  12. package/dist/types/core/load-module.d.ts +7 -0
  13. package/dist/types/core/package-root.d.ts +17 -0
  14. package/dist/types/core/project.d.ts +9 -0
  15. package/dist/types/core/schema.d.ts +3451 -0
  16. package/dist/types/core/sources/types.d.ts +107 -0
  17. package/dist/types/core/types.d.ts +245 -0
  18. package/dist/types/core/ui-packs/ar.d.ts +3 -0
  19. package/dist/types/core/ui-packs/bg.d.ts +3 -0
  20. package/dist/types/core/ui-packs/bn.d.ts +3 -0
  21. package/dist/types/core/ui-packs/ca.d.ts +3 -0
  22. package/dist/types/core/ui-packs/cs.d.ts +3 -0
  23. package/dist/types/core/ui-packs/da.d.ts +3 -0
  24. package/dist/types/core/ui-packs/de.d.ts +3 -0
  25. package/dist/types/core/ui-packs/el.d.ts +3 -0
  26. package/dist/types/core/ui-packs/es.d.ts +3 -0
  27. package/dist/types/core/ui-packs/fa.d.ts +3 -0
  28. package/dist/types/core/ui-packs/fi.d.ts +3 -0
  29. package/dist/types/core/ui-packs/fr.d.ts +3 -0
  30. package/dist/types/core/ui-packs/he.d.ts +3 -0
  31. package/dist/types/core/ui-packs/hi.d.ts +3 -0
  32. package/dist/types/core/ui-packs/hr.d.ts +3 -0
  33. package/dist/types/core/ui-packs/hu.d.ts +3 -0
  34. package/dist/types/core/ui-packs/id.d.ts +3 -0
  35. package/dist/types/core/ui-packs/index.d.ts +13 -0
  36. package/dist/types/core/ui-packs/it.d.ts +3 -0
  37. package/dist/types/core/ui-packs/ja.d.ts +3 -0
  38. package/dist/types/core/ui-packs/ko.d.ts +3 -0
  39. package/dist/types/core/ui-packs/nl.d.ts +3 -0
  40. package/dist/types/core/ui-packs/no.d.ts +3 -0
  41. package/dist/types/core/ui-packs/pl.d.ts +3 -0
  42. package/dist/types/core/ui-packs/pt-br.d.ts +3 -0
  43. package/dist/types/core/ui-packs/pt.d.ts +3 -0
  44. package/dist/types/core/ui-packs/ro.d.ts +3 -0
  45. package/dist/types/core/ui-packs/ru.d.ts +3 -0
  46. package/dist/types/core/ui-packs/sk.d.ts +3 -0
  47. package/dist/types/core/ui-packs/sr.d.ts +3 -0
  48. package/dist/types/core/ui-packs/sv.d.ts +3 -0
  49. package/dist/types/core/ui-packs/th.d.ts +3 -0
  50. package/dist/types/core/ui-packs/tr.d.ts +3 -0
  51. package/dist/types/core/ui-packs/uk.d.ts +3 -0
  52. package/dist/types/core/ui-packs/vi.d.ts +3 -0
  53. package/dist/types/core/ui-packs/zh-tw.d.ts +3 -0
  54. package/dist/types/core/ui-packs/zh.d.ts +3 -0
  55. package/dist/types/core/version.d.ts +8 -0
  56. package/dist/types/index.d.ts +10 -0
  57. package/dist/types/migrate/mintlify/config.d.ts +2 -0
  58. package/dist/types/migrate/mintlify/i18n.d.ts +7 -0
  59. package/dist/types/theme/fonts.d.ts +163 -0
  60. package/docs/01-quickstart.mdx +99 -0
  61. package/docs/02-deployment.mdx +129 -0
  62. package/docs/advanced/api-reference.mdx +114 -0
  63. package/docs/advanced/blog.mdx +121 -0
  64. package/docs/advanced/changelog.mdx +113 -0
  65. package/docs/advanced/custom-pages.mdx +268 -0
  66. package/docs/advanced/meta.ts +7 -0
  67. package/docs/changelog/v0-1-0.mdx +12 -0
  68. package/docs/changelog/v0-2-0.mdx +16 -0
  69. package/docs/configuration/ai.mdx +228 -0
  70. package/docs/configuration/analytics.mdx +98 -0
  71. package/docs/configuration/customization.mdx +91 -0
  72. package/docs/configuration/export.mdx +70 -0
  73. package/docs/configuration/index.mdx +290 -0
  74. package/docs/configuration/meta.ts +15 -0
  75. package/docs/configuration/search.mdx +172 -0
  76. package/docs/configuration/seo.mdx +196 -0
  77. package/docs/configuration/theming.mdx +178 -0
  78. package/docs/content/components.mdx +651 -0
  79. package/docs/content/i18n.mdx +205 -0
  80. package/docs/content/index.mdx +161 -0
  81. package/docs/content/islands.mdx +94 -0
  82. package/docs/content/meta.mdx +119 -0
  83. package/docs/content/meta.ts +15 -0
  84. package/docs/content/navigation.mdx +168 -0
  85. package/docs/content/sources.mdx +216 -0
  86. package/docs/content/syntax.mdx +445 -0
  87. package/docs/index.mdx +112 -0
  88. package/docs/reference/cli.mdx +43 -0
  89. package/docs/reference/frontmatter.mdx +74 -0
  90. package/docs/reference/meta.ts +7 -0
  91. package/package.json +150 -6
  92. package/src/ai/ask.ts +93 -0
  93. package/src/ai/llms.ts +64 -0
  94. package/src/ai/markdown.ts +31 -0
  95. package/src/ai/mcp/data.ts +74 -0
  96. package/src/ai/mcp/discovery.ts +49 -0
  97. package/src/ai/mcp/server.ts +225 -0
  98. package/src/ai/mcp/tools.ts +47 -0
  99. package/src/assets/icon.png +0 -0
  100. package/src/astro/examples.ts +104 -0
  101. package/src/astro/generate.ts +1006 -0
  102. package/src/astro/index.ts +4 -0
  103. package/src/astro/integration.ts +74 -0
  104. package/src/astro/islands.ts +131 -0
  105. package/src/astro/markdown-negotiation.ts +68 -0
  106. package/src/astro/pages.ts +87 -0
  107. package/src/astro/templates.ts +1326 -0
  108. package/src/cli/commands/add.ts +81 -0
  109. package/src/cli/commands/build.ts +103 -0
  110. package/src/cli/commands/dev.ts +108 -0
  111. package/src/cli/commands/doctor.ts +74 -0
  112. package/src/cli/commands/eject.ts +57 -0
  113. package/src/cli/commands/init.ts +98 -0
  114. package/src/cli/commands/migrate.ts +39 -0
  115. package/src/cli/commands/preview.ts +39 -0
  116. package/src/cli/commands/sync.ts +52 -0
  117. package/src/cli/commands/validate.ts +61 -0
  118. package/src/cli/index.ts +35 -0
  119. package/src/cli/log.ts +37 -0
  120. package/src/cli/prepare.ts +80 -0
  121. package/src/components/Icon.astro +99 -0
  122. package/src/components/content/Accordion.astro +8 -0
  123. package/src/components/content/AccordionItem.astro +121 -0
  124. package/src/components/content/AutoTypeTable.astro +51 -0
  125. package/src/components/content/Badge.astro +124 -0
  126. package/src/components/content/Callout.astro +73 -0
  127. package/src/components/content/Card.astro +104 -0
  128. package/src/components/content/CardGroup.astro +14 -0
  129. package/src/components/content/CodeBlock.astro +28 -0
  130. package/src/components/content/CodeGroup.astro +13 -0
  131. package/src/components/content/Color.astro +15 -0
  132. package/src/components/content/ColorItem.astro +87 -0
  133. package/src/components/content/ColorRow.astro +10 -0
  134. package/src/components/content/Column.astro +6 -0
  135. package/src/components/content/Columns.astro +9 -0
  136. package/src/components/content/Component.astro +65 -0
  137. package/src/components/content/Diff.astro +44 -0
  138. package/src/components/content/Expandable.astro +11 -0
  139. package/src/components/content/FileTree.astro +8 -0
  140. package/src/components/content/Frame.astro +70 -0
  141. package/src/components/content/GithubInfo.astro +110 -0
  142. package/src/components/content/Math.astro +24 -0
  143. package/src/components/content/Panel.astro +20 -0
  144. package/src/components/content/Prompt.astro +129 -0
  145. package/src/components/content/Step.astro +34 -0
  146. package/src/components/content/Steps.astro +20 -0
  147. package/src/components/content/Tab.astro +46 -0
  148. package/src/components/content/Tabs.astro +273 -0
  149. package/src/components/content/Tile.astro +42 -0
  150. package/src/components/content/Tooltip.astro +68 -0
  151. package/src/components/content/Tree.astro +300 -0
  152. package/src/components/content/TreeFile.astro +15 -0
  153. package/src/components/content/TreeFolder.astro +62 -0
  154. package/src/components/content/TypeTable.astro +106 -0
  155. package/src/components/content/Update.astro +66 -0
  156. package/src/components/content/Visibility.astro +12 -0
  157. package/src/components/content/Warning.astro +9 -0
  158. package/src/components/content/auto-type-table.ts +141 -0
  159. package/src/components/content/diff.ts +95 -0
  160. package/src/components/content/github-info.ts +79 -0
  161. package/src/components/content/mermaid-element.ts +68 -0
  162. package/src/components/github-mark.ts +9 -0
  163. package/src/components/index.ts +14 -0
  164. package/src/components/islands/AskAI.astro +12 -0
  165. package/src/components/islands/ask-ai.tsx +156 -0
  166. package/src/components/layout/Analytics.astro +63 -0
  167. package/src/components/layout/Banner.astro +50 -0
  168. package/src/components/layout/Breadcrumbs.astro +31 -0
  169. package/src/components/layout/Favicon.astro +22 -0
  170. package/src/components/layout/Fonts.astro +14 -0
  171. package/src/components/layout/Header.astro +188 -0
  172. package/src/components/layout/LanguageSwitcher.astro +56 -0
  173. package/src/components/layout/NavTree.astro +462 -0
  174. package/src/components/layout/PageActions.astro +438 -0
  175. package/src/components/layout/PageFeedback.astro +58 -0
  176. package/src/components/layout/PageLayout.astro +173 -0
  177. package/src/components/layout/Pagination.astro +56 -0
  178. package/src/components/layout/ReferenceLayout.astro +107 -0
  179. package/src/components/layout/RootLayout.astro +537 -0
  180. package/src/components/layout/Search.astro +608 -0
  181. package/src/components/layout/TableOfContents.astro +68 -0
  182. package/src/components/layout/analytics-client.ts +38 -0
  183. package/src/components/layout/head-scripts.ts +19 -0
  184. package/src/components/layout/nav-utils.ts +87 -0
  185. package/src/components/layout/overrides.ts +32 -0
  186. package/src/components/layout/search/algolia.ts +43 -0
  187. package/src/components/layout/search/endpoint.ts +22 -0
  188. package/src/components/layout/search/flexsearch.ts +52 -0
  189. package/src/components/layout/search/orama-cloud.ts +41 -0
  190. package/src/components/layout/search/orama.ts +26 -0
  191. package/src/components/layout/search/pagefind.ts +43 -0
  192. package/src/components/layout/search/types.ts +163 -0
  193. package/src/components/layout/search/typesense.ts +60 -0
  194. package/src/components/layout/toc-element.ts +108 -0
  195. package/src/core/bridge.ts +92 -0
  196. package/src/core/config.ts +112 -0
  197. package/src/core/content.ts +50 -0
  198. package/src/core/data.ts +128 -0
  199. package/src/core/define-components.ts +34 -0
  200. package/src/core/define-meta.ts +20 -0
  201. package/src/core/deployment-env.ts +73 -0
  202. package/src/core/diagnostics.ts +104 -0
  203. package/src/core/frontmatter.ts +43 -0
  204. package/src/core/graph.ts +128 -0
  205. package/src/core/i18n-ui.ts +171 -0
  206. package/src/core/i18n.ts +169 -0
  207. package/src/core/last-modified.ts +88 -0
  208. package/src/core/links.ts +352 -0
  209. package/src/core/load-module.ts +15 -0
  210. package/src/core/manifest.ts +126 -0
  211. package/src/core/meta.ts +97 -0
  212. package/src/core/navigation.ts +392 -0
  213. package/src/core/package-root.ts +37 -0
  214. package/src/core/project-graph.ts +153 -0
  215. package/src/core/project.ts +56 -0
  216. package/src/core/schema.ts +1057 -0
  217. package/src/core/server-features.ts +23 -0
  218. package/src/core/sources/assets.ts +77 -0
  219. package/src/core/sources/cache.ts +122 -0
  220. package/src/core/sources/filesystem.ts +99 -0
  221. package/src/core/sources/mdx-remote.ts +215 -0
  222. package/src/core/sources/mintlify.ts +161 -0
  223. package/src/core/sources/normalize.ts +243 -0
  224. package/src/core/sources/notion.ts +440 -0
  225. package/src/core/sources/portable-text.ts +143 -0
  226. package/src/core/sources/read.ts +36 -0
  227. package/src/core/sources/resolve.ts +158 -0
  228. package/src/core/sources/sanity.ts +218 -0
  229. package/src/core/sources/types.ts +105 -0
  230. package/src/core/tsconfig-aliases.ts +201 -0
  231. package/src/core/types.ts +261 -0
  232. package/src/core/ui-packs/ar.ts +47 -0
  233. package/src/core/ui-packs/bg.ts +47 -0
  234. package/src/core/ui-packs/bn.ts +47 -0
  235. package/src/core/ui-packs/ca.ts +47 -0
  236. package/src/core/ui-packs/cs.ts +47 -0
  237. package/src/core/ui-packs/da.ts +47 -0
  238. package/src/core/ui-packs/de.ts +47 -0
  239. package/src/core/ui-packs/el.ts +47 -0
  240. package/src/core/ui-packs/es.ts +47 -0
  241. package/src/core/ui-packs/fa.ts +47 -0
  242. package/src/core/ui-packs/fi.ts +47 -0
  243. package/src/core/ui-packs/fr.ts +47 -0
  244. package/src/core/ui-packs/he.ts +47 -0
  245. package/src/core/ui-packs/hi.ts +47 -0
  246. package/src/core/ui-packs/hr.ts +47 -0
  247. package/src/core/ui-packs/hu.ts +47 -0
  248. package/src/core/ui-packs/id.ts +47 -0
  249. package/src/core/ui-packs/index.ts +87 -0
  250. package/src/core/ui-packs/it.ts +47 -0
  251. package/src/core/ui-packs/ja.ts +47 -0
  252. package/src/core/ui-packs/ko.ts +47 -0
  253. package/src/core/ui-packs/nl.ts +47 -0
  254. package/src/core/ui-packs/no.ts +47 -0
  255. package/src/core/ui-packs/pl.ts +47 -0
  256. package/src/core/ui-packs/pt-br.ts +47 -0
  257. package/src/core/ui-packs/pt.ts +47 -0
  258. package/src/core/ui-packs/ro.ts +47 -0
  259. package/src/core/ui-packs/ru.ts +47 -0
  260. package/src/core/ui-packs/sk.ts +47 -0
  261. package/src/core/ui-packs/sr.ts +47 -0
  262. package/src/core/ui-packs/sv.ts +47 -0
  263. package/src/core/ui-packs/th.ts +47 -0
  264. package/src/core/ui-packs/tr.ts +47 -0
  265. package/src/core/ui-packs/uk.ts +47 -0
  266. package/src/core/ui-packs/vi.ts +47 -0
  267. package/src/core/ui-packs/zh-tw.ts +47 -0
  268. package/src/core/ui-packs/zh.ts +47 -0
  269. package/src/core/version.ts +23 -0
  270. package/src/deploy/robots.ts +20 -0
  271. package/src/deploy/rss.ts +128 -0
  272. package/src/deploy/sitemap.ts +28 -0
  273. package/src/index.ts +39 -0
  274. package/src/markdown/code-title.ts +71 -0
  275. package/src/markdown/directives.ts +83 -0
  276. package/src/markdown/heading-anchors.ts +137 -0
  277. package/src/markdown/index.ts +228 -0
  278. package/src/markdown/inline-code.ts +108 -0
  279. package/src/markdown/language-icon.ts +172 -0
  280. package/src/markdown/math.ts +32 -0
  281. package/src/markdown/mdast.ts +48 -0
  282. package/src/markdown/mermaid.ts +37 -0
  283. package/src/markdown/package-commands.ts +159 -0
  284. package/src/markdown/package-install.ts +40 -0
  285. package/src/migrate/fumadocs/config.ts +155 -0
  286. package/src/migrate/fumadocs/content.ts +365 -0
  287. package/src/migrate/fumadocs/frontmatter.ts +18 -0
  288. package/src/migrate/fumadocs/groups.ts +230 -0
  289. package/src/migrate/fumadocs/index.ts +337 -0
  290. package/src/migrate/fumadocs/meta.ts +244 -0
  291. package/src/migrate/migrate.ts +53 -0
  292. package/src/migrate/mintlify/config.ts +1040 -0
  293. package/src/migrate/mintlify/content.ts +98 -0
  294. package/src/migrate/mintlify/frontmatter.ts +126 -0
  295. package/src/migrate/mintlify/i18n.ts +51 -0
  296. package/src/migrate/mintlify/icons.ts +128 -0
  297. package/src/migrate/mintlify/index.ts +266 -0
  298. package/src/migrate/mintlify/snippets.ts +306 -0
  299. package/src/migrate/mintlify/transform.ts +80 -0
  300. package/src/migrate/nextra/content.ts +46 -0
  301. package/src/migrate/nextra/frontmatter.ts +40 -0
  302. package/src/migrate/nextra/index.ts +374 -0
  303. package/src/migrate/nextra/meta.ts +266 -0
  304. package/src/migrate/shared.ts +720 -0
  305. package/src/migrate/starlight/config.ts +459 -0
  306. package/src/migrate/starlight/content.ts +78 -0
  307. package/src/migrate/starlight/frontmatter.ts +111 -0
  308. package/src/migrate/starlight/i18n.ts +54 -0
  309. package/src/migrate/starlight/index.ts +131 -0
  310. package/src/og/card.ts +92 -0
  311. package/src/og/index.ts +2 -0
  312. package/src/openapi/scalar.ts +246 -0
  313. package/src/registry/eject.ts +310 -0
  314. package/src/registry/registry.ts +100 -0
  315. package/src/registry/rewrite-imports.ts +39 -0
  316. package/src/runtime/index.ts +14 -0
  317. package/src/search/build.ts +23 -0
  318. package/src/search/documents.ts +164 -0
  319. package/src/search/orama-index.ts +66 -0
  320. package/src/search/providers.ts +91 -0
  321. package/src/search/sync/algolia.ts +30 -0
  322. package/src/search/sync/index.ts +50 -0
  323. package/src/search/sync/orama-cloud.ts +40 -0
  324. package/src/search/sync/typesense.ts +65 -0
  325. package/src/seo/jsonld.ts +113 -0
  326. package/src/theme/entry.ts +637 -0
  327. package/src/theme/fonts.ts +198 -0
  328. package/src/theme/icons.ts +184 -0
  329. package/src/theme/palette.ts +143 -0
  330. package/src/theme/twoslash.ts +81 -0
@@ -0,0 +1,310 @@
1
+ import { existsSync } from "node:fs";
2
+ import { cp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
3
+
4
+ import { join, relative } from "pathe";
5
+
6
+ import { resolveAskBackend } from "../ai/ask.ts";
7
+ import { buildRawMarkdown } from "../ai/markdown.ts";
8
+ import { discoverExamples } from "../astro/examples.ts";
9
+ import {
10
+ buildRuntimeData,
11
+ collectStaged,
12
+ detectNeedsReact,
13
+ } from "../astro/generate.ts";
14
+ import { discoverIslands } from "../astro/islands.ts";
15
+ import { customOgRoutes, discoverPages } from "../astro/pages.ts";
16
+ import {
17
+ askEndpointTemplate,
18
+ astroConfigTemplate,
19
+ catchAllPageTemplate,
20
+ contentConfigTemplate,
21
+ envTemplate,
22
+ exampleMapTemplate,
23
+ exampleWrapperTemplate,
24
+ exampleSlug,
25
+ islandMapTemplate,
26
+ islandWrapperTemplate,
27
+ mixedbreadSearchEndpointTemplate,
28
+ ogEndpointTemplate,
29
+ rawMarkdownEndpointTemplate,
30
+ rssEndpointTemplate,
31
+ runtimeTsconfigTemplate,
32
+ searchClientTemplate,
33
+ searchEndpointTemplate,
34
+ userComponentsTemplate,
35
+ } from "../astro/templates.ts";
36
+ import { scanProject } from "../core/project-graph.ts";
37
+ import type { ProjectContext } from "../core/types.ts";
38
+ import { buildRssFeeds, renderRssFeed } from "../deploy/rss.ts";
39
+ import { buildReferenceFiles, hasReferences } from "../openapi/scalar.ts";
40
+ import { buildSearchDocuments } from "../search/documents.ts";
41
+ import { servesStaticIndex } from "../search/providers.ts";
42
+ import { tailwindEntryTemplate } from "../theme/entry.ts";
43
+ import { buildThemeCss } from "../theme/palette.ts";
44
+ import { twoslashCss } from "../theme/twoslash.ts";
45
+
46
+ const POSIX = (path: string): string => path.split("\\").join("/");
47
+
48
+ /**
49
+ * Promote the generated runtime into the project as an owned Astro app. After
50
+ * eject the project has a normal `astro.config.mjs` and `src/`, the `blume` CLI
51
+ * is no longer required, and the `blume` package remains importable.
52
+ *
53
+ * Returns the list of written files.
54
+ */
55
+ export const eject = async (root: string): Promise<string[]> => {
56
+ const project = await scanProject(root, { mode: "build" });
57
+ const { context, config } = project;
58
+
59
+ const srcDir = join(root, "src");
60
+ const genDir = join(srcDir, "generated");
61
+ const askEnabled = config.ai.ask?.enabled ?? false;
62
+ const exportPdf = config.export.pdf;
63
+ const exportEpub = config.export.epub;
64
+
65
+ const [pages, needsReactRaw, userTheme, rawMarkdown, islands, examples] =
66
+ await Promise.all([
67
+ context.pagesRoot
68
+ ? discoverPages(context.pagesRoot)
69
+ : Promise.resolve([]),
70
+ detectNeedsReact(root),
71
+ context.themeFile
72
+ ? readFile(context.themeFile, "utf-8")
73
+ : Promise.resolve(""),
74
+ buildRawMarkdown(project),
75
+ discoverIslands(root),
76
+ discoverExamples(root),
77
+ ]);
78
+ // Island/example frameworks drive which Astro renderers the ejected config
79
+ // wires in; React also switches on for project `.tsx`/`.jsx` and Ask AI.
80
+ const frameworks = new Set<string>([
81
+ ...islands.islands.map((island) => island.framework),
82
+ ...examples.examples.map((example) => example.framework),
83
+ ]);
84
+ const needsReact = needsReactRaw || askEnabled || frameworks.has("react");
85
+ const needsVue = frameworks.has("vue");
86
+ const needsSvelte = frameworks.has("svelte");
87
+
88
+ // A project-relative context so generated files use portable paths.
89
+ const relContext: ProjectContext = {
90
+ ...context,
91
+ contentRoot: POSIX(relative(root, context.contentRoot)),
92
+ outDir: ".",
93
+ root: ".",
94
+ };
95
+
96
+ const componentsImport = context.componentsFile
97
+ ? `../../${POSIX(relative(root, context.componentsFile))}`
98
+ : null;
99
+ const relPages = pages.map((page) => ({
100
+ entrypoint: POSIX(relative(root, page.entrypoint)),
101
+ pattern: page.pattern,
102
+ }));
103
+
104
+ // Non-filesystem sources eject their materialized MDX into `<root>/blume-staged`
105
+ // (a dedicated dir so it never clashes with a content root literally named
106
+ // `content`; the relative `staged` collection points there).
107
+ const staged = collectStaged(project);
108
+ const hasStaged = staged.size > 0;
109
+ const stagedDir = "blume-staged";
110
+
111
+ const files: { path: string; content: string }[] = [
112
+ {
113
+ content: astroConfigTemplate({
114
+ config,
115
+ contentRoutes: project.manifest.routes.map((route) => route.path),
116
+ context: relContext,
117
+ dataPath: "./src/generated/data.json",
118
+ examplesPath: "./src/generated/examples.ts",
119
+ needsReact,
120
+ needsSvelte,
121
+ needsVue,
122
+ pages: relPages,
123
+ searchClientPath: "./src/generated/search-client.ts",
124
+ themePath: "./src/generated/app.css",
125
+ }),
126
+ path: join(root, "astro.config.mjs"),
127
+ },
128
+ {
129
+ content: runtimeTsconfigTemplate(),
130
+ path: join(root, "tsconfig.json"),
131
+ },
132
+ { content: envTemplate(), path: join(srcDir, "env.d.ts") },
133
+ {
134
+ content: contentConfigTemplate({
135
+ config,
136
+ context: relContext,
137
+ staged: hasStaged,
138
+ stagedBase: stagedDir,
139
+ }),
140
+ path: join(srcDir, "content.config.ts"),
141
+ },
142
+ {
143
+ content: catchAllPageTemplate({
144
+ askEnabled,
145
+ exportEpub,
146
+ exportPdf,
147
+ mathEnabled: config.markdown.math,
148
+ }),
149
+ path: join(srcDir, "pages", "[...slug].astro"),
150
+ },
151
+ {
152
+ content: userComponentsTemplate(componentsImport),
153
+ path: join(genDir, "components.ts"),
154
+ },
155
+ // Island/example maps the catch-all imports; written even when empty so the
156
+ // relative import and the `blume:examples` alias always resolve.
157
+ {
158
+ content: islandMapTemplate(islands.islands),
159
+ path: join(genDir, "islands.ts"),
160
+ },
161
+ {
162
+ content: exampleMapTemplate(examples.examples),
163
+ path: join(genDir, "examples.ts"),
164
+ },
165
+ {
166
+ content: tailwindEntryTemplate({
167
+ configTokens: buildThemeCss(config.theme),
168
+ // Relative paths from src/generated/app.css keep the ejected app portable.
169
+ sources: [
170
+ "../../node_modules/blume/src/**/*.{astro,ts,tsx}",
171
+ "../../**/*.{astro,mdx,ts,tsx}",
172
+ ],
173
+ twoslashCss: twoslashCss(),
174
+ userTheme,
175
+ }),
176
+ path: join(genDir, "app.css"),
177
+ },
178
+ { content: buildRuntimeData(project), path: join(genDir, "data.json") },
179
+ {
180
+ content: `${JSON.stringify(rawMarkdown)}\n`,
181
+ path: join(genDir, "raw-markdown.json"),
182
+ },
183
+ {
184
+ content: rawMarkdownEndpointTemplate(),
185
+ path: join(srcDir, "pages", "[...slug].md.ts"),
186
+ },
187
+ {
188
+ content: rawMarkdownEndpointTemplate(),
189
+ path: join(srcDir, "pages", "[...slug].mdx.ts"),
190
+ },
191
+ ];
192
+
193
+ if (askEnabled) {
194
+ files.push({
195
+ content: askEndpointTemplate(resolveAskBackend(config.ai.ask)),
196
+ path: join(srcDir, "pages", "api", "ask.ts"),
197
+ });
198
+ }
199
+
200
+ if (config.seo.og.enabled) {
201
+ files.push({
202
+ content: ogEndpointTemplate(
203
+ customOgRoutes(pages, config.title, config.description)
204
+ ),
205
+ path: join(srcDir, "pages", "og", "[...slug].png.ts"),
206
+ });
207
+ }
208
+
209
+ // The provider-specific client loader behind the `blume:search-client` alias.
210
+ files.push({
211
+ content: searchClientTemplate(config),
212
+ path: join(genDir, "search-client.ts"),
213
+ });
214
+
215
+ if (servesStaticIndex(config.search.provider)) {
216
+ const documents = await buildSearchDocuments(project);
217
+ files.push(
218
+ {
219
+ content: `${JSON.stringify(documents)}\n`,
220
+ path: join(genDir, "search.json"),
221
+ },
222
+ {
223
+ content: searchEndpointTemplate(),
224
+ path: join(srcDir, "pages", "blume-search.json.ts"),
225
+ }
226
+ );
227
+ }
228
+
229
+ if (config.search.provider === "mixedbread") {
230
+ files.push({
231
+ content: mixedbreadSearchEndpointTemplate(
232
+ config.search.mixedbread?.storeId ?? ""
233
+ ),
234
+ path: join(srcDir, "pages", "api", "search.ts"),
235
+ });
236
+ }
237
+
238
+ const feeds = buildRssFeeds(project);
239
+ if (feeds.length > 0) {
240
+ const feedXml = Object.fromEntries(
241
+ feeds.map((feed) => [feed.type, renderRssFeed(feed)])
242
+ );
243
+ files.push(
244
+ {
245
+ content: `${JSON.stringify(feedXml)}\n`,
246
+ path: join(genDir, "rss.json"),
247
+ },
248
+ {
249
+ content: rssEndpointTemplate(),
250
+ path: join(srcDir, "pages", "[section]", "rss.xml.ts"),
251
+ }
252
+ );
253
+ }
254
+
255
+ // Scalar API/AsyncAPI reference pages, mirrored from the generated runtime so
256
+ // the ejected app keeps its reference routes.
257
+ if (hasReferences(config)) {
258
+ const references = await buildReferenceFiles({
259
+ config,
260
+ contentRoutes: new Set(project.graph.pages.map((page) => page.route)),
261
+ root,
262
+ });
263
+ for (const file of references.files) {
264
+ files.push({
265
+ content: file.content,
266
+ path: join(srcDir, "pages", file.pagePath),
267
+ });
268
+ }
269
+ }
270
+
271
+ // Per-island and per-example live wrappers referenced by the maps above.
272
+ files.push(
273
+ ...islands.islands.map((island) => ({
274
+ content: islandWrapperTemplate(island),
275
+ path: join(genDir, "islands", `${island.name}.astro`),
276
+ })),
277
+ ...examples.examples.map((example) => ({
278
+ content: exampleWrapperTemplate(example),
279
+ path: join(genDir, "examples", `${exampleSlug(example.path)}.astro`),
280
+ }))
281
+ );
282
+
283
+ // Materialize staged source bodies under `<root>/blume-staged/<source>/<ref>`,
284
+ // matching the relative `staged` collection base in the ejected config.
285
+ for (const [entryId, content] of staged) {
286
+ files.push({ content, path: join(root, stagedDir, entryId) });
287
+ }
288
+
289
+ await Promise.all(
290
+ files.map(async (file) => {
291
+ await mkdir(join(file.path, ".."), { recursive: true });
292
+ await writeFile(file.path, file.content, "utf-8");
293
+ })
294
+ );
295
+
296
+ // Materialized source assets (e.g. downloaded Notion images) live under the
297
+ // hidden runtime's public dir; copy them into the owned project's `public/`
298
+ // so the staged content's `/blume-assets/…` references still resolve.
299
+ const assetsSrc = join(context.outDir, "public", "blume-assets");
300
+ if (existsSync(assetsSrc)) {
301
+ await cp(assetsSrc, join(root, "public", "blume-assets"), {
302
+ recursive: true,
303
+ });
304
+ }
305
+
306
+ // The hidden runtime is no longer the source of truth.
307
+ await rm(context.outDir, { force: true, recursive: true });
308
+
309
+ return files.map((file) => file.path);
310
+ };
@@ -0,0 +1,100 @@
1
+ import { join } from "pathe";
2
+
3
+ import { packageRoot } from "../core/package-root.ts";
4
+
5
+ /** A file copied into the user's project by `blume add`. */
6
+ export interface RegistryFile {
7
+ /** Path to the source file, relative to the blume package `src` directory. */
8
+ source: string;
9
+ /** Destination path relative to the project root. */
10
+ target: string;
11
+ /** Rewrite the file's relative imports to `blume/*` specifiers on copy. */
12
+ rewrite?: boolean;
13
+ }
14
+
15
+ export interface RegistryItem {
16
+ name: string;
17
+ description: string;
18
+ files: RegistryFile[];
19
+ /** Lines printed after install to guide the user. */
20
+ postInstall: string[];
21
+ }
22
+
23
+ /** Absolute path to the blume package `src` directory (the copy source root). */
24
+ export const packageSrc = join(packageRoot(), "src");
25
+
26
+ /** Absolute path to the bundled registry item sources. */
27
+ export const itemsRoot = join(packageRoot(), "src", "registry", "items");
28
+
29
+ /**
30
+ * A built-in layout component offered as editable source. `blume add` rewrites
31
+ * its relative imports to `blume/*`, so it renders identically to the built-in
32
+ * until the user changes it, then registers under the matching `layout` slot.
33
+ */
34
+ const layoutComponent = (config: {
35
+ name: string;
36
+ description: string;
37
+ /** Source basename under `src/components/layout`. */
38
+ file: string;
39
+ /** Layout slot key, also the import name in the post-install hint. */
40
+ slot: string;
41
+ }): RegistryItem => {
42
+ const target = `components/blume/${config.file}`;
43
+ return {
44
+ description: config.description,
45
+ files: [
46
+ {
47
+ rewrite: true,
48
+ source: `components/layout/${config.file}`,
49
+ target,
50
+ },
51
+ ],
52
+ name: config.name,
53
+ postInstall: [
54
+ "Register it in components.ts:",
55
+ ' import { defineComponents } from "blume";',
56
+ ` import ${config.slot} from "./${target}";`,
57
+ "",
58
+ ` export default defineComponents({ layout: { ${config.slot} } });`,
59
+ "",
60
+ "It imports the rest from `blume/*`, so it matches the built-in until you edit it.",
61
+ ],
62
+ };
63
+ };
64
+
65
+ /** The built-in, Blume-owned source registry. */
66
+ export const registry: RegistryItem[] = [
67
+ layoutComponent({
68
+ description: "The top navigation bar (logo, search, nav links).",
69
+ file: "Header.astro",
70
+ name: "header",
71
+ slot: "Header",
72
+ }),
73
+ layoutComponent({
74
+ description: "The sidebar navigation tree.",
75
+ file: "NavTree.astro",
76
+ name: "sidebar",
77
+ slot: "Sidebar",
78
+ }),
79
+ layoutComponent({
80
+ description: "The breadcrumb trail shown above page content.",
81
+ file: "Breadcrumbs.astro",
82
+ name: "breadcrumbs",
83
+ slot: "Breadcrumbs",
84
+ }),
85
+ layoutComponent({
86
+ description: "The on-this-page table of contents.",
87
+ file: "TableOfContents.astro",
88
+ name: "table-of-contents",
89
+ slot: "TableOfContents",
90
+ }),
91
+ layoutComponent({
92
+ description: "The previous/next pagination footer.",
93
+ file: "Pagination.astro",
94
+ name: "pagination",
95
+ slot: "Pagination",
96
+ }),
97
+ ];
98
+
99
+ export const findItem = (name: string): RegistryItem | undefined =>
100
+ registry.find((item) => item.name === name);
@@ -0,0 +1,39 @@
1
+ import { dirname, relative, resolve } from "pathe";
2
+
3
+ // The module specifier in `... from "<spec>"` and side-effect `import "<spec>"`,
4
+ // limited to relative specifiers (those starting with `.`). Capturing the
5
+ // keyword, gap, and quote lets us rebuild the statement verbatim.
6
+ const RELATIVE_IMPORT =
7
+ /(?<kw>\bfrom|\bimport)(?<gap>\s+)(?<quote>["'])(?<spec>\.[^"']*)\k<quote>/gu;
8
+
9
+ /**
10
+ * Rewrite a built-in component's relative imports to `blume/*` package
11
+ * specifiers, so a copy installed by `blume add` resolves the rest of the
12
+ * framework from the package instead of broken relative paths.
13
+ *
14
+ * `sourceFile` is the original file's absolute path and `srcRoot` the package
15
+ * `src` directory; a relative import is resolved against the source, mapped to
16
+ * its path under `src`, and re-emitted as `blume/<that path>`. Two specifiers
17
+ * are left untouched: a self-reference (a component importing itself, e.g. the
18
+ * recursive nav tree) stays relative so the copy recurses into the copy, and
19
+ * anything resolving outside `src` is left as-is.
20
+ */
21
+ export const rewriteImports = (
22
+ content: string,
23
+ sourceFile: string,
24
+ srcRoot: string
25
+ ): string =>
26
+ content.replaceAll(
27
+ RELATIVE_IMPORT,
28
+ (match, kw: string, gap: string, quote: string, spec: string) => {
29
+ const resolved = resolve(dirname(sourceFile), spec);
30
+ if (resolved === sourceFile) {
31
+ return match;
32
+ }
33
+ const rel = relative(srcRoot, resolved);
34
+ if (rel.startsWith("..")) {
35
+ return match;
36
+ }
37
+ return `${kw}${gap}${quote}blume/${rel}${quote}`;
38
+ }
39
+ );
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Runtime helpers usable inside `.astro` pages and islands.
3
+ *
4
+ * These give custom pages and components access to Blume project data
5
+ * (config, navigation, page collections) without reaching into generated
6
+ * runtime internals. The surface grows with the customization milestone.
7
+ */
8
+ export type {
9
+ Heading,
10
+ NavNode,
11
+ Navigation,
12
+ NavTab,
13
+ PageRecord,
14
+ } from "../core/types.ts";
@@ -0,0 +1,23 @@
1
+ import { join } from "pathe";
2
+
3
+ /**
4
+ * Build a local Pagefind search index over the built site. Pagefind only
5
+ * indexes elements marked with `data-pagefind-body`, which Blume adds to the
6
+ * content of indexable pages, so nav chrome and excluded pages are skipped.
7
+ *
8
+ * Returns the number of pages indexed.
9
+ */
10
+ export const buildSearchIndex = async (outDir: string): Promise<number> => {
11
+ const pagefind = await import("pagefind");
12
+
13
+ const { index } = await pagefind.createIndex({});
14
+ if (!index) {
15
+ throw new Error("Failed to create Pagefind index.");
16
+ }
17
+
18
+ const result = await index.addDirectory({ path: outDir });
19
+ await index.writeFiles({ outputPath: join(outDir, "pagefind") });
20
+ await pagefind.close();
21
+
22
+ return result.page_count;
23
+ };
@@ -0,0 +1,164 @@
1
+ import matter from "../core/frontmatter.ts";
2
+ import { contentIndexable } from "../core/manifest.ts";
3
+ import type { BlumeProject } from "../core/project-graph.ts";
4
+ import { readEntryText } from "../core/sources/read.ts";
5
+ import type { NavNode } from "../core/types.ts";
6
+
7
+ /** A document indexed by the client-side search providers (Orama, FlexSearch). */
8
+ export interface SearchDocument {
9
+ route: string;
10
+ title: string;
11
+ description: string;
12
+ content: string;
13
+ /** Ancestor section labels for the result breadcrumb, e.g. `["Guides"]`. */
14
+ breadcrumb: string[];
15
+ /** Top-level section label, used by the search filter pills. */
16
+ section: string;
17
+ /** Locale code, so the dialog can filter results to the active language. */
18
+ locale: string;
19
+ /** Frontmatter `search.tags`, surfaced for hosted-provider faceting. */
20
+ tags?: string[];
21
+ }
22
+
23
+ /**
24
+ * A record uploaded to a hosted search backend (Algolia, Orama Cloud,
25
+ * Typesense, Mixedbread). `_id` is the stable per-page key; each sync adapts it
26
+ * to the backend's own id field (`objectID`, `id`, …).
27
+ */
28
+ export interface SearchRecord {
29
+ _id: string;
30
+ url: string;
31
+ title: string;
32
+ description: string;
33
+ content: string;
34
+ /** Locale code, carried as a facet for per-language filtering. */
35
+ locale: string;
36
+ /** Single faceting tag (the first frontmatter tag, when present). */
37
+ tag?: string;
38
+ }
39
+
40
+ const CODE_FENCE = /```[\s\S]*?```/gu;
41
+ const INLINE_CODE = /`(?<code>[^`]+)`/gu;
42
+ const HTML_OR_JSX = /<[^>]+>/gu;
43
+ const IMAGE = /!\[[^\]]*\]\([^)]*\)/gu;
44
+ const LINK = /\[(?<text>[^\]]*)\]\([^)]*\)/gu;
45
+ const HEADING_MARK = /^#{1,6}\s+/gmu;
46
+ const MARKDOWN_PUNCT = /[*_~>]+/gu;
47
+ const WHITESPACE = /\s+/gu;
48
+
49
+ /** Reduce Markdown/MDX to plain, searchable text. */
50
+ const toPlainText = (markdown: string): string =>
51
+ markdown
52
+ .replaceAll(CODE_FENCE, " ")
53
+ .replaceAll(IMAGE, " ")
54
+ .replaceAll(LINK, "$<text>")
55
+ .replaceAll(HTML_OR_JSX, " ")
56
+ .replaceAll(INLINE_CODE, "$<code>")
57
+ .replaceAll(HEADING_MARK, "")
58
+ .replaceAll(MARKDOWN_PUNCT, " ")
59
+ .replaceAll(WHITESPACE, " ")
60
+ .trim();
61
+
62
+ interface Crumbs {
63
+ breadcrumb: string[];
64
+ section: string;
65
+ }
66
+
67
+ /**
68
+ * Map each page route to its ancestor section labels by walking the nav sidebar
69
+ * once. The nearest ancestor group — the sidebar section a page appears under —
70
+ * becomes its `section`, the dimension the search filter pills group by, so the
71
+ * pills mirror the visible sidebar (and honor folder-meta renames).
72
+ */
73
+ const buildCrumbIndex = (sidebar: NavNode[]): Map<string, Crumbs> => {
74
+ const index = new Map<string, Crumbs>();
75
+ const walk = (nodes: NavNode[], trail: string[]): void => {
76
+ for (const node of nodes) {
77
+ if (node.kind === "group") {
78
+ walk(node.children, [...trail, node.label]);
79
+ } else if (node.route) {
80
+ index.set(node.route, {
81
+ breadcrumb: trail,
82
+ section: trail.at(-1) ?? "",
83
+ });
84
+ }
85
+ }
86
+ };
87
+ walk(sidebar, []);
88
+ return index;
89
+ };
90
+
91
+ /**
92
+ * Build search documents from the content graph. Only indexable pages are
93
+ * included (per the route manifest), and content comes from the source files,
94
+ * so the index is identical in dev and build.
95
+ *
96
+ * Pass `includeWhenDisabled` to index pages on their content merits even when
97
+ * the search provider is `none` — used by the MCP server, which is a separate
98
+ * feature from on-page search.
99
+ */
100
+ export const buildSearchDocuments = async (
101
+ project: BlumeProject,
102
+ options?: { includeWhenDisabled?: boolean }
103
+ ): Promise<SearchDocument[]> => {
104
+ const pageById = new Map(project.graph.pages.map((page) => [page.id, page]));
105
+
106
+ // Build the crumb index from every locale's sidebar (their nodes carry
107
+ // locale-prefixed routes), so localized pages get the right section/breadcrumb.
108
+ // Falls back to the single default-locale nav when i18n is off.
109
+ const byLocale = Object.values(project.graph.navigationByLocale ?? {});
110
+ const sidebars =
111
+ byLocale.length > 0
112
+ ? byLocale.map((nav) => nav.sidebar)
113
+ : [project.graph.navigation?.sidebar ?? []];
114
+ const crumbs = new Map<string, Crumbs>();
115
+ for (const sidebar of sidebars) {
116
+ for (const [route, crumb] of buildCrumbIndex(sidebar)) {
117
+ crumbs.set(route, crumb);
118
+ }
119
+ }
120
+
121
+ const indexable = project.manifest.routes.filter((route) => {
122
+ if (!options?.includeWhenDisabled) {
123
+ return route.indexable;
124
+ }
125
+ const page = pageById.get(route.id);
126
+ return page ? contentIndexable(page, project.config) : false;
127
+ });
128
+
129
+ return await Promise.all(
130
+ indexable.map(async (route) => {
131
+ const page = pageById.get(route.id);
132
+ const raw = page ? await readEntryText(project, page) : "";
133
+ const body = raw ? toPlainText(matter(raw).content) : "";
134
+ const tags = page?.meta?.search?.tags;
135
+ const crumb = crumbs.get(route.path);
136
+ return {
137
+ breadcrumb: crumb?.breadcrumb ?? [],
138
+ content: body,
139
+ description: page?.description ?? "",
140
+ locale: route.locale,
141
+ route: route.path,
142
+ section: crumb?.section || "Docs",
143
+ tags: tags && tags.length > 0 ? tags : undefined,
144
+ title: route.title,
145
+ };
146
+ })
147
+ );
148
+ };
149
+
150
+ /**
151
+ * Map per-page search documents to the flat record shape hosted backends
152
+ * ingest. One record per page, keyed by route; the first tag becomes the
153
+ * faceting `tag`.
154
+ */
155
+ export const toSearchRecords = (documents: SearchDocument[]): SearchRecord[] =>
156
+ documents.map((doc) => ({
157
+ _id: doc.route,
158
+ content: doc.content,
159
+ description: doc.description,
160
+ locale: doc.locale,
161
+ tag: doc.tags?.[0],
162
+ title: doc.title,
163
+ url: doc.route,
164
+ }));