blume 2.0.1 → 2.0.3

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 (492) hide show
  1. package/CHANGELOG.md +147 -0
  2. package/dist/cli/{chunk-qwsrynx5.js → chunk-1d7ve1dm.js} +38 -19
  3. package/dist/cli/{chunk-qwsrynx5.js.map → chunk-1d7ve1dm.js.map} +4 -4
  4. package/dist/cli/{chunk-s6jhgk0q.js → chunk-2eytanqx.js} +2 -2
  5. package/dist/cli/{chunk-kdp5q7ke.js → chunk-2hsdwb9n.js} +18 -19
  6. package/dist/cli/{chunk-kdp5q7ke.js.map → chunk-2hsdwb9n.js.map} +2 -2
  7. package/dist/cli/{chunk-hdpx1tax.js → chunk-2z928egk.js} +5 -5
  8. package/dist/cli/{chunk-jwyddg7y.js → chunk-35d4wj9f.js} +30 -24
  9. package/dist/cli/chunk-35d4wj9f.js.map +15 -0
  10. package/dist/cli/{chunk-ah61y8py.js → chunk-364znk6q.js} +2 -2
  11. package/dist/cli/{chunk-jts8mvcz.js → chunk-3em5wd2y.js} +25 -6
  12. package/dist/cli/{chunk-jts8mvcz.js.map → chunk-3em5wd2y.js.map} +3 -3
  13. package/dist/cli/{chunk-s1p84fyh.js → chunk-5f86nr5m.js} +63 -22
  14. package/dist/cli/chunk-5f86nr5m.js.map +11 -0
  15. package/dist/cli/{chunk-2hn4b8z7.js → chunk-5m5nmvyq.js} +170 -76
  16. package/dist/cli/chunk-5m5nmvyq.js.map +13 -0
  17. package/dist/cli/{chunk-epjnccmv.js → chunk-5xvm6tfj.js} +18 -15
  18. package/dist/cli/chunk-5xvm6tfj.js.map +10 -0
  19. package/dist/cli/{chunk-fz5wtpmh.js → chunk-6dsbexzp.js} +18 -15
  20. package/dist/cli/chunk-6dsbexzp.js.map +10 -0
  21. package/dist/cli/{chunk-vtk4a6dg.js → chunk-8ktnccpt.js} +1 -1
  22. package/dist/cli/{chunk-6hsn950k.js → chunk-9t7a85s3.js} +178 -48
  23. package/dist/cli/chunk-9t7a85s3.js.map +10 -0
  24. package/dist/cli/{chunk-fxypxtvm.js → chunk-a58773jm.js} +2 -2
  25. package/dist/cli/{chunk-mb2919y2.js → chunk-acanzt5p.js} +17 -6
  26. package/dist/cli/chunk-acanzt5p.js.map +10 -0
  27. package/dist/cli/{chunk-27g6wdth.js → chunk-akbpwfxc.js} +90 -26
  28. package/dist/cli/chunk-akbpwfxc.js.map +10 -0
  29. package/dist/cli/{chunk-wm7js3j9.js → chunk-b07cmahc.js} +2 -2
  30. package/dist/cli/{chunk-wgm7m9qk.js → chunk-c8chx29p.js} +179 -51
  31. package/dist/cli/chunk-c8chx29p.js.map +36 -0
  32. package/dist/cli/{chunk-qs4q5p4e.js → chunk-crgn1q09.js} +32 -14
  33. package/dist/cli/chunk-crgn1q09.js.map +10 -0
  34. package/dist/cli/chunk-e04dxsz1.js +39 -0
  35. package/dist/cli/chunk-e04dxsz1.js.map +10 -0
  36. package/dist/cli/{chunk-yt5n7ppj.js → chunk-ey84smr6.js} +17 -7
  37. package/dist/cli/chunk-ey84smr6.js.map +10 -0
  38. package/dist/cli/{chunk-f2z5v128.js → chunk-g4hq16wv.js} +14 -15
  39. package/dist/cli/{chunk-f2z5v128.js.map → chunk-g4hq16wv.js.map} +2 -2
  40. package/dist/cli/{chunk-fa25z98p.js → chunk-ga0pf4aj.js} +9 -8
  41. package/dist/cli/chunk-ga0pf4aj.js.map +11 -0
  42. package/dist/cli/{chunk-kpf8rrjc.js → chunk-hm3vjy5s.js} +109 -58
  43. package/dist/cli/chunk-hm3vjy5s.js.map +19 -0
  44. package/dist/cli/{chunk-fs23ddbb.js → chunk-j85vccga.js} +625 -750
  45. package/dist/cli/chunk-j85vccga.js.map +36 -0
  46. package/dist/cli/{chunk-ch6g3ar0.js → chunk-p3v96n38.js} +6 -6
  47. package/dist/cli/{chunk-ch6g3ar0.js.map → chunk-p3v96n38.js.map} +3 -3
  48. package/dist/cli/{chunk-q5163e60.js → chunk-p73c0m7w.js} +21 -19
  49. package/dist/cli/chunk-p73c0m7w.js.map +11 -0
  50. package/dist/cli/{chunk-dh8cwk36.js → chunk-pehfxfta.js} +24 -9
  51. package/dist/cli/chunk-pehfxfta.js.map +10 -0
  52. package/dist/cli/{chunk-qkqwkpte.js → chunk-pv29h0wf.js} +3688 -1313
  53. package/dist/cli/chunk-pv29h0wf.js.map +190 -0
  54. package/dist/cli/{chunk-6vm74dry.js → chunk-q58y5e6a.js} +10 -10
  55. package/dist/cli/{chunk-6vm74dry.js.map → chunk-q58y5e6a.js.map} +3 -3
  56. package/dist/cli/{chunk-zxcczpyx.js → chunk-r20tn01b.js} +1 -1
  57. package/dist/cli/{chunk-m3vmjgmq.js → chunk-r9rcc4w7.js} +16 -8
  58. package/dist/cli/chunk-r9rcc4w7.js.map +10 -0
  59. package/dist/cli/{chunk-5shv93fd.js → chunk-tkacnehg.js} +2 -2
  60. package/dist/cli/{chunk-zxh4d9vy.js → chunk-tzmab476.js} +4 -4
  61. package/dist/cli/{chunk-yw7dm696.js → chunk-vg9r4eb9.js} +24 -11
  62. package/dist/cli/chunk-vg9r4eb9.js.map +14 -0
  63. package/dist/cli/{chunk-79jhk4py.js → chunk-wrr3j9w9.js} +260 -132
  64. package/dist/cli/chunk-wrr3j9w9.js.map +35 -0
  65. package/dist/cli/{chunk-6crbhc3x.js → chunk-yfyb25rh.js} +54 -27
  66. package/dist/cli/chunk-yfyb25rh.js.map +14 -0
  67. package/dist/cli/index.js +162 -35
  68. package/dist/cli/index.js.map +4 -4
  69. package/dist/types/ai/agent-surface.d.ts +32 -0
  70. package/dist/types/ai/api-catalog.d.ts +7 -1
  71. package/dist/types/ai/ask-context.d.ts +7 -0
  72. package/dist/types/ai/component-markdown.d.ts +4 -4
  73. package/dist/types/ai/link-headers.d.ts +8 -1
  74. package/dist/types/ai/openapi-components.d.ts +5 -2
  75. package/dist/types/ai/relative-links.d.ts +11 -4
  76. package/dist/types/ai/skills.d.ts +4 -1
  77. package/dist/types/ai/static-expression.d.ts +28 -0
  78. package/dist/types/ai/tar.d.ts +1 -3
  79. package/dist/types/analytics/databuddy.d.ts +43 -0
  80. package/dist/types/analytics/index.d.ts +4 -0
  81. package/dist/types/analytics/one-dollar-stats.d.ts +48 -0
  82. package/dist/types/analytics/schema.d.ts +28 -0
  83. package/dist/types/astro/integration.d.ts +3 -2
  84. package/dist/types/cli/env.d.ts +5 -0
  85. package/dist/types/cli/init/scaffold.d.ts +19 -3
  86. package/dist/types/cli/init/starter-spec.d.ts +11 -0
  87. package/dist/types/core/base-path.d.ts +24 -1
  88. package/dist/types/core/config-input.d.ts +13 -11
  89. package/dist/types/core/config.d.ts +2 -2
  90. package/dist/types/core/directive-diagnostics.d.ts +12 -0
  91. package/dist/types/core/graph.d.ts +2 -0
  92. package/dist/types/core/heading-markers.d.ts +5 -7
  93. package/dist/types/core/i18n-ui.d.ts +31 -0
  94. package/dist/types/core/i18n.d.ts +9 -1
  95. package/dist/types/core/last-modified.d.ts +10 -0
  96. package/dist/types/core/links.d.ts +3 -1
  97. package/dist/types/core/load-module.d.ts +10 -0
  98. package/dist/types/core/locale-links.d.ts +12 -2
  99. package/dist/types/core/meta.d.ts +13 -1
  100. package/dist/types/core/nav-diagnostics.d.ts +10 -0
  101. package/dist/types/core/navigation.d.ts +38 -0
  102. package/dist/types/core/ordering-prefix.d.ts +4 -0
  103. package/dist/types/core/safe-links.d.ts +3 -1
  104. package/dist/types/core/schema.d.ts +59 -13
  105. package/dist/types/core/sources/github-releases.d.ts +5 -0
  106. package/dist/types/core/sources/lower.d.ts +38 -13
  107. package/dist/types/core/sources/normalize.d.ts +23 -2
  108. package/dist/types/core/sources/remote.d.ts +11 -1
  109. package/dist/types/core/sources/resolve.d.ts +12 -0
  110. package/dist/types/core/sources/types.d.ts +31 -0
  111. package/dist/types/core/sources/watch.d.ts +12 -5
  112. package/dist/types/core/standard-schema.d.ts +5 -0
  113. package/dist/types/core/types.d.ts +9 -0
  114. package/dist/types/deploy/adapters/node.d.ts +5 -2
  115. package/dist/types/deploy/adapters/types.d.ts +7 -0
  116. package/dist/types/deploy/artifacts.d.ts +6 -4
  117. package/dist/types/deploy/cloudflare-negotiation.d.ts +3 -2
  118. package/dist/types/deploy/headers.d.ts +36 -7
  119. package/dist/types/deploy/node-headers.d.ts +43 -8
  120. package/dist/types/deploy/platforms/netlify.d.ts +27 -2
  121. package/dist/types/deploy/platforms/node.d.ts +6 -5
  122. package/dist/types/deploy/platforms/types.d.ts +15 -0
  123. package/dist/types/deploy/platforms/vercel.d.ts +3 -2
  124. package/dist/types/deploy/redirects.d.ts +31 -15
  125. package/dist/types/deploy/vercel-negotiation.d.ts +3 -2
  126. package/dist/types/markdown/directives.d.ts +62 -0
  127. package/dist/types/markdown/features.d.ts +21 -0
  128. package/dist/types/markdown/mdast.d.ts +63 -0
  129. package/dist/types/openapi/asyncapi.d.ts +4 -2
  130. package/dist/types/openapi/model.d.ts +20 -6
  131. package/dist/types/search/sync/algolia.d.ts +3 -1
  132. package/docs/01-quickstart.mdx +3 -2
  133. package/docs/02-deployment.mdx +20 -9
  134. package/docs/08-faq.mdx +10 -3
  135. package/docs/advanced/changelog.mdx +1 -1
  136. package/docs/advanced/custom-pages.mdx +7 -5
  137. package/docs/cli/audit.mdx +19 -3
  138. package/docs/cli/doctor.mdx +2 -2
  139. package/docs/cli/evals.mdx +4 -4
  140. package/docs/cli/index.mdx +4 -1
  141. package/docs/cli/translate.mdx +4 -4
  142. package/docs/cli/version.mdx +1 -1
  143. package/docs/configuration/analytics.mdx +42 -2
  144. package/docs/configuration/assistant.mdx +1 -1
  145. package/docs/configuration/customization.mdx +6 -3
  146. package/docs/configuration/index.mdx +5 -3
  147. package/docs/configuration/search.mdx +2 -2
  148. package/docs/content/components.mdx +2 -2
  149. package/docs/content/frontmatter.mdx +5 -1
  150. package/docs/content/i18n.mdx +3 -1
  151. package/docs/content/index.mdx +8 -4
  152. package/docs/content/islands.mdx +1 -1
  153. package/docs/content/meta.mdx +7 -3
  154. package/docs/content/navigation.mdx +29 -4
  155. package/docs/content/sources.mdx +18 -16
  156. package/docs/content/syntax.mdx +14 -0
  157. package/docs/content/versioning.mdx +2 -1
  158. package/docs/discoverability/agent-discovery.mdx +22 -8
  159. package/docs/discoverability/index.mdx +4 -3
  160. package/docs/discoverability/llms-txt.mdx +2 -5
  161. package/docs/discoverability/markdown.mdx +6 -4
  162. package/docs/discoverability/mcp.mdx +4 -0
  163. package/docs/discoverability/metadata.mdx +3 -2
  164. package/docs/discoverability/open-graph.mdx +8 -4
  165. package/docs/discoverability/rss.mdx +4 -2
  166. package/docs/references/asyncapi.mdx +2 -2
  167. package/docs/references/graphql.mdx +2 -2
  168. package/docs/references/openapi.mdx +6 -4
  169. package/package.json +1 -1
  170. package/skills/blume-migrate/SKILL.md +6 -6
  171. package/skills/blume-migrate/references/docusaurus.md +6 -6
  172. package/skills/blume-migrate/references/fumadocs.md +1 -1
  173. package/skills/blume-migrate/references/mintlify.md +3 -3
  174. package/skills/blume-migrate/references/monorepo.md +1 -1
  175. package/skills/blume-migrate/references/nextra.md +2 -2
  176. package/skills/blume-migrate/references/starlight.md +5 -5
  177. package/skills/blume-migrate/scripts/mintlify-codemod.mjs +9 -4
  178. package/skills/blume-update-docs/references/audit-checklist.md +1 -1
  179. package/src/ai/agent-readability.ts +2 -2
  180. package/src/ai/agent-surface.ts +56 -0
  181. package/src/ai/ai-catalog.ts +6 -2
  182. package/src/ai/api/handlers.ts +2 -2
  183. package/src/ai/api/spec.ts +17 -4
  184. package/src/ai/api-catalog.ts +13 -3
  185. package/src/ai/ask-context.ts +14 -2
  186. package/src/ai/ask-data.ts +26 -12
  187. package/src/ai/changelog-markdown.ts +2 -2
  188. package/src/ai/component-markdown.ts +43 -37
  189. package/src/ai/link-headers.ts +22 -8
  190. package/src/ai/llms.ts +22 -10
  191. package/src/ai/markdown.ts +23 -11
  192. package/src/ai/mcp/discovery.ts +2 -2
  193. package/src/ai/mcp/query.ts +2 -2
  194. package/src/ai/mcp/server.ts +80 -9
  195. package/src/ai/openapi-components.ts +22 -6
  196. package/src/ai/relative-links.ts +99 -15
  197. package/src/ai/serializers.ts +2 -1
  198. package/src/ai/skills.ts +14 -1
  199. package/src/ai/static-expression.ts +416 -0
  200. package/src/ai/tar.ts +139 -12
  201. package/src/ai/visibility.ts +45 -14
  202. package/src/analytics/databuddy.ts +67 -0
  203. package/src/analytics/head.ts +8 -0
  204. package/src/analytics/index.ts +7 -0
  205. package/src/analytics/one-dollar-stats.ts +86 -0
  206. package/src/analytics/posthog.ts +21 -3
  207. package/src/analytics/schema.ts +4 -0
  208. package/src/astro/generate.ts +72 -28
  209. package/src/astro/include-hmr.ts +31 -12
  210. package/src/astro/include-refresh.ts +19 -8
  211. package/src/astro/integration.ts +108 -14
  212. package/src/astro/runtime-modules.ts +28 -13
  213. package/src/astro/templates.ts +139 -57
  214. package/src/audit/catalog.ts +2 -2
  215. package/src/audit/checks/assets.ts +25 -5
  216. package/src/audit/checks/content.ts +20 -2
  217. package/src/audit/checks/i18n.ts +46 -8
  218. package/src/audit/checks/indexability.ts +39 -19
  219. package/src/audit/checks/links.ts +22 -1
  220. package/src/audit/checks/llms.ts +6 -3
  221. package/src/audit/checks/network.ts +3 -1
  222. package/src/audit/checks/og-image.ts +10 -0
  223. package/src/audit/checks/robots.ts +6 -1
  224. package/src/audit/checks/sitemap.ts +56 -31
  225. package/src/audit/checks/social.ts +21 -2
  226. package/src/audit/crawl.ts +88 -15
  227. package/src/audit/graph.ts +3 -1
  228. package/src/audit/report.ts +54 -17
  229. package/src/audit/run.ts +13 -9
  230. package/src/audit/snapshot.ts +5 -0
  231. package/src/audit/types.ts +18 -0
  232. package/src/audit/url.ts +37 -6
  233. package/src/cli/args.ts +32 -0
  234. package/src/cli/build-failure.ts +50 -0
  235. package/src/cli/commands/audit.ts +11 -1
  236. package/src/cli/commands/build.ts +19 -5
  237. package/src/cli/commands/check.ts +2 -0
  238. package/src/cli/commands/doctor.ts +3 -1
  239. package/src/cli/commands/eval.ts +20 -18
  240. package/src/cli/commands/init.ts +15 -5
  241. package/src/cli/commands/preview.ts +15 -0
  242. package/src/cli/commands/sync.ts +2 -0
  243. package/src/cli/commands/translate.ts +18 -18
  244. package/src/cli/commands/upgrade.ts +11 -0
  245. package/src/cli/commands/validate.ts +3 -1
  246. package/src/cli/dev-lock.ts +157 -37
  247. package/src/cli/eject-scripts.ts +32 -7
  248. package/src/cli/env.ts +12 -1
  249. package/src/cli/init/scaffold.ts +117 -11
  250. package/src/cli/init/starter-spec.ts +235 -0
  251. package/src/cli/report-format.ts +11 -6
  252. package/src/components/colors.ts +19 -0
  253. package/src/components/content/AccordionItem.astro +26 -22
  254. package/src/components/content/Badge.astro +7 -12
  255. package/src/components/content/Card.astro +2 -2
  256. package/src/components/content/Component.astro +11 -5
  257. package/src/components/content/Expandable.astro +5 -1
  258. package/src/components/content/Frame.astro +2 -7
  259. package/src/components/content/GithubInfo.astro +12 -2
  260. package/src/components/content/Prompt.astro +2 -7
  261. package/src/components/content/Tab.astro +0 -1
  262. package/src/components/content/Tabs.astro +58 -5
  263. package/src/components/content/Tile.astro +1 -1
  264. package/src/components/content/Tooltip.astro +69 -7
  265. package/src/components/content/Tree.astro +7 -2
  266. package/src/components/content/TypeTable.astro +10 -5
  267. package/src/components/content/Update.astro +8 -2
  268. package/src/components/content/auto-type-table.ts +4 -1
  269. package/src/components/content/badge-color.ts +20 -0
  270. package/src/components/content/base-href.ts +14 -22
  271. package/src/components/content/inline-markdown.ts +27 -7
  272. package/src/components/copy-feedback.ts +35 -8
  273. package/src/components/islands/assistant.tsx +37 -6
  274. package/src/components/islands/base-path.ts +47 -10
  275. package/src/components/islands/hooks.ts +42 -21
  276. package/src/components/islands/webmcp.ts +16 -10
  277. package/src/components/layout/Banner.astro +23 -4
  278. package/src/components/layout/Breadcrumbs.astro +5 -2
  279. package/src/components/layout/DiscoveryLinks.astro +9 -5
  280. package/src/components/layout/Header.astro +23 -10
  281. package/src/components/layout/LanguageSwitcher.astro +2 -2
  282. package/src/components/layout/Logo.astro +5 -0
  283. package/src/components/layout/NavSelector.astro +8 -3
  284. package/src/components/layout/NavTabMenu.astro +133 -0
  285. package/src/components/layout/NavTree.astro +31 -12
  286. package/src/components/layout/NavTreeCache.astro +5 -2
  287. package/src/components/layout/NavTreeScript.astro +45 -5
  288. package/src/components/layout/PageActions.astro +4 -3
  289. package/src/components/layout/PageLayout.astro +28 -13
  290. package/src/components/layout/Pagination.astro +3 -3
  291. package/src/components/layout/ReferenceLayout.astro +4 -1
  292. package/src/components/layout/RootLayout.astro +25 -12
  293. package/src/components/layout/Search.astro +38 -14
  294. package/src/components/layout/VersionBanner.astro +2 -2
  295. package/src/components/layout/analytics-client.ts +14 -0
  296. package/src/components/layout/toc-active.ts +41 -0
  297. package/src/components/layout/toc-element.ts +8 -14
  298. package/src/components/openapi/ApiTagOperations.astro +2 -2
  299. package/src/components/openapi/AsyncApiOperation.astro +7 -4
  300. package/src/components/openapi/GraphqlChip.astro +2 -2
  301. package/src/components/openapi/GraphqlType.astro +11 -3
  302. package/src/components/openapi/MessageComposer.astro +1 -1
  303. package/src/components/openapi/Operation.astro +23 -5
  304. package/src/components/openapi/PanelTabs.astro +4 -1
  305. package/src/components/openapi/Playground.astro +8 -2
  306. package/src/components/openapi/RequestPanel.astro +13 -6
  307. package/src/components/openapi/SchemaProperty.astro +11 -33
  308. package/src/components/openapi/SchemaTable.astro +34 -52
  309. package/src/components/openapi/async.ts +38 -6
  310. package/src/components/openapi/helpers.ts +100 -12
  311. package/src/components/openapi/message-composer.ts +14 -1
  312. package/src/components/openapi/message-model.ts +12 -2
  313. package/src/components/openapi/message.ts +21 -3
  314. package/src/components/openapi/operation-model.ts +119 -17
  315. package/src/components/openapi/panel.ts +31 -5
  316. package/src/components/openapi/param-style.ts +181 -0
  317. package/src/components/openapi/playground-client.ts +110 -23
  318. package/src/components/openapi/playground-schema.ts +25 -6
  319. package/src/components/openapi/request.ts +192 -26
  320. package/src/components/openapi/schema-tree.ts +209 -0
  321. package/src/components/openapi/snippets.ts +113 -19
  322. package/src/components/openapi/validate-json.ts +1 -1
  323. package/src/components/openapi/ws-client.ts +18 -2
  324. package/src/core/base-path.ts +69 -8
  325. package/src/core/config-input.ts +13 -11
  326. package/src/core/config.ts +18 -4
  327. package/src/core/diagnostics.ts +219 -30
  328. package/src/core/directive-diagnostics.ts +99 -0
  329. package/src/core/frontmatter.ts +21 -18
  330. package/src/core/graph.ts +120 -11
  331. package/src/core/heading-markers.ts +5 -18
  332. package/src/core/i18n-ui.ts +39 -2
  333. package/src/core/i18n.ts +44 -5
  334. package/src/core/last-modified.ts +25 -3
  335. package/src/core/links.ts +7 -4
  336. package/src/core/load-module.ts +20 -0
  337. package/src/core/locale-links.ts +22 -18
  338. package/src/core/manifest.ts +3 -2
  339. package/src/core/meta.ts +89 -12
  340. package/src/core/nav-diagnostics.ts +56 -1
  341. package/src/core/navigation.ts +272 -83
  342. package/src/core/ordering-prefix.ts +27 -0
  343. package/src/core/project-graph.ts +50 -6
  344. package/src/core/safe-href.ts +53 -1
  345. package/src/core/safe-links.ts +11 -2
  346. package/src/core/schema.ts +94 -18
  347. package/src/core/sources/assets.ts +83 -41
  348. package/src/core/sources/contentful-rich-text.ts +28 -20
  349. package/src/core/sources/contentful.ts +25 -12
  350. package/src/core/sources/filesystem.ts +25 -3
  351. package/src/core/sources/github-releases.ts +131 -11
  352. package/src/core/sources/lexical.ts +30 -20
  353. package/src/core/sources/lower.ts +276 -56
  354. package/src/core/sources/mdx-remote.ts +52 -16
  355. package/src/core/sources/normalize.ts +456 -119
  356. package/src/core/sources/notion.ts +77 -34
  357. package/src/core/sources/obsidian.ts +40 -9
  358. package/src/core/sources/payload.ts +1 -0
  359. package/src/core/sources/portable-text.ts +85 -49
  360. package/src/core/sources/remote.ts +18 -2
  361. package/src/core/sources/resolve.ts +52 -33
  362. package/src/core/sources/sanity.ts +1 -0
  363. package/src/core/sources/strapi-blocks.ts +28 -19
  364. package/src/core/sources/strapi.ts +1 -0
  365. package/src/core/sources/types.ts +31 -0
  366. package/src/core/sources/watch.ts +20 -7
  367. package/src/core/standard-schema.ts +10 -6
  368. package/src/core/types.ts +9 -0
  369. package/src/core/ui-packs/ar.ts +13 -0
  370. package/src/core/ui-packs/bg.ts +13 -0
  371. package/src/core/ui-packs/bn.ts +13 -0
  372. package/src/core/ui-packs/ca.ts +13 -0
  373. package/src/core/ui-packs/cs.ts +13 -0
  374. package/src/core/ui-packs/da.ts +13 -0
  375. package/src/core/ui-packs/de.ts +13 -0
  376. package/src/core/ui-packs/el.ts +13 -0
  377. package/src/core/ui-packs/es.ts +13 -0
  378. package/src/core/ui-packs/fa.ts +13 -0
  379. package/src/core/ui-packs/fi.ts +13 -0
  380. package/src/core/ui-packs/fr.ts +13 -0
  381. package/src/core/ui-packs/he.ts +13 -0
  382. package/src/core/ui-packs/hi.ts +13 -0
  383. package/src/core/ui-packs/hr.ts +13 -0
  384. package/src/core/ui-packs/hu.ts +13 -0
  385. package/src/core/ui-packs/id.ts +13 -0
  386. package/src/core/ui-packs/it.ts +13 -0
  387. package/src/core/ui-packs/ja.ts +13 -0
  388. package/src/core/ui-packs/ko.ts +13 -0
  389. package/src/core/ui-packs/nl.ts +13 -0
  390. package/src/core/ui-packs/no.ts +13 -0
  391. package/src/core/ui-packs/pl.ts +13 -0
  392. package/src/core/ui-packs/pt-br.ts +13 -0
  393. package/src/core/ui-packs/pt.ts +13 -0
  394. package/src/core/ui-packs/ro.ts +13 -0
  395. package/src/core/ui-packs/ru.ts +13 -0
  396. package/src/core/ui-packs/sk.ts +13 -0
  397. package/src/core/ui-packs/sr.ts +13 -0
  398. package/src/core/ui-packs/sv.ts +13 -0
  399. package/src/core/ui-packs/th.ts +13 -0
  400. package/src/core/ui-packs/tr.ts +13 -0
  401. package/src/core/ui-packs/uk.ts +13 -0
  402. package/src/core/ui-packs/vi.ts +13 -0
  403. package/src/core/ui-packs/zh-tw.ts +13 -0
  404. package/src/core/ui-packs/zh.ts +13 -0
  405. package/src/core/version-cut.ts +69 -17
  406. package/src/core/versions.ts +4 -1
  407. package/src/deploy/adapters/node.ts +5 -2
  408. package/src/deploy/adapters/registry.ts +2 -1
  409. package/src/deploy/adapters/types.ts +13 -1
  410. package/src/deploy/artifacts.ts +52 -11
  411. package/src/deploy/cloudflare-negotiation.ts +27 -16
  412. package/src/deploy/headers.ts +75 -55
  413. package/src/deploy/node-headers.ts +148 -27
  414. package/src/deploy/platforms/cloudflare.ts +13 -3
  415. package/src/deploy/platforms/netlify.ts +83 -5
  416. package/src/deploy/platforms/node.ts +8 -5
  417. package/src/deploy/platforms/static.ts +2 -0
  418. package/src/deploy/platforms/types.ts +15 -0
  419. package/src/deploy/platforms/vercel.ts +10 -3
  420. package/src/deploy/redirects.ts +68 -21
  421. package/src/deploy/robots.ts +2 -2
  422. package/src/deploy/rss.ts +4 -3
  423. package/src/deploy/sitemap.ts +7 -5
  424. package/src/deploy/vercel-negotiation.ts +35 -3
  425. package/src/eval/agents.ts +10 -2
  426. package/src/eval/run.ts +25 -0
  427. package/src/markdown/base-links.ts +74 -29
  428. package/src/markdown/directives.ts +242 -36
  429. package/src/markdown/features.ts +17 -0
  430. package/src/markdown/index.ts +13 -9
  431. package/src/markdown/mdast.ts +5 -2
  432. package/src/markdown/relative-links.ts +15 -27
  433. package/src/markdown/route-snapshot.ts +37 -0
  434. package/src/og/card.ts +149 -9
  435. package/src/og/derive.ts +156 -5
  436. package/src/og/index.ts +1 -0
  437. package/src/openapi/asyncapi.ts +4 -2
  438. package/src/openapi/graphql-build.ts +28 -2
  439. package/src/openapi/model.ts +99 -27
  440. package/src/openapi/proxy.ts +63 -10
  441. package/src/openapi/render-mdx.ts +51 -3
  442. package/src/registry/eject.ts +297 -43
  443. package/src/search/adapters/version-scope.ts +30 -0
  444. package/src/search/documents.ts +89 -27
  445. package/src/search/popular.ts +2 -1
  446. package/src/search/sync/algolia.ts +36 -2
  447. package/src/seo/jsonld.ts +7 -3
  448. package/src/sources/registry.ts +5 -0
  449. package/src/theme/entry.ts +11 -4
  450. package/src/translate/agents.ts +6 -1
  451. package/src/translate/ledger.ts +26 -3
  452. package/src/translate/meta.ts +76 -24
  453. package/src/translate/run.ts +14 -8
  454. package/src/translate/validate.ts +14 -2
  455. package/src/translate/work-list.ts +91 -19
  456. package/src/upgrade/upgrade.ts +36 -4
  457. package/dist/cli/chunk-27g6wdth.js.map +0 -10
  458. package/dist/cli/chunk-2hn4b8z7.js.map +0 -12
  459. package/dist/cli/chunk-6crbhc3x.js.map +0 -14
  460. package/dist/cli/chunk-6hsn950k.js.map +0 -10
  461. package/dist/cli/chunk-79jhk4py.js.map +0 -35
  462. package/dist/cli/chunk-82bbrxdn.js +0 -51
  463. package/dist/cli/chunk-82bbrxdn.js.map +0 -10
  464. package/dist/cli/chunk-abh8yjkn.js +0 -31
  465. package/dist/cli/chunk-abh8yjkn.js.map +0 -10
  466. package/dist/cli/chunk-ce574jw2.js +0 -23
  467. package/dist/cli/chunk-ce574jw2.js.map +0 -10
  468. package/dist/cli/chunk-dh8cwk36.js.map +0 -10
  469. package/dist/cli/chunk-epjnccmv.js.map +0 -10
  470. package/dist/cli/chunk-fa25z98p.js.map +0 -11
  471. package/dist/cli/chunk-fs23ddbb.js.map +0 -35
  472. package/dist/cli/chunk-fz5wtpmh.js.map +0 -10
  473. package/dist/cli/chunk-jwyddg7y.js.map +0 -15
  474. package/dist/cli/chunk-kpf8rrjc.js.map +0 -19
  475. package/dist/cli/chunk-m3vmjgmq.js.map +0 -10
  476. package/dist/cli/chunk-mb2919y2.js.map +0 -10
  477. package/dist/cli/chunk-q5163e60.js.map +0 -11
  478. package/dist/cli/chunk-qkqwkpte.js.map +0 -182
  479. package/dist/cli/chunk-qs4q5p4e.js.map +0 -10
  480. package/dist/cli/chunk-s1p84fyh.js.map +0 -10
  481. package/dist/cli/chunk-wgm7m9qk.js.map +0 -36
  482. package/dist/cli/chunk-yt5n7ppj.js.map +0 -10
  483. package/dist/cli/chunk-yw7dm696.js.map +0 -14
  484. /package/dist/cli/{chunk-s6jhgk0q.js.map → chunk-2eytanqx.js.map} +0 -0
  485. /package/dist/cli/{chunk-hdpx1tax.js.map → chunk-2z928egk.js.map} +0 -0
  486. /package/dist/cli/{chunk-ah61y8py.js.map → chunk-364znk6q.js.map} +0 -0
  487. /package/dist/cli/{chunk-vtk4a6dg.js.map → chunk-8ktnccpt.js.map} +0 -0
  488. /package/dist/cli/{chunk-fxypxtvm.js.map → chunk-a58773jm.js.map} +0 -0
  489. /package/dist/cli/{chunk-wm7js3j9.js.map → chunk-b07cmahc.js.map} +0 -0
  490. /package/dist/cli/{chunk-zxcczpyx.js.map → chunk-r20tn01b.js.map} +0 -0
  491. /package/dist/cli/{chunk-5shv93fd.js.map → chunk-tkacnehg.js.map} +0 -0
  492. /package/dist/cli/{chunk-zxh4d9vy.js.map → chunk-tzmab476.js.map} +0 -0
@@ -1,8 +1,15 @@
1
1
  import { basename } from "pathe";
2
2
 
3
- import { normalizeBasePath, withBasePath } from "../core/base-path.ts";
3
+ import {
4
+ isInternalPath,
5
+ mountBasePath,
6
+ normalizeBasePath,
7
+ withAuthoredBasePath,
8
+ withBasePath,
9
+ } from "../core/base-path.ts";
4
10
  import { nextFenceState } from "../core/code-fences.ts";
5
11
  import type { FenceState } from "../core/code-fences.ts";
12
+ import { localePrefix } from "../core/i18n.ts";
6
13
  import {
7
14
  buildFileRouteIndex,
8
15
  isIndexFileName,
@@ -10,19 +17,27 @@ import {
10
17
  routeOfLinkedFile,
11
18
  } from "../core/links.ts";
12
19
  import type { RelativeLinkBase } from "../core/links.ts";
20
+ import { localizeHref, servesRoute } from "../core/locale-links.ts";
13
21
  import type { BlumeProject } from "../core/project-graph.ts";
14
22
  import { extractLinks } from "../core/sources/normalize.ts";
15
23
 
16
24
  /**
17
- * Relative page links on the agent surfaces — the `.md`/`.mdx` mirrors,
25
+ * Page links on the agent surfaces — the `.md`/`.mdx` mirrors,
18
26
  * llms-full.txt, MCP `get_page` and `resources/read`. An agent resolves
19
27
  * `[Install](./install)` against the URL it fetched (`/guides.md`), so the
20
28
  * link lands on `/install`; the rendered page rewrites it to the route it
21
29
  * means (`markdown/relative-links.ts`), and the Markdown an agent reads gets
22
30
  * 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.
31
+ * point at one page. Root-relative links (`[x](/guide)`,
32
+ * `<Card href="/guide">`, `![logo](/logo.png)`) get what the rendered page
33
+ * gives them too (`markdown/base-links.ts`,
34
+ * `components/content/base-href.ts`): a page link the `deployment.base` +
35
+ * `basePath` prefix, a public file (`/spec.pdf`) or image the
36
+ * `deployment.base` alone, and, on a page in a prefixed locale, a page link
37
+ * that locale's copy of the route when it is served
38
+ * (`components/layout/LocaleLinks.astro`). Inline links, images, component
39
+ * `href`s, and reference definitions are rewritten; fenced and inline code,
40
+ * relative images and asset links, and external URLs are left as written.
26
41
  */
27
42
 
28
43
  /** The page a Markdown text was written for. */
@@ -47,6 +62,14 @@ const DEFINITION =
47
62
  const MAYBE_RELATIVE =
48
63
  /\]\([\t ]*<?(?![a-z][\d+.a-z-]*:|\/|#)|^ {0,3}\[[^\]\n]+\]:[\t ]*<?(?![a-z][\d+.a-z-]*:|\/|#)|\shref=["'](?![a-z][\d+.a-z-]*:|\/|#)/imu;
49
64
 
65
+ /**
66
+ * {@link MAYBE_RELATIVE}, or a root-relative target (`/x`, never `//host`):
67
+ * the gate when root-relative links are rewritten too — a base or basePath is
68
+ * set, or the page sits under a locale prefix.
69
+ */
70
+ const MAYBE_PAGE_LINK =
71
+ /\]\([\t ]*<?(?![a-z][\d+.a-z-]*:|\/\/|#)|^ {0,3}\[[^\]\n]+\]:[\t ]*<?(?![a-z][\d+.a-z-]*:|\/\/|#)|\shref=["'](?![a-z][\d+.a-z-]*:|\/\/|#)/imu;
72
+
50
73
  interface Splice {
51
74
  column: number;
52
75
  length: number;
@@ -82,18 +105,23 @@ const definitionSplice = (
82
105
  return { column: lead.length + angled, length: target.length, text: route };
83
106
  };
84
107
 
85
- /** Every routed splice in `text`, by 0-based line: links, then definitions. */
108
+ /**
109
+ * Every routed splice in `text`, by 0-based line: links and images, then
110
+ * definitions. An image target goes through `imaged`, everything else through
111
+ * `routed`.
112
+ */
86
113
  const collectSplices = (
87
114
  text: string,
88
115
  lines: readonly string[],
89
- routed: (target: string) => string | undefined
116
+ routed: (target: string) => string | undefined,
117
+ imaged: (target: string) => string | undefined
90
118
  ): Map<number, Splice[]> => {
91
119
  const splices = new Map<number, Splice[]>();
92
120
  const add = (index: number, splice: Splice): void => {
93
121
  splices.set(index, [...(splices.get(index) ?? []), splice]);
94
122
  };
95
123
  for (const link of extractLinks(text)) {
96
- const route = link.image ? undefined : routed(link.target);
124
+ const route = (link.image ? imaged : routed)(link.target);
97
125
  if (route !== undefined) {
98
126
  add(link.line - 1, {
99
127
  column: link.column - 1,
@@ -124,7 +152,7 @@ const collectSplices = (
124
152
  export const relativeLinkRewriter = (
125
153
  project: BlumeProject
126
154
  ): RelativeLinkRewriter => {
127
- const { deployment, i18n } = project.config;
155
+ const { basePath, deployment, i18n } = project.config;
128
156
  const deployBase = normalizeBasePath(deployment.options.base);
129
157
  const localeTokens = i18n
130
158
  ? ["$", ...i18n.locales.map((locale) => locale.code)]
@@ -137,11 +165,20 @@ export const relativeLinkRewriter = (
137
165
  ])
138
166
  );
139
167
  const routes = new Set(project.manifest.routes.map((route) => route.path));
168
+ const localeByRoute = new Map(
169
+ project.manifest.routes.map((route) => [route.path, route.locale])
170
+ );
140
171
 
141
- return (text, page) => {
172
+ /**
173
+ * A page's relative-link resolver; `undefined` when the page has no file
174
+ * to resolve its links from.
175
+ */
176
+ const relativeRoute = (
177
+ page: LinkedPage
178
+ ): ((target: string) => string | undefined) | undefined => {
142
179
  const { sourcePath } = page;
143
- if (!sourcePath || !MAYBE_RELATIVE.test(text)) {
144
- return text;
180
+ if (!sourcePath) {
181
+ return;
145
182
  }
146
183
  const from: RelativeLinkBase = {
147
184
  isIndex: isIndexFileName(basename(sourcePath), localeTokens),
@@ -150,15 +187,62 @@ export const relativeLinkRewriter = (
150
187
  const navPath = navPathBySource.get(sourcePath) ?? basename(sourcePath);
151
188
  const resolveFile = (path: string): string | undefined =>
152
189
  routeOfLinkedFile(fileRoutes, { navPath, sourcePath }, path);
153
- const routed = (target: string): string | undefined => {
190
+ // A resolved route is one Blume serves, so the deployment base goes on
191
+ // unconditionally — even over a route that starts with the base's name.
192
+ return (target) => {
154
193
  const route = resolveRelativeHref(target, from, resolveFile, (path) =>
155
194
  routes.has(path)
156
195
  );
157
- return route === undefined ? undefined : withBasePath(deployBase, route);
196
+ return route === undefined ? undefined : mountBasePath(deployBase, route);
197
+ };
198
+ };
199
+
200
+ /** Whether a page is served at a based, fragment-less path. */
201
+ const servesPage = (route: string): boolean => servesRoute(routes, route);
202
+
203
+ return (text, page) => {
204
+ const locale = localeByRoute.get(page.route);
205
+ // The page's locale moves root links only when it has a URL prefix.
206
+ const localize =
207
+ i18n && locale !== undefined && localePrefix(locale, i18n) !== ""
208
+ ? { basePath, deployBase, i18n, locale, routes }
209
+ : undefined;
210
+ // A root-relative link only changes when the site is mounted under a
211
+ // prefix or the page reads in a prefixed locale.
212
+ const rootLinks =
213
+ deployBase !== "" || basePath !== "" || localize !== undefined;
214
+ if (
215
+ !(page.sourcePath || rootLinks) ||
216
+ !(rootLinks ? MAYBE_PAGE_LINK : MAYBE_RELATIVE).test(text)
217
+ ) {
218
+ return text;
219
+ }
220
+ const relative = relativeRoute(page);
221
+ // What the rendered page makes of `/guide`: based (a public file like
222
+ // `/spec.pdf` under the deployment base alone), then moved into the
223
+ // page's locale when that route is served.
224
+ const rooted = (target: string): string | undefined => {
225
+ const based = withAuthoredBasePath(
226
+ deployBase,
227
+ basePath,
228
+ target,
229
+ servesPage
230
+ );
231
+ const href = localize ? localizeHref(based, localize) : based;
232
+ return href === target ? undefined : href;
233
+ };
234
+ const routed = (target: string): string | undefined =>
235
+ isInternalPath(target) ? rooted(target) : relative?.(target);
236
+ // An image is always a file: `public/` (or a generated asset endpoint),
237
+ // served under the deployment base but never `basePath`. A relative one
238
+ // is left to `core/content-assets.ts`.
239
+ const imaged = (target: string): string | undefined => {
240
+ const based = withBasePath(deployBase, target);
241
+ return based === target ? undefined : based;
158
242
  };
159
243
 
160
244
  const lines = text.split("\n");
161
- const splices = collectSplices(text, lines, routed);
245
+ const splices = collectSplices(text, lines, routed, imaged);
162
246
 
163
247
  return lines
164
248
  .map((line, index) => {
@@ -18,7 +18,8 @@ export const projectComponentSerializers = (
18
18
  ({
19
19
  ...exampleComponentSerializers(project.examples ?? {}),
20
20
  ...openapiComponentSerializers(
21
- project.sources.find(isOpenApiSource)?.openApiData() ?? {}
21
+ project.sources.find(isOpenApiSource)?.openApiData() ?? {},
22
+ project.config.deployment.options.base
22
23
  ),
23
24
  ...project.config.agents.markdownComponents,
24
25
  }) satisfies Record<string, ComponentMarkdown>;
package/src/ai/skills.ts CHANGED
@@ -151,11 +151,16 @@ const skillMeta = (raw: string, dirName: string): SkillMetaResult => {
151
151
  * hold a `SKILL.md` (the layout `npx skills add` consumes). Subdirectories
152
152
  * without one are ignored silently — the directory may hold other assets —
153
153
  * while a present-but-invalid skill earns a warning so it isn't dropped
154
- * behind the publisher's back.
154
+ * behind the publisher's back. Two directories declaring the same `name`
155
+ * would publish to the same artifact path under two index entries, so the
156
+ * first directory in sorted order keeps the name and the other is skipped
157
+ * with a warning naming both.
155
158
  */
156
159
  export const collectSkills = async (dir: string): Promise<CollectedSkills> => {
157
160
  const skills: SkillArtifact[] = [];
158
161
  const warnings: string[] = [];
162
+ /** Skill name → the directory that claimed it first. */
163
+ const owners = new Map<string, string>();
159
164
  const items = await readdir(dir, { withFileTypes: true });
160
165
  for (const item of items.toSorted((a, b) => (a.name < b.name ? -1 : 1))) {
161
166
  if (!item.isDirectory() || item.name.startsWith(".")) {
@@ -177,6 +182,14 @@ export const collectSkills = async (dir: string): Promise<CollectedSkills> => {
177
182
  }
178
183
  continue;
179
184
  }
185
+ const owner = owners.get(meta.name);
186
+ if (owner) {
187
+ warnings.push(
188
+ `Skills "${owner}" and "${item.name}" both declare the name "${meta.name}"; publishing "${owner}" and skipping "${item.name}".`
189
+ );
190
+ continue;
191
+ }
192
+ owners.set(meta.name, item.name);
180
193
  // A lone SKILL.md ships verbatim; supporting resources ship as an
181
194
  // archive so the skill's relative references resolve after unpacking.
182
195
  const single = entries.length === 1;
@@ -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
+ };