blume 2.0.1 → 2.0.2

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 (410) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/dist/cli/{chunk-vtk4a6dg.js → chunk-00gs3wqs.js} +1 -1
  3. package/dist/cli/{chunk-fa25z98p.js → chunk-273ygyr4.js} +4 -3
  4. package/dist/cli/chunk-273ygyr4.js.map +11 -0
  5. package/dist/cli/{chunk-yw7dm696.js → chunk-3yce002v.js} +21 -8
  6. package/dist/cli/{chunk-yw7dm696.js.map → chunk-3yce002v.js.map} +3 -3
  7. package/dist/cli/{chunk-yt5n7ppj.js → chunk-4e9b9ra6.js} +17 -7
  8. package/dist/cli/chunk-4e9b9ra6.js.map +10 -0
  9. package/dist/cli/{chunk-fs23ddbb.js → chunk-5r8g91qn.js} +352 -676
  10. package/dist/cli/chunk-5r8g91qn.js.map +34 -0
  11. package/dist/cli/{chunk-6vm74dry.js → chunk-6dtt0zfn.js} +8 -8
  12. package/dist/cli/{chunk-qwsrynx5.js → chunk-6k4ftwze.js} +38 -19
  13. package/dist/cli/{chunk-qwsrynx5.js.map → chunk-6k4ftwze.js.map} +4 -4
  14. package/dist/cli/{chunk-qs4q5p4e.js → chunk-7mbqtmgb.js} +16 -7
  15. package/dist/cli/chunk-7mbqtmgb.js.map +10 -0
  16. package/dist/cli/{chunk-q5163e60.js → chunk-7vtckvaw.js} +21 -19
  17. package/dist/cli/chunk-7vtckvaw.js.map +11 -0
  18. package/dist/cli/{chunk-kdp5q7ke.js → chunk-8g8ytmgx.js} +17 -18
  19. package/dist/cli/{chunk-kdp5q7ke.js.map → chunk-8g8ytmgx.js.map} +2 -2
  20. package/dist/cli/{chunk-epjnccmv.js → chunk-91ws1n6j.js} +18 -15
  21. package/dist/cli/chunk-91ws1n6j.js.map +10 -0
  22. package/dist/cli/{chunk-fxypxtvm.js → chunk-bbnwccaz.js} +2 -2
  23. package/dist/cli/{chunk-m3vmjgmq.js → chunk-bfwp9vp6.js} +16 -8
  24. package/dist/cli/chunk-bfwp9vp6.js.map +10 -0
  25. package/dist/cli/{chunk-f2z5v128.js → chunk-d1v5rhy0.js} +14 -15
  26. package/dist/cli/{chunk-f2z5v128.js.map → chunk-d1v5rhy0.js.map} +2 -2
  27. package/dist/cli/{chunk-6crbhc3x.js → chunk-ddndchfr.js} +21 -6
  28. package/dist/cli/chunk-ddndchfr.js.map +14 -0
  29. package/dist/cli/{chunk-ce574jw2.js → chunk-esh98wmb.js} +1 -1
  30. package/dist/cli/{chunk-zxcczpyx.js → chunk-fsmrqk8a.js} +1 -1
  31. package/dist/cli/{chunk-hdpx1tax.js → chunk-g698a744.js} +5 -5
  32. package/dist/cli/{chunk-jts8mvcz.js → chunk-gs7r695n.js} +9 -3
  33. package/dist/cli/{chunk-jts8mvcz.js.map → chunk-gs7r695n.js.map} +3 -3
  34. package/dist/cli/{chunk-zxh4d9vy.js → chunk-h2ez8dzb.js} +4 -4
  35. package/dist/cli/{chunk-5shv93fd.js → chunk-h7k3nq3v.js} +2 -2
  36. package/dist/cli/{chunk-79jhk4py.js → chunk-hqp2ajnh.js} +251 -122
  37. package/dist/cli/chunk-hqp2ajnh.js.map +35 -0
  38. package/dist/cli/{chunk-2hn4b8z7.js → chunk-hr8ne106.js} +109 -40
  39. package/dist/cli/chunk-hr8ne106.js.map +13 -0
  40. package/dist/cli/{chunk-6hsn950k.js → chunk-j85scx15.js} +62 -18
  41. package/dist/cli/chunk-j85scx15.js.map +10 -0
  42. package/dist/cli/{chunk-s1p84fyh.js → chunk-k7pj68a8.js} +63 -22
  43. package/dist/cli/chunk-k7pj68a8.js.map +11 -0
  44. package/dist/cli/{chunk-ah61y8py.js → chunk-mqc662a6.js} +2 -2
  45. package/dist/cli/{chunk-ch6g3ar0.js → chunk-n1yg3tj3.js} +4 -4
  46. package/dist/cli/{chunk-mb2919y2.js → chunk-nfcyttvj.js} +17 -6
  47. package/dist/cli/chunk-nfcyttvj.js.map +10 -0
  48. package/dist/cli/{chunk-kpf8rrjc.js → chunk-pbg5a4s3.js} +56 -24
  49. package/dist/cli/chunk-pbg5a4s3.js.map +19 -0
  50. package/dist/cli/{chunk-jwyddg7y.js → chunk-ppzjqwx2.js} +21 -14
  51. package/dist/cli/{chunk-jwyddg7y.js.map → chunk-ppzjqwx2.js.map} +4 -4
  52. package/dist/cli/{chunk-dh8cwk36.js → chunk-qkb5a8sa.js} +24 -9
  53. package/dist/cli/chunk-qkb5a8sa.js.map +10 -0
  54. package/dist/cli/{chunk-wm7js3j9.js → chunk-sqw4ekg1.js} +2 -2
  55. package/dist/cli/{chunk-qkqwkpte.js → chunk-v6ya5kcb.js} +3083 -953
  56. package/dist/cli/chunk-v6ya5kcb.js.map +189 -0
  57. package/dist/cli/{chunk-fz5wtpmh.js → chunk-w4bxdvsa.js} +18 -15
  58. package/dist/cli/chunk-w4bxdvsa.js.map +10 -0
  59. package/dist/cli/{chunk-27g6wdth.js → chunk-wdrt2k2v.js} +2 -2
  60. package/dist/cli/{chunk-wgm7m9qk.js → chunk-xh43dwgw.js} +158 -43
  61. package/dist/cli/chunk-xh43dwgw.js.map +36 -0
  62. package/dist/cli/{chunk-s6jhgk0q.js → chunk-zp79m0ts.js} +2 -2
  63. package/dist/cli/index.js +160 -35
  64. package/dist/cli/index.js.map +4 -4
  65. package/dist/types/ai/agent-surface.d.ts +32 -0
  66. package/dist/types/ai/api-catalog.d.ts +7 -1
  67. package/dist/types/ai/ask-context.d.ts +7 -0
  68. package/dist/types/ai/component-markdown.d.ts +4 -4
  69. package/dist/types/ai/relative-links.d.ts +9 -4
  70. package/dist/types/ai/static-expression.d.ts +28 -0
  71. package/dist/types/analytics/databuddy.d.ts +43 -0
  72. package/dist/types/analytics/index.d.ts +2 -0
  73. package/dist/types/analytics/schema.d.ts +14 -0
  74. package/dist/types/cli/env.d.ts +5 -0
  75. package/dist/types/cli/init/scaffold.d.ts +19 -3
  76. package/dist/types/cli/init/starter-spec.d.ts +11 -0
  77. package/dist/types/core/base-path.d.ts +9 -0
  78. package/dist/types/core/config-input.d.ts +4 -4
  79. package/dist/types/core/config.d.ts +2 -2
  80. package/dist/types/core/graph.d.ts +2 -0
  81. package/dist/types/core/i18n-ui.d.ts +31 -0
  82. package/dist/types/core/i18n.d.ts +7 -1
  83. package/dist/types/core/links.d.ts +3 -1
  84. package/dist/types/core/load-module.d.ts +10 -0
  85. package/dist/types/core/locale-links.d.ts +12 -2
  86. package/dist/types/core/meta.d.ts +5 -1
  87. package/dist/types/core/nav-diagnostics.d.ts +10 -0
  88. package/dist/types/core/navigation.d.ts +38 -0
  89. package/dist/types/core/ordering-prefix.d.ts +4 -0
  90. package/dist/types/core/safe-links.d.ts +3 -1
  91. package/dist/types/core/schema.d.ts +52 -13
  92. package/dist/types/core/sources/github-releases.d.ts +5 -0
  93. package/dist/types/core/sources/lower.d.ts +14 -2
  94. package/dist/types/core/sources/normalize.d.ts +10 -1
  95. package/dist/types/core/sources/remote.d.ts +11 -1
  96. package/dist/types/core/sources/resolve.d.ts +12 -0
  97. package/dist/types/core/sources/types.d.ts +31 -0
  98. package/dist/types/core/types.d.ts +9 -0
  99. package/dist/types/deploy/adapters/node.d.ts +5 -2
  100. package/dist/types/deploy/adapters/types.d.ts +7 -0
  101. package/dist/types/deploy/cloudflare-negotiation.d.ts +3 -2
  102. package/dist/types/deploy/headers.d.ts +31 -7
  103. package/dist/types/deploy/node-headers.d.ts +43 -8
  104. package/dist/types/deploy/platforms/netlify.d.ts +27 -2
  105. package/dist/types/deploy/platforms/node.d.ts +6 -5
  106. package/dist/types/deploy/platforms/types.d.ts +7 -0
  107. package/dist/types/deploy/platforms/vercel.d.ts +3 -2
  108. package/dist/types/deploy/redirects.d.ts +7 -2
  109. package/dist/types/deploy/vercel-negotiation.d.ts +3 -2
  110. package/dist/types/openapi/model.d.ts +20 -6
  111. package/dist/types/search/sync/algolia.d.ts +3 -1
  112. package/docs/01-quickstart.mdx +3 -2
  113. package/docs/02-deployment.mdx +17 -8
  114. package/docs/08-faq.mdx +9 -2
  115. package/docs/advanced/changelog.mdx +1 -1
  116. package/docs/advanced/custom-pages.mdx +4 -2
  117. package/docs/cli/audit.mdx +2 -2
  118. package/docs/cli/doctor.mdx +2 -2
  119. package/docs/cli/evals.mdx +2 -2
  120. package/docs/cli/index.mdx +4 -1
  121. package/docs/cli/translate.mdx +1 -1
  122. package/docs/configuration/analytics.mdx +23 -2
  123. package/docs/configuration/assistant.mdx +1 -1
  124. package/docs/configuration/customization.mdx +4 -3
  125. package/docs/configuration/index.mdx +5 -3
  126. package/docs/configuration/search.mdx +1 -1
  127. package/docs/content/components.mdx +1 -1
  128. package/docs/content/frontmatter.mdx +5 -1
  129. package/docs/content/i18n.mdx +1 -1
  130. package/docs/content/index.mdx +4 -2
  131. package/docs/content/meta.mdx +5 -3
  132. package/docs/content/navigation.mdx +29 -4
  133. package/docs/content/sources.mdx +18 -12
  134. package/docs/content/versioning.mdx +1 -0
  135. package/docs/discoverability/agent-discovery.mdx +21 -7
  136. package/docs/discoverability/index.mdx +2 -2
  137. package/docs/discoverability/llms-txt.mdx +2 -5
  138. package/docs/discoverability/markdown.mdx +5 -3
  139. package/docs/discoverability/mcp.mdx +4 -0
  140. package/docs/discoverability/metadata.mdx +3 -2
  141. package/docs/discoverability/open-graph.mdx +6 -4
  142. package/docs/discoverability/rss.mdx +3 -1
  143. package/docs/references/asyncapi.mdx +1 -1
  144. package/docs/references/graphql.mdx +2 -2
  145. package/docs/references/openapi.mdx +3 -3
  146. package/package.json +1 -1
  147. package/skills/blume-migrate/SKILL.md +6 -6
  148. package/skills/blume-migrate/references/docusaurus.md +6 -6
  149. package/skills/blume-migrate/references/mintlify.md +3 -3
  150. package/skills/blume-migrate/references/monorepo.md +1 -1
  151. package/skills/blume-migrate/references/nextra.md +1 -1
  152. package/skills/blume-migrate/references/starlight.md +5 -5
  153. package/skills/blume-migrate/scripts/mintlify-codemod.mjs +2 -2
  154. package/skills/blume-update-docs/references/audit-checklist.md +1 -1
  155. package/src/ai/agent-surface.ts +56 -0
  156. package/src/ai/api/spec.ts +15 -2
  157. package/src/ai/api-catalog.ts +7 -1
  158. package/src/ai/ask-context.ts +14 -2
  159. package/src/ai/ask-data.ts +26 -12
  160. package/src/ai/component-markdown.ts +43 -37
  161. package/src/ai/link-headers.ts +8 -3
  162. package/src/ai/llms.ts +4 -3
  163. package/src/ai/markdown.ts +23 -11
  164. package/src/ai/mcp/server.ts +78 -7
  165. package/src/ai/relative-links.ts +78 -10
  166. package/src/ai/static-expression.ts +416 -0
  167. package/src/ai/visibility.ts +45 -14
  168. package/src/analytics/databuddy.ts +67 -0
  169. package/src/analytics/head.ts +4 -0
  170. package/src/analytics/index.ts +2 -0
  171. package/src/analytics/posthog.ts +21 -3
  172. package/src/analytics/schema.ts +2 -0
  173. package/src/astro/generate.ts +71 -27
  174. package/src/astro/templates.ts +61 -23
  175. package/src/audit/catalog.ts +2 -2
  176. package/src/audit/checks/assets.ts +25 -5
  177. package/src/audit/checks/content.ts +20 -2
  178. package/src/audit/checks/i18n.ts +46 -8
  179. package/src/audit/checks/indexability.ts +39 -19
  180. package/src/audit/checks/links.ts +14 -0
  181. package/src/audit/checks/llms.ts +6 -3
  182. package/src/audit/checks/network.ts +3 -1
  183. package/src/audit/checks/og-image.ts +10 -0
  184. package/src/audit/checks/robots.ts +6 -1
  185. package/src/audit/checks/sitemap.ts +56 -31
  186. package/src/audit/checks/social.ts +21 -2
  187. package/src/audit/crawl.ts +88 -15
  188. package/src/audit/report.ts +54 -17
  189. package/src/audit/run.ts +1 -0
  190. package/src/audit/types.ts +18 -0
  191. package/src/audit/url.ts +37 -6
  192. package/src/cli/build-failure.ts +50 -0
  193. package/src/cli/commands/audit.ts +7 -0
  194. package/src/cli/commands/build.ts +19 -5
  195. package/src/cli/commands/check.ts +2 -0
  196. package/src/cli/commands/eval.ts +1 -7
  197. package/src/cli/commands/init.ts +15 -5
  198. package/src/cli/commands/preview.ts +15 -0
  199. package/src/cli/commands/sync.ts +2 -0
  200. package/src/cli/commands/translate.ts +9 -6
  201. package/src/cli/commands/upgrade.ts +11 -0
  202. package/src/cli/eject-scripts.ts +32 -7
  203. package/src/cli/env.ts +12 -1
  204. package/src/cli/init/scaffold.ts +117 -11
  205. package/src/cli/init/starter-spec.ts +235 -0
  206. package/src/components/content/AccordionItem.astro +26 -22
  207. package/src/components/content/Badge.astro +2 -9
  208. package/src/components/content/Card.astro +2 -2
  209. package/src/components/content/Component.astro +9 -3
  210. package/src/components/content/Expandable.astro +5 -1
  211. package/src/components/content/Frame.astro +2 -7
  212. package/src/components/content/GithubInfo.astro +12 -2
  213. package/src/components/content/Prompt.astro +2 -7
  214. package/src/components/content/Tabs.astro +54 -5
  215. package/src/components/content/Tile.astro +1 -1
  216. package/src/components/content/Tooltip.astro +69 -7
  217. package/src/components/content/Tree.astro +7 -2
  218. package/src/components/content/TypeTable.astro +10 -5
  219. package/src/components/content/Update.astro +8 -2
  220. package/src/components/content/auto-type-table.ts +4 -1
  221. package/src/components/content/badge-color.ts +17 -0
  222. package/src/components/content/base-href.ts +18 -3
  223. package/src/components/content/inline-markdown.ts +27 -7
  224. package/src/components/copy-feedback.ts +35 -8
  225. package/src/components/islands/assistant.tsx +16 -2
  226. package/src/components/islands/hooks.ts +7 -2
  227. package/src/components/islands/webmcp.ts +12 -8
  228. package/src/components/layout/Banner.astro +23 -4
  229. package/src/components/layout/Header.astro +22 -9
  230. package/src/components/layout/Logo.astro +5 -0
  231. package/src/components/layout/NavSelector.astro +6 -1
  232. package/src/components/layout/NavTabMenu.astro +133 -0
  233. package/src/components/layout/NavTree.astro +15 -6
  234. package/src/components/layout/NavTreeCache.astro +5 -2
  235. package/src/components/layout/NavTreeScript.astro +45 -5
  236. package/src/components/layout/PageLayout.astro +22 -8
  237. package/src/components/layout/ReferenceLayout.astro +4 -1
  238. package/src/components/layout/RootLayout.astro +21 -8
  239. package/src/components/layout/Search.astro +5 -1
  240. package/src/components/layout/analytics-client.ts +2 -0
  241. package/src/components/openapi/GraphqlType.astro +11 -3
  242. package/src/components/openapi/MessageComposer.astro +1 -1
  243. package/src/components/openapi/Operation.astro +21 -5
  244. package/src/components/openapi/PanelTabs.astro +4 -1
  245. package/src/components/openapi/Playground.astro +8 -2
  246. package/src/components/openapi/RequestPanel.astro +9 -5
  247. package/src/components/openapi/SchemaProperty.astro +11 -33
  248. package/src/components/openapi/SchemaTable.astro +27 -52
  249. package/src/components/openapi/helpers.ts +90 -12
  250. package/src/components/openapi/message-composer.ts +8 -0
  251. package/src/components/openapi/message-model.ts +12 -2
  252. package/src/components/openapi/message.ts +4 -1
  253. package/src/components/openapi/operation-model.ts +40 -8
  254. package/src/components/openapi/panel.ts +29 -5
  255. package/src/components/openapi/playground-client.ts +37 -7
  256. package/src/components/openapi/playground-schema.ts +25 -6
  257. package/src/components/openapi/request.ts +2 -0
  258. package/src/components/openapi/schema-tree.ts +203 -0
  259. package/src/components/openapi/snippets.ts +30 -15
  260. package/src/components/openapi/validate-json.ts +1 -1
  261. package/src/core/base-path.ts +15 -0
  262. package/src/core/config-input.ts +4 -4
  263. package/src/core/config.ts +18 -4
  264. package/src/core/diagnostics.ts +217 -30
  265. package/src/core/graph.ts +120 -11
  266. package/src/core/i18n-ui.ts +39 -2
  267. package/src/core/i18n.ts +13 -2
  268. package/src/core/links.ts +7 -4
  269. package/src/core/load-module.ts +20 -0
  270. package/src/core/locale-links.ts +17 -17
  271. package/src/core/manifest.ts +3 -2
  272. package/src/core/meta.ts +69 -6
  273. package/src/core/nav-diagnostics.ts +56 -1
  274. package/src/core/navigation.ts +245 -76
  275. package/src/core/ordering-prefix.ts +27 -0
  276. package/src/core/project-graph.ts +23 -1
  277. package/src/core/safe-href.ts +53 -1
  278. package/src/core/safe-links.ts +11 -2
  279. package/src/core/schema.ts +70 -14
  280. package/src/core/sources/assets.ts +83 -41
  281. package/src/core/sources/contentful-rich-text.ts +3 -2
  282. package/src/core/sources/contentful.ts +25 -12
  283. package/src/core/sources/filesystem.ts +5 -1
  284. package/src/core/sources/github-releases.ts +106 -5
  285. package/src/core/sources/lexical.ts +9 -4
  286. package/src/core/sources/lower.ts +79 -26
  287. package/src/core/sources/mdx-remote.ts +1 -0
  288. package/src/core/sources/normalize.ts +132 -30
  289. package/src/core/sources/notion.ts +26 -10
  290. package/src/core/sources/obsidian.ts +17 -3
  291. package/src/core/sources/payload.ts +1 -0
  292. package/src/core/sources/portable-text.ts +63 -44
  293. package/src/core/sources/remote.ts +18 -2
  294. package/src/core/sources/resolve.ts +52 -33
  295. package/src/core/sources/sanity.ts +1 -0
  296. package/src/core/sources/strapi-blocks.ts +9 -4
  297. package/src/core/sources/strapi.ts +1 -0
  298. package/src/core/sources/types.ts +31 -0
  299. package/src/core/types.ts +9 -0
  300. package/src/core/ui-packs/ar.ts +13 -0
  301. package/src/core/ui-packs/bg.ts +13 -0
  302. package/src/core/ui-packs/bn.ts +13 -0
  303. package/src/core/ui-packs/ca.ts +13 -0
  304. package/src/core/ui-packs/cs.ts +13 -0
  305. package/src/core/ui-packs/da.ts +13 -0
  306. package/src/core/ui-packs/de.ts +13 -0
  307. package/src/core/ui-packs/el.ts +13 -0
  308. package/src/core/ui-packs/es.ts +13 -0
  309. package/src/core/ui-packs/fa.ts +13 -0
  310. package/src/core/ui-packs/fi.ts +13 -0
  311. package/src/core/ui-packs/fr.ts +13 -0
  312. package/src/core/ui-packs/he.ts +13 -0
  313. package/src/core/ui-packs/hi.ts +13 -0
  314. package/src/core/ui-packs/hr.ts +13 -0
  315. package/src/core/ui-packs/hu.ts +13 -0
  316. package/src/core/ui-packs/id.ts +13 -0
  317. package/src/core/ui-packs/it.ts +13 -0
  318. package/src/core/ui-packs/ja.ts +13 -0
  319. package/src/core/ui-packs/ko.ts +13 -0
  320. package/src/core/ui-packs/nl.ts +13 -0
  321. package/src/core/ui-packs/no.ts +13 -0
  322. package/src/core/ui-packs/pl.ts +13 -0
  323. package/src/core/ui-packs/pt-br.ts +13 -0
  324. package/src/core/ui-packs/pt.ts +13 -0
  325. package/src/core/ui-packs/ro.ts +13 -0
  326. package/src/core/ui-packs/ru.ts +13 -0
  327. package/src/core/ui-packs/sk.ts +13 -0
  328. package/src/core/ui-packs/sr.ts +13 -0
  329. package/src/core/ui-packs/sv.ts +13 -0
  330. package/src/core/ui-packs/th.ts +13 -0
  331. package/src/core/ui-packs/tr.ts +13 -0
  332. package/src/core/ui-packs/uk.ts +13 -0
  333. package/src/core/ui-packs/vi.ts +13 -0
  334. package/src/core/ui-packs/zh-tw.ts +13 -0
  335. package/src/core/ui-packs/zh.ts +13 -0
  336. package/src/core/version-cut.ts +17 -2
  337. package/src/core/versions.ts +4 -1
  338. package/src/deploy/adapters/node.ts +5 -2
  339. package/src/deploy/adapters/registry.ts +2 -1
  340. package/src/deploy/adapters/types.ts +13 -1
  341. package/src/deploy/artifacts.ts +25 -5
  342. package/src/deploy/cloudflare-negotiation.ts +23 -4
  343. package/src/deploy/headers.ts +67 -51
  344. package/src/deploy/node-headers.ts +148 -27
  345. package/src/deploy/platforms/cloudflare.ts +1 -0
  346. package/src/deploy/platforms/netlify.ts +82 -5
  347. package/src/deploy/platforms/node.ts +7 -5
  348. package/src/deploy/platforms/static.ts +1 -0
  349. package/src/deploy/platforms/types.ts +7 -0
  350. package/src/deploy/platforms/vercel.ts +9 -3
  351. package/src/deploy/redirects.ts +14 -3
  352. package/src/deploy/vercel-negotiation.ts +35 -3
  353. package/src/eval/agents.ts +10 -2
  354. package/src/eval/run.ts +25 -0
  355. package/src/markdown/base-links.ts +55 -8
  356. package/src/markdown/index.ts +6 -3
  357. package/src/markdown/relative-links.ts +3 -23
  358. package/src/markdown/route-snapshot.ts +37 -0
  359. package/src/og/card.ts +20 -4
  360. package/src/og/derive.ts +145 -4
  361. package/src/openapi/graphql-build.ts +28 -2
  362. package/src/openapi/model.ts +77 -13
  363. package/src/openapi/render-mdx.ts +10 -1
  364. package/src/registry/eject.ts +119 -22
  365. package/src/search/documents.ts +22 -1
  366. package/src/search/sync/algolia.ts +36 -2
  367. package/src/sources/registry.ts +5 -0
  368. package/src/theme/entry.ts +11 -4
  369. package/src/translate/agents.ts +6 -1
  370. package/src/translate/ledger.ts +26 -3
  371. package/src/translate/meta.ts +11 -3
  372. package/src/translate/run.ts +11 -5
  373. package/src/translate/validate.ts +10 -1
  374. package/src/translate/work-list.ts +36 -3
  375. package/src/upgrade/upgrade.ts +36 -4
  376. package/dist/cli/chunk-2hn4b8z7.js.map +0 -12
  377. package/dist/cli/chunk-6crbhc3x.js.map +0 -14
  378. package/dist/cli/chunk-6hsn950k.js.map +0 -10
  379. package/dist/cli/chunk-79jhk4py.js.map +0 -35
  380. package/dist/cli/chunk-82bbrxdn.js +0 -51
  381. package/dist/cli/chunk-82bbrxdn.js.map +0 -10
  382. package/dist/cli/chunk-abh8yjkn.js +0 -31
  383. package/dist/cli/chunk-abh8yjkn.js.map +0 -10
  384. package/dist/cli/chunk-dh8cwk36.js.map +0 -10
  385. package/dist/cli/chunk-epjnccmv.js.map +0 -10
  386. package/dist/cli/chunk-fa25z98p.js.map +0 -11
  387. package/dist/cli/chunk-fs23ddbb.js.map +0 -35
  388. package/dist/cli/chunk-fz5wtpmh.js.map +0 -10
  389. package/dist/cli/chunk-kpf8rrjc.js.map +0 -19
  390. package/dist/cli/chunk-m3vmjgmq.js.map +0 -10
  391. package/dist/cli/chunk-mb2919y2.js.map +0 -10
  392. package/dist/cli/chunk-q5163e60.js.map +0 -11
  393. package/dist/cli/chunk-qkqwkpte.js.map +0 -182
  394. package/dist/cli/chunk-qs4q5p4e.js.map +0 -10
  395. package/dist/cli/chunk-s1p84fyh.js.map +0 -10
  396. package/dist/cli/chunk-wgm7m9qk.js.map +0 -36
  397. package/dist/cli/chunk-yt5n7ppj.js.map +0 -10
  398. /package/dist/cli/{chunk-vtk4a6dg.js.map → chunk-00gs3wqs.js.map} +0 -0
  399. /package/dist/cli/{chunk-6vm74dry.js.map → chunk-6dtt0zfn.js.map} +0 -0
  400. /package/dist/cli/{chunk-fxypxtvm.js.map → chunk-bbnwccaz.js.map} +0 -0
  401. /package/dist/cli/{chunk-ce574jw2.js.map → chunk-esh98wmb.js.map} +0 -0
  402. /package/dist/cli/{chunk-zxcczpyx.js.map → chunk-fsmrqk8a.js.map} +0 -0
  403. /package/dist/cli/{chunk-hdpx1tax.js.map → chunk-g698a744.js.map} +0 -0
  404. /package/dist/cli/{chunk-zxh4d9vy.js.map → chunk-h2ez8dzb.js.map} +0 -0
  405. /package/dist/cli/{chunk-5shv93fd.js.map → chunk-h7k3nq3v.js.map} +0 -0
  406. /package/dist/cli/{chunk-ah61y8py.js.map → chunk-mqc662a6.js.map} +0 -0
  407. /package/dist/cli/{chunk-ch6g3ar0.js.map → chunk-n1yg3tj3.js.map} +0 -0
  408. /package/dist/cli/{chunk-wm7js3j9.js.map → chunk-sqw4ekg1.js.map} +0 -0
  409. /package/dist/cli/{chunk-27g6wdth.js.map → chunk-wdrt2k2v.js.map} +0 -0
  410. /package/dist/cli/{chunk-s6jhgk0q.js.map → chunk-zp79m0ts.js.map} +0 -0
@@ -2,7 +2,7 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
2
  import type { ServerOptions } from "@modelcontextprotocol/sdk/server/index.js";
3
3
  import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
4
4
  import {
5
- CallToolRequestSchema,
5
+ ErrorCode,
6
6
  ListResourcesRequestSchema,
7
7
  ListToolsRequestSchema,
8
8
  McpError,
@@ -11,6 +11,7 @@ import {
11
11
  import { z } from "zod";
12
12
 
13
13
  import { withBasePath } from "../../core/base-path.ts";
14
+ import { readCappedText } from "../../core/request-body.ts";
14
15
  import type { McpData } from "./data.ts";
15
16
  import {
16
17
  createIndexProvider,
@@ -42,6 +43,12 @@ const RESOURCE_MIME_TYPE = "text/markdown";
42
43
  const RESOURCE_NOT_FOUND = -32_002;
43
44
  /** URI scheme for page resources when no `deployment.site` is configured. */
44
45
  const LOCAL_RESOURCE_SCHEME = "blume:";
46
+ /**
47
+ * The largest request body the HTTP endpoint reads. A JSON-RPC call to these
48
+ * tools is a few hundred bytes; the cap keeps one oversized POST from being
49
+ * buffered whole, as the assistant route's does.
50
+ */
51
+ const MCP_BODY_LIMIT_BYTES = 65_536;
45
52
 
46
53
  const CORS_HEADERS = {
47
54
  "Access-Control-Allow-Headers":
@@ -108,6 +115,20 @@ const resourceRoute = (uri: string, data: McpData): string =>
108
115
  data
109
116
  );
110
117
 
118
+ /**
119
+ * The `tools/call` params, as far as the server reads them before dispatch:
120
+ * each tool validates its own `arguments` against `TOOL_INPUTS`.
121
+ */
122
+ const TOOL_CALL_PARAMS = z.object({
123
+ arguments: z.record(z.string(), z.unknown()).optional(),
124
+ name: z.string(),
125
+ });
126
+
127
+ /** A tool call's `arguments` object, before the tool's own validation. */
128
+ type ToolArguments = NonNullable<
129
+ z.output<typeof TOOL_CALL_PARAMS>["arguments"]
130
+ >;
131
+
111
132
  /** A tool call's text result, marked as an error when `isError` is set. */
112
133
  const text = (value: string, isError = false) => {
113
134
  const content = [{ text: value, type: "text" as const }];
@@ -165,9 +186,7 @@ export const buildServer = (
165
186
  };
166
187
  });
167
188
 
168
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
169
- const { arguments: args = {}, name } = request.params;
170
-
189
+ const callTool = async (name: string, args: ToolArguments) => {
171
190
  if (name === "search_docs") {
172
191
  const results = await searchDocs(
173
192
  data,
@@ -206,12 +225,44 @@ export const buildServer = (
206
225
  return text(JSON.stringify(result.navigation, null, 2));
207
226
  }
208
227
 
209
- return text(`Unknown tool: ${name}`, true);
210
- });
228
+ // The MCP spec answers an unknown tool with a protocol error, not a
229
+ // failed tool result.
230
+ throw new McpError(ErrorCode.InvalidParams, `Unknown tool: ${name}`);
231
+ };
232
+
233
+ // `tools/call` goes through the fallback handler rather than a registered
234
+ // one: the SDK parses a registered handler's request against its own
235
+ // schema first and reports a malformed call (`arguments` that aren't an
236
+ // object) as -32603 Internal error carrying a raw Zod dump. Reading the
237
+ // params here answers it as -32602 Invalid params with a short message.
238
+ server.fallbackRequestHandler = async (request) => {
239
+ if (request.method !== "tools/call") {
240
+ throw new McpError(ErrorCode.MethodNotFound, "Method not found");
241
+ }
242
+ const params = TOOL_CALL_PARAMS.safeParse(request.params);
243
+ if (!params.success) {
244
+ throw new McpError(
245
+ ErrorCode.InvalidParams,
246
+ "Invalid tools/call params: `name` must be a string and `arguments`, when given, an object."
247
+ );
248
+ }
249
+ return await callTool(params.data.name, params.data.arguments ?? {});
250
+ };
211
251
 
212
252
  return server;
213
253
  };
214
254
 
255
+ /** A JSON-RPC error response the transport would send, for errors raised before it runs. */
256
+ const jsonRpcError = (
257
+ status: number,
258
+ code: number,
259
+ message: string
260
+ ): Response =>
261
+ Response.json(
262
+ { error: { code, message }, id: null, jsonrpc: "2.0" },
263
+ { headers: CORS_HEADERS, status }
264
+ );
265
+
215
266
  /**
216
267
  * Build a stateless Streamable-HTTP MCP request handler from a data snapshot.
217
268
  *
@@ -239,6 +290,26 @@ export const createMcpFetchHandler = (
239
290
  });
240
291
  }
241
292
 
293
+ // The transport would read the body with `request.json()`, buffering
294
+ // whatever a client sends; read it under the cap first and hand the
295
+ // transport a copy.
296
+ let forwarded = request;
297
+ if (request.method === "POST") {
298
+ const body = await readCappedText(request, MCP_BODY_LIMIT_BYTES);
299
+ if (body === undefined) {
300
+ return jsonRpcError(
301
+ 413,
302
+ ErrorCode.InvalidRequest,
303
+ "Request too large: the body must be at most 64 KB."
304
+ );
305
+ }
306
+ forwarded = new Request(request.url, {
307
+ body,
308
+ headers: request.headers,
309
+ method: "POST",
310
+ });
311
+ }
312
+
242
313
  const server = buildServer(data, index);
243
314
  const transport = new WebStandardStreamableHTTPServerTransport({
244
315
  enableJsonResponse: true,
@@ -248,7 +319,7 @@ export const createMcpFetchHandler = (
248
319
  sessionIdGenerator: undefined,
249
320
  });
250
321
  await server.connect(transport);
251
- const response = await transport.handleRequest(request);
322
+ const response = await transport.handleRequest(forwarded);
252
323
 
253
324
  const headers = new Headers(response.headers);
254
325
  for (const [key, value] of Object.entries(CORS_HEADERS)) {
@@ -1,8 +1,14 @@
1
1
  import { basename } from "pathe";
2
2
 
3
- import { normalizeBasePath, withBasePath } from "../core/base-path.ts";
3
+ import {
4
+ isInternalPath,
5
+ normalizeBasePath,
6
+ withBasePath,
7
+ withComposedBasePath,
8
+ } from "../core/base-path.ts";
4
9
  import { nextFenceState } from "../core/code-fences.ts";
5
10
  import type { FenceState } from "../core/code-fences.ts";
11
+ import { localePrefix } from "../core/i18n.ts";
6
12
  import {
7
13
  buildFileRouteIndex,
8
14
  isIndexFileName,
@@ -10,19 +16,25 @@ import {
10
16
  routeOfLinkedFile,
11
17
  } from "../core/links.ts";
12
18
  import type { RelativeLinkBase } from "../core/links.ts";
19
+ import { localizeHref } from "../core/locale-links.ts";
13
20
  import type { BlumeProject } from "../core/project-graph.ts";
14
21
  import { extractLinks } from "../core/sources/normalize.ts";
15
22
 
16
23
  /**
17
- * Relative page links on the agent surfaces — the `.md`/`.mdx` mirrors,
24
+ * Page links on the agent surfaces — the `.md`/`.mdx` mirrors,
18
25
  * llms-full.txt, MCP `get_page` and `resources/read`. An agent resolves
19
26
  * `[Install](./install)` against the URL it fetched (`/guides.md`), so the
20
27
  * link lands on `/install`; the rendered page rewrites it to the route it
21
28
  * means (`markdown/relative-links.ts`), and the Markdown an agent reads gets
22
29
  * the same rewrite from the same resolver (`resolveRelativeHref`), so both
23
- * point at one page. Inline links and reference definitions are rewritten;
24
- * fenced and inline code, images, and anything not a relative page link are
25
- * left as written.
30
+ * point at one page. Root-relative page links (`[x](/guide)`,
31
+ * `<Card href="/guide">`) get what the rendered page gives them too: the
32
+ * `deployment.base` + `basePath` prefix (`markdown/base-links.ts`,
33
+ * `components/content/base-href.ts`) and, on a page in a prefixed locale,
34
+ * that locale's copy of the route when it is served
35
+ * (`components/layout/LocaleLinks.astro`). Inline links, component `href`s,
36
+ * and reference definitions are rewritten; fenced and inline code, images,
37
+ * asset links, and external URLs are left as written.
26
38
  */
27
39
 
28
40
  /** The page a Markdown text was written for. */
@@ -47,6 +59,21 @@ const DEFINITION =
47
59
  const MAYBE_RELATIVE =
48
60
  /\]\([\t ]*<?(?![a-z][\d+.a-z-]*:|\/|#)|^ {0,3}\[[^\]\n]+\]:[\t ]*<?(?![a-z][\d+.a-z-]*:|\/|#)|\shref=["'](?![a-z][\d+.a-z-]*:|\/|#)/imu;
49
61
 
62
+ /**
63
+ * {@link MAYBE_RELATIVE}, or a root-relative target (`/x`, never `//host`):
64
+ * the gate when root-relative links are rewritten too — a base or basePath is
65
+ * set, or the page sits under a locale prefix.
66
+ */
67
+ const MAYBE_PAGE_LINK =
68
+ /\]\([\t ]*<?(?![a-z][\d+.a-z-]*:|\/\/|#)|^ {0,3}\[[^\]\n]+\]:[\t ]*<?(?![a-z][\d+.a-z-]*:|\/\/|#)|\shref=["'](?![a-z][\d+.a-z-]*:|\/\/|#)/imu;
69
+
70
+ /**
71
+ * A path whose final segment carries a file extension — a `public/` asset or
72
+ * a raw `.md` twin, which the rendered page neither bases nor localizes (see
73
+ * `markdown/base-links.ts`).
74
+ */
75
+ const ASSET_PATH = /\.[\da-z]+$/iu;
76
+
50
77
  interface Splice {
51
78
  column: number;
52
79
  length: number;
@@ -124,7 +151,7 @@ const collectSplices = (
124
151
  export const relativeLinkRewriter = (
125
152
  project: BlumeProject
126
153
  ): RelativeLinkRewriter => {
127
- const { deployment, i18n } = project.config;
154
+ const { basePath, deployment, i18n } = project.config;
128
155
  const deployBase = normalizeBasePath(deployment.options.base);
129
156
  const localeTokens = i18n
130
157
  ? ["$", ...i18n.locales.map((locale) => locale.code)]
@@ -137,11 +164,20 @@ export const relativeLinkRewriter = (
137
164
  ])
138
165
  );
139
166
  const routes = new Set(project.manifest.routes.map((route) => route.path));
167
+ const localeByRoute = new Map(
168
+ project.manifest.routes.map((route) => [route.path, route.locale])
169
+ );
140
170
 
141
- return (text, page) => {
171
+ /**
172
+ * A page's relative-link resolver; `undefined` when the page has no file
173
+ * to resolve its links from.
174
+ */
175
+ const relativeRoute = (
176
+ page: LinkedPage
177
+ ): ((target: string) => string | undefined) | undefined => {
142
178
  const { sourcePath } = page;
143
- if (!sourcePath || !MAYBE_RELATIVE.test(text)) {
144
- return text;
179
+ if (!sourcePath) {
180
+ return;
145
181
  }
146
182
  const from: RelativeLinkBase = {
147
183
  isIndex: isIndexFileName(basename(sourcePath), localeTokens),
@@ -150,12 +186,44 @@ export const relativeLinkRewriter = (
150
186
  const navPath = navPathBySource.get(sourcePath) ?? basename(sourcePath);
151
187
  const resolveFile = (path: string): string | undefined =>
152
188
  routeOfLinkedFile(fileRoutes, { navPath, sourcePath }, path);
153
- const routed = (target: string): string | undefined => {
189
+ return (target) => {
154
190
  const route = resolveRelativeHref(target, from, resolveFile, (path) =>
155
191
  routes.has(path)
156
192
  );
157
193
  return route === undefined ? undefined : withBasePath(deployBase, route);
158
194
  };
195
+ };
196
+
197
+ return (text, page) => {
198
+ const locale = localeByRoute.get(page.route);
199
+ // The page's locale moves root links only when it has a URL prefix.
200
+ const localize =
201
+ i18n && locale !== undefined && localePrefix(locale, i18n) !== ""
202
+ ? { basePath, deployBase, i18n, locale, routes }
203
+ : undefined;
204
+ // A root-relative link only changes when the site is mounted under a
205
+ // prefix or the page reads in a prefixed locale.
206
+ const rootLinks =
207
+ deployBase !== "" || basePath !== "" || localize !== undefined;
208
+ if (
209
+ !(page.sourcePath || rootLinks) ||
210
+ !(rootLinks ? MAYBE_PAGE_LINK : MAYBE_RELATIVE).test(text)
211
+ ) {
212
+ return text;
213
+ }
214
+ const relative = relativeRoute(page);
215
+ // What the rendered page makes of `/guide`: based, then moved into the
216
+ // page's locale when that route is served.
217
+ const rooted = (target: string): string | undefined => {
218
+ if (ASSET_PATH.test(target.replace(/[#?].*$/u, ""))) {
219
+ return undefined;
220
+ }
221
+ const based = withComposedBasePath(deployBase, basePath, target);
222
+ const href = localize ? localizeHref(based, localize) : based;
223
+ return href === target ? undefined : href;
224
+ };
225
+ const routed = (target: string): string | undefined =>
226
+ isInternalPath(target) ? rooted(target) : relative?.(target);
159
227
 
160
228
  const lines = text.split("\n");
161
229
  const splices = collectSplices(text, lines, routed);
@@ -0,0 +1,416 @@
1
+ import type { EvaluatedValue } from "./component-markdown.ts";
2
+
3
+ /**
4
+ * Read an MDX attribute expression (`prop={...}`) as literal data, without
5
+ * running it. The agent Markdown downleveler converts components on every
6
+ * page it is handed — plain `.md` pages Astro never executes, and content a
7
+ * CMS or a release feed supplies — so an expression is parsed, never
8
+ * evaluated: only data a reader could see by looking at it is recovered.
9
+ *
10
+ * Accepted: string, number, boolean, `null`, and `undefined` literals; array
11
+ * and object literals, nested; unary minus on a number; template literals
12
+ * whose substitutions are themselves accepted; and `frontmatter.<path>`
13
+ * member access (dot steps, or bracketed string and number literals),
14
+ * resolved against the page's front matter the way `prop={frontmatter.status}`
15
+ * resolves when Astro renders the page. Comments are skipped. Anything else —
16
+ * a call, an operator, a spread, a shorthand property, any other name — is
17
+ * not static, and the caller keeps the component's JSX as written.
18
+ */
19
+
20
+ /** The outcome of reading an expression: its value, or `ok: false`. */
21
+ export interface StaticExpressionResult {
22
+ ok: boolean;
23
+ value: EvaluatedValue;
24
+ }
25
+
26
+ /**
27
+ * Stop reading: the expression isn't literal data. The throw unwinds the
28
+ * recursive descent to {@link readStaticExpression}, which reports it.
29
+ */
30
+ const notStatic = (): never => {
31
+ throw new Error("not a static expression");
32
+ };
33
+
34
+ const isText = (value: EvaluatedValue): value is string =>
35
+ typeof value === "string";
36
+
37
+ const isNumber = (value: EvaluatedValue): value is number =>
38
+ typeof value === "number";
39
+
40
+ /** A nested map of values: an object literal or a front-matter mapping. */
41
+ const isValueMap = (
42
+ value: EvaluatedValue
43
+ ): value is { [key: string]: EvaluatedValue } =>
44
+ typeof value === "object" &&
45
+ value !== null &&
46
+ !Array.isArray(value) &&
47
+ !(value instanceof Date);
48
+
49
+ /** Whitespace, a line comment, or a block comment. */
50
+ const SPACE = /\s+|\/\/[^\n\r]*|\/\*[\s\S]*?\*\//uy;
51
+
52
+ /** An IdentifierName (ASCII): object keys, member names, keywords. */
53
+ const IDENTIFIER = /[$A-Z_a-z][\w$]*/uy;
54
+
55
+ /**
56
+ * A numeric literal: hex, octal, and binary forms, or a decimal with an
57
+ * optional fraction and exponent, `_` separators allowed between digits. A
58
+ * legacy octal (`017`) matches only its leading `0`, and the check on the
59
+ * following character rejects it, as strict-mode JavaScript does.
60
+ */
61
+ const NUMBER =
62
+ /0[Xx][\dA-Fa-f](?:_?[\dA-Fa-f])*|0[Oo][0-7](?:_?[0-7])*|0[Bb][01](?:_?[01])*|(?:(?:0|[1-9](?:_?\d)*)(?:\.(?:\d(?:_?\d)*)?)?|\.\d(?:_?\d)*)(?:[Ee][+-]?\d(?:_?\d)*)?/uy;
63
+
64
+ /** A character that may not directly follow a number (`1n`, `1px`, `017`). */
65
+ const AFTER_NUMBER = /[\w$.]/u;
66
+
67
+ const DIGIT = /\d/u;
68
+ const HEX_DIGITS = /^[\dA-Fa-f]+$/u;
69
+
70
+ /** A canonical array index: `0`, `12`, never `01`. */
71
+ const INDEX = /^(?:0|[1-9]\d*)$/u;
72
+
73
+ /** The largest Unicode code point, the cap on a `\u{…}` escape. */
74
+ const MAX_CODE_POINT = 0x10_ff_ff;
75
+
76
+ /** U+2028 and U+2029, the line terminators outside ASCII. */
77
+ const LINE_SEPARATORS = new Set([
78
+ String.fromCodePoint(0x20_28),
79
+ String.fromCodePoint(0x20_29),
80
+ ]);
81
+
82
+ const SIMPLE_ESCAPES = new Map([
83
+ ["b", "\b"],
84
+ ["f", "\f"],
85
+ ["n", "\n"],
86
+ ["r", "\r"],
87
+ ["t", "\t"],
88
+ ["v", "\v"],
89
+ ]);
90
+
91
+ /** A key that, set through a literal, would replace the object's prototype. */
92
+ const PROTO_KEY = "__proto__";
93
+
94
+ /**
95
+ * `base[key]` on a front-matter value, own data only: a missing key reads as
96
+ * `undefined` (as it does at render time), stepping into `null` or
97
+ * `undefined` is not static (it would throw), and inherited members —
98
+ * methods, `constructor` — are never reached.
99
+ */
100
+ const member = (base: EvaluatedValue, key: string): EvaluatedValue => {
101
+ if (base === null || base === undefined) {
102
+ return notStatic();
103
+ }
104
+ if (isText(base) || Array.isArray(base)) {
105
+ if (key === "length") {
106
+ return base.length;
107
+ }
108
+ return INDEX.test(key) ? base[Number(key)] : undefined;
109
+ }
110
+ return isValueMap(base) && Object.hasOwn(base, key) ? base[key] : undefined;
111
+ };
112
+
113
+ /** A recursive-descent reader over one expression's source. */
114
+ class Reader {
115
+ readonly frontmatter: Record<string, EvaluatedValue> | undefined;
116
+ pos = 0;
117
+ readonly source: string;
118
+
119
+ constructor(
120
+ source: string,
121
+ frontmatter: Record<string, EvaluatedValue> | undefined
122
+ ) {
123
+ this.source = source;
124
+ this.frontmatter = frontmatter;
125
+ }
126
+
127
+ /** The character under the cursor (`""` at the end). */
128
+ get char(): string {
129
+ return this.source.charAt(this.pos);
130
+ }
131
+
132
+ get done(): boolean {
133
+ return this.pos >= this.source.length;
134
+ }
135
+
136
+ /** Match a sticky pattern at the cursor and consume it. */
137
+ match(pattern: RegExp): string | undefined {
138
+ pattern.lastIndex = this.pos;
139
+ const found = pattern.exec(this.source)?.[0];
140
+ if (found !== undefined) {
141
+ this.pos += found.length;
142
+ }
143
+ return found;
144
+ }
145
+
146
+ /** Skip whitespace and comments. */
147
+ space(): void {
148
+ let skipped = this.match(SPACE);
149
+ while (skipped !== undefined) {
150
+ skipped = this.match(SPACE);
151
+ }
152
+ }
153
+
154
+ /** Consume `expected` (after any space), or stop. */
155
+ expect(expected: string): void {
156
+ this.space();
157
+ if (!this.source.startsWith(expected, this.pos)) {
158
+ notStatic();
159
+ }
160
+ this.pos += expected.length;
161
+ }
162
+
163
+ value(): EvaluatedValue {
164
+ this.space();
165
+ const { char } = this;
166
+ if (char === "{") {
167
+ return this.object();
168
+ }
169
+ if (char === "[") {
170
+ return this.array();
171
+ }
172
+ if (char === '"' || char === "'") {
173
+ return this.string(char);
174
+ }
175
+ if (char === "`") {
176
+ return this.template();
177
+ }
178
+ if (char === "-") {
179
+ this.pos += 1;
180
+ // `--1` is a decrement, not two negations.
181
+ const operand = this.char === "-" ? notStatic() : this.value();
182
+ return isNumber(operand) ? -operand : notStatic();
183
+ }
184
+ return this.number() ?? this.name();
185
+ }
186
+
187
+ number(): number | undefined {
188
+ const text = this.match(NUMBER);
189
+ if (text === undefined) {
190
+ return;
191
+ }
192
+ if (
193
+ AFTER_NUMBER.test(this.char) ||
194
+ (text === "0" && DIGIT.test(this.char))
195
+ ) {
196
+ return notStatic();
197
+ }
198
+ return Number(text.replaceAll("_", ""));
199
+ }
200
+
201
+ /** A keyword literal, or `frontmatter` and its member steps. */
202
+ name(): EvaluatedValue {
203
+ const name = this.match(IDENTIFIER);
204
+ if (name === "true") {
205
+ return true;
206
+ }
207
+ if (name === "false") {
208
+ return false;
209
+ }
210
+ if (name === "null") {
211
+ return null;
212
+ }
213
+ if (name === "undefined") {
214
+ return undefined;
215
+ }
216
+ if (name !== "frontmatter") {
217
+ return notStatic();
218
+ }
219
+ let current: EvaluatedValue = this.frontmatter;
220
+ for (;;) {
221
+ this.space();
222
+ if (this.char === ".") {
223
+ this.pos += 1;
224
+ this.space();
225
+ current = member(current, this.match(IDENTIFIER) ?? notStatic());
226
+ } else if (this.char === "[") {
227
+ this.pos += 1;
228
+ const key = this.value();
229
+ this.expect("]");
230
+ current = member(
231
+ current,
232
+ isText(key) || isNumber(key) ? String(key) : notStatic()
233
+ );
234
+ } else {
235
+ return current;
236
+ }
237
+ }
238
+ }
239
+
240
+ object(): EvaluatedValue {
241
+ this.pos += 1;
242
+ const out: { [key: string]: EvaluatedValue } = {};
243
+ for (;;) {
244
+ this.space();
245
+ if (this.char === "}") {
246
+ this.pos += 1;
247
+ return out;
248
+ }
249
+ const key = this.key();
250
+ this.expect(":");
251
+ out[key] = this.value();
252
+ this.space();
253
+ if (this.char === ",") {
254
+ this.pos += 1;
255
+ } else if (this.char !== "}") {
256
+ return notStatic();
257
+ }
258
+ }
259
+ }
260
+
261
+ /** An object key: a name, a string, or a number (as its canonical string). */
262
+ key(): string {
263
+ const { char } = this;
264
+ let key: string;
265
+ if (char === '"' || char === "'") {
266
+ key = this.string(char);
267
+ } else {
268
+ const number = this.number();
269
+ key =
270
+ number === undefined
271
+ ? (this.match(IDENTIFIER) ?? notStatic())
272
+ : String(number);
273
+ }
274
+ // `{ __proto__: … }` sets the prototype rather than a property.
275
+ return key === PROTO_KEY ? notStatic() : key;
276
+ }
277
+
278
+ array(): EvaluatedValue[] {
279
+ this.pos += 1;
280
+ const out: EvaluatedValue[] = [];
281
+ for (;;) {
282
+ this.space();
283
+ if (this.char === "]") {
284
+ this.pos += 1;
285
+ return out;
286
+ }
287
+ out.push(this.value());
288
+ this.space();
289
+ if (this.char === ",") {
290
+ this.pos += 1;
291
+ } else if (this.char !== "]") {
292
+ return notStatic();
293
+ }
294
+ }
295
+ }
296
+
297
+ string(quote: string): string {
298
+ this.pos += 1;
299
+ let out = "";
300
+ for (;;) {
301
+ const { char } = this;
302
+ if (this.done || char === "\n" || char === "\r") {
303
+ return notStatic();
304
+ }
305
+ this.pos += 1;
306
+ if (char === quote) {
307
+ return out;
308
+ }
309
+ out += char === "\\" ? this.escape() : char;
310
+ }
311
+ }
312
+
313
+ template(): string {
314
+ this.pos += 1;
315
+ let out = "";
316
+ for (;;) {
317
+ if (this.done) {
318
+ return notStatic();
319
+ }
320
+ const { char } = this;
321
+ this.pos += 1;
322
+ if (char === "`") {
323
+ return out;
324
+ }
325
+ if (char === "\\") {
326
+ out += this.escape();
327
+ } else if (char === "$" && this.char === "{") {
328
+ this.pos += 1;
329
+ out += String(this.value());
330
+ this.expect("}");
331
+ } else if (char === "\r") {
332
+ // A raw line break in a template reads as `\n`, `\r\n` included.
333
+ if (this.char === "\n") {
334
+ this.pos += 1;
335
+ }
336
+ out += "\n";
337
+ } else {
338
+ out += char;
339
+ }
340
+ }
341
+ }
342
+
343
+ /** What an escape after `\` stands for, under strict-mode rules. */
344
+ escape(): string {
345
+ if (this.done) {
346
+ return notStatic();
347
+ }
348
+ const { char } = this;
349
+ this.pos += 1;
350
+ if (DIGIT.test(char)) {
351
+ // `\0` is NUL; octal escapes, `\8`, and `\9` are errors in strict code.
352
+ return char === "0" && !DIGIT.test(this.char) ? "\0" : notStatic();
353
+ }
354
+ if (char === "x") {
355
+ return String.fromCodePoint(this.hex(2));
356
+ }
357
+ if (char === "u") {
358
+ return String.fromCodePoint(
359
+ this.char === "{" ? this.codePoint() : this.hex(4)
360
+ );
361
+ }
362
+ if (char === "\r") {
363
+ // A line continuation: `\` and the line break stand for nothing.
364
+ if (this.char === "\n") {
365
+ this.pos += 1;
366
+ }
367
+ return "";
368
+ }
369
+ if (char === "\n" || LINE_SEPARATORS.has(char)) {
370
+ return "";
371
+ }
372
+ return SIMPLE_ESCAPES.get(char) ?? char;
373
+ }
374
+
375
+ /** Exactly `length` hex digits, as a code point. */
376
+ hex(length: number): number {
377
+ const digits = this.source.slice(this.pos, this.pos + length);
378
+ if (digits.length !== length || !HEX_DIGITS.test(digits)) {
379
+ return notStatic();
380
+ }
381
+ this.pos += length;
382
+ return Number.parseInt(digits, 16);
383
+ }
384
+
385
+ /** A braced `\u{…}` code point, the cursor on its `{`. */
386
+ codePoint(): number {
387
+ const end = this.source.indexOf("}", this.pos);
388
+ const digits = end === -1 ? "" : this.source.slice(this.pos + 1, end);
389
+ const code = HEX_DIGITS.test(digits) ? Number.parseInt(digits, 16) : -1;
390
+ if (code < 0 || code > MAX_CODE_POINT) {
391
+ return notStatic();
392
+ }
393
+ this.pos = end + 1;
394
+ return code;
395
+ }
396
+ }
397
+
398
+ /**
399
+ * Read `raw` — an attribute expression's source — as static data, with
400
+ * `frontmatter` (the page's front matter, when the caller has it) behind
401
+ * `frontmatter.<path>` references. Never executes anything.
402
+ */
403
+ export const readStaticExpression = (
404
+ raw: string,
405
+ frontmatter?: Record<string, EvaluatedValue>
406
+ ): StaticExpressionResult => {
407
+ const reader = new Reader(raw, frontmatter);
408
+ try {
409
+ const value = reader.value();
410
+ reader.space();
411
+ return reader.done ? { ok: true, value } : { ok: false, value: undefined };
412
+ } catch {
413
+ // Not literal data (or nested past the stack): keep the JSX as written.
414
+ return { ok: false, value: undefined };
415
+ }
416
+ };