blume 2.1.3 → 2.2.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 (397) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +134 -0
  3. package/README.md +4 -4
  4. package/dist/cli/{chunk-y4msrwm9.js → chunk-080mfch6.js} +15 -15
  5. package/dist/cli/{chunk-gfc5fqvf.js → chunk-1ch3ct3r.js} +18 -18
  6. package/dist/cli/{chunk-gfc5fqvf.js.map → chunk-1ch3ct3r.js.map} +1 -1
  7. package/dist/cli/{chunk-32khwcb6.js → chunk-2ssh60qh.js} +42 -69
  8. package/dist/cli/{chunk-32khwcb6.js.map → chunk-2ssh60qh.js.map} +9 -10
  9. package/dist/cli/{chunk-9qzgg6mj.js → chunk-34wap3ed.js} +3 -3
  10. package/dist/cli/{chunk-k3gr1nm9.js → chunk-40vk8kgf.js} +13 -16
  11. package/dist/cli/{chunk-k3gr1nm9.js.map → chunk-40vk8kgf.js.map} +3 -3
  12. package/dist/cli/chunk-487b95qy.js +329 -0
  13. package/dist/cli/chunk-487b95qy.js.map +13 -0
  14. package/dist/cli/chunk-4zxh25hc.js +157 -0
  15. package/dist/cli/chunk-4zxh25hc.js.map +10 -0
  16. package/dist/cli/chunk-69rjf786.js +146 -0
  17. package/dist/cli/chunk-69rjf786.js.map +11 -0
  18. package/dist/cli/{chunk-dqhg54mr.js → chunk-9h8mh2tk.js} +3 -3
  19. package/dist/cli/{chunk-dqhg54mr.js.map → chunk-9h8mh2tk.js.map} +2 -2
  20. package/dist/cli/{chunk-bqxaqn5p.js → chunk-9jqmq5hv.js} +8 -8
  21. package/dist/cli/{chunk-bqxaqn5p.js.map → chunk-9jqmq5hv.js.map} +1 -1
  22. package/dist/cli/{chunk-dv2z0rzf.js → chunk-aafhwx74.js} +38 -13
  23. package/dist/cli/{chunk-dv2z0rzf.js.map → chunk-aafhwx74.js.map} +5 -4
  24. package/dist/cli/{chunk-desvm72z.js → chunk-b680sv30.js} +22 -26
  25. package/dist/cli/chunk-b680sv30.js.map +13 -0
  26. package/dist/cli/{chunk-0w6hs479.js → chunk-beb0z983.js} +30 -18
  27. package/dist/cli/chunk-beb0z983.js.map +10 -0
  28. package/dist/cli/{chunk-1f882900.js → chunk-c5bjxq8v.js} +2 -2
  29. package/dist/cli/chunk-e1n41w9m.js +155 -0
  30. package/dist/cli/chunk-e1n41w9m.js.map +11 -0
  31. package/dist/cli/{chunk-sc93apfs.js → chunk-etckvbrh.js} +17 -17
  32. package/dist/cli/{chunk-sc93apfs.js.map → chunk-etckvbrh.js.map} +2 -2
  33. package/dist/cli/{chunk-bxwhtvw8.js → chunk-f260jdrn.js} +5 -5
  34. package/dist/cli/{chunk-74wfhv79.js → chunk-fnxgtjeq.js} +1 -1
  35. package/dist/cli/{chunk-ms9z66nz.js → chunk-gwy55cx3.js} +2 -2
  36. package/dist/cli/{chunk-ms9z66nz.js.map → chunk-gwy55cx3.js.map} +2 -2
  37. package/dist/cli/{chunk-dhwtqnnd.js → chunk-gxhcf2hs.js} +2 -2
  38. package/dist/cli/{chunk-tfycddbt.js → chunk-h0qwdbem.js} +2 -2
  39. package/dist/cli/{chunk-nbyvgnnm.js → chunk-hktq4j5r.js} +109 -49
  40. package/dist/cli/chunk-hktq4j5r.js.map +36 -0
  41. package/dist/cli/{chunk-twy7psdz.js → chunk-hn3g2b89.js} +14 -10
  42. package/dist/cli/chunk-hn3g2b89.js.map +10 -0
  43. package/dist/cli/{chunk-j1x92mx9.js → chunk-j4nfgn2j.js} +1 -1
  44. package/dist/cli/{chunk-dpy8qdjf.js → chunk-k45sq16q.js} +74 -183
  45. package/dist/cli/chunk-k45sq16q.js.map +18 -0
  46. package/dist/cli/{chunk-5cmts6dv.js → chunk-kmswjxj9.js} +7 -7
  47. package/dist/cli/{chunk-5cmts6dv.js.map → chunk-kmswjxj9.js.map} +1 -1
  48. package/dist/cli/{chunk-36bxs9c7.js → chunk-nqna8dzp.js} +27 -23
  49. package/dist/cli/chunk-nqna8dzp.js.map +10 -0
  50. package/dist/cli/{chunk-a57yh0h9.js → chunk-p834rkcn.js} +7 -7
  51. package/dist/cli/{chunk-a57yh0h9.js.map → chunk-p834rkcn.js.map} +1 -1
  52. package/dist/cli/{chunk-60hz265r.js → chunk-ppa6mg4d.js} +24 -11
  53. package/dist/cli/chunk-ppa6mg4d.js.map +10 -0
  54. package/dist/cli/{chunk-s0bdnc7v.js → chunk-q1wwrxzw.js} +5 -4
  55. package/dist/cli/chunk-q1wwrxzw.js.map +10 -0
  56. package/dist/cli/{chunk-ave1wcbr.js → chunk-qcyxkj44.js} +1 -1
  57. package/dist/cli/{chunk-124fvmzq.js → chunk-r5nm9w0c.js} +11183 -8806
  58. package/dist/cli/{chunk-124fvmzq.js.map → chunk-r5nm9w0c.js.map} +82 -66
  59. package/dist/cli/{chunk-ckrcm08h.js → chunk-rtvgmg2j.js} +48 -62
  60. package/dist/cli/{chunk-ckrcm08h.js.map → chunk-rtvgmg2j.js.map} +4 -5
  61. package/dist/cli/{chunk-297ajdcv.js → chunk-t2h8pwy1.js} +6 -6
  62. package/dist/cli/{chunk-297ajdcv.js.map → chunk-t2h8pwy1.js.map} +3 -3
  63. package/dist/cli/{chunk-gxtg6ed2.js → chunk-x60e15st.js} +21 -21
  64. package/dist/cli/{chunk-gxtg6ed2.js.map → chunk-x60e15st.js.map} +3 -3
  65. package/dist/cli/{chunk-58bt3hag.js → chunk-x7bh6d9j.js} +4 -4
  66. package/dist/cli/{chunk-hvcxng5f.js → chunk-ydqzkhjn.js} +6 -6
  67. package/dist/cli/{chunk-kdpzw38a.js → chunk-yg8ckc3g.js} +95 -7
  68. package/dist/cli/chunk-yg8ckc3g.js.map +11 -0
  69. package/dist/cli/{chunk-0pmb4tr4.js → chunk-zxd5jrgx.js} +1205 -222
  70. package/dist/cli/chunk-zxd5jrgx.js.map +44 -0
  71. package/dist/cli/index.js +78 -42
  72. package/dist/cli/index.js.map +7 -6
  73. package/dist/types/ai/changelog-components.d.ts +18 -0
  74. package/dist/types/ai/changelog-markdown.d.ts +12 -0
  75. package/dist/types/ai/openapi-components.d.ts +7 -7
  76. package/dist/types/ai/operation-details.d.ts +39 -0
  77. package/dist/types/ai/relative-links.d.ts +3 -1
  78. package/dist/types/ai/serializers.d.ts +6 -4
  79. package/dist/types/analytics/head.d.ts +4 -2
  80. package/dist/types/analytics/script.d.ts +8 -2
  81. package/dist/types/components/content/youtube.d.ts +20 -0
  82. package/dist/types/components/layout/nav-utils.d.ts +8 -0
  83. package/dist/types/components/openapi/example-panels.d.ts +33 -0
  84. package/dist/types/components/openapi/helpers.d.ts +53 -11
  85. package/dist/types/components/openapi/snippets.d.ts +7 -1
  86. package/dist/types/core/alert-diagnostics.d.ts +10 -0
  87. package/dist/types/core/builtin-tags.d.ts +16 -0
  88. package/dist/types/core/changelog-list.d.ts +48 -0
  89. package/dist/types/core/closest-match.d.ts +6 -0
  90. package/dist/types/core/code-fence-diagnostics.d.ts +18 -0
  91. package/dist/types/core/code-fences.d.ts +8 -1
  92. package/dist/types/core/config-input.d.ts +38 -6
  93. package/dist/types/core/content-assets.d.ts +16 -18
  94. package/dist/types/core/data.d.ts +6 -2
  95. package/dist/types/core/diagnostics.d.ts +14 -0
  96. package/dist/types/core/directive-diagnostics.d.ts +22 -8
  97. package/dist/types/core/former-routes.d.ts +14 -0
  98. package/dist/types/core/github.d.ts +5 -4
  99. package/dist/types/core/heading-markers.d.ts +65 -2
  100. package/dist/types/core/hidden-pages.d.ts +3 -0
  101. package/dist/types/core/includes.d.ts +23 -0
  102. package/dist/types/core/last-modified.d.ts +18 -6
  103. package/dist/types/core/links.d.ts +42 -2
  104. package/dist/types/core/load-module.d.ts +19 -4
  105. package/dist/types/core/manifest.d.ts +14 -1
  106. package/dist/types/core/markdown-mirrors.d.ts +22 -0
  107. package/dist/types/core/mcp-clients.d.ts +47 -0
  108. package/dist/types/core/mdx-elements.d.ts +32 -0
  109. package/dist/types/core/mdx-syntax.d.ts +100 -0
  110. package/dist/types/core/meta-diagnostics.d.ts +7 -0
  111. package/dist/types/core/meta.d.ts +32 -5
  112. package/dist/types/core/nav-diagnostics.d.ts +16 -2
  113. package/dist/types/core/navigation.d.ts +13 -0
  114. package/dist/types/core/ordering-prefix.d.ts +6 -0
  115. package/dist/types/core/pinned-ids.d.ts +23 -0
  116. package/dist/types/core/project-graph.d.ts +9 -1
  117. package/dist/types/core/redirect-patterns.d.ts +18 -0
  118. package/dist/types/core/relative-files.d.ts +31 -0
  119. package/dist/types/core/schema.d.ts +322 -11
  120. package/dist/types/core/sources/normalize.d.ts +101 -9
  121. package/dist/types/core/sources/types.d.ts +16 -0
  122. package/dist/types/core/static-files.d.ts +10 -0
  123. package/dist/types/core/static-host.d.ts +42 -0
  124. package/dist/types/core/svg-images.d.ts +26 -0
  125. package/dist/types/core/syntax-diagnostics.d.ts +14 -0
  126. package/dist/types/core/types.d.ts +42 -1
  127. package/dist/types/deploy/generated-files.d.ts +41 -0
  128. package/dist/types/deploy/redirects.d.ts +21 -7
  129. package/dist/types/markdown/code-title.d.ts +9 -5
  130. package/dist/types/markdown/directives.d.ts +8 -0
  131. package/dist/types/markdown/fence-language.d.ts +51 -0
  132. package/dist/types/markdown/fence-meta.d.ts +5 -0
  133. package/dist/types/markdown/github-alerts.d.ts +35 -0
  134. package/dist/types/openapi/asyncapi.d.ts +6 -5
  135. package/dist/types/openapi/checks.d.ts +10 -5
  136. package/dist/types/openapi/model.d.ts +55 -8
  137. package/dist/types/openapi/parse.d.ts +4 -1
  138. package/dist/types/ratelimit/index.d.ts +2 -0
  139. package/dist/types/ratelimit/memory.d.ts +1 -1
  140. package/dist/types/ratelimit/schema.d.ts +40 -0
  141. package/dist/types/ratelimit/unkey.d.ts +48 -0
  142. package/dist/types/sources/filesystem.d.ts +14 -4
  143. package/dist/types/sources/mdx-remote.d.ts +6 -3
  144. package/dist/types/sources/registry.d.ts +3 -3
  145. package/dist/types/theme/color.d.ts +33 -0
  146. package/dist/types/theme/palette.d.ts +122 -0
  147. package/docs/01-quickstart.mdx +1 -1
  148. package/docs/02-deployment.mdx +24 -8
  149. package/docs/04-migrating.mdx +16 -4
  150. package/docs/advanced/changelog.mdx +27 -1
  151. package/docs/advanced/skills.mdx +1 -1
  152. package/docs/cli/audit.mdx +1 -1
  153. package/docs/cli/doctor.mdx +2 -2
  154. package/docs/cli/evals.mdx +1 -1
  155. package/docs/cli/index.mdx +4 -2
  156. package/docs/cli/validate.mdx +10 -7
  157. package/docs/configuration/analytics.mdx +2 -0
  158. package/docs/configuration/customization.mdx +3 -1
  159. package/docs/configuration/index.mdx +7 -6
  160. package/docs/configuration/rate-limiting.mdx +20 -1
  161. package/docs/configuration/theming.mdx +5 -1
  162. package/docs/content/components.mdx +26 -6
  163. package/docs/content/frontmatter.mdx +1 -1
  164. package/docs/content/i18n.mdx +1 -1
  165. package/docs/content/includes.mdx +6 -2
  166. package/docs/content/index.mdx +11 -1
  167. package/docs/content/islands.mdx +2 -0
  168. package/docs/content/meta.mdx +9 -3
  169. package/docs/content/navigation.mdx +14 -6
  170. package/docs/content/sources/obsidian.mdx +1 -1
  171. package/docs/content/sources/remote-mdx.mdx +3 -1
  172. package/docs/content/syntax.mdx +76 -8
  173. package/docs/content/variables.mdx +3 -1
  174. package/docs/discoverability/agent-discovery.mdx +1 -1
  175. package/docs/discoverability/json-api.mdx +1 -1
  176. package/docs/discoverability/llms-txt.mdx +1 -1
  177. package/docs/discoverability/markdown.mdx +2 -2
  178. package/docs/discoverability/mcp.mdx +40 -8
  179. package/docs/discoverability/open-graph.mdx +1 -1
  180. package/docs/discoverability/sitemap-and-robots.mdx +1 -1
  181. package/docs/references/graphql.mdx +2 -0
  182. package/docs/references/openapi.mdx +45 -7
  183. package/package.json +4 -4
  184. package/skills/blume/SKILL.md +2 -2
  185. package/skills/blume-migrate/SKILL.md +51 -29
  186. package/skills/blume-migrate/references/docsify.md +262 -0
  187. package/skills/blume-migrate/references/docus.md +298 -0
  188. package/skills/blume-migrate/references/docusaurus.md +5 -5
  189. package/skills/blume-migrate/references/fern.md +327 -0
  190. package/skills/blume-migrate/references/fumadocs.md +1 -1
  191. package/skills/blume-migrate/references/gitbook.md +267 -0
  192. package/skills/blume-migrate/references/github-wiki.md +259 -0
  193. package/skills/blume-migrate/references/jekyll.md +253 -0
  194. package/skills/blume-migrate/references/mdbook.md +256 -0
  195. package/skills/blume-migrate/references/mintlify.md +5 -5
  196. package/skills/blume-migrate/references/mkdocs.md +236 -0
  197. package/skills/blume-migrate/references/monorepo.md +20 -8
  198. package/skills/blume-migrate/references/nextra.md +5 -5
  199. package/skills/blume-migrate/references/readme.md +257 -0
  200. package/skills/blume-migrate/references/redocly.md +198 -0
  201. package/skills/blume-migrate/references/starlight.md +3 -3
  202. package/skills/blume-migrate/references/vitepress.md +204 -0
  203. package/skills/blume-migrate/references/vuepress.md +271 -0
  204. package/skills/blume-migrate/scripts/docsify-codemod.mjs +2387 -0
  205. package/skills/blume-migrate/scripts/docus-codemod.mjs +2542 -0
  206. package/skills/blume-migrate/scripts/fern-codemod.mjs +2633 -0
  207. package/skills/blume-migrate/scripts/github-wiki-codemod.mjs +2808 -0
  208. package/skills/blume-migrate/scripts/include-excerpts.mjs +649 -0
  209. package/skills/blume-migrate/scripts/jekyll-codemod.mjs +3649 -0
  210. package/skills/blume-migrate/scripts/mintlify-codemod.mjs +17 -13
  211. package/skills/blume-migrate/scripts/mkdocs-codemod.mjs +2526 -0
  212. package/skills/blume-migrate/scripts/operation-routes.mjs +508 -0
  213. package/skills/blume-migrate/scripts/pin-heading-ids.mjs +1049 -0
  214. package/skills/blume-migrate/scripts/readme-codemod.mjs +4139 -0
  215. package/skills/blume-migrate/scripts/vitepress-codemod.mjs +1511 -0
  216. package/src/ai/changelog-components.ts +24 -0
  217. package/src/ai/changelog-markdown.ts +45 -41
  218. package/src/ai/component-markdown.ts +22 -9
  219. package/src/ai/llms.ts +36 -5
  220. package/src/ai/markdown.ts +2 -2
  221. package/src/ai/openapi-components.ts +25 -9
  222. package/src/ai/operation-details.ts +208 -0
  223. package/src/ai/relative-links.ts +48 -8
  224. package/src/ai/serializers.ts +7 -6
  225. package/src/ai/site-skill.ts +6 -2
  226. package/src/analytics/head.ts +11 -5
  227. package/src/analytics/script.ts +13 -3
  228. package/src/astro/generate.ts +47 -11
  229. package/src/astro/index.ts +1 -0
  230. package/src/astro/integration.ts +27 -1
  231. package/src/astro/mdx-source-errors.ts +46 -0
  232. package/src/astro/templates.ts +93 -189
  233. package/src/audit/checks/redirects.ts +5 -1
  234. package/src/audit/redirects.ts +36 -19
  235. package/src/audit/report.ts +22 -9
  236. package/src/audit/run.ts +12 -28
  237. package/src/audit/terms.ts +23 -0
  238. package/src/audit/url.ts +2 -2
  239. package/src/cli/build-failure.ts +256 -7
  240. package/src/cli/command-meta.ts +1 -1
  241. package/src/cli/commands/audit.ts +1 -1
  242. package/src/cli/commands/build.ts +12 -3
  243. package/src/cli/commands/check.ts +13 -2
  244. package/src/cli/commands/dev.ts +1 -1
  245. package/src/cli/commands/doctor.ts +4 -2
  246. package/src/cli/commands/init.ts +3 -3
  247. package/src/cli/commands/preview.ts +17 -1
  248. package/src/cli/commands/validate.ts +22 -10
  249. package/src/cli/prepare.ts +1 -1
  250. package/src/cli/preview-pages.ts +161 -0
  251. package/src/cli/required-secrets.ts +0 -1
  252. package/src/cli/unknown-flags.ts +1 -42
  253. package/src/components/code-copy.ts +51 -0
  254. package/src/components/code-language.ts +75 -0
  255. package/src/components/content/AccordionItem.astro +16 -1
  256. package/src/components/content/Callout.astro +11 -1
  257. package/src/components/content/Changelog.astro +189 -0
  258. package/src/components/content/CodeBlock.astro +8 -11
  259. package/src/components/content/Tabs.astro +16 -2
  260. package/src/components/content/YouTube.astro +6 -4
  261. package/src/components/content/code-block.ts +33 -0
  262. package/src/components/content/content-strings.ts +5 -1
  263. package/src/components/content/inline-markdown.ts +35 -8
  264. package/src/components/content/youtube.ts +49 -7
  265. package/src/components/layout/Analytics.astro +5 -1
  266. package/src/components/layout/NavTree.astro +8 -3
  267. package/src/components/layout/PageActions.astro +81 -23
  268. package/src/components/layout/RootLayout.astro +35 -32
  269. package/src/components/layout/SiteFooter.astro +31 -8
  270. package/src/components/layout/nav-utils.ts +14 -0
  271. package/src/components/layout/search/pagefind.ts +2 -1
  272. package/src/components/openapi/Operation.astro +4 -0
  273. package/src/components/openapi/PanelTabs.astro +9 -3
  274. package/src/components/openapi/RequestBody.astro +42 -8
  275. package/src/components/openapi/RequestPanel.astro +34 -53
  276. package/src/components/openapi/example-panels.ts +110 -0
  277. package/src/components/openapi/helpers.ts +195 -20
  278. package/src/components/openapi/operation-model.ts +63 -26
  279. package/src/components/openapi/request.ts +13 -0
  280. package/src/components/openapi/snippets.ts +14 -2
  281. package/src/core/alert-diagnostics.ts +89 -0
  282. package/src/core/builtin-tags.ts +76 -0
  283. package/src/core/changelog-list.ts +87 -0
  284. package/src/core/closest-match.ts +41 -0
  285. package/src/core/code-fence-diagnostics.ts +267 -0
  286. package/src/core/code-fences.ts +43 -7
  287. package/src/core/component-diagnostics.ts +65 -12
  288. package/src/core/config-input.ts +39 -5
  289. package/src/core/config.ts +46 -1
  290. package/src/core/content-assets.ts +64 -144
  291. package/src/core/data.ts +10 -3
  292. package/src/core/diagnostics.ts +36 -3
  293. package/src/core/directive-diagnostics.ts +265 -47
  294. package/src/core/former-routes.ts +46 -0
  295. package/src/core/git-ignored.ts +53 -0
  296. package/src/core/github.ts +5 -4
  297. package/src/core/heading-markers.ts +156 -2
  298. package/src/core/hidden-pages.ts +64 -0
  299. package/src/core/includes.ts +181 -41
  300. package/src/core/last-modified.ts +47 -10
  301. package/src/core/links.ts +438 -83
  302. package/src/core/load-module.ts +98 -9
  303. package/src/core/manifest.ts +12 -9
  304. package/src/core/markdown-mirrors.ts +52 -0
  305. package/src/core/mcp-clients.ts +87 -0
  306. package/src/core/mdx-elements.ts +153 -0
  307. package/src/core/mdx-syntax.ts +377 -0
  308. package/src/core/meta-diagnostics.ts +134 -0
  309. package/src/core/meta.ts +154 -55
  310. package/src/core/nav-diagnostics.ts +41 -4
  311. package/src/core/navigation.ts +70 -10
  312. package/src/core/ordering-prefix.ts +22 -2
  313. package/src/core/pinned-ids.ts +141 -0
  314. package/src/core/project-graph.ts +221 -29
  315. package/src/core/redirect-patterns.ts +41 -0
  316. package/src/core/relative-files.ts +145 -0
  317. package/src/core/schema.ts +110 -6
  318. package/src/core/sources/mdx-remote.ts +75 -14
  319. package/src/core/sources/normalize.ts +885 -179
  320. package/src/core/sources/notion.ts +5 -4
  321. package/src/core/sources/obsidian.ts +2 -5
  322. package/src/core/sources/types.ts +17 -0
  323. package/src/core/static-files.ts +40 -0
  324. package/src/core/static-host.ts +125 -0
  325. package/src/core/svg-images.ts +98 -0
  326. package/src/core/syntax-diagnostics.ts +491 -0
  327. package/src/core/types.ts +43 -1
  328. package/src/deploy/artifacts.ts +2 -28
  329. package/src/deploy/generated-files.ts +128 -0
  330. package/src/deploy/platforms/vercel.ts +25 -1
  331. package/src/deploy/redirects.ts +43 -16
  332. package/src/deploy/sitemap.ts +2 -1
  333. package/src/markdown/base-links.ts +87 -6
  334. package/src/markdown/code-title.ts +29 -13
  335. package/src/markdown/content-assets.ts +210 -0
  336. package/src/markdown/directives.ts +69 -5
  337. package/src/markdown/fence-language.ts +106 -0
  338. package/src/markdown/fence-meta.ts +42 -0
  339. package/src/markdown/github-alerts.ts +172 -0
  340. package/src/markdown/heading-anchors.ts +265 -56
  341. package/src/markdown/include.ts +30 -3
  342. package/src/markdown/index.ts +145 -40
  343. package/src/markdown/prompt-text.ts +95 -0
  344. package/src/markdown/relative-links.ts +37 -2
  345. package/src/markdown/route-snapshot.ts +22 -12
  346. package/src/markdown/svg-fallback.ts +51 -0
  347. package/src/markdown/title-heading.ts +134 -0
  348. package/src/migrate/migrate.ts +127 -2
  349. package/src/og/card.ts +14 -4
  350. package/src/openapi/asyncapi.ts +14 -6
  351. package/src/openapi/checks.ts +160 -15
  352. package/src/openapi/model.ts +172 -33
  353. package/src/openapi/parse.ts +13 -4
  354. package/src/openapi/render-mdx.ts +55 -9
  355. package/src/openapi/source.ts +87 -6
  356. package/src/ratelimit/index.ts +2 -0
  357. package/src/ratelimit/memory.ts +1 -1
  358. package/src/ratelimit/runtime.ts +81 -0
  359. package/src/ratelimit/schema.ts +2 -0
  360. package/src/ratelimit/unkey.ts +61 -0
  361. package/src/registry/eject.ts +0 -1
  362. package/src/registry/registry.ts +1 -1
  363. package/src/search/documents.ts +10 -2
  364. package/src/sources/filesystem.ts +28 -3
  365. package/src/sources/mdx-remote.ts +7 -15
  366. package/src/theme/color.ts +14 -0
  367. package/src/theme/entry.ts +22 -1
  368. package/src/theme/palette.ts +74 -1
  369. package/dist/cli/chunk-0pmb4tr4.js.map +0 -35
  370. package/dist/cli/chunk-0w6hs479.js.map +0 -10
  371. package/dist/cli/chunk-150jk7km.js +0 -52
  372. package/dist/cli/chunk-150jk7km.js.map +0 -10
  373. package/dist/cli/chunk-36bxs9c7.js.map +0 -10
  374. package/dist/cli/chunk-53jqw8z0.js +0 -89
  375. package/dist/cli/chunk-53jqw8z0.js.map +0 -10
  376. package/dist/cli/chunk-60hz265r.js.map +0 -10
  377. package/dist/cli/chunk-desvm72z.js.map +0 -14
  378. package/dist/cli/chunk-dpy8qdjf.js.map +0 -17
  379. package/dist/cli/chunk-es8h0v3j.js +0 -441
  380. package/dist/cli/chunk-es8h0v3j.js.map +0 -11
  381. package/dist/cli/chunk-kdpzw38a.js.map +0 -11
  382. package/dist/cli/chunk-nbyvgnnm.js.map +0 -36
  383. package/dist/cli/chunk-s0bdnc7v.js.map +0 -10
  384. package/dist/cli/chunk-twy7psdz.js.map +0 -10
  385. package/dist/cli/chunk-ydp3065p.js +0 -64
  386. package/dist/cli/chunk-ydp3065p.js.map +0 -10
  387. /package/dist/cli/{chunk-y4msrwm9.js.map → chunk-080mfch6.js.map} +0 -0
  388. /package/dist/cli/{chunk-9qzgg6mj.js.map → chunk-34wap3ed.js.map} +0 -0
  389. /package/dist/cli/{chunk-1f882900.js.map → chunk-c5bjxq8v.js.map} +0 -0
  390. /package/dist/cli/{chunk-bxwhtvw8.js.map → chunk-f260jdrn.js.map} +0 -0
  391. /package/dist/cli/{chunk-74wfhv79.js.map → chunk-fnxgtjeq.js.map} +0 -0
  392. /package/dist/cli/{chunk-dhwtqnnd.js.map → chunk-gxhcf2hs.js.map} +0 -0
  393. /package/dist/cli/{chunk-tfycddbt.js.map → chunk-h0qwdbem.js.map} +0 -0
  394. /package/dist/cli/{chunk-j1x92mx9.js.map → chunk-j4nfgn2j.js.map} +0 -0
  395. /package/dist/cli/{chunk-ave1wcbr.js.map → chunk-qcyxkj44.js.map} +0 -0
  396. /package/dist/cli/{chunk-58bt3hag.js.map → chunk-x7bh6d9j.js.map} +0 -0
  397. /package/dist/cli/{chunk-hvcxng5f.js.map → chunk-ydqzkhjn.js.map} +0 -0
@@ -0,0 +1,262 @@
1
+ # Docsify → Blume
2
+
3
+ Docsify has no build step. One `index.html` sets `window.$docsify = {…}`, loads Docsify and its plugins from a CDN, and renders each page's Markdown in the browser. Watch for five traps:
4
+
5
+ - **URLs live after a `#`** (`/#/guide/setup?id=install`). A server never sees that part, so `redirects` can't match an old URL. The site needs a client-side redirect script (URLs below).
6
+ - **Links resolve from the docs root**, not the page's folder, unless `relativePath: true`. Images and includes resolve from the page's folder either way, even with a leading `/`. Raw HTML `href`/`src` resolve from the docs root, because the document URL is always the folder holding `index.html`.
7
+ - **Pages have no frontmatter.** The title is usually the first `# H1`, but not always.
8
+ - **Navigation is a Markdown list** (`_sidebar.md`), and its groups usually sit flat in one folder.
9
+ - **Heading ids differ from Blume's**, and old deep links carry them as `?id=`.
10
+
11
+ **Fingerprint the version.** It changes callouts and heading ids. `docsify@4`, or any script URL with `/lib/` (even an unversioned `…/npm/docsify/lib/docsify.min.js`), runs v4 code: the v5 package still ships the v4 build under `lib/`. `docsify@5/dist/…` or `new Docsify({…})` is v5.
12
+
13
+ ## Detect
14
+
15
+ - An **`index.html`** whose inline script sets **`window.$docsify`** (v5 can also use `new Docsify({…})`), usually in `docs/`. The folder holding it is the content root.
16
+ - `_sidebar.md`, `_navbar.md`, `_coverpage.md`, and `.nojekyll` beside it, with `README.md` as the home page.
17
+ - `docsify-cli` or `docsify` in `package.json`. Without either, the source has to be named: `npx blume migrate docsify`.
18
+ - A GitHub wiki has `_Sidebar.md` and `Home.md` but no `index.html`. It isn't Docsify.
19
+
20
+ **Where the Blume project goes.** Put `blume.config.ts` beside the `package.json` that runs `docsify serve <dir>` (usually the repo root), so the default `content.root: "docs"` matches. With no manifest, scaffold one there.
21
+
22
+ Read the whole `index.html`: the `$docsify` object (it can be a function), every `<script src>` (each is a plugin), the `<head>`, and the `<body>`. Open a few routes on the live site too. **A page whose include target is missing rendered blank in Docsify 4**, so some "pages" never worked.
23
+
24
+ ## Run the codemod first
25
+
26
+ On the Docsify folder, from the directory that will hold `blume.config.ts`, dry-run the bundled codemod, then apply it once, before any hand edits:
27
+
28
+ ```bash
29
+ curl -s https://api.github.com/emojis > emojis.json # only if pages use :shortcodes:
30
+ node <skill>/scripts/docsify-codemod.mjs docs
31
+ node <skill>/scripts/docsify-codemod.mjs --write --snapshot .docsify-old \
32
+ --group-folders --emoji emojis.json --site https://docs.example.com docs
33
+ ```
34
+
35
+ It's zero-dependency and idempotent, and reads `version`, `relativePath`, `routerMode`, `noEmoji`, `homepage`, `loadSidebar`, `loadNavbar`, and `coverpage` from `index.html` (override with `--version` and `--relative-path`). It converts:
36
+
37
+ - `!>` → `:::warning`, `?>` → `:::tip`, and `> [!TYPE]` alerts → directives, leaving fenced and indented code alone;
38
+ - docsify-tabs → `<Tabs>`;
39
+ - `':include'` lines → `<include>`, resolved from the page's folder as Docsify did, even with a leading `/`. A `:fragment=` is copied to its own file where Docsify cut one: code includes, plus Markdown ones in v5. Docsify 4 showed a Markdown include whole, so it stays whole;
40
+ - `:id=` → `[#id]`, `{docsify-ignore}` → `[!toc]`, and setext headings → ATX;
41
+ - links resolved the way Docsify resolved them (hash links, `?id=`, and `--site` absolute links included) → relative file links, without an anchor that named the target's title H1 (that heading becomes `title`);
42
+ - images → rebased for moved pages. Docsify read a leading-slash image from the page's folder too, where Blume would read it from `public/`;
43
+ - link and image attribute strings → dropped;
44
+ - relative raw HTML URLs and `':ignore'` links → root paths. An `':ignore'` link to a page's own file opened the raw Markdown, so it becomes a raw `<a href>` to the page's Markdown copy (`/<new route>.md`): a Markdown link naming a page's file, even root-relative, lands on the page itself;
45
+ - emoji shortcodes → Unicode;
46
+ - the H1 or sidebar label → `title`, plus `sidebar.label`, `seo.title`, and `hidden: true`, with later H1s demoted;
47
+ - `README.md` → `index.md`, and pages that need MDX → `.mdx` with MDX-safe comments, `<br>`, void tags, and autolinks.
48
+
49
+ `--group-folders` moves each page into a folder per sidebar group, writes each folder's `meta.ts` in sidebar order plus a root `meta.ts` that keeps the groups in sidebar order, and writes the old → new route table to `docsify-routes.json`. Leave the flag off to keep the files where they are. With `homepage: "intro.md"`, the table also maps `/intro` → `/`, since Docsify served that page at both.
50
+
51
+ `--snapshot` records every page's headings with the ids Docsify gave them, plus `old-routes.txt`. The pin step (URLs below) needs it, and it only works on the first run, while the pages are untouched. On Zenroom it matched Docsify 4.13.1's rendered ids on every page that rendered.
52
+
53
+ **Every `REVIEW` line is a to-do**, and the sections below say what to do with each. The codemod never invents a fix: a missing include stays as written, and a file that must move to `public/` is only listed.
54
+
55
+ ## Config: `window.$docsify` → `blume.config.ts`
56
+
57
+ | Docsify | Blume |
58
+ | --- | --- |
59
+ | `name` (may hold HTML) | `title`, as plain text. The `<title>` and `<meta name="description">` in `index.html` give `title` (when `name` is unset) and `description` |
60
+ | `logo` | `logo`, with the file moved to `public/`. Docsify shows the logo instead of the name, so a wordmark needs `logo: { image, text: "" }`. Check it in dark mode |
61
+ | `nameLink` | `logo.href` |
62
+ | `repo` | `github: { owner, repo }` (with `branch` from an edit-on-github URL). A non-GitHub repo goes in `navigation.actions` |
63
+ | `themeColor` (v4), `--theme-color` in a stylesheet (v5) | `theme.accent` |
64
+ | A dark-only theme (`dark.css`, a themeable `*-dark.css`) | `theme.mode: "dark"`. Other themes map to nothing |
65
+ | Fonts in the stylesheet or `<link>`s | `theme.fonts` |
66
+ | `<link rel="icon">` | Copy the file to `public/favicon.<ext>` |
67
+ | `<meta>` verification tags | `seo.metatags`. `twitter:site` goes to `seo.x.handle`. Drop `og:*` and `twitter:` card tags, since Blume writes them |
68
+ | Custom CSS | A root `theme.css` with only the rules whose markup still exists. Docsify selectors (`.markdown-section`, `.sidebar`, `.cover`) match nothing |
69
+ | Site URL (`CNAME`, a sitemap) | `deployment.site`. GitHub Pages isn't auto-detected |
70
+ | `subMaxLevel: N` | `toc: { maxHeadingLevel: N }` when N ≥ 2 and isn't 3. Drop `maxLevel` |
71
+ | `homepage: "x.md"` | Handled by the codemod. A remote URL needs vendoring |
72
+ | `alias` | Local-to-local entries go in the route table. Remote targets: see Includes and aliases. Drop sidebar plumbing (`'/.*/_sidebar.md'`) and any alias whose target doesn't exist |
73
+ | `externalLinkTarget: '_blank'` (explicit) | `markdown: { externalLinks: true }`. Docsify opens external links in a new tab even when this is unset, so report the change |
74
+ | `notFoundPage` / `_404.md` | Blume's 404 page, or a custom `pages/404.astro` |
75
+ | `formatUpdated`, `{docsify-updated}`, a last-modified plugin | `lastModified: "git"` (and `fetch-depth: 0` in CI) |
76
+ | `collapsibleSidebarGroups` (v5) | `navigation.sidebar.display: "group"`, plus `collapsed: false` in each `meta.ts` that should start open |
77
+ | `vueComponents`, `vueMounts`, `vueGlobalOptions`, `executeScript` | An island per widget (`islands/Name.vue`, `docs/content/islands.mdx`). Report |
78
+ | `markdown` (marked options or a `renderer`) | Drop, and report what the renderer did. Common ones are mermaid (now native) and a `json` pretty-printer: minified JSON includes now render as one line, so fix that in whatever generates them. `breaks: true` has no equivalent |
79
+ | `routes` (virtual routes) | Static ones become pages. Report dynamic ones |
80
+ | `auto2top`, `topMargin`, `maxLevel`, `cornerExternalLinkTarget`, `requestHeaders`, `noCompileLinks`, `sidebarPosition`, `keyBindings`, `skipLink`, the search and pagination options, … | Drop |
81
+
82
+ **Plugins** are `<script>` tags:
83
+
84
+ - **Built in:** search, `docsify-copy-code`, `docsify-pagination`, and `zoom-image`. Delete them.
85
+ - **Converted by the codemod:** emoji, `docsify-tabs`, and flexible-alerts.
86
+ - **Prism language packs:** delete them. Shiki highlights the common languages in any case (` ```JSON ` works), and a fence in a language it doesn't know, or a misspelled one, renders as plain text and warns `BLUME_UNKNOWN_CODE_LANGUAGE`: fix the name, or write `text`.
87
+ - **Mermaid** plugins or renderers → native ` ```mermaid ` in `.mdx`.
88
+ - **KaTeX** → `$$…$$` in `.mdx`, with inline `$…$` becoming `$$…$$` inside its sentence.
89
+ - **`ga` / `gtag`** → `googleAnalytics({ id })` for a `G-` id. A `UA-` id no longer collects data, so report it. Other analytics → `script()`.
90
+ - **Comments** (Disqus, Gitalk, giscus) → nothing built in. Use a `PageFooter` slot, or report them.
91
+ - **Anything else** (bibtex, remote-markdown, demo boxes, custom inline plugins) → report.
92
+
93
+ ## Navigation: `_sidebar.md` → folders and `meta.ts`
94
+
95
+ - **Groups over pages in one flat folder** (everything in `docs/` or `docs/pages/`) flatten silently. Use `--group-folders`: it moves each group's pages into a folder and writes `meta.ts` and the route table, and the hash script maps every old route. This departs from SKILL.md's `(group)/`-first rule: every Docsify URL changes form anyway (`/#/x` → `/x`), so the script runs for each old link either way, and the codemod rebases the moved pages' links and images. To keep URLs instead, move pages at the content root into `(group)/` folders, which add no URL segment; under a subfolder like `pages/`, they keep `/pages/x` but nest every group under a "Pages" group. Or keep the files and write an explicit `navigation.sidebar`.
96
+ - **Groups whose pages already sit in a folder named for the group** (`Guide` → `guide/`) stay put, and `--group-folders` writes that folder's `meta.ts`. It moves the pages of a group whose folder has another name into one named for the group, so on a site already laid out in folders, leave the flag off and write each `meta.ts` by hand, with the group label and the sidebar's order.
97
+ - **The home page** stays the root `index`. The generated sidebar lists it first, never inside a group.
98
+ - **Loose pages listed after groups** can't keep that order, because Blume lists loose pages first. Move them into a folder, or report the change.
99
+ - **External links, and links to files** like a PDF or HTML in `public/`, become `navigation.featured`. A file link there warns at build ("no page matches it"); the warning is harmless.
100
+ - **A `_sidebar.md` inside a folder** replaced the sidebar under that folder. Map the folder to a `navigation.tabs` entry.
101
+ - **Pages the sidebar doesn't list** get `hidden: true` from the codemod. They stay reachable, as before.
102
+ - **Sidebar link titles** (`[x](x.md "Tab title")`) become `seo.title`. Report any that look copy-pasted from another page: Docsify showed them as the browser tab title.
103
+
104
+ ## Navbar and cover page
105
+
106
+ - **`_navbar.md`:** doc-section links become `navigation.tabs` (a nested list becomes a tab with `items`), and other links become `navigation.actions`. With `mergeNavbar: true`, put the other links in `navigation.featured` instead, not in both: featured shows on every screen size, while actions hide on phones. Language links (`/zh-cn/`) become `i18n`.
107
+ - **`_coverpage.md`** (shown above `README.md` on `/`; the whole home page with `onlyCover: true`): rebuild it as the top of `index.md`. The `# name <small>1.2</small>` heading is already the title. The tagline is prose or `description`, the logo is a Markdown image, and the buttons become links or `<Card>`s. Drop the background image or `![color](#…)`, and report it. Use `mode: center` when the cover was the whole page.
108
+
109
+ ## Content: what the codemod leaves to you
110
+
111
+ **Titles.** Docsify drops only the first H1 from its sidebar headings, so pages often have several. The codemod takes a leading H1 as the title and demotes every later heading from the second H1 on. When content comes before the first H1, it takes the sidebar label as the title and demotes everything. Check every title it flags: about one page in five on Zenroom started with a section ("Key generation", "Intro"). For those, use the sidebar label as the title, put the H1 back as an `##` section, and demote every heading.
112
+
113
+ **MDX hazards** in a renamed page. The codemod lists each one, and all but the first fail the build:
114
+
115
+ - An indented code block renders as a plain paragraph: fence it.
116
+ - An HTML table whose cell tags share a line with text: give each `<tr>`/`<td>` its own line.
117
+ - A block element inside `<p>`, typical of README banners: make the `<p>` a `<div>`.
118
+ - A repeated attribute: keep one.
119
+ - A brace in prose: escape it as `\{`.
120
+
121
+ **Includes and aliases.**
122
+
123
+ - **A missing include target:** the page was blank on the old site. Repoint the include to the file's new name (a folder was usually renamed), or drop it, and report it.
124
+ - **A fragment copied from a generated file** goes stale. Have the generator write one file per fragment.
125
+ - **An included file's own H1** becomes a second `<h1>`: remove it.
126
+ - **An alias to a raw URL of a file in the same repo** (`'/CHANGELOG': 'https://raw.githubusercontent.com/<owner>/<repo>/main/CHANGELOG.md'`) becomes a page that `<include>`s the file through a symlink in `_snippets/`. Removing that file's H1 changes a file people read on GitHub, so say so.
127
+ - **Other remote aliases and includes:** vendor a copy (with a source comment), or use `mdxRemote()`.
128
+ - **iframe, video, and audio embeds** become raw HTML, with the file in `public/`.
129
+
130
+ **Files the pages link** (listed under "must be served from public/"): Docsify served every file in its folder at its path. Blume publishes a file a page links relatively with that page, but at a new URL, and the codemod writes Docsify's root-resolved links as root paths, which keep the old URLs and are served from `public/`.
131
+
132
+ - Copy each file into `public/` at the same path.
133
+ - If a generator writes the files (test fixtures, examples), symlink the folder instead: `public/_media/examples → ../docs/examples`. Astro copies the target, and `validate` checks the links.
134
+ - An HTML document saved as `.md` (generated API docs) goes to `public/` as HTML.
135
+ - `blume validate --strict` reports a missing one as `BLUME_BROKEN_ASSET`.
136
+
137
+ **Everything else:**
138
+
139
+ - **Link attributes** the codemod dropped (`:target`, `:size`, `:no-zoom`, `:class`): restore any that matter as raw HTML (`<img src="/x.png" width="200" data-no-zoom />`, with the file in `public/`).
140
+ - **Emoji shortcodes left over:** paste the emoji.
141
+ - **Page `<script>`s:** Docsify 4 ran them only with `executeScript` or Vue, so they often never ran. Move one that did into an island.
142
+ - **Vendored READMEs** keep Blume's ids (they had GitHub-style ids already), and they aren't in the snapshot.
143
+
144
+ ## URLs and redirects
145
+
146
+ **Hash mode (the default).** Every page lived at `/#/<route>`, also reachable as `/#/<route>.md` and `/index.html#/<route>`, with folder indexes at `#/<folder>/` and headings at `?id=<id>`. The server only saw `/`.
147
+
148
+ **A. Blume takes over the old address** (the usual case). `docsify-routes.json` holds the page moves the codemod made. Add an entry for each alias you turned into a page. Routes that now land on a file in `public/` go in a separate `files` map that only the script reads: a redirect page would sit where the file is. Name a `public/` folder with its slash (`/api/`) or name the file itself (`/api/index.html`): static hosts, `blume dev`, and `blume preview` serve its `index.html` at `/api/` (and redirect `/api` there), where its relative links resolve.
149
+
150
+ ```ts blume.config.ts
151
+ import { defineConfig } from "blume";
152
+
153
+ import moved from "./docsify-routes.json" with { type: "json" };
154
+
155
+ // Old routes that now land on a file in public/: the script sends readers
156
+ // there, but a redirect can't, so they stay out of `redirects`.
157
+ const files = {}; // e.g. { "/onepage": "/onepage.md" }
158
+
159
+ // Docsify served every page at /#/<route>; a server never sees the hash.
160
+ // Swap an old hash URL for its new route, and ?id=<heading> for #<heading>.
161
+ const base = ""; // the site's deployment.base plus basePath, if it has either
162
+ const docsifyRedirects = `(() => {
163
+ const base = ${JSON.stringify(base)};
164
+ const routes = ${JSON.stringify({ ...moved, ...files })};
165
+ const go = () => {
166
+ const hash = location.hash;
167
+ if (!hash.startsWith("#/")) return;
168
+ const q = hash.indexOf("?");
169
+ let path = q === -1 ? hash.slice(1) : hash.slice(1, q);
170
+ const id = q === -1 ? null : new URLSearchParams(hash.slice(q + 1)).get("id");
171
+ try { path = decodeURI(new URL(path, "http://x").pathname); } catch {}
172
+ path = path.replace(/\\.md$/i, "").replace(/(^|\\/)README$/i, "$1");
173
+ if (path.length > 1) path = path.replace(/\\/+$/, "");
174
+ // location.origin keeps a crafted #//other.host/ link on this site.
175
+ location.replace(location.origin + base + (routes[path] ?? path) + (id ? "#" + id : ""));
176
+ };
177
+ go();
178
+ addEventListener("hashchange", go);
179
+ })();`;
180
+
181
+ export default defineConfig({
182
+ redirects: Object.entries(moved).map(([from, to]) => ({ from, to })),
183
+ integrations: [
184
+ {
185
+ name: "docsify-hash-redirects",
186
+ hooks: {
187
+ "astro:config:setup": ({ injectScript }) => {
188
+ injectScript("head-inline", docsifyRedirects);
189
+ },
190
+ },
191
+ },
192
+ ],
193
+ });
194
+ ```
195
+
196
+ The script runs from every page's `<head>`, the 404 page included, before the body loads, in `blume dev` and in every build. Keep its two safeguards: `new URL(…).pathname` resolves the `./` and `../` segments Docsify left in links (`#/./guide`), and `location.origin` stops a crafted `#//other.host/` from sending readers off the site. Unmoved routes need no entry. The `redirects` also cover direct visits to a moved path and its `/old.md` Markdown twin. Don't use the `script()` analytics adapter for this: it's emitted in production builds only, and held until consent when `consent` is set.
197
+
198
+ **B. The old site keeps its own address** (an old GitHub Pages URL, while the docs move to a new domain). Replace the old `index.html` with a page running the same parsing, with the new origin in place of `location.origin`, and a `<noscript><meta http-equiv="refresh" content="0; url=https://new.example/"></noscript>` fallback.
199
+
200
+ **History mode** (`routerMode: 'history'`). URLs were real paths, so the route table and `redirects` carry moved pages. Anchors were still `?id=`, so add this after `go();`:
201
+
202
+ ```js
203
+ if (!location.hash) {
204
+ const id = new URLSearchParams(location.search).get("id");
205
+ if (id) location.replace(location.pathname + "#" + id);
206
+ }
207
+ ```
208
+
209
+ **Keep old heading anchors.** Docsify 4 leaks entity names into ids (`Foo & Bar` → `foo-amp-bar`, `Don't` → `don39t`), collapses dash runs, keeps emoji and shortcode names, and prefixes a leading digit with `_`. Docsify 5 is closer to Blume, but still differs in that prefix, in non-ASCII capitals, and where a heading has emphasis (`_When_` stays in the id) or a shortcode. After the first build, pin each old id where it differs, and rebuild until it reports 0:
210
+
211
+ ```bash
212
+ node <skill>/scripts/pin-heading-ids.mjs --old .docsify-old --map docsify-routes.json --write
213
+ ```
214
+
215
+ On Zenroom, one pass kept every old id except those of title H1s (58 of 968), and a link to one of those lands at the top of the page anyway. A page that never rendered on the old site (a missing include) has nothing to pin.
216
+
217
+ **Check every old route** against `blume preview`. In `.docsify-old/old-routes.txt`, a folder route is listed without its slash, but Docsify served it only with one. Both forms reach the script.
218
+
219
+ ```bash
220
+ node -e '
221
+ const routes = { ...require("./docsify-routes.json") }; // add the files map too
222
+ const old = require("fs").readFileSync(".docsify-old/old-routes.txt", "utf8").split("\n").filter(Boolean);
223
+ (async () => { for (const r of old) {
224
+ const res = await fetch("http://localhost:4321" + (routes[r] ?? r));
225
+ if (res.status !== 200) console.log(res.status, r);
226
+ } })();'
227
+ ```
228
+
229
+ Then open a few old hash URLs, with and without `?id=`, in a browser on the deployed site. A plain `curl` can't test the hash script.
230
+
231
+ **GitHub Pages**, where most Docsify sites live:
232
+
233
+ - Switch the Pages source from the branch folder to GitHub Actions, using the workflow in https://useblume.dev/guides/markdown-docs-github-pages (with `fetch-depth: 0` for `lastModified: "git"`).
234
+ - Pages has no redirect rules of its own. A static build writes each exact `redirects` entry as a page that forwards to the new URL, which is all the table holds; a pattern redirect wouldn't work there.
235
+ - A project URL (`user.github.io/repo/`) becomes `deployment: { site: "https://user.github.io", base: "/repo" }`, and that base goes in the script too. Blume adds the base to root paths in Markdown and raw HTML alike, so the codemod's root paths need nothing more.
236
+ - The custom domain stays in the Pages settings. Neither `CNAME` nor `.nojekyll` is needed.
237
+ - The workflow's `npm ci` needs a lockfile, so commit one.
238
+
239
+ ## Teardown
240
+
241
+ After harvesting what they declare, delete:
242
+
243
+ - `index.html`;
244
+ - `_sidebar.md`, `_navbar.md`, `_coverpage.md`, and `_404.md` (Blume ignores `_` files meanwhile), or the files `loadSidebar`, `loadNavbar`, and `coverpage` name instead. Without the `_`, Blume publishes them as pages until they're gone;
245
+ - `.nojekyll`, the theme CSS, and generators that read the sidebar or Docsify syntax (sitemaps, one-page builds).
246
+
247
+ Swap `docsify-cli`/`docsify` for `blume` and `docsify serve` for `blume dev`/`blume build`/`blume preview`. Keep `docsify-routes.json` and `.docsify-old/` until the pins are done and old links stop mattering.
248
+
249
+ ## Dropped — report these
250
+
251
+ - The cover's background and the navbar's language links (now `i18n`).
252
+ - The home page's place in a sidebar group, and loose pages after groups.
253
+ - Link and image attributes you didn't restore.
254
+ - Fragments now frozen as copies.
255
+ - Vendored or `mdxRemote()` aliases.
256
+ - Pages that were blank on the old site (missing includes) and what you did with each.
257
+ - Title H1 anchors (they land at the page top).
258
+ - Comment plugins, a `UA-` analytics id, Vue widgets, and page scripts.
259
+ - Custom `markdown` renderers (JSON pretty-printing included) and `breaks: true`.
260
+ - Dynamic virtual `routes`.
261
+ - Theme CSS with no Blume target, and copy-pasted sidebar link titles now in `seo.title`.
262
+ - New-tab external links, unless you set `markdown.externalLinks`.
@@ -0,0 +1,298 @@
1
+ # Docus → Blume
2
+
3
+ Docus is a Nuxt layer for docs sites. It's built on Nuxt Content (pages are Markdown files in `content/`, routed by their path) and Nuxt UI (the theme, and the prose components pages use). Navigation is the file tree plus per-folder `.navigation.yml` files, and settings live in `app.config.ts` and `nuxt.config.ts`. Pages are written in **MDC**, Nuxt Content's Markdown-with-components syntax. The bundled codemod converts most of it; these traps are why it exists:
4
+
5
+ - **MDC's `::note` … `::` looks like a Blume directive and isn't one.** Blume prints `::note` and its `::` closer as text (in `.md`, every MDC construct), and `blume check` and `build` warn `BLUME_MDC_SYNTAX` at each block opener and inline component (`:badge[New]{color="primary"}`) left in either format: convert them as below.
6
+ - **MDX is stricter than MDC.** `[text]{.class}` spans fail the build, and so do `{…}`, `<Name>`, and `<50` in prose. Prompt bodies are full of these.
7
+ - **Icons are Iconify names** (`i-lucide-zap`, `i-simple-icons-github`). Blume renders nothing for them, and warns only for sidebar and tab icons.
8
+ - **Docus sorts siblings by file path as text,** so `10.faq.md` lists between `1.` and `2.`. Blume sorts the prefixes as numbers.
9
+
10
+ Scope:
11
+
12
+ - **Docus 3.x–5.x** is covered: the `docus` package on Nuxt Content 3 and Nuxt UI, whose content syntax is the same across the three. 5.x is current and is by far the most installed, and it's the one this reference was run on.
13
+ - **Docus 1.x** (`@nuxt-themes/docus`, on Nuxt Content 2 with Nuxt Elements components) is flagged. Its section near the end was written from the package source, not tested on a site, and the codemod doesn't convert its components.
14
+ - **The Nuxt UI docs template** (`nuxt-ui-templates/docs`, no `docus` dependency) writes the same content: the codemod and this reference apply, and its `app.config.ts` is read by hand.
15
+
16
+ ## Detect
17
+
18
+ - **A `docus` dependency** means 3.x–5.x, and **`@nuxt-themes/docus`** means 1.x. It's usually in the docs package's own `package.json` (`docs/`, `apps/docs/`) in a module's monorepo.
19
+ - Usually a `nuxt.config.ts` with `extends: ['docus']`. The 4.x starters have no config and run `nuxt dev --extends docus`, and 3.x ran `docus dev`.
20
+ - A `content/` folder beside it with `.navigation.yml` files (3.x+) or `_dir.yml` files (1.x).
21
+
22
+ **Where the Blume project goes.** Put `blume.config.ts` beside the `package.json` that depends on `docus`, and set `content.root: "content"`. `public/` stays where it is: both tools serve it at the site root. In a workspace, read `references/monorepo.md` too.
23
+
24
+ - **No `content/docs/` folder:** every file under `content/` is a page, and `content/index.md` is the landing page at `/`.
25
+ - **A `content/docs/` folder:** Docus publishes only `content/docs/**`, under `/docs/…`, plus `content/index.md`. Exclude anything else under `content/` and report it. Docus hides the `docs` level in its sidebar. In Blume, add a tab `{ label: "Docs", path: "/docs" }`, or the section tabs below, or the sidebar wraps everything in a "Docs" group.
26
+ - **`content/<locale>/` folders:** see i18n.
27
+
28
+ ## Record the old site first
29
+
30
+ Before editing anything, save the URL list, the old heading ids, and the record of what rendered:
31
+
32
+ - **URLs:** the live `/sitemap.xml` (on an i18n site, an index of one sitemap per locale), plus every `routeRules` redirect source in `nuxt.config.ts`.
33
+ - **HTML:** `nuxt generate` writes `.output/public`, one `<route>.html` per page plus a meta-refresh stub per redirect. Copy it outside the repo; it's `<old build>` below. Or point the anchor script at the live origin.
34
+ - **Check components against the live site.** A local build can miss a component a Nuxt module registers in its own build: it shows as an empty tag (`<InstallButton …></InstallButton>`) while production renders it. Request live pages with a browser user agent (`curl -A 'Mozilla/5.0'`): some Docus deployments answer `curl` with Markdown.
35
+
36
+ ## Run the codemod first
37
+
38
+ On the `content/` directory, dry-run and apply the bundled codemod:
39
+
40
+ ```bash
41
+ node <skill>/scripts/docus-codemod.mjs content # report only
42
+ node <skill>/scripts/docus-codemod.mjs --write content # apply
43
+ ```
44
+
45
+ It's zero-dependency, deterministic, and idempotent. Icon names are checked against Blume's Lucide set once `blume` is installed (or with `--lucide <icons.json>`).
46
+
47
+ **What it does:**
48
+
49
+ - **Pairs MDC blocks** as remark-mdc does (see MDC syntax).
50
+ - **Converts the Nuxt UI prose components:**
51
+ - callouts → `:::type` directives, or `<Callout type icon>` when a callout's icon isn't its type's own
52
+ - `card-group`/`card`, `field-group`/`field` → `<ResponseField>`, `steps`, `tabs`, `accordion`, `collapsible`
53
+ - `code-group` → `<CodeGroup>`, or one `package-install` fence when the group only repeats an install command
54
+ - `code-collapse`, `code-tree`, `code-preview`, `prompt`, `badge`
55
+ - the inline `:icon`, `:kbd`, `:badge`, `:video`, and `:u-color-mode-image`
56
+ - **Strips Markdown attributes:** `[text]{.class}` spans and `{…}` attributes go.
57
+ - **Rewrites fence labels and frontmatter:** fence `[label]`s become titles, `navigation` → `sidebar`, `seo.ogImage` → `seo.image`, `links` → `related`, a body H1 → `title`, and `.navigation.yml` → `meta.ts`.
58
+ - **Renames to `.mdx`** each page that now needs MDX, and makes its prose MDX-safe (see MDC syntax).
59
+
60
+ Every `REVIEW` line is your to-do list, including:
61
+
62
+ - custom and landing-page components, left as written
63
+ - blocks with a slot the conversion doesn't read (a card's `#footer`), left as written
64
+ - card descriptions, which Nuxt UI's prose card never rendered
65
+ - callouts that linked somewhere
66
+ - icons outside Lucide
67
+ - unclosed blocks and stray closers
68
+ - `links` lists longer than `related` takes, and frontmatter keys Blume rejects
69
+ - MDC blocks inside blockquotes
70
+ - file names whose URL changes
71
+
72
+ ## Config: `app.config.ts` → `blume.config.ts`
73
+
74
+ Read `app/app.config.ts` (`app.config.ts` at the project root on Nuxt 3), `nuxt.config.ts`, the CSS that `css` lists, and `.env.example`.
75
+
76
+ | `app.config.ts` | Blume |
77
+ | --- | --- |
78
+ | `seo.title` | `title`. Docus falls back to `site.name` in `nuxt.config.ts`, then the `package.json` `name`, then the git repository's name, so carry the one the site showed |
79
+ | `seo.description` | `description` (Docus falls back to the `package.json` `description`) |
80
+ | `seo.titleTemplate` | drop. The default `%s - <site>` is Blume's; report a custom one |
81
+ | `seo.schema` | drop (report). Blume writes its own JSON-LD |
82
+ | `header.title` | `title`, when there's no logo |
83
+ | `header.logo.{light,dark,alt}` | `logo: { image: { light, dark, alt }, text: "" }`. Docus shows the logo without the title. A monochrome SVG → `currentColor` and the string form (SKILL.md) |
84
+ | `header.logo.display: 'wordmark'` | the `wordmark` files as the logo |
85
+ | `socials` (Simple Icons slug → URL) | `footer.socials` for the platforms Blume lists (`twitter` → `x`). Others (`nuxt`, `npm`) → `footer.links`, or drop (report) |
86
+ | `github` (`url`, `branch`, `rootDir`), or nothing | `github: { owner, repo, branch, dir }`. **Docus reads the git remote when `github` isn't set,** so edit links showed anyway: take `owner`/`repo` from the remote. `dir` = `rootDir`; `branch` only when it isn't `main`. `github: false` → leave it out |
87
+ | `toc.bottom.links` | `navigation.featured` (`to` → `href`). Report the move |
88
+ | `navigation.sub` | `navigation.tabs` (see Navigation). `'aside'` anchors have no Blume form; report |
89
+ | `docus.locale` (not `en`) | a one-locale `i18n` block |
90
+ | `docus.colorMode` | `theme.mode`. Blume always shows the toggle; report |
91
+ | `assistant.faqQuestions` | `ai.assistant.suggestions` (`[{ label }]`; flatten categories) |
92
+ | `ui.colors.primary` | `theme.accent`. Nuxt UI paints shade 500 in light mode and 400 in dark: a Blume preset of the same name, else `{ light, dark }` with those Tailwind values. **Not declared means Docus's emerald:** the site turns Blume's default color unless you offer `theme.accent: "green"`; report it either way |
93
+ | `--ui-radius: 0` in CSS | `theme.radius: "none"` |
94
+ | `toc.title`, `docus.shortcuts`, `search.fts`, other `assistant.*` and `ui.*` keys | drop (report the `ui` overrides) |
95
+
96
+ | `nuxt.config.ts` | Blume |
97
+ | --- | --- |
98
+ | `site.url` / `NUXT_SITE_URL` | `deployment.site`, unless the host is Vercel or Netlify (SKILL.md) |
99
+ | `routeRules` with `redirect` | `redirects` (see URLs and redirects) |
100
+ | `@nuxtjs/i18n` and `i18n` | see i18n |
101
+ | `llms.notes`, `llms.sections` | `agents.llmsTxt: { details }` as Markdown, if worth keeping; `llms.title`/`description` drop |
102
+ | `mcp` | Docus serves an MCP server at `/mcp` (or `mcp.route`) unless `mcp: false` or `mcp.enabled: false`. To keep one: `agents.mcp: { enabled: true, name }` (and `route` when it isn't `/mcp`) plus a host adapter (server output). Blume's tools are fixed, so report the site's own tools and prompts in `server/mcp/` |
103
+ | `docus.assistant` | Docus turns the assistant on when the build has `AI_GATEWAY_API_KEY` or `VERCEL_OIDC_TOKEN`: check the live site for it. Keep it as `ai: { assistant: { enabled: true, provider: gateway({ model }) } }` (`gateway` from `blume/ai`; Docus's default model is `google/gemini-3-flash`) plus a host adapter, or drop it (report) |
104
+ | `app.head`, `@vercel/analytics`, `@nuxtjs/plausible`, `@nuxt/scripts` analytics | the provider's factory from `blume/analytics` (`vercel()`, `plausible({ domain })`, `cloudflare({ token })`); other scripts → `script()`; `meta` → `seo.metatags`. `@vercel/speed-insights` has no adapter (report) |
105
+ | `content.build.markdown` (`toc`, `highlight.theme`, plugins), `mdc.highlight.theme` | `toc` and `markdown.code.theme` only when they differ from the defaults; each remark/rehype plugin's syntax converted by hand (report) |
106
+ | `css` | what still applies → a root `theme.css` against Blume's tokens |
107
+ | `studio`, `agentDiscovery`, `ogImage`, `sitemap`, `robots`, `schemaOrg`, `image`, `icon`, `nitro`, `vite` | drop (report). Blume writes its own sitemap, robots, OG images, and JSON-LD |
108
+
109
+ **A project `content.config.ts`** adds collections such as a blog or changelog. Those aren't docs pages: map a blog to `type: blog` and a changelog to `type: changelog` (or `githubReleases()`, SKILL.md Changelogs), and report anything else.
110
+
111
+ **Theme overrides** (`AppHeader*.vue`, `AppFooter*.vue`, `Docs*.vue`) aren't content. Map what they add to config (`navigation.cta`, `navigation.actions`, `footer.links`, `banner`), but read their `v-if`s first: a button shown only on the landing page (`route.path === '/'`) belongs on the landing page, since Blume's `cta` shows site-wide.
112
+
113
+ ## Navigation
114
+
115
+ The codemod writes each `.navigation.yml` as a `meta.ts` (`title`, `icon`) and each page's `navigation` as `sidebar` (`title` → `label`, `icon`, `false` → `hidden`). What's left:
116
+
117
+ - **A folder's `navigation: false`** hides it: set `sidebar.hidden: true` on its pages (the codemod reports it).
118
+ - **A folder with an `index.md` but no `.navigation.yml`:** Docus labels it with the index page's title. Blume uses the humanized folder name, so set `meta.ts` `title` when they differ.
119
+ - **Order:**
120
+ - Docus compares paths as text: `1.intro`, `10.faq`, `11.glossary`, `2.setup`. Blume sorts the numbers. In a folder with ten or more unpadded siblings, keep Blume's order (almost always what the author meant) and report the change.
121
+ - Docus also interleaves pages and subfolders. Blume lists a level's pages above its subfolders, so report a page that sorted after a folder.
122
+ - **Display:** Docus draws every folder as an always-open group, which is Blume's default `flat` display.
123
+ - **`navigation.sub`** (a header tab bar, or anchors in the sidebar) makes each top-level folder a section that scopes the sidebar. That's `navigation.tabs`: one `{ label, path, icon? }` per top-level folder with pages, with `label` and `icon` from its `.navigation.yml`. A section with no index page needs no `href`, since a Blume tab falls back to the section's first page, as Docus did.
124
+
125
+ ## Pages: frontmatter and titles
126
+
127
+ The codemod maps `navigation`, `links` (header buttons → `related` cards at the foot of the page), and `seo.ogImage`. It removes `layout: docs` and `sitemap`. What's left:
128
+
129
+ - **No `description`:** when a page opens with a paragraph (after an optional `# H1`), Docus took it as the description, so it showed twice. Blume derives none. To keep the meta description without the repeat, set `seo.description` to that paragraph (optional; say which you did).
130
+ - **`sitemap: false`** has no sitemap-only switch: add `seo: { noindex: true }` only when the page should also leave search engines.
131
+ - **`toc: false`** → `mode: wide` (report), or drop. Another `layout` → a Blume `mode`.
132
+ - **Other keys** Nuxt Content kept and Blume rejects: drop and report each one, or declare it with `frontmatter.extend` if a component you wrote reads it.
133
+ - **Titles:** Docus renders frontmatter `title` in the page header, falling back to the first `# H1`, so a body H1 showed twice. The codemod moves a lone H1 into `title` and reports the rest.
134
+
135
+ ## MDC syntax
136
+
137
+ The codemod applies these rules; you need them for what it leaves. They're remark-mdc's behavior (checked against `remark-mdc@3.11.1`):
138
+
139
+ - **Block component:** an opening line `::name`, optionally followed by `{attributes}` and nothing else, then Markdown, then a closing line of colons only.
140
+ - Props are inline (`{key="value" flag .class #id}`) or a YAML block, from `---` right after the opener to the next `---`. A `:key='[…]'` holds JSON.
141
+ - Slots are `#name` lines inside the block.
142
+ - An inline component is `:name[text]{attrs}`; alone on a line, it renders as a block.
143
+ - **Pairing:**
144
+ - Every opener nests, even at the same colon count.
145
+ - A closer closes the innermost open block **whose opener has exactly as many colons**, together with anything still open inside it. It must not be indented deeper than that opener. Any other colon line, or a `::` mid-line, is text.
146
+ - An unclosed block runs to the end of its parent, and a closer inside a code fence is code.
147
+ - Check the old HTML for a literal `::` to see what the live site already showed broken.
148
+ - **MDX safety:** in a `.mdx` page, prose must escape `{` and `}` as `\{` and `\}`, and turn **any `<` followed by a non-space character** into `&lt;` (or `\<`) unless it opens a real tag: `useFetch<User>()`, `<script setup>` written as text, `<50 ms`, `<= 5`. Case matters as in JSX: `<input>` is HTML, but `<Input>` is a component, so a generic like `useTool<Input>()` is text. HTML comments become `{/* … */}`, `<https://…>` autolinks become links, and `<br>` becomes `<br />`. Code spans and `$$` math stay as written.
149
+ - **Code fences:** ` ```ts [file.ts]{2} ` → ` ```ts file.ts {2} ` (`title="Two words"` for a label with spaces). Blume reads ` ```ts [file.ts]{2} ` and a multi-word title as written too, the range highlighting its lines, so this only tidies. Leave MDC samples inside fences alone: they're documentation.
150
+
151
+ ## What the codemod leaves
152
+
153
+ - **Custom components:** `app/components/content/*.vue`, or a Nuxt module's. Read the SFC to see what it renders and which props and slots it reads, then convert it:
154
+ - to Markdown
155
+ - to a Blume component
156
+ - to a static `.astro` component registered in `components.ts`
157
+ - to the `.vue` file as an island in `islands/`, with `@astrojs/vue` and `vue` installed. `:items='["a"]'` becomes `items={["a"]}`, and named slots become props or children.
158
+
159
+ Report each one. A component that rendered nothing on the live site (an unknown tag) has nothing to convert.
160
+
161
+ - **Callouts with `to`:** the whole callout was a link. Add the link to its text when it isn't there already.
162
+ - **Card descriptions:** Nuxt UI's prose card renders neither a `description` prop nor a `#description` slot, so that text never showed. The codemod keeps it as the card's body and flags it: keep it, or delete it to match the old page.
163
+ - **Blocks with a slot the conversion doesn't read** (a card's `#footer`, a tab's extra slot) stay as written: convert them by hand.
164
+ - **`code-tree`** becomes a `<CodeGroup>` titled by file path. The tree view is gone; a `<FileTree>` before it can show the layout.
165
+ - **`prompt`:** Blume has no `windsurf` or `claude` action, and no prompt icon (the codemod reports both).
166
+ - **Prose about features that didn't carry over:** grep the content for the dropped MCP tool and prompt names, `docus`, `/.well-known/skills`, and `Studio`, and list every page that now says something false.
167
+
168
+ ## Landing page
169
+
170
+ `content/index.md` renders full-width, with no sidebar, title, or table of contents. Its `title` and `description` feed only the meta tags. The codemod leaves its `u-page-*` and custom components as written.
171
+
172
+ - **Markdown landing** → `index.mdx` with `mode: custom` (no title or sidebar rendered, so write the heading) and `sidebar.hidden: true` (Docus never listed it).
173
+ - **A designed landing**, or `app/pages/index.vue` → `pages/index.astro` on `PageLayout`.
174
+
175
+ | Nuxt UI | Blume, in the `mode: custom` page |
176
+ | --- | --- |
177
+ | `u-page-hero` (`title`, `description`, `links`, or slots) | `# Title`, a paragraph, and a line of Markdown links; the primary one can also be `navigation.cta` |
178
+ | `u-page-section` | `## Title`, a paragraph, and its features as cards |
179
+ | `u-page-feature`, `u-page-card` (`title`, `description`, `icon`, `to`) | `<Card title icon href>description</Card>` in `<CardGroup cols={3}>` |
180
+ | `u-page-grid`, `u-page-columns` | `<CardGroup cols={3}>` |
181
+ | `u-button`, `u-page-cta` | Markdown links; a heading and a paragraph |
182
+ | `u-page-logos`, decorative `div`s (glows, gradients) | a row of images, or drop (report) |
183
+
184
+ ## Icons
185
+
186
+ Blume resolves Lucide names only (`zap`, or `lucide:zap`); **anything else renders nothing.** The codemod converts `i-lucide-*` (aliases of renamed icons included) and reports the rest:
187
+
188
+ - **Other sets** (`i-heroicons-*`, `i-ph-*`): the closest Lucide name, checked at [lucide.dev/icons](https://lucide.dev/icons).
189
+ - **Brands** (`i-simple-icons-*`): Lucide has a few (`github`, `gitlab`, `slack`, `linkedin`, `youtube`), but **`i-simple-icons-x` is not Lucide's `x`, a close cross.** Otherwise, save the SVG under `public/` and pass its path (`icon="/icons/nuxt.svg"`), or drop it (report).
190
+ - **`i-custom-*`** (Docus's `app/assets/icons/*.svg`): copy the SVG to `public/icons/` and pass the path.
191
+
192
+ ## i18n
193
+
194
+ Docus i18n is `@nuxtjs/i18n` (`defaultLocale`, `locales: [{ code, name }]`) plus one `content/<code>/` folder per locale, and it forces the `prefix` strategy: every locale is under its code, the default included, and `/` redirects to the default.
195
+
196
+ - Move `content/<default>/` up to `content/` (a default-locale folder warns `BLUME_I18N_DEFAULT_LOCALE_FOLDER`). Keep the others as `content/<code>/`, and run the codemod on `content/`.
197
+ - Write `i18n: { defaultLocale, locales: [{ code, label }], hideDefaultLocalePrefix: false }`, `label` = `name`. That keeps every `/en/…` URL. Nothing serves `/` then, so add `{ from: "/", to: "/<default>", status: 302 }` as Docus did.
198
+ - **Regional codes are lowercase in Docus URLs** (`zh-TW` → `/zh-tw/…`): use the lowercase code as Blume's `code` and folder.
199
+ - Leave out any configured locale with no `content/<code>/` folder: Docus skipped it.
200
+
201
+ ## URLs and redirects
202
+
203
+ Docus routes a page by its path under `content/`. It strips an `N.` prefix from each segment, drops `index`, and slugifies (lowercase, spaces → `-`). Blume strips `N.`, `N-`, and `N_` prefixes and `index` the same way, so **most URLs carry over unchanged**, with no trailing slash or `.html` to redirect. The codemod reports the exceptions:
204
+
205
+ - **A dash prefix** (`01-intro.md`) stays in the Docus URL and Blume strips it: pin `slug` or redirect.
206
+ - **Uppercase letters or spaces** were lowercased by Docus: rename the file.
207
+ - **A version name** (`1.x`, `2.0`) stays whole in Docus: pin `slug`.
208
+ - **`_` files** are pages in Docus and excluded by Blume: rename them.
209
+
210
+ **Redirects:**
211
+
212
+ - **`routeRules` → `redirects`.** A string `redirect` is a 307 in Nitro (`status: 307`); `{ to, statusCode }` → `{ to, status }`. A `/**` key strips its base: `'/old/**': { redirect: '/new/**' }` → `{ from: "/old/:path*", to: "/new/:path*" }`.
213
+ - **`/raw/<route>.md`** is the Markdown copy of each page, which Docus links from `llms.txt`. Blume serves it at `/<route>.md`, so one rule covers it: `{ from: "/raw/:path*", to: "/:path*" }`. A `routeRules` redirect between two `/raw/…md` paths should target Blume's copy directly (`/raw/old.md` → `/new.md`), or it chains through that rule.
214
+ - **Agent skills:** a `skills/` folder Docus published under `/.well-known/skills/` → `agents.skills: "./skills"`. Blume publishes them under `/.well-known/agent-skills/`, a skill with references as one `.tar.gz`. Redirect the old index to the new one, and report the change. Links can name the new index (`/.well-known/agent-skills/index.json`): `blume validate` accepts the files Blume generates.
215
+ - **Anchors:** MDC slugs headings like Blume (`github-slugger`), then collapses repeated dashes, trims edge dashes, and prefixes a leading digit with `_`. `## Name & Title` is `#name-title` in Docus and `#name--title` in Blume; `## 1. Install` is `#_1-install`. After the first build, run `node <skill>/scripts/pin-heading-ids.mjs --old <old build>`, `--write`, and rebuild until it reports 0. On a server build (`vercel()`, `node()`) it reads the pages from `dist/client` itself.
216
+ - **Verify** every old URL: a built page, or a redirect `from` whose target is built (SKILL.md Verification).
217
+
218
+ ## Deploy and teardown
219
+
220
+ - **Keeping the assistant or the MCP server** needs server output: `deployment: vercel()` (or `node()`, `netlify()`, `cloudflare()`). `blume preview` can't serve a `vercel()` build, so check it with `blume dev`, then a preview deployment.
221
+ - **Vercel with `vercel()`:**
222
+ - The build writes the Build Output API folder `.vercel/output` in the Blume project, not `dist/`.
223
+ - In the project's `vercel.json`, set `"framework": null` and the install and build commands (`references/monorepo.md` §4 for a workspace), and **no `outputDirectory`**. `framework: null` overrides a project still set to the Nuxt preset.
224
+ - In the dashboard, set the Root Directory to the docs package and Node to 22.19 or later. Set `AI_GATEWAY_API_KEY` unless the project relies on Vercel's OIDC token.
225
+ - With Turborepo, add `.vercel/output/**` to the docs' build outputs.
226
+ - List the dashboard settings as steps for the user.
227
+ - **Dependencies:** remove `docus`, `nuxt`, the `@nuxt/*`/`@nuxtjs/*` modules, `nuxt-studio`, `better-sqlite3`, `@iconify-json/*`, `tailwindcss` (if only the theme used it), mapped analytics packages, `@nuxt/eslint-config`, and `vue` (keep it with `@astrojs/vue` for islands). Keep what the site's tests or scripts still import.
228
+ - **Scripts:** `dev`/`build`/`generate`/`preview` → `blume dev`/`build`/`build`/`preview`. **Delete `postinstall: nuxt prepare`**, which fails once Nuxt is gone.
229
+ - **Files:** after harvesting, delete `nuxt.config.ts`, `app/`, `content.config.ts`, `server/` (report its routes), and a Nuxt `eslint.config.mjs`. The codemod leaves a `.navigation.yml` whose keys `meta.ts` can't hold.
230
+ - **`tsconfig.json`:** a Nuxt one points into `.nuxt/`, and `blume build` then fails with `BLUME_TSCONFIG_EXTENDS`. Delete or replace it.
231
+ - **`.gitignore`:** in the docs package's own file, `.nuxt`, `.output`, `.data` → `.blume`, `dist`.
232
+ - **CI:** grep for `nuxt generate`, `nuxi`, `.output/public`, and `--extends docus`.
233
+
234
+ ## Docus 1.x (`@nuxt-themes/docus`)
235
+
236
+ Written from `@nuxt-themes/docus@1.15.1` and `@nuxt-themes/elements@0.9.5`. Everything above applies (run the codemod for fences, frontmatter, and the H1), with these differences:
237
+
238
+ - **Config** is `app.config.ts` → `docus: { … }`:
239
+ - `title`, `description` → same keys
240
+ - `url` → `deployment.site`
241
+ - `socials` → `footer.socials`. Values are handles (`twitter: '@nuxt_js'`, `github: 'owner/repo'`), so build each URL.
242
+ - `github: { owner, repo, branch, dir }` → `github`
243
+ - `header.logo`/`title` → `logo`/`title`
244
+ - `aside.collapsed` → `navigation.sidebar.display: "group"`
245
+ - `footer.textLinks`/`iconLinks` → `footer.links`
246
+ - everything else → drop (report)
247
+ - **Folders** use `_dir.yml` (`title`, `icon`, `navigation: false`, `navigation.redirect` → a redirect from the folder's route).
248
+ - **Titles** are the body `# H1` (the codemod moves it).
249
+ - **Frontmatter:**
250
+ - `layout: page` → `mode: custom` or `mode: center`
251
+ - `aside: false` → `mode: center`
252
+ - `toc: false` → `mode: wide`
253
+ - `redirect` → a redirect
254
+ - `bottom`, `header`, `fluid`, `main` → drop
255
+ - **Components** (Nuxt Elements, left as written and reported):
256
+ - `::alert{type}` → that directive (`primary` → `tip`)
257
+ - `::callout{type}` with `#summary`/`#content` → the directive holding the summary, then `<Expandable title="Details">` around the content
258
+ - `::list{type}` → a plain list
259
+ - `::badge{type}` → `<Badge>`
260
+ - `::button-link{href}` → a link
261
+ - `::terminal{content}` → a `bash` fence
262
+ - `::card-grid` + `::card{icon}` with `#title`/`#description` → `<CardGroup>` + `<Card>`
263
+ - `::block-hero` → the landing page
264
+ - `::code-block{label}` → a titled fence in `<CodeGroup>`
265
+ - `::video-player{src}` → `<video>`
266
+ - `::sandbox`, `::props`, `::copy-button`, `:ellipsis` → drop (report)
267
+ - **URLs:** Nuxt Content 2 routes the same way, so check the old sitemap for trailing slashes and pin heading ids as above.
268
+
269
+ ## Dropped — report these
270
+
271
+ Report each of these, and anything else you drop:
272
+
273
+ - **Theme:**
274
+ - the brand color, when it was Docus's default emerald
275
+ - Nuxt UI overrides and theme CSS that doesn't map
276
+ - a custom `seo.titleTemplate`, `seo.schema`, and the TOC title
277
+ - logo extras, and socials with no Blume platform
278
+ - a forced color mode (the toggle stays)
279
+ - `navigation.sub: 'aside'` anchors
280
+ - a header button that showed only on the landing page (now site-wide)
281
+ - **Navigation:** text-order sorting (`10.` before `2.`), and pages that sorted after a folder.
282
+ - **Frontmatter:**
283
+ - `links` (now `related` cards)
284
+ - `sitemap: false`, `toc`, `layout`, and other unknown keys
285
+ - derived descriptions
286
+ - **Components:**
287
+ - callout `to` links, callout colors (mapped to the nearest type; `<Callout color>` takes a CSS color when a tint matters), and card colors
288
+ - prompt icons and Windsurf/Claude actions
289
+ - the `code-tree` file browser and `code-preview` live rendering
290
+ - Nuxt UI landing decorations
291
+ - custom and module components
292
+ - brand icons with no SVG
293
+ - **Server features:**
294
+ - the site's own MCP tools and prompts
295
+ - server routes and `@vercel/speed-insights`
296
+ - the assistant, if not kept
297
+ - old `/.well-known/skills/` file URLs, and pages whose prose describes any of these
298
+ - **Anchors:** old ids that were already broken.
@@ -23,10 +23,10 @@ Read `themeConfig`, `presets`, and `plugins`:
23
23
  | `themeConfig.colorMode.defaultMode` | `theme.mode` (`respectPrefersColorScheme: true` → `"system"`) |
24
24
  | `themeConfig.prism.theme` / `.darkTheme` | `markdown.code.theme: { light, dark }` (map Prism theme names to Shiki themes, e.g. `github`/`github-dark`) |
25
25
  | `themeConfig.metadata` / `themeConfig.image` | per-page `seo` frontmatter / `seo.og`; report what doesn't fit |
26
- | `url` + `baseUrl` | **`url` → `deployment.site`**, unless the target host is Vercel, Netlify, or Cloudflare Pages, which Blume auto-detects (see SKILL.md — GitHub Pages, a common Docusaurus host, is not one of them); `baseUrl` (when not `/`) → `deployment: { base: "/…" }`, or the `base` option of a host adapter (`vercel({ base })`) when the site also needs one |
26
+ | `url` + `baseUrl` | **`url` → `deployment.site`**, unless the target host is Vercel or Netlify, which Blume auto-detects (see SKILL.md — GitHub Pages, a common Docusaurus host, is not one of them); `baseUrl` (when not `/`) → `deployment: { base: "/…" }`, or the `base` option of a host adapter (`vercel({ base })`) when the site also needs one |
27
27
  | preset `docs.routeBasePath` — **including the default!** | Docusaurus serves docs at **`/docs/…` by default**; the "map only declared fields" rule does **not** apply here because the _URLs_ are load-bearing. Either keep them with top-level **`basePath: "/docs"`** (invisible to the sidebar), or intentionally move to root and emit a `redirects` entry per page. Decide explicitly and say which. (`routeBasePath: '/'` = docs-only mode — nothing to do.) |
28
28
  | preset `docs.editUrl` | `github` (owner/repo/branch; a path after the branch → `github.dir`; **an origin other than `https://github.com` → `github.host`** — a GitHub Enterprise repo's edit links and header mark point at the public site without it) |
29
- | `themeConfig.footer` | `footer.links`, one row of `{ label, href }`: every column's items flatten into it in order (an item's `to`, a route, is its `href`); column `title`s, `copyright`, `logo`, and `style` drop (report), or keep the whole footer as a Footer override (`defineComponents` layout slot) |
29
+ | `themeConfig.footer` | `footer.links`, one row of `{ label, href }`: every column's items flatten into it in order (an item's `to`, a route, is its `href`); `copyright` (HTML) → `footer.copyright`, plain text: strip the tags, decode entities (`&copy;` → `©`), keep a `${new Date().getFullYear()}` template literal as written, and drop a "Built with Docusaurus." credit; a link inside it loses its URL (report, or add it to `footer.links`); a translated one in `i18n/<locale>/docusaurus-theme-classic/footer.json` → a locale map; column `title`s, `logo`, and `style` drop (report), or keep the whole footer as a Footer override (`defineComponents` layout slot) |
30
30
  | `themeConfig.announcementBar` | `banner` (`{ content, dismissible, id }` — `isCloseable` → `dismissible`; colors drop) |
31
31
  | `i18n.locales` / `defaultLocale` | `i18n` — translated files live at `i18n/<locale>/docusaurus-plugin-content-docs/current/…`; move them to `<locale>/…` under `content.root` |
32
32
  | `themeConfig.algolia` | drop — Blume ships built-in search (Orama); remove the Algolia dep. To keep the index, `search: algolia({ appId, apiKey, indexName })` from `blume/search` (the DocSearch `apiKey` is the search-only key) |
@@ -59,11 +59,11 @@ A Docusaurus `blog/` → Blume `type: blog` pages. **Dates come from filenames/f
59
59
  ## Content & components
60
60
 
61
61
  - **`.md` vs `.mdx` — both majors need renames, for opposite reasons.** Blume parses `.md` as plain Markdown: no directives, no JSX, no `$$` math, no mermaid/package-install fences. **v3** treats `.md` as MDX (so a `.md` with imports/JSX/`{}` renders them as literal text in Blume); **v2** content is looser MDX v1. Rule: **rename any `.md` that contains admonitions, JSX, imports, or math to `.mdx`** — for typical Docusaurus repos that is most files.
62
- - **Admonitions are directives — but check the version.** v3: `:::note`, `:::tip`, `:::info`, `:::warning`, `:::danger` pass through; `:::caution` → `:::warning` (or rely on Blume's alias); titles `:::note[Title]` work. **v2:** titles are space-separated (`:::note Your Title`) — rewrite to brackets or the title is silently lost; and v2's `:::warning` rendered **red/danger** — audit whether it should become `:::danger`.
62
+ - **Admonitions are directives — but check the version.** v3: `:::note`, `:::tip`, `:::info`, `:::warning`, `:::danger` pass through; `:::caution` → `:::warning` (or rely on Blume's alias); titles `:::note[Title]` work. **v2:** titles are space-separated (`:::note Your Title`) — rewrite to brackets or the title is lost (`BLUME_DIRECTIVE_OPENING_TEXT` names each one); and v2's `:::warning` rendered **red/danger** — audit whether it should become `:::danger`.
63
63
  - **Tabs:** `<Tabs>`/`<TabItem label="…" value="…">` → `<Tabs>`/`<Tab title="…">`. Drop `groupId`/`queryString`/`value`; strip the `@theme/Tabs` imports.
64
64
  - **Theme JSX in content:** `<DocCardList/>` (standard on category index pages) → delete it and set `directory: "card"` in that folder's `meta.ts`: Blume then lists the folder's pages as cards below the `index` page's content and keeps the list current (nested folders inherit the setting; give one `directory: "none"` to opt out). A hand-written `CardGroup` is only for a curated subset; `<TOCInline/>` → drop (report); `<CodeBlock>` JSX → a fenced code block; `<Admonition>` → the matching directive; `<details>`/`<summary>` → `<Accordion>`/`<AccordionItem>` or leave as raw HTML.
65
65
  - **`@theme/*` / `@site/*` imports** — strip `@theme/*` (Blume injects components globally); rewrite `@site/` asset/module paths to `/public` URLs or inline. **MDX partials** (`_partial.mdx` imports) → inline the partial's body (Blume's default `**/_*` exclude already hides the partial files themselves).
66
- - **Code blocks:** `title="file.js"` → works as-is; `showLineNumbers` → `lineNumbers`; **magic comments** (`// highlight-next-line`, `highlight-start`/`end`) → `{ranges}` or `// [!code highlight]` — unconverted they ship as literal comments in every sample; ` ```bash npm2yarn ` → ` ```package-install `.
66
+ - **Code blocks:** `title="file.js"` → works as-is; `showLineNumbers` → `lineNumbers` (`BLUME_CODE_FENCE_OPTION` flags any left); **magic comments** (`// highlight-next-line`, `highlight-start`/`end`) → `{ranges}` or `// [!code highlight]` — unconverted they ship as literal comments in every sample; ` ```bash npm2yarn ` → ` ```package-install `.
67
67
  - **MDX v1 (v2 sources) pitfalls:** unescaped `<`/`{` in prose, HTML comments `<!-- -->` (→ `{/* */}`), string `style="…"` attributes (→ objects). Fix as build errors surface.
68
68
 
69
69
  ## Frontmatter
@@ -95,4 +95,4 @@ Remove `@docusaurus/*` and Algolia deps; delete `docusaurus.config.*`, `sidebars
95
95
 
96
96
  ## Dropped — report these
97
97
 
98
- Custom/swizzled theme components (layout slots or `blume eject`), the footer's copyright, logo, and HTML items, the blog's generated tag, author, and archive pages, Algolia config, `sidebar_custom_props` and the other dropped frontmatter keys, `createRedirects` functions (→ host rules), React pages under `src/pages/`, `<TOCInline>`, per-category `className`/`customProps`, and any `@theme/*` component with no Blume equivalent.
98
+ Custom/swizzled theme components (layout slots or `blume eject`), the footer's logo and HTML items (and any link inside its copyright), the blog's generated tag, author, and archive pages, Algolia config, `sidebar_custom_props` and the other dropped frontmatter keys, `createRedirects` functions (→ host rules), React pages under `src/pages/`, `<TOCInline>`, per-category `className`/`customProps`, and any `@theme/*` component with no Blume equivalent.