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,491 @@
1
+ import { slug } from "github-slugger";
2
+ import type { Nodes } from "mdast";
3
+ import { markdownToMdast } from "satteri";
4
+
5
+ import { MARKDOWN_BODY_FEATURES } from "../markdown/features.ts";
6
+ import { nextFenceState } from "./code-fences.ts";
7
+ import type { FenceState } from "./code-fences.ts";
8
+ import {
9
+ ATTRIBUTE,
10
+ HTML_COMMENT,
11
+ INLINE_CODE,
12
+ strippedLineOffset,
13
+ trailingCurlyMarker,
14
+ } from "./sources/normalize.ts";
15
+ import type { SourceEntry } from "./sources/types.ts";
16
+ import type { Diagnostic, PageRecord } from "./types.ts";
17
+
18
+ /**
19
+ * Syntax from other docs tools that Blume doesn't read, found in a page body
20
+ * outside code and comments. Each one either ships as literal text on a green
21
+ * build or, in `.mdx`, breaks the page only once it renders, so it's reported
22
+ * at its line instead:
23
+ *
24
+ * - `BLUME_TEMPLATE_TAG`: a Liquid or Markdoc tag (`{% include x %}`) or a
25
+ * Liquid output (`{{ site.title }}`) in `.md`. A `{{name}}` that could be a
26
+ * Blume variable is left to the variables pass.
27
+ * - `BLUME_WIKILINK_UNSUPPORTED`: a wiki link (`[[Page]]`, `[[Text|Page]]`)
28
+ * to one of the site's pages, or any with a `|`. A bare `[[Name]]` that
29
+ * names no page is left alone: JavaScript docs write the spec's internal
30
+ * slots that way (`[[Prototype]]`). The Obsidian source rewrites its own
31
+ * links before this sees them.
32
+ * - `BLUME_MDC_SYNTAX`: a Nuxt Content (MDC) block (`::callout` … `::`) or
33
+ * inline component (`:badge[New]{color="primary"}`).
34
+ * - `BLUME_MDX_ATTRIBUTE_LIST`: an attribute list (`{ width="300" }`,
35
+ * `{ .class }`) in `.mdx`, which MDX reads as JavaScript.
36
+ * - `BLUME_MD_ATTRIBUTE_LIST`: an attribute list in `.md`, kramdown's IAL
37
+ * (`{: .note }`) or MkDocs' `attr_list`, which shows as written. A heading's
38
+ * `{#id}` marker is left to `BLUME_MD_CURLY_ANCHOR`.
39
+ * - `BLUME_MDX_UNCLOSED_ELEMENT`: a void element written as HTML (`<img …>`)
40
+ * in `.mdx`, which MDX reads as an unclosed JSX element.
41
+ */
42
+
43
+ /** A problem at a line of the scanned text, before it's placed in a file. */
44
+ interface Finding {
45
+ code: string;
46
+ column: number;
47
+ line: number;
48
+ message: string;
49
+ suggestion: string;
50
+ }
51
+
52
+ /** A match's position in the scanned text. */
53
+ interface Site {
54
+ column: number;
55
+ line: number;
56
+ }
57
+
58
+ /** The route of the page a wiki link names, if one does. */
59
+ type PageRoute = (name: string) => string | undefined;
60
+
61
+ /** What a check reads: the masked body, where its lines start, and the site's pages. */
62
+ interface Scan {
63
+ pageRoute: PageRoute;
64
+ starts: number[];
65
+ text: string;
66
+ }
67
+
68
+ /** The site's pages by the names a wiki link can call them (see {@link wikiKey}). */
69
+ export type PageNameIndex = ReadonlyMap<string, readonly PageRecord[]>;
70
+
71
+ /** A backslash escapes the character after it, so only an even run is text. */
72
+ const UNESCAPED = String.raw`(?<=(?:^|[^\\])(?:\\\\)*)`;
73
+
74
+ // Liquid and Markdoc tags, and Liquid output, on one line.
75
+ const TEMPLATE_TAG = new RegExp(String.raw`${UNESCAPED}\{%[^\n]*?%\}`, "gu");
76
+ const TEMPLATE_OUTPUT = new RegExp(
77
+ String.raw`${UNESCAPED}\{\{(?<expression>[^\n{}]*)\}\}`,
78
+ "gu"
79
+ );
80
+ // What a Blume variable reference holds (see `variables.ts`).
81
+ const VARIABLE_REFERENCE = /^\s*[\w-]+\s*$/u;
82
+
83
+ // `[[Page]]` or `[[Text|Page]]`. An embed (`![[x]]`) and a link whose label
84
+ // is in brackets (`[[x]](/url)`, `[[x]][ref]`) aren't wiki links.
85
+ const WIKILINK = /(?<![!\\[])\[\[(?<inner>[^[\]\n]+)\]\](?![([:])/gu;
86
+
87
+ // An MDC block component opener on its own line: two colons, a name, and an
88
+ // optional `[slot]` and `{props}`. Three colons is a `:::` container.
89
+ const MDC_BLOCK =
90
+ /^[\t ]*(?<written>::(?<name>[a-z][\w-]*)(?:\[[^\]\n]*\])?(?:\{[^}\n]*\})?)[\t ]*$/gimu;
91
+ // An inline MDC component: a colon and a name, then a `[slot]`, `{props}`,
92
+ // or both. A word character before the colon (`og:image`, `16:9`) is prose.
93
+ // Two colons is a block opener that shares its line with text, which no
94
+ // parser reads as a block.
95
+ const MDC_INLINE =
96
+ /(?<![\w:\\])(?<written>:{1,2}(?<name>[a-z][\w-]*)(?:\[[^\]\n]*\](?:\{[^}\n]*\})?|\{[^}\n]*\}))/giu;
97
+
98
+ // A brace-wrapped attribute list (`{ width="300" }`, kramdown's `{: .note }`).
99
+ // After `=` it's a JSX attribute's value (`style={…}`).
100
+ const ATTRIBUTE_LIST = new RegExp(
101
+ String.raw`(?<![=\\])\{:?[\t ]*${ATTRIBUTE}(?:[\t ]+${ATTRIBUTE})*[\t ]*\}`,
102
+ "gu"
103
+ );
104
+ // A directive's `{attributes}` (`:::note{title="x"}`, `::name{a="b"}`), which
105
+ // the directive parser reads before MDX can.
106
+ const DIRECTIVE_ATTRIBUTES = /:+[a-z][\w-]*(?:\[[^\]\n]*\])?\{[^}\n]*\}/giu;
107
+ const ATX_HEADING = /^ {0,3}#{1,6}[\t ]/u;
108
+ const SETEXT_UNDERLINE = /^ {0,3}(?:=+|-+)[\t ]*$/u;
109
+
110
+ // An HTML void element not closed with `/>`. Attribute values may hold `/`
111
+ // and `>`, so quoted ones are skipped whole.
112
+ const VOID_ELEMENT = new RegExp(
113
+ String.raw`${UNESCAPED}<(?<tag>area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)(?=[\s/>])(?:[^<>"'/]|"[^"]*"|'[^']*'|\/(?!>))*>`,
114
+ "gu"
115
+ );
116
+
117
+ // The cheap tests that gate the scans both formats share.
118
+ const SUSPECT_WIKILINK = /\[\[/u;
119
+ const SUSPECT_MDC = /(?:^|[^\w:]):{1,2}[a-z][\w-]*[[{]|^[\t ]*::[a-z]/imu;
120
+
121
+ // An MDX comment, `{/* … */}`, which renders nothing.
122
+ const MDX_COMMENT = /\{\s*\/\*[\s\S]*?\*\/\s*\}/gu;
123
+
124
+ // A link or image destination in angle brackets, after `](` or a reference
125
+ // definition's `]:` (`![shot](<img/a b.png>)`, `[ref]: <br/x.png>`). Both
126
+ // formats read it as a URL, never as HTML, though it can open with a tag name.
127
+ const ANGLE_DESTINATION =
128
+ /(?<=\]\([\t ]*|^ {0,3}\[[^\]\n]+\]:[\t ]*)<[^<>\n]*>/gmu;
129
+
130
+ /** `text` with every character but line breaks turned into a space. */
131
+ const blank = (text: string): string => text.replaceAll(/[^\n]/gu, " ");
132
+
133
+ /**
134
+ * An `.md` body with its code (fenced, indented, and inline) and HTML
135
+ * comments blanked, exactly as the renderer's parser reads them.
136
+ */
137
+ const maskMarkdown = (text: string): string => {
138
+ const ranges: [number, number][] = [];
139
+ const walk = (node: Nodes): void => {
140
+ const start = node.position?.start.offset ?? 0;
141
+ const end = node.position?.end.offset ?? start;
142
+ if (
143
+ node.type === "code" ||
144
+ node.type === "inlineCode" ||
145
+ (node.type === "html" && node.value.startsWith("<!--"))
146
+ ) {
147
+ ranges.push([start, end]);
148
+ } else if ("children" in node) {
149
+ for (const child of node.children) {
150
+ walk(child);
151
+ }
152
+ }
153
+ };
154
+ walk(
155
+ markdownToMdast(text, {
156
+ features: { ...MARKDOWN_BODY_FEATURES, gfm: true },
157
+ })
158
+ );
159
+ let masked = "";
160
+ let cursor = 0;
161
+ for (const [start, end] of ranges) {
162
+ masked += text.slice(cursor, start) + blank(text.slice(start, end));
163
+ cursor = end;
164
+ }
165
+ return masked + text.slice(cursor);
166
+ };
167
+
168
+ /**
169
+ * An `.mdx` body with fenced code, inline code, comments, and angle-bracket
170
+ * link destinations blanked, line by line rather than parsed: the problems
171
+ * this looks for are what keeps MDX from parsing a page. MDX has no indented
172
+ * code blocks.
173
+ */
174
+ const maskMdx = (text: string): string => {
175
+ let fence: FenceState = null;
176
+ return text
177
+ .split("\n")
178
+ .map((line) => {
179
+ const next = nextFenceState(line, fence);
180
+ const inFence = fence !== null || next !== null;
181
+ fence = next;
182
+ return inFence ? blank(line) : line.replaceAll(INLINE_CODE, blank);
183
+ })
184
+ .join("\n")
185
+ .replaceAll(MDX_COMMENT, blank)
186
+ .replaceAll(HTML_COMMENT, blank)
187
+ .replaceAll(ANGLE_DESTINATION, blank);
188
+ };
189
+
190
+ /** Where each line of `text` starts. */
191
+ const lineStartsOf = (text: string): number[] => {
192
+ const starts = [0];
193
+ for (
194
+ let index = text.indexOf("\n");
195
+ index !== -1;
196
+ index = text.indexOf("\n", index + 1)
197
+ ) {
198
+ starts.push(index + 1);
199
+ }
200
+ return starts;
201
+ };
202
+
203
+ /** The 1-based line and column of offset `at`. */
204
+ const siteOf = (starts: readonly number[], at: number): Site => {
205
+ const line = starts.findLastIndex((start) => start <= at);
206
+ return { column: at - (starts[line] ?? 0) + 1, line: line + 1 };
207
+ };
208
+
209
+ const templateFindings = ({ starts, text }: Scan): Finding[] => [
210
+ ...[...text.matchAll(TEMPLATE_TAG)].map((match) => ({
211
+ ...siteOf(starts, match.index),
212
+ code: "BLUME_TEMPLATE_TAG",
213
+ message: `\`${match[0]}\` is a Liquid or Markdoc tag, which Blume doesn't run, so the page shows it as written.`,
214
+ suggestion:
215
+ "Rewrite what it produced as Markdown or a Blume component, or remove it. To show it as text, put it in inline code.",
216
+ })),
217
+ ...[...text.matchAll(TEMPLATE_OUTPUT)]
218
+ .filter((match) => !VARIABLE_REFERENCE.test(match.groups?.expression ?? ""))
219
+ .map((match) => ({
220
+ ...siteOf(starts, match.index),
221
+ code: "BLUME_TEMPLATE_TAG",
222
+ message: `\`${match[0]}\` is a Liquid output tag, which Blume doesn't fill in, so the page shows it as written.`,
223
+ suggestion:
224
+ "Write the value in its place, or define it under `variables` in blume.config.ts and write `{{name}}`. To show it as text, put it in inline code.",
225
+ })),
226
+ ];
227
+
228
+ /**
229
+ * A page name the way wiki links match it: GitHub wikis link `[[Getting
230
+ * Started]]` to `Getting-Started.md`, and case doesn't count.
231
+ */
232
+ const wikiKey = (name: string): string =>
233
+ name
234
+ .trim()
235
+ .toLowerCase()
236
+ .replaceAll(/[\s_-]+/gu, "-");
237
+
238
+ /** Index pages by file name and title, for {@link syntaxDiagnostics}. */
239
+ export const indexPageNames = (pages: readonly PageRecord[]): PageNameIndex => {
240
+ const index = new Map<string, PageRecord[]>();
241
+ for (const page of pages) {
242
+ const stem = (page.source.ref.split("/").at(-1) ?? "").replace(
243
+ /\.mdx?$/iu,
244
+ ""
245
+ );
246
+ for (const key of new Set([wikiKey(stem), wikiKey(page.title)])) {
247
+ const named = index.get(key);
248
+ if (named) {
249
+ named.push(page);
250
+ } else {
251
+ index.set(key, [page]);
252
+ }
253
+ }
254
+ }
255
+ return index;
256
+ };
257
+
258
+ /** One side of a wiki link's `|`, and the route it names, if any. */
259
+ interface WikiPart {
260
+ route?: string;
261
+ text: string;
262
+ }
263
+
264
+ /** A wiki link side's route, with a `#heading` as the anchor it slugs to. */
265
+ const partRoute = (text: string, pageRoute: PageRoute): string | undefined => {
266
+ const [name = "", heading] = text.split("#", 2);
267
+ const route = pageRoute(name);
268
+ return route && heading ? `${route}#${slug(heading)}` : route;
269
+ };
270
+
271
+ const wikilinkFindings = ({ pageRoute, starts, text }: Scan): Finding[] =>
272
+ [...text.matchAll(WIKILINK)].flatMap((match) => {
273
+ const parts: WikiPart[] = (match.groups?.inner ?? "")
274
+ .split("|")
275
+ .map((part) => ({
276
+ route: partRoute(part, pageRoute),
277
+ text: part.trim(),
278
+ }));
279
+ const target = parts.find((part) => part.route !== undefined);
280
+ if (!target && parts.length === 1) {
281
+ return [];
282
+ }
283
+ const label = parts.find((part) => part !== target) ?? target;
284
+ return [
285
+ {
286
+ ...siteOf(starts, match.index),
287
+ code: "BLUME_WIKILINK_UNSUPPORTED",
288
+ message: `\`${match[0]}\` is a wiki link, which Blume reads only in an Obsidian vault source, so the page shows it as written${target ? ` instead of linking to ${target.route}` : ""}.`,
289
+ suggestion: target
290
+ ? `Rewrite it as a Markdown link: \`[${label?.text}](${target.route})\`.`
291
+ : "Rewrite it as a Markdown link to the page, like `[Text](./page.md)`.",
292
+ },
293
+ ];
294
+ });
295
+
296
+ const mdcFindings = ({ starts, text }: Scan): Finding[] => {
297
+ const blocks = [...text.matchAll(MDC_BLOCK)].map((match) => ({
298
+ at: match.index + match[0].indexOf("::"),
299
+ written: match.groups?.written,
300
+ }));
301
+ const opened = new Set(blocks.map(({ at }) => at));
302
+ return [
303
+ ...blocks.map(({ at, written }) => ({
304
+ ...siteOf(starts, at),
305
+ code: "BLUME_MDC_SYNTAX",
306
+ message: `\`${written}\` opens a Nuxt Content (MDC) block component, which Blume doesn't render, so the page shows its \`::\` lines as text.`,
307
+ suggestion:
308
+ "Rewrite the block as the matching Blume component (`<Callout>`, `<Card>`, `<Tabs>`, …) or a `:::` callout.",
309
+ })),
310
+ ...[...text.matchAll(MDC_INLINE)]
311
+ .filter((match) => !opened.has(match.index))
312
+ .map((match) => ({
313
+ ...siteOf(starts, match.index),
314
+ code: "BLUME_MDC_SYNTAX",
315
+ message: `\`${match.groups?.written}\` is an inline component in Nuxt Content (MDC) syntax, which Blume doesn't render, so the page shows it as written.`,
316
+ suggestion:
317
+ "Rewrite it as the matching Blume component (`<Badge>`, `<Icon>`, …) or as plain text.",
318
+ })),
319
+ ];
320
+ };
321
+
322
+ /**
323
+ * Whether the list at `column` of `line` is a heading's trailing `{#id}`
324
+ * marker, which `BLUME_MDX_CURLY_ANCHOR` or `BLUME_MD_CURLY_ANCHOR` already
325
+ * reports (an unspaced `{#id}` pins the anchor in `.md`). In `.mdx`, one that
326
+ * also sets classes or attributes (`{#id .wide}`) is reported here instead.
327
+ */
328
+ const isHeadingMarker = (
329
+ lines: readonly string[],
330
+ site: Site,
331
+ written: string,
332
+ format: "md" | "mdx"
333
+ ): boolean => {
334
+ const line = lines[site.line - 1] ?? "";
335
+ const heading =
336
+ ATX_HEADING.test(line) || SETEXT_UNDERLINE.test(lines[site.line] ?? "");
337
+ const marker = trailingCurlyMarker(line);
338
+ return (
339
+ heading &&
340
+ marker?.marker === written &&
341
+ marker.index === site.column - 1 &&
342
+ (format === "md" || !marker.attributes)
343
+ );
344
+ };
345
+
346
+ /** What an attribute list does to a page in each format, and the way out. */
347
+ const ATTRIBUTE_LIST_PROBLEMS = {
348
+ md: {
349
+ code: "BLUME_MD_ATTRIBUTE_LIST",
350
+ effect: "which Blume doesn't read, so the page shows it as written",
351
+ suggestion:
352
+ 'Remove it. To keep the attributes, write the element as HTML (`<img src="…" width="300">`), or pin a heading\'s anchor with `[#id]`. To show the braces as text, put them in inline code.',
353
+ },
354
+ mdx: {
355
+ code: "BLUME_MDX_ATTRIBUTE_LIST",
356
+ effect:
357
+ "which MDX reads as a JavaScript expression, so the page fails to build",
358
+ suggestion:
359
+ 'Remove it. To keep the attributes, write the element as JSX (`<img src="…" width="300" />`), or pin a heading\'s anchor with `[#id]`. To show the braces as text, escape them: `\\{…\\}`.',
360
+ },
361
+ } as const;
362
+
363
+ /**
364
+ * The attribute lists in a body in `format`. A directive's own attributes and
365
+ * a `{{ }}` variable or Liquid output aren't one.
366
+ */
367
+ const attributeListFindings =
368
+ (format: "md" | "mdx") =>
369
+ ({ starts, text }: Scan): Finding[] => {
370
+ const { code, effect, suggestion } = ATTRIBUTE_LIST_PROBLEMS[format];
371
+ const scanned = text
372
+ .replaceAll(DIRECTIVE_ATTRIBUTES, blank)
373
+ .replaceAll(TEMPLATE_OUTPUT, blank);
374
+ const lines = text.split("\n");
375
+ return [...scanned.matchAll(ATTRIBUTE_LIST)]
376
+ .map((match) => ({
377
+ site: siteOf(starts, match.index),
378
+ written: match[0],
379
+ }))
380
+ .filter(
381
+ ({ site, written }) => !isHeadingMarker(lines, site, written, format)
382
+ )
383
+ .map(({ site, written }) => ({
384
+ ...site,
385
+ code,
386
+ message: `\`${written}\` is an attribute list, ${effect}.`,
387
+ suggestion,
388
+ }));
389
+ };
390
+
391
+ // An attribute list's opening brace and first attribute.
392
+ const SUSPECT_ATTRIBUTE_LIST = /\{:?[\t ]*(?:[#.][A-Za-z_-]|[\w-]+=)/u;
393
+
394
+ const voidElementFindings = ({ starts, text }: Scan): Finding[] =>
395
+ [...text.matchAll(VOID_ELEMENT)]
396
+ .filter(
397
+ (match) =>
398
+ !text
399
+ .slice(match.index + match[0].length)
400
+ .trimStart()
401
+ .startsWith(`</${match.groups?.tag}>`)
402
+ )
403
+ .map((match) => ({
404
+ ...siteOf(starts, match.index),
405
+ code: "BLUME_MDX_UNCLOSED_ELEMENT",
406
+ message: `\`<${match.groups?.tag}>\` isn't closed. MDX reads HTML as JSX, where it needs \`/>\`: unclosed, it fails the page, or in an included file it takes in everything after it.`,
407
+ suggestion: `Close it: \`<${match.groups?.tag} … />\`.`,
408
+ }));
409
+
410
+ /** A scan over masked text, and the cheap check that says it's worth running. */
411
+ interface Check {
412
+ find: (scan: Scan) => Finding[];
413
+ suspect: RegExp;
414
+ }
415
+
416
+ /** The checks each body format runs. */
417
+ const CHECKS: Record<"md" | "mdx", Check[]> = {
418
+ md: [
419
+ { find: templateFindings, suspect: /\{[%{]/u },
420
+ { find: wikilinkFindings, suspect: SUSPECT_WIKILINK },
421
+ { find: mdcFindings, suspect: SUSPECT_MDC },
422
+ { find: attributeListFindings("md"), suspect: SUSPECT_ATTRIBUTE_LIST },
423
+ ],
424
+ mdx: [
425
+ { find: wikilinkFindings, suspect: SUSPECT_WIKILINK },
426
+ { find: mdcFindings, suspect: SUSPECT_MDC },
427
+ { find: attributeListFindings("mdx"), suspect: SUSPECT_ATTRIBUTE_LIST },
428
+ {
429
+ find: voidElementFindings,
430
+ suspect:
431
+ /<(?:area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)[\s/>]/u,
432
+ },
433
+ ],
434
+ };
435
+
436
+ /**
437
+ * Every finding in `text`, a body in `format`, in source order. The body is
438
+ * masked only when a check's cheap test matches, so most pages cost a few
439
+ * regex tests.
440
+ */
441
+ const syntaxFindings = (
442
+ text: string,
443
+ format: "md" | "mdx",
444
+ pageRoute: PageRoute
445
+ ): Finding[] => {
446
+ const checks = CHECKS[format].filter((check) => check.suspect.test(text));
447
+ if (checks.length === 0) {
448
+ return [];
449
+ }
450
+ const masked = format === "mdx" ? maskMdx(text) : maskMarkdown(text);
451
+ const scan = { pageRoute, starts: lineStartsOf(masked), text: masked };
452
+ return checks
453
+ .flatMap((check) => check.find(scan))
454
+ .toSorted((a, b) => a.line - b.line || a.column - b.column);
455
+ };
456
+
457
+ /**
458
+ * Warn about syntax from other docs tools in an entry's body (see the module
459
+ * comment). `pages` are the site's pages by name, which a wiki link resolves
460
+ * against, preferring one in the entry's own `locale`. Lines point into the
461
+ * file the author wrote, a partial's own file for syntax an `<include>`
462
+ * brought in.
463
+ */
464
+ export const syntaxDiagnostics = (
465
+ entry: SourceEntry,
466
+ sourceName: string,
467
+ pages: PageNameIndex = new Map(),
468
+ locale = ""
469
+ ): Diagnostic[] => {
470
+ const page = entry.sourcePath ?? `${sourceName}:${entry.ref}`;
471
+ const offset =
472
+ entry.bodyLineOffset ?? strippedLineOffset(entry.raw, entry.body.text);
473
+ const pageRoute = (name: string): string | undefined => {
474
+ const named = pages.get(wikiKey(name));
475
+ return (named?.find((other) => other.locale === locale) ?? named?.[0])
476
+ ?.route;
477
+ };
478
+ return syntaxFindings(
479
+ entry.expanded?.text ?? entry.body.text,
480
+ entry.body.format,
481
+ pageRoute
482
+ ).map(({ line, ...finding }) => {
483
+ const origin = entry.expanded?.origins[line - 1];
484
+ return {
485
+ ...finding,
486
+ file: origin?.file ?? page,
487
+ line: origin?.line ?? line + offset,
488
+ severity: "warning",
489
+ };
490
+ });
491
+ };
package/src/core/types.ts CHANGED
@@ -56,8 +56,13 @@ export interface Heading {
56
56
 
57
57
  /** A link target discovered in page content, anchored to its source line. */
58
58
  export interface PageLink {
59
- /** Raw link target as written, e.g. `./foo`, `/api#auth`, `https://x.dev`. */
59
+ /** Raw link target as written, e.g. `./foo`, `/api#auth`, `https://x.dev`.
60
+ * A Markdown destination is read as the renderer reads it: without its
61
+ * `<…>` brackets, and with backslash escapes resolved. */
60
62
  target: string;
63
+ /** Length of the target's text in the source, when escapes make it differ
64
+ * from `target`'s (`image\(1\).png` is read as `image(1).png`). */
65
+ sourceLength?: number;
61
66
  /** Set when the target was written as an image embed (`![alt](target)`) —
62
67
  * only those go through the image pipeline; a plain link to the same path
63
68
  * resolves as a site route. */
@@ -66,6 +71,11 @@ export interface PageLink {
66
71
  * the Markdown pipeline neither resolves its relative path file-style nor
67
72
  * mounts the base path on it, so it's checked the way a browser reads it. */
68
73
  raw?: boolean;
74
+ /** The element, when the target is a media element's `src` (`img`,
75
+ * `source`, `video`, `audio`). It's `raw` too: the browser resolves it as
76
+ * written, unless it names a file beside the page, which the build
77
+ * publishes and points it at. */
78
+ src?: string;
69
79
  /** 1-based line number in the source file. */
70
80
  line: number;
71
81
  /** 1-based column of the target within the line. */
@@ -88,6 +98,26 @@ export interface ExampleUse {
88
98
  file?: string;
89
99
  }
90
100
 
101
+ /**
102
+ * A JSX element in an `.mdx` body whose props don't render as written (see
103
+ * `core/mdx-elements.ts`), located like a {@link PageLink}: an HTML element
104
+ * with a JavaScript event handler (`handler`), or a childless built-in given
105
+ * props it doesn't take (`prop`).
106
+ */
107
+ export interface ElementUse {
108
+ /** 1-based column of the element's `<` within the line. */
109
+ column: number;
110
+ /** Absolute path of the included partial it was written in, if not the page. */
111
+ file?: string;
112
+ kind: "handler" | "prop";
113
+ /** 1-based line number in the source file. */
114
+ line: number;
115
+ /** The handler props, or the props the built-in doesn't take. */
116
+ props: string[];
117
+ /** The element's name as written (`button`, `Badge`). */
118
+ tag: string;
119
+ }
120
+
91
121
  /**
92
122
  * Resolved project paths. Computed once per CLI invocation and threaded
93
123
  * through the core pipeline.
@@ -170,6 +200,12 @@ export interface PageRecord {
170
200
  * checks skip these.
171
201
  */
172
202
  fallback?: boolean;
203
+ /**
204
+ * The route an earlier Blume published the page at, when it differs from
205
+ * `route`: a date-named file or folder (`12-05-2022`) lost its first number
206
+ * as an ordering prefix. The scan redirects it to `route`.
207
+ */
208
+ formerRoute?: string;
173
209
  /**
174
210
  * True for pages from a source that publishes one language only (GitHub
175
211
  * Releases): they render without a language switcher.
@@ -212,6 +248,12 @@ export interface PageRecord {
212
248
  componentsUsed?: string[];
213
249
  /** The string `path` of each `<Component>` example in the body (`.mdx` only). */
214
250
  examplesUsed?: ExampleUse[];
251
+ /**
252
+ * Childless built-ins given props they don't take (`.mdx` only). Reported
253
+ * once the project's own components are known, since an override takes
254
+ * whatever props it declares.
255
+ */
256
+ unknownProps?: ElementUse[];
215
257
  /** Resolved "last updated" ISO date, when the feature is enabled. */
216
258
  lastModified?: string;
217
259
  /** Absolute paths of files this page `<include>`s, transitively. Drives the
@@ -5,12 +5,6 @@ import { dirname, join, resolve } from "pathe";
5
5
 
6
6
  import { buildAgentReadability } from "../ai/agent-readability.ts";
7
7
  import { advertisedConfig, servesMcp } from "../ai/agent-surface.ts";
8
- import {
9
- AI_CATALOG_PATH,
10
- ARD_MANIFEST_PATH,
11
- buildAiCatalog,
12
- } from "../ai/ai-catalog.ts";
13
- import { API_CATALOG_PATH, buildApiCatalog } from "../ai/api-catalog.ts";
14
8
  import { buildHomeLinkHeader } from "../ai/link-headers.ts";
15
9
  import { buildLlmsFiles } from "../ai/llms.ts";
16
10
  import { markdownRoutePaths } from "../ai/markdown.ts";
@@ -26,10 +20,6 @@ import {
26
20
  collectSkills,
27
21
  } from "../ai/skills.ts";
28
22
  import type { SkillArtifact } from "../ai/skills.ts";
29
- import {
30
- buildSignaturesDirectory,
31
- SIGNATURES_DIRECTORY_PATH,
32
- } from "../ai/web-bot-auth.ts";
33
23
  import { normalizeBasePath } from "../core/base-path.ts";
34
24
  import { discoverPagesSync } from "../core/custom-pages.ts";
35
25
  import type { BlumeProject } from "../core/project-graph.ts";
@@ -38,6 +28,7 @@ import { buildNarration } from "../narration/build.ts";
38
28
  import { buildSearchIndex } from "../search/build.ts";
39
29
  import { syncSearchProvider } from "../search/sync/index.ts";
40
30
  import { readsHeaderFiles } from "./adapter-output.ts";
31
+ import { wellKnownFiles } from "./generated-files.ts";
41
32
  import { buildNetlifyHeaders, buildVercelHeaders } from "./headers.ts";
42
33
  import { deployPlatform } from "./platforms/index.ts";
43
34
  import { VERCEL_JSON_FILE } from "./platforms/vercel.ts";
@@ -352,24 +343,7 @@ const emitWellKnownFiles = async (
352
343
  skills: readonly SkillArtifact[],
353
344
  logger: ArtifactLogger
354
345
  ): Promise<void> => {
355
- // One document, two paths: the ai-catalog spec's well-known URI and the
356
- // ARD v0.91 one (see `ai/ai-catalog.ts`).
357
- const aiCatalog = buildAiCatalog(config, skills);
358
- const files = [
359
- {
360
- content: buildSignaturesDirectory(config),
361
- label: "Web Bot Auth",
362
- path: SIGNATURES_DIRECTORY_PATH,
363
- },
364
- {
365
- content: buildApiCatalog(config),
366
- label: "RFC 9727",
367
- path: API_CATALOG_PATH,
368
- },
369
- { content: aiCatalog, label: "AI Catalog", path: AI_CATALOG_PATH },
370
- { content: aiCatalog, label: "ARD manifest", path: ARD_MANIFEST_PATH },
371
- ];
372
- for (const file of files) {
346
+ for (const file of wellKnownFiles(config, skills)) {
373
347
  const target = join(distDir, file.path.slice(1));
374
348
  if (!file.content || existsSync(target)) {
375
349
  continue;