blume 0.0.0 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (263) hide show
  1. package/bin/blume.mjs +18 -0
  2. package/dist/cli/index.js +11989 -0
  3. package/dist/cli/index.js.map +141 -0
  4. package/docs/01-quickstart.mdx +99 -0
  5. package/docs/02-deployment.mdx +129 -0
  6. package/docs/advanced/api-reference.mdx +114 -0
  7. package/docs/advanced/blog.mdx +121 -0
  8. package/docs/advanced/changelog.mdx +113 -0
  9. package/docs/advanced/custom-pages.mdx +187 -0
  10. package/docs/advanced/meta.ts +7 -0
  11. package/docs/changelog/v0-1-0.mdx +12 -0
  12. package/docs/changelog/v0-2-0.mdx +16 -0
  13. package/docs/configuration/ai.mdx +228 -0
  14. package/docs/configuration/analytics.mdx +98 -0
  15. package/docs/configuration/customization.mdx +91 -0
  16. package/docs/configuration/export.mdx +70 -0
  17. package/docs/configuration/index.mdx +271 -0
  18. package/docs/configuration/meta.ts +15 -0
  19. package/docs/configuration/search.mdx +172 -0
  20. package/docs/configuration/seo.mdx +196 -0
  21. package/docs/configuration/theming.mdx +178 -0
  22. package/docs/content/components.mdx +565 -0
  23. package/docs/content/i18n.mdx +205 -0
  24. package/docs/content/index.mdx +161 -0
  25. package/docs/content/islands.mdx +94 -0
  26. package/docs/content/meta.mdx +119 -0
  27. package/docs/content/meta.ts +15 -0
  28. package/docs/content/navigation.mdx +168 -0
  29. package/docs/content/sources.mdx +216 -0
  30. package/docs/content/syntax.mdx +445 -0
  31. package/docs/index.mdx +112 -0
  32. package/docs/reference/cli.mdx +43 -0
  33. package/docs/reference/frontmatter.mdx +74 -0
  34. package/docs/reference/meta.ts +7 -0
  35. package/package.json +140 -6
  36. package/src/ai/ask.ts +93 -0
  37. package/src/ai/llms.ts +65 -0
  38. package/src/ai/markdown.ts +31 -0
  39. package/src/ai/mcp/data.ts +74 -0
  40. package/src/ai/mcp/discovery.ts +49 -0
  41. package/src/ai/mcp/server.ts +225 -0
  42. package/src/ai/mcp/tools.ts +47 -0
  43. package/src/assets/icon.png +0 -0
  44. package/src/astro/generate.ts +878 -0
  45. package/src/astro/index.ts +4 -0
  46. package/src/astro/integration.ts +74 -0
  47. package/src/astro/islands.ts +131 -0
  48. package/src/astro/markdown-negotiation.ts +68 -0
  49. package/src/astro/pages.ts +28 -0
  50. package/src/astro/templates.ts +1199 -0
  51. package/src/cli/commands/add.ts +81 -0
  52. package/src/cli/commands/build.ts +103 -0
  53. package/src/cli/commands/dev.ts +108 -0
  54. package/src/cli/commands/doctor.ts +74 -0
  55. package/src/cli/commands/eject.ts +57 -0
  56. package/src/cli/commands/init.ts +98 -0
  57. package/src/cli/commands/migrate.ts +39 -0
  58. package/src/cli/commands/preview.ts +39 -0
  59. package/src/cli/commands/sync.ts +52 -0
  60. package/src/cli/commands/validate.ts +60 -0
  61. package/src/cli/index.ts +35 -0
  62. package/src/cli/log.ts +37 -0
  63. package/src/cli/prepare.ts +80 -0
  64. package/src/components/Icon.astro +99 -0
  65. package/src/components/content/Accordion.astro +8 -0
  66. package/src/components/content/AccordionItem.astro +121 -0
  67. package/src/components/content/AutoTypeTable.astro +51 -0
  68. package/src/components/content/Badge.astro +124 -0
  69. package/src/components/content/Callout.astro +73 -0
  70. package/src/components/content/Card.astro +104 -0
  71. package/src/components/content/CardGroup.astro +14 -0
  72. package/src/components/content/CodeGroup.astro +13 -0
  73. package/src/components/content/Color.astro +15 -0
  74. package/src/components/content/ColorItem.astro +87 -0
  75. package/src/components/content/ColorRow.astro +10 -0
  76. package/src/components/content/Column.astro +6 -0
  77. package/src/components/content/Columns.astro +9 -0
  78. package/src/components/content/Expandable.astro +11 -0
  79. package/src/components/content/FileTree.astro +8 -0
  80. package/src/components/content/Frame.astro +70 -0
  81. package/src/components/content/GithubInfo.astro +110 -0
  82. package/src/components/content/Math.astro +24 -0
  83. package/src/components/content/Panel.astro +20 -0
  84. package/src/components/content/Prompt.astro +129 -0
  85. package/src/components/content/Step.astro +34 -0
  86. package/src/components/content/Steps.astro +20 -0
  87. package/src/components/content/Tab.astro +40 -0
  88. package/src/components/content/Tabs.astro +273 -0
  89. package/src/components/content/Tile.astro +42 -0
  90. package/src/components/content/Tooltip.astro +68 -0
  91. package/src/components/content/Tree.astro +300 -0
  92. package/src/components/content/TreeFile.astro +15 -0
  93. package/src/components/content/TreeFolder.astro +62 -0
  94. package/src/components/content/TypeTable.astro +106 -0
  95. package/src/components/content/Update.astro +66 -0
  96. package/src/components/content/Visibility.astro +12 -0
  97. package/src/components/content/Warning.astro +9 -0
  98. package/src/components/content/auto-type-table.ts +141 -0
  99. package/src/components/content/github-info.ts +79 -0
  100. package/src/components/content/mermaid-element.ts +68 -0
  101. package/src/components/github-mark.ts +9 -0
  102. package/src/components/index.ts +14 -0
  103. package/src/components/islands/AskAI.astro +12 -0
  104. package/src/components/islands/ask-ai.tsx +156 -0
  105. package/src/components/layout/Analytics.astro +63 -0
  106. package/src/components/layout/Banner.astro +50 -0
  107. package/src/components/layout/Breadcrumbs.astro +31 -0
  108. package/src/components/layout/Favicon.astro +15 -0
  109. package/src/components/layout/Fonts.astro +14 -0
  110. package/src/components/layout/Header.astro +188 -0
  111. package/src/components/layout/LanguageSwitcher.astro +56 -0
  112. package/src/components/layout/NavTree.astro +462 -0
  113. package/src/components/layout/PageActions.astro +438 -0
  114. package/src/components/layout/PageFeedback.astro +58 -0
  115. package/src/components/layout/Pagination.astro +56 -0
  116. package/src/components/layout/ReferenceLayout.astro +102 -0
  117. package/src/components/layout/RootLayout.astro +533 -0
  118. package/src/components/layout/Search.astro +608 -0
  119. package/src/components/layout/TableOfContents.astro +68 -0
  120. package/src/components/layout/analytics-client.ts +38 -0
  121. package/src/components/layout/nav-utils.ts +87 -0
  122. package/src/components/layout/overrides.ts +32 -0
  123. package/src/components/layout/search/algolia.ts +43 -0
  124. package/src/components/layout/search/endpoint.ts +22 -0
  125. package/src/components/layout/search/flexsearch.ts +52 -0
  126. package/src/components/layout/search/orama-cloud.ts +41 -0
  127. package/src/components/layout/search/orama.ts +26 -0
  128. package/src/components/layout/search/pagefind.ts +43 -0
  129. package/src/components/layout/search/types.ts +163 -0
  130. package/src/components/layout/search/typesense.ts +60 -0
  131. package/src/components/layout/toc-element.ts +108 -0
  132. package/src/core/bridge.ts +92 -0
  133. package/src/core/config.ts +112 -0
  134. package/src/core/content.ts +50 -0
  135. package/src/core/define-components.ts +34 -0
  136. package/src/core/define-meta.ts +20 -0
  137. package/src/core/deployment-env.ts +73 -0
  138. package/src/core/diagnostics.ts +104 -0
  139. package/src/core/graph.ts +128 -0
  140. package/src/core/i18n-ui.ts +171 -0
  141. package/src/core/i18n.ts +169 -0
  142. package/src/core/last-modified.ts +88 -0
  143. package/src/core/links.ts +336 -0
  144. package/src/core/load-module.ts +15 -0
  145. package/src/core/manifest.ts +126 -0
  146. package/src/core/meta.ts +97 -0
  147. package/src/core/navigation.ts +392 -0
  148. package/src/core/package-root.ts +37 -0
  149. package/src/core/project-graph.ts +153 -0
  150. package/src/core/project.ts +56 -0
  151. package/src/core/schema.ts +1057 -0
  152. package/src/core/server-features.ts +23 -0
  153. package/src/core/sources/assets.ts +77 -0
  154. package/src/core/sources/cache.ts +122 -0
  155. package/src/core/sources/filesystem.ts +99 -0
  156. package/src/core/sources/mdx-remote.ts +216 -0
  157. package/src/core/sources/mintlify.ts +161 -0
  158. package/src/core/sources/normalize.ts +227 -0
  159. package/src/core/sources/notion.ts +440 -0
  160. package/src/core/sources/portable-text.ts +143 -0
  161. package/src/core/sources/read.ts +36 -0
  162. package/src/core/sources/resolve.ts +158 -0
  163. package/src/core/sources/sanity.ts +218 -0
  164. package/src/core/sources/types.ts +105 -0
  165. package/src/core/types.ts +261 -0
  166. package/src/core/ui-packs/ar.ts +47 -0
  167. package/src/core/ui-packs/bg.ts +47 -0
  168. package/src/core/ui-packs/bn.ts +47 -0
  169. package/src/core/ui-packs/ca.ts +47 -0
  170. package/src/core/ui-packs/cs.ts +47 -0
  171. package/src/core/ui-packs/da.ts +47 -0
  172. package/src/core/ui-packs/de.ts +47 -0
  173. package/src/core/ui-packs/el.ts +47 -0
  174. package/src/core/ui-packs/es.ts +47 -0
  175. package/src/core/ui-packs/fa.ts +47 -0
  176. package/src/core/ui-packs/fi.ts +47 -0
  177. package/src/core/ui-packs/fr.ts +47 -0
  178. package/src/core/ui-packs/he.ts +47 -0
  179. package/src/core/ui-packs/hi.ts +47 -0
  180. package/src/core/ui-packs/hr.ts +47 -0
  181. package/src/core/ui-packs/hu.ts +47 -0
  182. package/src/core/ui-packs/id.ts +47 -0
  183. package/src/core/ui-packs/index.ts +87 -0
  184. package/src/core/ui-packs/it.ts +47 -0
  185. package/src/core/ui-packs/ja.ts +47 -0
  186. package/src/core/ui-packs/ko.ts +47 -0
  187. package/src/core/ui-packs/nl.ts +47 -0
  188. package/src/core/ui-packs/no.ts +47 -0
  189. package/src/core/ui-packs/pl.ts +47 -0
  190. package/src/core/ui-packs/pt-br.ts +47 -0
  191. package/src/core/ui-packs/pt.ts +47 -0
  192. package/src/core/ui-packs/ro.ts +47 -0
  193. package/src/core/ui-packs/ru.ts +47 -0
  194. package/src/core/ui-packs/sk.ts +47 -0
  195. package/src/core/ui-packs/sr.ts +47 -0
  196. package/src/core/ui-packs/sv.ts +47 -0
  197. package/src/core/ui-packs/th.ts +47 -0
  198. package/src/core/ui-packs/tr.ts +47 -0
  199. package/src/core/ui-packs/uk.ts +47 -0
  200. package/src/core/ui-packs/vi.ts +47 -0
  201. package/src/core/ui-packs/zh-tw.ts +47 -0
  202. package/src/core/ui-packs/zh.ts +47 -0
  203. package/src/core/version.ts +23 -0
  204. package/src/deploy/robots.ts +20 -0
  205. package/src/deploy/rss.ts +128 -0
  206. package/src/deploy/sitemap.ts +28 -0
  207. package/src/index.ts +27 -0
  208. package/src/markdown/code-title.ts +71 -0
  209. package/src/markdown/directives.ts +83 -0
  210. package/src/markdown/heading-anchors.ts +137 -0
  211. package/src/markdown/index.ts +159 -0
  212. package/src/markdown/inline-code.ts +108 -0
  213. package/src/markdown/language-icon.ts +172 -0
  214. package/src/markdown/math.ts +32 -0
  215. package/src/markdown/mdast.ts +48 -0
  216. package/src/markdown/mermaid.ts +37 -0
  217. package/src/markdown/package-commands.ts +159 -0
  218. package/src/markdown/package-install.ts +40 -0
  219. package/src/migrate/fumadocs/config.ts +106 -0
  220. package/src/migrate/fumadocs/content.ts +365 -0
  221. package/src/migrate/fumadocs/frontmatter.ts +18 -0
  222. package/src/migrate/fumadocs/index.ts +252 -0
  223. package/src/migrate/fumadocs/meta.ts +114 -0
  224. package/src/migrate/migrate.ts +53 -0
  225. package/src/migrate/mintlify/config.ts +1040 -0
  226. package/src/migrate/mintlify/content.ts +98 -0
  227. package/src/migrate/mintlify/frontmatter.ts +126 -0
  228. package/src/migrate/mintlify/i18n.ts +51 -0
  229. package/src/migrate/mintlify/icons.ts +128 -0
  230. package/src/migrate/mintlify/index.ts +266 -0
  231. package/src/migrate/mintlify/snippets.ts +305 -0
  232. package/src/migrate/mintlify/transform.ts +81 -0
  233. package/src/migrate/nextra/content.ts +46 -0
  234. package/src/migrate/nextra/frontmatter.ts +40 -0
  235. package/src/migrate/nextra/index.ts +374 -0
  236. package/src/migrate/nextra/meta.ts +266 -0
  237. package/src/migrate/shared.ts +623 -0
  238. package/src/migrate/starlight/config.ts +459 -0
  239. package/src/migrate/starlight/content.ts +78 -0
  240. package/src/migrate/starlight/frontmatter.ts +111 -0
  241. package/src/migrate/starlight/i18n.ts +54 -0
  242. package/src/migrate/starlight/index.ts +131 -0
  243. package/src/og/card.ts +92 -0
  244. package/src/og/index.ts +2 -0
  245. package/src/openapi/scalar.ts +246 -0
  246. package/src/registry/eject.ts +263 -0
  247. package/src/registry/registry.ts +100 -0
  248. package/src/registry/rewrite-imports.ts +39 -0
  249. package/src/runtime/index.ts +14 -0
  250. package/src/search/build.ts +23 -0
  251. package/src/search/documents.ts +165 -0
  252. package/src/search/orama-index.ts +66 -0
  253. package/src/search/providers.ts +91 -0
  254. package/src/search/sync/algolia.ts +30 -0
  255. package/src/search/sync/index.ts +50 -0
  256. package/src/search/sync/orama-cloud.ts +40 -0
  257. package/src/search/sync/typesense.ts +65 -0
  258. package/src/seo/jsonld.ts +113 -0
  259. package/src/theme/entry.ts +608 -0
  260. package/src/theme/fonts.ts +198 -0
  261. package/src/theme/icons.ts +184 -0
  262. package/src/theme/palette.ts +143 -0
  263. package/src/theme/twoslash.ts +81 -0
@@ -0,0 +1,252 @@
1
+ import { existsSync } from "node:fs";
2
+ import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
3
+
4
+ import matter from "gray-matter";
5
+ import { dirname, join, relative } from "pathe";
6
+ import { glob } from "tinyglobby";
7
+
8
+ import { writeBlumeConfig } from "../shared.ts";
9
+ import { loadFumadocsConfig } from "./config.ts";
10
+ import {
11
+ inlineFumadocsIncludes,
12
+ rewriteFumadocsCallouts,
13
+ rewriteFumadocsContainers,
14
+ rewriteFumadocsTabs,
15
+ stripFumadocsImports,
16
+ unsupportedFumadocsComponents,
17
+ } from "./content.ts";
18
+ import { normalizeFumadocsPageMeta } from "./frontmatter.ts";
19
+ import { translateFumadocsMeta } from "./meta.ts";
20
+
21
+ export interface FumadocsMigrationResult {
22
+ moved: number;
23
+ warnings: string[];
24
+ }
25
+
26
+ /** Fumadocs keeps documentation under `content/docs/`. */
27
+ const SOURCE_DIR = "content/docs";
28
+ const PAGE_GLOB = "**/*.{md,mdx}";
29
+ const META_GLOB = "**/meta.json";
30
+ const IGNORE = ["**/node_modules/**"];
31
+
32
+ interface PageResult {
33
+ includeWarnings: string[];
34
+ moved: number;
35
+ removed: string[];
36
+ skipped: string | null;
37
+ unsupported: string[];
38
+ }
39
+
40
+ /** Rewrite a single page to idiomatic Blume MDX and move it into `docs/`. */
41
+ const movePage = async (
42
+ abs: string,
43
+ base: string,
44
+ root: string
45
+ ): Promise<PageResult> => {
46
+ const rel = relative(base, abs);
47
+ const dest = join(root, "docs", rel);
48
+ if (existsSync(dest)) {
49
+ return {
50
+ includeWarnings: [],
51
+ moved: 0,
52
+ removed: [],
53
+ skipped: rel,
54
+ unsupported: [],
55
+ };
56
+ }
57
+
58
+ const raw = await readFile(abs, "utf-8");
59
+ const included = await inlineFumadocsIncludes(raw, { filePath: abs });
60
+ let text = stripFumadocsImports(included.content);
61
+ text = rewriteFumadocsCallouts(text);
62
+ text = rewriteFumadocsContainers(text);
63
+ text = rewriteFumadocsTabs(text);
64
+ const unsupported = unsupportedFumadocsComponents(text);
65
+
66
+ const parsed = matter(text);
67
+ const { data, removed } = normalizeFumadocsPageMeta(parsed.data);
68
+ const content =
69
+ Object.keys(data).length > 0
70
+ ? matter.stringify(parsed.content, data)
71
+ : parsed.content;
72
+
73
+ await mkdir(dirname(dest), { recursive: true });
74
+ await writeFile(dest, content, "utf-8");
75
+ await rm(abs, { force: true });
76
+ return {
77
+ includeWarnings: included.warnings,
78
+ moved: 1,
79
+ removed,
80
+ skipped: null,
81
+ unsupported,
82
+ };
83
+ };
84
+
85
+ /** Convert one `meta.json` into a typed `meta.ts`, or relocate it if unparseable. */
86
+ const convertMeta = async (
87
+ abs: string,
88
+ base: string,
89
+ root: string
90
+ ): Promise<string[]> => {
91
+ const rel = relative(base, abs);
92
+ const raw = await readFile(abs, "utf-8");
93
+ let parsed: unknown;
94
+ try {
95
+ parsed = JSON.parse(raw);
96
+ } catch {
97
+ const dest = join(root, "docs", rel);
98
+ if (existsSync(dest)) {
99
+ return [`Skipped ${rel} (target already exists)`];
100
+ }
101
+ await mkdir(dirname(dest), { recursive: true });
102
+ await rename(abs, dest);
103
+ return [
104
+ `Could not parse ${rel}; moved as-is — convert it to meta.ts by hand.`,
105
+ ];
106
+ }
107
+
108
+ const { meta, warnings } = translateFumadocsMeta(parsed);
109
+ const dir = dirname(rel) === "." ? "" : dirname(rel);
110
+ if (Object.keys(meta).length > 0) {
111
+ const dest = join(root, "docs", dir, "meta.ts");
112
+ if (existsSync(dest)) {
113
+ await rm(abs, { force: true });
114
+ return [...warnings, `Skipped ${rel} (target already exists)`];
115
+ }
116
+ await mkdir(dirname(dest), { recursive: true });
117
+ await writeFile(
118
+ dest,
119
+ `import { defineMeta } from "blume";\n\nexport default defineMeta(${JSON.stringify(meta, null, 2)});\n`,
120
+ "utf-8"
121
+ );
122
+ }
123
+ await rm(abs, { force: true });
124
+ return warnings;
125
+ };
126
+
127
+ interface PageSummary {
128
+ includeWarnings: string[];
129
+ moved: number;
130
+ removedKeys: string[];
131
+ skipped: string[];
132
+ unsupported: string[];
133
+ }
134
+
135
+ const summarizePages = (results: PageResult[]): PageSummary => {
136
+ let moved = 0;
137
+ const removedKeys = new Set<string>();
138
+ const unsupported = new Set<string>();
139
+ const includeWarnings: string[] = [];
140
+ const skipped: string[] = [];
141
+ for (const result of results) {
142
+ moved += result.moved;
143
+ if (result.skipped) {
144
+ skipped.push(result.skipped);
145
+ }
146
+ includeWarnings.push(...result.includeWarnings);
147
+ for (const key of result.removed) {
148
+ removedKeys.add(key);
149
+ }
150
+ for (const name of result.unsupported) {
151
+ unsupported.add(name);
152
+ }
153
+ }
154
+ return {
155
+ includeWarnings,
156
+ moved,
157
+ removedKeys: [...removedKeys],
158
+ skipped,
159
+ unsupported: [...unsupported],
160
+ };
161
+ };
162
+
163
+ /** Remove `content/docs` and `content` once emptied; warn about leftovers. */
164
+ const cleanupSourceDirs = async (root: string): Promise<string[]> => {
165
+ const docs = join(root, "content", "docs");
166
+ if (existsSync(docs)) {
167
+ const remaining = await glob(["**/*"], { cwd: docs, dot: true });
168
+ if (remaining.length > 0) {
169
+ return [
170
+ `Kept ${remaining.length} non-page file(s) under content/docs; move them into docs/ manually.`,
171
+ ];
172
+ }
173
+ await rm(docs, { force: true, recursive: true });
174
+ }
175
+ const content = join(root, "content");
176
+ if (existsSync(content)) {
177
+ const remaining = await glob(["**/*"], { cwd: content, dot: true });
178
+ if (remaining.length === 0) {
179
+ await rm(content, { force: true, recursive: true });
180
+ }
181
+ }
182
+ return [];
183
+ };
184
+
185
+ /**
186
+ * Migrate a Fumadocs project to Blume. Moves every `content/docs` page into
187
+ * `docs/`, rewrites Fumadocs MDX (callouts to directives, `<Cards>`/
188
+ * `<Accordions>`/`<Files>` to their Blume equivalents, `<Tabs items>` to
189
+ * per-`<Tab>` titles, and `<include>`s inlined), and converts each `meta.json`
190
+ * into a typed `meta.ts`. The `loader({ baseUrl })` route prefix is preserved
191
+ * via a `content.sources` filesystem source.
192
+ */
193
+ export const migrateFumadocsProject = async (
194
+ root: string
195
+ ): Promise<FumadocsMigrationResult> => {
196
+ const { config, warnings: configWarnings } = await loadFumadocsConfig(root);
197
+
198
+ const base = join(root, SOURCE_DIR);
199
+ if (!existsSync(base)) {
200
+ await writeBlumeConfig(root, config);
201
+ return {
202
+ moved: 0,
203
+ warnings: [
204
+ `No Fumadocs content directory (${SOURCE_DIR}) found; wrote a default config.`,
205
+ ],
206
+ };
207
+ }
208
+
209
+ const pageFiles = await glob([PAGE_GLOB], {
210
+ absolute: true,
211
+ cwd: base,
212
+ ignore: IGNORE,
213
+ });
214
+ const metaFiles = await glob([META_GLOB], {
215
+ absolute: true,
216
+ cwd: base,
217
+ ignore: IGNORE,
218
+ });
219
+
220
+ const pageResults = await Promise.all(
221
+ pageFiles.map((abs) => movePage(abs, base, root))
222
+ );
223
+ const pages = summarizePages(pageResults);
224
+ const metaResults = await Promise.all(
225
+ metaFiles.map((abs) => convertMeta(abs, base, root))
226
+ );
227
+ const metaWarnings = metaResults.flat();
228
+
229
+ await writeBlumeConfig(root, config);
230
+ const cleanupWarnings = await cleanupSourceDirs(root);
231
+
232
+ const warnings = [
233
+ ...configWarnings,
234
+ ...metaWarnings,
235
+ ...pages.includeWarnings,
236
+ ...pages.skipped.map((rel) => `Skipped ${rel} (target already exists)`),
237
+ ...cleanupWarnings,
238
+ ];
239
+ if (pages.removedKeys.length > 0) {
240
+ warnings.push(
241
+ `Dropped unsupported page frontmatter keys: ${pages.removedKeys.join(", ")}.`
242
+ );
243
+ }
244
+ if (pages.unsupported.length > 0) {
245
+ warnings.push(
246
+ `Components without a drop-in Blume equivalent need manual review: ${pages.unsupported.join(", ")}.`
247
+ );
248
+ }
249
+ warnings.push("Review blume.config.ts and the generated meta.ts files.");
250
+
251
+ return { moved: pages.moved, warnings };
252
+ };
@@ -0,0 +1,114 @@
1
+ import type { FolderMeta } from "../../core/schema.ts";
2
+
3
+ /**
4
+ * Translate a Fumadocs folder `meta.json` into a Blume `FolderMeta` (`meta.ts`).
5
+ * Unlike Nextra, Fumadocs declares a folder's own title/icon/ordering in *that
6
+ * folder's* `meta.json`, so the mapping is self-contained. Fumadocs-only
7
+ * concepts with no Blume equivalent are dropped and reported:
8
+ *
9
+ * - `defaultOpen` -> `collapsed` (inverted) + `display: "group"` (collapsible).
10
+ * - `root: true` -> `display: "page"` (the closest analogue to a page-tree root).
11
+ * - `description` -> dropped (folders carry no description in Blume).
12
+ * - `pages` entries: plain slugs are kept as ordering; `"..."` (the rest marker,
13
+ * already Blume's default) is dropped silently; `"---Section---"` separators
14
+ * and `"[Text](url)"` links have no folder-meta home and are dropped with a
15
+ * warning.
16
+ */
17
+
18
+ interface FumadocsMeta {
19
+ defaultOpen?: unknown;
20
+ description?: unknown;
21
+ icon?: unknown;
22
+ pages?: unknown;
23
+ root?: unknown;
24
+ title?: unknown;
25
+ }
26
+
27
+ const SEPARATOR = /^---(?<label>.*)---$/u;
28
+ const LINK = /^(?:\[[^\]]*\])?\[(?<text>[^\]]*)\]\((?<href>[^)]*)\)$/u;
29
+ const REST = "...";
30
+
31
+ interface PagesResult {
32
+ pages: string[];
33
+ warnings: string[];
34
+ }
35
+
36
+ const filterPages = (raw: unknown): PagesResult => {
37
+ if (!Array.isArray(raw)) {
38
+ return { pages: [], warnings: [] };
39
+ }
40
+
41
+ const pages: string[] = [];
42
+ const warnings: string[] = [];
43
+ for (const entry of raw) {
44
+ if (typeof entry !== "string") {
45
+ continue;
46
+ }
47
+ const value = entry.trim();
48
+ if (value === REST || value === "") {
49
+ continue;
50
+ }
51
+ const separator = SEPARATOR.exec(value);
52
+ if (separator) {
53
+ const label = separator.groups?.label?.trim();
54
+ warnings.push(
55
+ `Dropped sidebar separator "${label || value}" — recreate it with a sidebar group if needed.`
56
+ );
57
+ continue;
58
+ }
59
+ const link = LINK.exec(value);
60
+ if (link) {
61
+ warnings.push(
62
+ `Dropped sidebar link "${link.groups?.text}" (${link.groups?.href}) — add it to navbar.links manually.`
63
+ );
64
+ continue;
65
+ }
66
+ pages.push(value);
67
+ }
68
+
69
+ return { pages, warnings };
70
+ };
71
+
72
+ export interface FumadocsMetaConversion {
73
+ meta: FolderMeta;
74
+ warnings: string[];
75
+ }
76
+
77
+ /** Map a parsed Fumadocs `meta.json` object onto a Blume `FolderMeta`. */
78
+ export const translateFumadocsMeta = (
79
+ value: unknown
80
+ ): FumadocsMetaConversion => {
81
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
82
+ return { meta: {}, warnings: [] };
83
+ }
84
+
85
+ const source = value as FumadocsMeta;
86
+ const meta: FolderMeta = {};
87
+ const warnings: string[] = [];
88
+
89
+ if (typeof source.title === "string") {
90
+ meta.title = source.title;
91
+ }
92
+ if (typeof source.icon === "string") {
93
+ meta.icon = source.icon;
94
+ }
95
+ if (source.root === true) {
96
+ meta.display = "page";
97
+ } else if (typeof source.defaultOpen === "boolean") {
98
+ meta.display = "group";
99
+ meta.collapsed = !source.defaultOpen;
100
+ }
101
+ if (typeof source.description === "string") {
102
+ warnings.push(
103
+ "Dropped folder `description` — Blume folders have no description field."
104
+ );
105
+ }
106
+
107
+ const { pages, warnings: pageWarnings } = filterPages(source.pages);
108
+ if (pages.length > 0) {
109
+ meta.pages = pages;
110
+ }
111
+ warnings.push(...pageWarnings);
112
+
113
+ return { meta, warnings };
114
+ };
@@ -0,0 +1,53 @@
1
+ import { migrateFumadocsProject } from "./fumadocs/index.ts";
2
+ import { migrateMintlifyProject } from "./mintlify/index.ts";
3
+ import { migrateNextraProject } from "./nextra/index.ts";
4
+ import { migrateStarlightProject } from "./starlight/index.ts";
5
+
6
+ export interface MigrationResult {
7
+ moved: number;
8
+ warnings: string[];
9
+ }
10
+
11
+ /**
12
+ * Migrate a Mintlify project (`docs.json`/`mint.json` + MDX). Translates the
13
+ * config, rewrites pages to idiomatic Blume MDX in place, and relocates assets.
14
+ * Unlike the other migrators, content stays at the project root.
15
+ */
16
+ export const migrateMintlify = (root: string): Promise<MigrationResult> =>
17
+ migrateMintlifyProject(root);
18
+
19
+ /**
20
+ * Migrate a Nextra project (`content/` or `pages/` + `_meta` files). Moves pages
21
+ * into `docs/`, rewrites `<Callout>`s to directives, and converts every `_meta`
22
+ * into a typed `meta.ts`, preserving navigation order and titles.
23
+ */
24
+ export const migrateNextra = (root: string): Promise<MigrationResult> =>
25
+ migrateNextraProject(root);
26
+
27
+ /**
28
+ * Migrate a Fumadocs project (`content/docs` + `meta.json`). Moves pages into
29
+ * `docs/`, rewrites Fumadocs MDX (callouts, `<Cards>`/`<Accordions>`/`<Files>`,
30
+ * `<Tabs items>`, `<include>`) to idiomatic Blume markup, converts every
31
+ * `meta.json` into a typed `meta.ts`, and preserves the `/docs` route prefix.
32
+ */
33
+ export const migrateFumadocs = (root: string): Promise<MigrationResult> =>
34
+ migrateFumadocsProject(root);
35
+
36
+ /**
37
+ * Migrate a Starlight project (`src/content/docs` + `astro.config.*`). Translates
38
+ * the `starlight({...})` options into `blume.config.ts` and rewrites each page to
39
+ * idiomatic Blume MDX in place (asides → directives, component renames,
40
+ * frontmatter mapping). Content stays under `src/content/docs`.
41
+ */
42
+ export const migrateStarlight = (root: string): Promise<MigrationResult> =>
43
+ migrateStarlightProject(root);
44
+
45
+ export const migrators: Record<
46
+ string,
47
+ (root: string) => Promise<MigrationResult>
48
+ > = {
49
+ fumadocs: migrateFumadocs,
50
+ mintlify: migrateMintlify,
51
+ nextra: migrateNextra,
52
+ starlight: migrateStarlight,
53
+ };