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,108 @@
1
+ /**
2
+ * Client behaviour for the `<blume-toc>` custom element wrapping each "On this
3
+ * page" list. As the reader scrolls, it marks the link for the section currently
4
+ * in view with `aria-current="location"` — styled via Tailwind `aria-[current]`
5
+ * variants in RootLayout — giving the table of contents a live scrollspy.
6
+ *
7
+ * The active heading is chosen by rect (the last heading at or above a trigger
8
+ * line just below the sticky header), so it stays correct for sections taller
9
+ * than the viewport and at the bottom of the page. An IntersectionObserver is the
10
+ * cheap primary trigger — it only fires as headings cross the band near the top —
11
+ * and a passive, rAF-throttled scroll listener covers the one case it can't: a
12
+ * final section too short to push its heading past the trigger line.
13
+ *
14
+ * Imported for its side effect (registers the element) from RootLayout's script.
15
+ */
16
+
17
+ interface TocEntry {
18
+ heading: HTMLElement;
19
+ link: HTMLAnchorElement;
20
+ }
21
+
22
+ // Matches the theme's `scroll-padding-top: 4.5rem`, so the highlighted heading
23
+ // agrees with where a clicked anchor lands beneath the sticky header.
24
+ const TRIGGER_OFFSET = 72;
25
+
26
+ class BlumeToc extends HTMLElement {
27
+ #entries: TocEntry[] = [];
28
+ #observer: IntersectionObserver | null = null;
29
+ #current: HTMLAnchorElement | null = null;
30
+ #ticking = false;
31
+
32
+ connectedCallback() {
33
+ for (const link of this.querySelectorAll<HTMLAnchorElement>(
34
+ 'a[href^="#"]'
35
+ )) {
36
+ const id = decodeURIComponent(link.hash.slice(1));
37
+ const heading = id
38
+ ? document.querySelector<HTMLElement>(`#${CSS.escape(id)}`)
39
+ : null;
40
+ if (heading) {
41
+ this.#entries.push({ heading, link });
42
+ }
43
+ }
44
+ if (this.#entries.length === 0) {
45
+ return;
46
+ }
47
+
48
+ this.#observer = new IntersectionObserver(() => this.#update(), {
49
+ rootMargin: `-${TRIGGER_OFFSET}px 0px -70% 0px`,
50
+ threshold: 0,
51
+ });
52
+ for (const { heading } of this.#entries) {
53
+ this.#observer.observe(heading);
54
+ }
55
+ window.addEventListener("scroll", this.#onScroll, { passive: true });
56
+ this.#update();
57
+ }
58
+
59
+ disconnectedCallback() {
60
+ this.#observer?.disconnect();
61
+ this.#observer = null;
62
+ window.removeEventListener("scroll", this.#onScroll);
63
+ }
64
+
65
+ #onScroll = () => {
66
+ if (this.#ticking) {
67
+ return;
68
+ }
69
+ this.#ticking = true;
70
+ requestAnimationFrame(() => {
71
+ this.#ticking = false;
72
+ this.#update();
73
+ });
74
+ };
75
+
76
+ #update() {
77
+ const active = this.#activeLink();
78
+ if (active === this.#current) {
79
+ return;
80
+ }
81
+ this.#current?.removeAttribute("aria-current");
82
+ active?.setAttribute("aria-current", "location");
83
+ this.#current = active;
84
+ }
85
+
86
+ #activeLink(): HTMLAnchorElement | null {
87
+ const scrolledToBottom =
88
+ window.innerHeight + window.scrollY >=
89
+ document.documentElement.scrollHeight - 2;
90
+ if (scrolledToBottom) {
91
+ return this.#entries.at(-1)?.link ?? null;
92
+ }
93
+
94
+ // Headings are in document order, so the last one whose top has reached the
95
+ // trigger line is the section currently being read; default to the first.
96
+ let active = this.#entries[0]?.link ?? null;
97
+ for (const { heading, link } of this.#entries) {
98
+ if (heading.getBoundingClientRect().top <= TRIGGER_OFFSET) {
99
+ active = link;
100
+ }
101
+ }
102
+ return active;
103
+ }
104
+ }
105
+
106
+ if (!customElements.get("blume-toc")) {
107
+ customElements.define("blume-toc", BlumeToc);
108
+ }
@@ -0,0 +1,92 @@
1
+ import { existsSync } from "node:fs";
2
+ import { readFile } from "node:fs/promises";
3
+
4
+ import { join } from "pathe";
5
+
6
+ import { loadMintlifyConfig } from "../migrate/mintlify/config.ts";
7
+ import { mintlifyI18n } from "../migrate/mintlify/i18n.ts";
8
+ import type { BlumeConfig } from "./schema.ts";
9
+
10
+ /** A detected docs-tool config that Blume can serve without a migration. */
11
+ export interface BridgeDetection {
12
+ /** Which foreign docs tool was detected. */
13
+ tool: "mintlify";
14
+ /** Absolute path of the detected config file (`docs.json`/`mint.json`). */
15
+ configFile: string;
16
+ /** A Blume config synthesized from the foreign config, ready to validate. */
17
+ raw: BlumeConfig;
18
+ }
19
+
20
+ const MINTLIFY_CONFIG_FILES = ["docs.json", "mint.json"];
21
+
22
+ /**
23
+ * Detect a Mintlify project at `root` and synthesize an equivalent Blume config.
24
+ *
25
+ * "Bridge mode" lets a team swap `mintlify dev` for `blume dev` with no file
26
+ * changes: `docs.json` is translated to Blume config (`loadMintlifyConfig`) and
27
+ * its content block is rewired to a single `mintlify` content source, which
28
+ * transforms each MDX page to Blume idiom at scan time. The original
29
+ * `content.root`/`exclude` and `variables` move onto the source (Blume has no
30
+ * runtime variable substitution, so globals are inlined into content there).
31
+ *
32
+ * Returns `null` when no Mintlify config is present. Only called when no
33
+ * `blume.config.*` exists, so an explicit Blume config always takes precedence.
34
+ */
35
+ export const detectMintlifyBridge = async (
36
+ root: string
37
+ ): Promise<BridgeDetection | null> => {
38
+ const configFile = MINTLIFY_CONFIG_FILES.map((name) => join(root, name)).find(
39
+ (candidate) => existsSync(candidate)
40
+ );
41
+ if (!configFile) {
42
+ return null;
43
+ }
44
+
45
+ const config = await loadMintlifyConfig(root, configFile);
46
+
47
+ // i18n is derived from the raw spec (language dirs), mirroring the migrator.
48
+ const spec = JSON.parse(await readFile(configFile, "utf-8")) as Record<
49
+ string,
50
+ unknown
51
+ >;
52
+ const i18n = mintlifyI18n(spec);
53
+ if (i18n) {
54
+ config.i18n = i18n;
55
+ // Language switching is handled by Blume i18n, not a nav selector.
56
+ if (config.navigation?.selectors) {
57
+ config.navigation.selectors = config.navigation.selectors.filter(
58
+ (selector) => selector.kind !== "language"
59
+ );
60
+ }
61
+ }
62
+
63
+ const variables = (config.variables as Record<string, string>) ?? {};
64
+ const root_ = config.content?.root ?? ".";
65
+ const exclude = config.content?.exclude ?? [];
66
+
67
+ return {
68
+ configFile,
69
+ raw: {
70
+ ...config,
71
+ content: {
72
+ // Mirror the excludes onto `content.exclude` too: the generated Astro
73
+ // `docs` collection globs `content.root` (here the project root) and
74
+ // must skip node_modules/snippets just like the source does.
75
+ exclude,
76
+ root: root_,
77
+ sources: [
78
+ {
79
+ configFile,
80
+ exclude,
81
+ root: root_,
82
+ type: "mintlify",
83
+ variables,
84
+ },
85
+ ],
86
+ },
87
+ // Globals are inlined into content by the source; drop the top-level copy.
88
+ variables: undefined,
89
+ },
90
+ tool: "mintlify",
91
+ };
92
+ };
@@ -0,0 +1,112 @@
1
+ import { detectMintlifyBridge } from "./bridge.ts";
2
+ import type { BridgeDetection } from "./bridge.ts";
3
+ import { applyDeploymentEnv } from "./deployment-env.ts";
4
+ import { BlumeError, diagnosticsFromZod } from "./diagnostics.ts";
5
+ import { createModuleLoader } from "./load-module.ts";
6
+ import { findConfigFile } from "./project.ts";
7
+ import { blumeConfigSchema } from "./schema.ts";
8
+ import type { BlumeConfig, ResolvedConfig } from "./schema.ts";
9
+ import type { Diagnostic } from "./types.ts";
10
+
11
+ /**
12
+ * Identity helper for authoring `blume.config.ts`. Exists for type inference
13
+ * and a stable future home for plugin hooks; it does not transform input.
14
+ */
15
+ export const defineConfig = (config: BlumeConfig): BlumeConfig => config;
16
+
17
+ /** Bridge mode info: a foreign docs tool Blume is serving without migrating. */
18
+ export interface ConfigBridge {
19
+ tool: "mintlify";
20
+ /** Absolute path of the foreign config file (`docs.json`/`mint.json`). */
21
+ configFile: string;
22
+ }
23
+
24
+ /** Result of loading + validating a project config. */
25
+ export interface ConfigLoadResult {
26
+ config: ResolvedConfig;
27
+ /** Absolute path of the config file used, or null when defaults were used. */
28
+ configFile: string | null;
29
+ /** Set when a foreign docs config (e.g. Mintlify) is being bridged. */
30
+ bridge: ConfigBridge | null;
31
+ diagnostics: Diagnostic[];
32
+ }
33
+
34
+ const importConfigModule = createModuleLoader();
35
+
36
+ /**
37
+ * Load and validate the project config. When no config file exists, schema
38
+ * defaults produce a fully resolved config so the zero-boilerplate path works.
39
+ */
40
+ export const loadConfig = async (
41
+ root: string,
42
+ /**
43
+ * Supplied only by `blume dev`: the local dev server URL, used as the
44
+ * `deployment.site` fallback when none is configured or detected. Builds
45
+ * never pass it, so production output can't end up pointing at localhost.
46
+ */
47
+ options: { devServerUrl?: string } = {}
48
+ ): Promise<ConfigLoadResult> => {
49
+ const configFile = findConfigFile(root);
50
+
51
+ // With no Blume config, a Mintlify `docs.json` activates bridge mode: serve
52
+ // the unconverted project by synthesizing config + a `mintlify` content source.
53
+ let bridge: BridgeDetection | null = null;
54
+ let raw: unknown = {};
55
+ if (configFile) {
56
+ try {
57
+ raw = await importConfigModule(configFile);
58
+ } catch (error) {
59
+ throw new BlumeError({
60
+ code: "BLUME_CONFIG_LOAD_FAILED",
61
+ file: configFile,
62
+ message: `Failed to load config: ${(error as Error).message}`,
63
+ severity: "error",
64
+ });
65
+ }
66
+ } else {
67
+ bridge = await detectMintlifyBridge(root);
68
+ if (bridge) {
69
+ ({ raw } = bridge);
70
+ }
71
+ }
72
+
73
+ const sourceFile = bridge?.configFile ?? configFile;
74
+ const parsed = blumeConfigSchema.safeParse(raw ?? {});
75
+ if (!parsed.success) {
76
+ const diagnostics = diagnosticsFromZod(parsed.error, {
77
+ code: "BLUME_CONFIG_INVALID",
78
+ file: sourceFile ?? undefined,
79
+ });
80
+ throw new BlumeError(
81
+ diagnostics[0] ?? {
82
+ code: "BLUME_CONFIG_INVALID",
83
+ file: sourceFile ?? undefined,
84
+ message: "Invalid Blume config.",
85
+ severity: "error",
86
+ }
87
+ );
88
+ }
89
+
90
+ // Resolve the canonical site URL, then SEO defaults that depend on it.
91
+ // Precedence: explicit config > platform env (Vercel/Netlify/Cloudflare, via
92
+ // applyDeploymentEnv) > the local dev server URL (dev only).
93
+ const config = applyDeploymentEnv(parsed.data);
94
+ const site = config.deployment.site ?? options.devServerUrl;
95
+
96
+ // OG images need an absolute `og:image`, so they default on once a site URL
97
+ // is known and off otherwise. An explicit `seo.og.enabled` always wins.
98
+ const ogEnabled = config.seo.og.enabled ?? Boolean(site);
99
+
100
+ return {
101
+ bridge: bridge
102
+ ? { configFile: bridge.configFile, tool: bridge.tool }
103
+ : null,
104
+ config: {
105
+ ...config,
106
+ deployment: { ...config.deployment, site },
107
+ seo: { ...config.seo, og: { ...config.seo.og, enabled: ogEnabled } },
108
+ },
109
+ configFile: sourceFile,
110
+ diagnostics: [],
111
+ };
112
+ };
@@ -0,0 +1,50 @@
1
+ import type { ResolvedI18nConfig } from "./schema.ts";
2
+ import { filesystemSource } from "./sources/filesystem.ts";
3
+ import { normalizeEntry } from "./sources/normalize.ts";
4
+ import type { Diagnostic, PageRecord } from "./types.ts";
5
+
6
+ // Re-export the shared parsing helpers from their new home so existing importers
7
+ // (and tests) keep resolving them from `core/content.ts`.
8
+ export {
9
+ extractHeadings,
10
+ extractLinks,
11
+ normalizeEntry,
12
+ slugify,
13
+ } from "./sources/normalize.ts";
14
+
15
+ /**
16
+ * Discover and normalize all content pages under a content root. Thin wrapper
17
+ * around the filesystem `ContentSource` + `normalizeEntry`, kept for callers and
18
+ * tests that scan a single directory directly.
19
+ */
20
+ export const discoverContent = async (options: {
21
+ contentRoot: string;
22
+ include: string[];
23
+ exclude: string[];
24
+ defaultType: string;
25
+ i18n?: ResolvedI18nConfig;
26
+ }): Promise<{ pages: PageRecord[]; diagnostics: Diagnostic[] }> => {
27
+ const source = filesystemSource({
28
+ exclude: options.exclude,
29
+ include: options.include,
30
+ name: "filesystem",
31
+ projectRoot: options.contentRoot,
32
+ root: options.contentRoot,
33
+ });
34
+
35
+ const { entries, diagnostics: loadDiagnostics } = await source.load();
36
+ const pages: PageRecord[] = [];
37
+ const diagnostics: Diagnostic[] = [...loadDiagnostics];
38
+
39
+ for (const entry of entries) {
40
+ const normalized = normalizeEntry(entry, {
41
+ defaultType: options.defaultType,
42
+ i18n: options.i18n,
43
+ source: { name: source.name, prefix: source.prefix, staged: false },
44
+ });
45
+ pages.push(...normalized.pages);
46
+ diagnostics.push(...normalized.diagnostics);
47
+ }
48
+
49
+ return { diagnostics, pages };
50
+ };
@@ -0,0 +1,128 @@
1
+ import type { UIStrings } from "./i18n-ui.ts";
2
+ import type { ResolvedConfig, SearchProvider } from "./schema.ts";
3
+ import type { Navigation, RouteAlternate } from "./types.ts";
4
+
5
+ /**
6
+ * The shape of the `blume:data` virtual module — the resolved, serializable
7
+ * snapshot of a site that custom `.astro` pages read with
8
+ * `import data from "blume:data"`. `buildRuntimeData` (`astro/generate.ts`)
9
+ * produces exactly this object and annotates it with {@link BlumeData}, so the
10
+ * documented type and the emitted JSON stay in lockstep.
11
+ */
12
+
13
+ /** Resolved site logo: an inlined SVG, or light/dark image URLs. */
14
+ export interface BlumeLogo {
15
+ svg?: string;
16
+ light?: string;
17
+ dark?: string;
18
+ alt: string;
19
+ href: string;
20
+ }
21
+
22
+ /** A favicon or apple-touch-icon: a link href plus an optional MIME type. */
23
+ export interface BlumeFavicon {
24
+ href: string;
25
+ type?: string;
26
+ }
27
+
28
+ /** Announcement banner, normalized from its config (string shorthand or object). */
29
+ export interface BlumeBanner {
30
+ content: string;
31
+ link?: { href: string; text: string };
32
+ dismissible: boolean;
33
+ /** Dismissal key: the configured id, else the content itself. */
34
+ key: string;
35
+ }
36
+
37
+ /** A generated syndication feed surfaced in the UI. */
38
+ export interface BlumeFeed {
39
+ href: string;
40
+ title: string;
41
+ }
42
+
43
+ /** One configured locale, as exposed to the runtime. */
44
+ export interface BlumeDataLocale {
45
+ code: string;
46
+ dir: "ltr" | "rtl";
47
+ label: string;
48
+ }
49
+
50
+ /** Resolved i18n settings; `null` when the site is single-locale. */
51
+ export interface BlumeDataI18n {
52
+ defaultLocale: string;
53
+ /** Locale whose content renders for a missing translation; `null` disables it. */
54
+ fallbackLocale: string | null;
55
+ hideDefaultLocalePrefix: boolean;
56
+ locales: BlumeDataLocale[];
57
+ }
58
+
59
+ /** A single content route, with the metadata custom pages can read. */
60
+ export interface BlumeRoute {
61
+ /** Locales this logical page is translated into (excludes fallbacks). */
62
+ alternates: RouteAlternate[];
63
+ /** Astro collection the entry renders through (`"docs"` | `"staged"`). */
64
+ collection: string;
65
+ draft: boolean;
66
+ /** "Edit this page" URL, or `null` when no repo/source provides one. */
67
+ editUrl: string | null;
68
+ /** Astro collection entry id (for `getEntry`/`getCollection`); matches `id`. */
69
+ entryId: string;
70
+ /** True when this route renders fallback content for a missing translation. */
71
+ fallback: boolean;
72
+ hidden: boolean;
73
+ id: string;
74
+ /** Whether the page is part of the search index. */
75
+ indexable: boolean;
76
+ /** ISO "last updated" date when the feature is on, else `null`. */
77
+ lastModified: string | null;
78
+ /** Resolved locale code (the default locale when not under i18n). */
79
+ locale: string;
80
+ path: string;
81
+ title: string;
82
+ }
83
+
84
+ /** Site-wide settings derived from `blume.config` — the `config` field of {@link BlumeData}. */
85
+ export interface BlumeDataConfig {
86
+ analytics: NonNullable<ResolvedConfig["analytics"]> | null;
87
+ /** Apple touch icon, or `null` when none is configured/detected. */
88
+ appleIcon: BlumeFavicon | null;
89
+ banner: BlumeBanner | null;
90
+ /** `markdown.code.wrap`: wrap long code lines instead of scrolling. */
91
+ codeWrap: boolean;
92
+ description: string | undefined;
93
+ favicon: BlumeFavicon;
94
+ feedback: boolean;
95
+ i18n: BlumeDataI18n | null;
96
+ /** `markdown.imageZoom`: click-to-zoom content images. */
97
+ imageZoom: boolean;
98
+ logo: BlumeLogo | null;
99
+ /** Hosted MCP server, or `null` when MCP is off. */
100
+ mcp: { name: string; route: string } | null;
101
+ /** Open Graph image generation. */
102
+ og: { enabled: boolean };
103
+ /** Repository URL for header/edit links, or `null`. */
104
+ repoUrl: string | null;
105
+ search: { enabled: boolean; provider: SearchProvider };
106
+ /** Deployment site URL, or `null` when none is configured/detected. */
107
+ site: string | null;
108
+ structuredData: boolean;
109
+ theme: ResolvedConfig["theme"];
110
+ title: string;
111
+ }
112
+
113
+ /** The `blume:data` module a Blume site's custom pages import. */
114
+ export interface BlumeData {
115
+ config: BlumeDataConfig;
116
+ feeds: BlumeFeed[];
117
+ /** CSS variable names for the configured fonts (Astro `<Font>` integration). */
118
+ fontCssVars: string[];
119
+ /** Sidebar + tab tree for the default locale. */
120
+ navigation: Navigation;
121
+ /** Per-locale navigation trees, keyed by locale code (empty without i18n). */
122
+ navigationByLocale: Record<string, Navigation>;
123
+ routes: BlumeRoute[];
124
+ /** Resolved UI strings for the default locale. */
125
+ ui: UIStrings;
126
+ /** Per-locale UI strings, keyed by locale code (empty without i18n). */
127
+ uiByLocale: Record<string, UIStrings>;
128
+ }
@@ -0,0 +1,34 @@
1
+ import type { HydrationMode } from "./schema.ts";
2
+
3
+ /**
4
+ * A reference to a component. Either an imported component (preferred, for type
5
+ * safety) or a string path resolved relative to the project root.
6
+ */
7
+ export type ComponentReference = unknown | string;
8
+
9
+ /** An interactive component plus its hydration strategy. */
10
+ export interface IslandDescriptor {
11
+ component: ComponentReference;
12
+ client: HydrationMode;
13
+ /** Required when `client` is `"media"`. */
14
+ media?: string;
15
+ }
16
+
17
+ /** A component override: a static component or a hydrated island. */
18
+ export type ComponentOverride = ComponentReference | IslandDescriptor;
19
+
20
+ /** User-authored component overrides, grouped by surface. */
21
+ export interface ComponentOverrides {
22
+ /** MDX component map overrides (`Callout`, `Card`, ...). */
23
+ mdx?: Record<string, ComponentOverride>;
24
+ /** Layout slot overrides (`Header`, `Sidebar`, `Search`, ...). */
25
+ layout?: Record<string, ComponentOverride>;
26
+ }
27
+
28
+ /**
29
+ * Identity helper for authoring `components.ts`. Provides type inference and a
30
+ * stable home for future normalization; it does not transform input.
31
+ */
32
+ export const defineComponents = (
33
+ overrides: ComponentOverrides
34
+ ): ComponentOverrides => overrides;
@@ -0,0 +1,20 @@
1
+ import type { FolderMeta } from "./schema.ts";
2
+
3
+ /** A function that computes folder meta, optionally asynchronously. */
4
+ export type FolderMetaFactory = () => FolderMeta | Promise<FolderMeta>;
5
+
6
+ /** What a `meta.ts` may default-export: a plain object or a (async) factory. */
7
+ export type FolderMetaDefinition = FolderMeta | FolderMetaFactory;
8
+
9
+ /**
10
+ * Identity helper for authoring a folder's `meta.ts`. Provides type inference;
11
+ * it does not transform input. Pass an object, or a function (sync or async)
12
+ * that returns one when you need to compute the meta:
13
+ *
14
+ * ```ts
15
+ * export default defineMeta({ title: "Guides", order: 2 });
16
+ * export default defineMeta(async () => ({ pages: await orderPages() }));
17
+ * ```
18
+ */
19
+ export const defineMeta = (meta: FolderMetaDefinition): FolderMetaDefinition =>
20
+ meta;
@@ -0,0 +1,73 @@
1
+ import type { ResolvedConfig } from "./schema.ts";
2
+
3
+ /** The deployment platforms Blume can infer from runtime/CI env vars. */
4
+ type DeploymentAdapter = NonNullable<ResolvedConfig["deployment"]["adapter"]>;
5
+
6
+ interface Platform {
7
+ /** Astro adapter to use when building for server output on this platform. */
8
+ adapter: DeploymentAdapter;
9
+ /** True when the env indicates the build is running on this platform. */
10
+ detect: (env: NodeJS.ProcessEnv) => boolean;
11
+ /** Resolve the canonical site URL from the platform's env vars, or null. */
12
+ site: (env: NodeJS.ProcessEnv) => string | null;
13
+ }
14
+
15
+ /** Prefix a bare host with `https://`; pass values that are already absolute. */
16
+ const toUrl = (value: string | undefined): string | null => {
17
+ const trimmed = value?.trim();
18
+ if (!trimmed) {
19
+ return null;
20
+ }
21
+ return /^https?:\/\//u.test(trimmed) ? trimmed : `https://${trimmed}`;
22
+ };
23
+
24
+ /**
25
+ * Platforms checked in order; the first whose `detect` matches wins. Site URLs
26
+ * prefer the stable production domain over per-deployment preview URLs so the
27
+ * inferred origin (sitemap, OG, RSS) stays put across deploys.
28
+ */
29
+ const PLATFORMS: Platform[] = [
30
+ {
31
+ adapter: "vercel",
32
+ detect: (env) => Boolean(env.VERCEL),
33
+ site: (env) => toUrl(env.VERCEL_PROJECT_PRODUCTION_URL ?? env.VERCEL_URL),
34
+ },
35
+ {
36
+ adapter: "netlify",
37
+ detect: (env) => Boolean(env.NETLIFY),
38
+ site: (env) => toUrl(env.URL ?? env.DEPLOY_PRIME_URL ?? env.DEPLOY_URL),
39
+ },
40
+ {
41
+ adapter: "cloudflare",
42
+ detect: (env) => Boolean(env.CF_PAGES),
43
+ site: (env) => toUrl(env.CF_PAGES_URL),
44
+ },
45
+ ];
46
+
47
+ /**
48
+ * Fill in `deployment.adapter` and `deployment.site` from platform env vars
49
+ * (Vercel, Netlify, Cloudflare Pages) when the user hasn't set them. Explicit
50
+ * config always wins, and the adapter is only inferred for server output (it
51
+ * has no effect on static builds). Mirrors Astro's platform auto-detection so a
52
+ * project deployed to a known host gets a working canonical origin for free.
53
+ */
54
+ export const applyDeploymentEnv = (
55
+ config: ResolvedConfig,
56
+ env: NodeJS.ProcessEnv = process.env
57
+ ): ResolvedConfig => {
58
+ const platform = PLATFORMS.find((candidate) => candidate.detect(env));
59
+ if (!platform) {
60
+ return config;
61
+ }
62
+
63
+ const { deployment } = config;
64
+ const adapter =
65
+ deployment.adapter ??
66
+ (deployment.output === "server" ? platform.adapter : null);
67
+ const site = deployment.site ?? platform.site(env) ?? undefined;
68
+
69
+ if (adapter === deployment.adapter && site === deployment.site) {
70
+ return config;
71
+ }
72
+ return { ...config, deployment: { ...deployment, adapter, site } };
73
+ };