blume 2.0.3 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (607) hide show
  1. package/AGENTS.md +1 -0
  2. package/CHANGELOG.md +142 -0
  3. package/README.md +1 -1
  4. package/dist/cli/{chunk-5f86nr5m.js → chunk-073rdvgb.js} +21 -48
  5. package/dist/cli/{chunk-5f86nr5m.js.map → chunk-073rdvgb.js.map} +4 -5
  6. package/dist/cli/{chunk-ga0pf4aj.js → chunk-0n9xkp3d.js} +12 -3
  7. package/dist/cli/{chunk-ga0pf4aj.js.map → chunk-0n9xkp3d.js.map} +3 -3
  8. package/dist/cli/chunk-150jk7km.js +52 -0
  9. package/dist/cli/chunk-150jk7km.js.map +10 -0
  10. package/dist/cli/{chunk-9t7a85s3.js → chunk-313vnn40.js} +30 -19
  11. package/dist/cli/chunk-313vnn40.js.map +10 -0
  12. package/dist/cli/{chunk-tkacnehg.js → chunk-3v3ffbrj.js} +2 -2
  13. package/dist/cli/{chunk-q58y5e6a.js → chunk-3x6x3pph.js} +46 -24
  14. package/dist/cli/chunk-3x6x3pph.js.map +10 -0
  15. package/dist/cli/{chunk-1d7ve1dm.js → chunk-42t4jxdx.js} +37 -5
  16. package/dist/cli/chunk-42t4jxdx.js.map +12 -0
  17. package/dist/cli/{chunk-8ktnccpt.js → chunk-54ztw1nj.js} +21 -2
  18. package/dist/cli/chunk-54ztw1nj.js.map +10 -0
  19. package/dist/cli/{chunk-hm3vjy5s.js → chunk-5vjtgs6e.js} +28 -10
  20. package/dist/cli/{chunk-hm3vjy5s.js.map → chunk-5vjtgs6e.js.map} +4 -4
  21. package/dist/cli/chunk-66w9j7p6.js +322 -0
  22. package/dist/cli/chunk-66w9j7p6.js.map +14 -0
  23. package/dist/cli/{chunk-2hsdwb9n.js → chunk-7ppf1rva.js} +34 -18
  24. package/dist/cli/chunk-7ppf1rva.js.map +10 -0
  25. package/dist/cli/{chunk-r9rcc4w7.js → chunk-8y2kmjv8.js} +7 -7
  26. package/dist/cli/{chunk-r20tn01b.js → chunk-c1y538kd.js} +8 -2
  27. package/dist/cli/{chunk-r20tn01b.js.map → chunk-c1y538kd.js.map} +3 -3
  28. package/dist/cli/{chunk-2eytanqx.js → chunk-c887fj0s.js} +2 -2
  29. package/dist/cli/{chunk-p3v96n38.js → chunk-cfyg47zg.js} +16 -6
  30. package/dist/cli/chunk-cfyg47zg.js.map +10 -0
  31. package/dist/cli/chunk-cm89scdv.js +443 -0
  32. package/dist/cli/chunk-cm89scdv.js.map +11 -0
  33. package/dist/cli/{chunk-akbpwfxc.js → chunk-dzwehpy7.js} +2 -2
  34. package/dist/cli/{chunk-wrr3j9w9.js → chunk-e6atmn4h.js} +299 -72
  35. package/dist/cli/chunk-e6atmn4h.js.map +36 -0
  36. package/dist/cli/{chunk-tzmab476.js → chunk-e8xw9qkh.js} +8 -6
  37. package/dist/cli/{chunk-tzmab476.js.map → chunk-e8xw9qkh.js.map} +3 -3
  38. package/dist/cli/{chunk-6dsbexzp.js → chunk-e9q7a2ak.js} +17 -15
  39. package/dist/cli/{chunk-6dsbexzp.js.map → chunk-e9q7a2ak.js.map} +2 -2
  40. package/dist/cli/{chunk-pv29h0wf.js → chunk-ehweg4fr.js} +5155 -1249
  41. package/dist/cli/chunk-ehweg4fr.js.map +218 -0
  42. package/dist/cli/{chunk-c8chx29p.js → chunk-ep6nd0rn.js} +390 -318
  43. package/dist/cli/chunk-ep6nd0rn.js.map +36 -0
  44. package/dist/cli/{chunk-p73c0m7w.js → chunk-fdrkhpjy.js} +18 -15
  45. package/dist/cli/{chunk-p73c0m7w.js.map → chunk-fdrkhpjy.js.map} +3 -3
  46. package/dist/cli/{chunk-2z928egk.js → chunk-ft9vfys6.js} +5 -5
  47. package/dist/cli/{chunk-5m5nmvyq.js → chunk-gb420vvz.js} +316 -79
  48. package/dist/cli/chunk-gb420vvz.js.map +17 -0
  49. package/dist/cli/{chunk-vg9r4eb9.js → chunk-hebn0tpx.js} +86 -13
  50. package/dist/cli/chunk-hebn0tpx.js.map +14 -0
  51. package/dist/cli/{chunk-35d4wj9f.js → chunk-htrwgabr.js} +41 -23
  52. package/dist/cli/chunk-htrwgabr.js.map +15 -0
  53. package/dist/cli/{chunk-g4hq16wv.js → chunk-hx3xqnk9.js} +16 -14
  54. package/dist/cli/{chunk-g4hq16wv.js.map → chunk-hx3xqnk9.js.map} +2 -2
  55. package/dist/cli/chunk-j134vrp2.js +65 -0
  56. package/dist/cli/chunk-j134vrp2.js.map +10 -0
  57. package/dist/cli/{chunk-a58773jm.js → chunk-kma9f6sf.js} +2 -2
  58. package/dist/cli/chunk-kwrg63tm.js +31 -0
  59. package/dist/cli/chunk-kwrg63tm.js.map +10 -0
  60. package/dist/cli/{chunk-ey84smr6.js → chunk-mrcccgnh.js} +6 -6
  61. package/dist/cli/{chunk-ey84smr6.js.map → chunk-mrcccgnh.js.map} +1 -1
  62. package/dist/cli/{chunk-yfyb25rh.js → chunk-n0v7m8ry.js} +24 -8
  63. package/dist/cli/chunk-n0v7m8ry.js.map +14 -0
  64. package/dist/cli/{chunk-j85vccga.js → chunk-ncj7w596.js} +576 -367
  65. package/dist/cli/chunk-ncj7w596.js.map +35 -0
  66. package/dist/cli/{chunk-5xvm6tfj.js → chunk-nq1d20w2.js} +17 -15
  67. package/dist/cli/{chunk-5xvm6tfj.js.map → chunk-nq1d20w2.js.map} +2 -2
  68. package/dist/cli/{chunk-pehfxfta.js → chunk-nrns10mj.js} +6 -98
  69. package/dist/cli/chunk-nrns10mj.js.map +10 -0
  70. package/dist/cli/{chunk-crgn1q09.js → chunk-sds923fa.js} +4 -4
  71. package/dist/cli/chunk-t2m5j4pr.js +104 -0
  72. package/dist/cli/chunk-t2m5j4pr.js.map +11 -0
  73. package/dist/cli/{chunk-acanzt5p.js → chunk-tdqhyxfz.js} +6 -6
  74. package/dist/cli/chunk-tphac74f.js +126 -0
  75. package/dist/cli/chunk-tphac74f.js.map +11 -0
  76. package/dist/cli/chunk-w1r4vase.js +18 -0
  77. package/dist/cli/chunk-w1r4vase.js.map +10 -0
  78. package/dist/cli/{chunk-3em5wd2y.js → chunk-zccbjcj0.js} +4 -3
  79. package/dist/cli/{chunk-3em5wd2y.js.map → chunk-zccbjcj0.js.map} +2 -2
  80. package/dist/cli/index.js +31 -20
  81. package/dist/cli/index.js.map +5 -5
  82. package/dist/types/ai/agent-surface.d.ts +8 -7
  83. package/dist/types/ai/ai-catalog.d.ts +7 -0
  84. package/dist/types/ai/ask-context.d.ts +26 -0
  85. package/dist/types/ai/ask.d.ts +271 -58
  86. package/dist/types/ai/component-markdown.d.ts +2 -0
  87. package/dist/types/ai/index.d.ts +2 -2
  88. package/dist/types/ai/openapi-components.d.ts +4 -1
  89. package/dist/types/ai/site-skill.d.ts +26 -0
  90. package/dist/types/ai/skills.d.ts +16 -0
  91. package/dist/types/astro/integration.d.ts +9 -0
  92. package/dist/types/astro/markdown-negotiation.d.ts +3 -0
  93. package/dist/types/astro/pages.d.ts +10 -0
  94. package/dist/types/captcha/hcaptcha.d.ts +27 -0
  95. package/dist/types/captcha/index.d.ts +28 -0
  96. package/dist/types/captcha/schema.d.ts +29 -0
  97. package/dist/types/captcha/turnstile.d.ts +28 -0
  98. package/dist/types/cli/init/scaffold.d.ts +1 -1
  99. package/dist/types/components/content/api-field.d.ts +62 -0
  100. package/dist/types/components/content/api-page.d.ts +100 -0
  101. package/dist/types/components/layout/nav-utils.d.ts +26 -0
  102. package/dist/types/components/openapi/code-samples.d.ts +29 -0
  103. package/dist/types/components/openapi/helpers.d.ts +158 -0
  104. package/dist/types/components/openapi/security.d.ts +95 -0
  105. package/dist/types/components/openapi/snippets.d.ts +60 -0
  106. package/dist/types/consent/ethyca.d.ts +48 -0
  107. package/dist/types/consent/index.d.ts +37 -0
  108. package/dist/types/consent/native.d.ts +28 -0
  109. package/dist/types/consent/osano.d.ts +42 -0
  110. package/dist/types/consent/schema.d.ts +110 -0
  111. package/dist/types/core/changelog-index.d.ts +8 -0
  112. package/dist/types/core/config-input.d.ts +217 -23
  113. package/dist/types/core/data.d.ts +53 -4
  114. package/dist/types/core/footer.d.ts +8 -0
  115. package/dist/types/core/i18n-ui.d.ts +88 -0
  116. package/dist/types/core/includes.d.ts +8 -2
  117. package/dist/types/core/links.d.ts +8 -0
  118. package/dist/types/core/localizable.d.ts +7 -0
  119. package/dist/types/core/page-modes.d.ts +29 -0
  120. package/dist/types/core/probe.d.ts +2 -0
  121. package/dist/types/core/project-graph.d.ts +6 -0
  122. package/dist/types/core/redirect-patterns.d.ts +152 -0
  123. package/dist/types/core/related.d.ts +40 -0
  124. package/dist/types/core/schema.d.ts +670 -56
  125. package/dist/types/core/sources/cache.d.ts +3 -0
  126. package/dist/types/core/sources/contentful-rich-text.d.ts +11 -2
  127. package/dist/types/core/sources/contentful.d.ts +17 -1
  128. package/dist/types/core/sources/lexical.d.ts +5 -0
  129. package/dist/types/core/sources/normalize.d.ts +20 -1
  130. package/dist/types/core/sources/read.d.ts +5 -0
  131. package/dist/types/core/sources/remote.d.ts +37 -10
  132. package/dist/types/core/sources/sanity.d.ts +1 -1
  133. package/dist/types/core/types.d.ts +18 -1
  134. package/dist/types/core/variables.d.ts +42 -0
  135. package/dist/types/deploy/adapter-output.d.ts +2 -1
  136. package/dist/types/deploy/adapters/cloudflare.d.ts +7 -5
  137. package/dist/types/deploy/adapters/node.d.ts +58 -23
  138. package/dist/types/deploy/adapters/registry.d.ts +10 -0
  139. package/dist/types/deploy/adapters/types.d.ts +3 -2
  140. package/dist/types/deploy/node-headers.d.ts +13 -2
  141. package/dist/types/deploy/platforms/cloudflare.d.ts +15 -7
  142. package/dist/types/deploy/platforms/netlify.d.ts +7 -0
  143. package/dist/types/deploy/platforms/node.d.ts +3 -1
  144. package/dist/types/deploy/platforms/types.d.ts +18 -7
  145. package/dist/types/deploy/platforms/vercel.d.ts +26 -1
  146. package/dist/types/deploy/redirects.d.ts +55 -11
  147. package/dist/types/deploy/sitemap.d.ts +2 -1
  148. package/dist/types/deploy/vercel-negotiation.d.ts +51 -21
  149. package/dist/types/markdown/code-title.d.ts +17 -0
  150. package/dist/types/markdown/features.d.ts +3 -2
  151. package/dist/types/narration/build.d.ts +70 -0
  152. package/dist/types/narration/provider.d.ts +26 -0
  153. package/dist/types/narration/script.d.ts +154 -0
  154. package/dist/types/openapi/checks.d.ts +27 -0
  155. package/dist/types/openapi/graphql.d.ts +6 -0
  156. package/dist/types/openapi/model.d.ts +27 -10
  157. package/dist/types/openapi/overlay.d.ts +54 -0
  158. package/dist/types/openapi/parse.d.ts +16 -0
  159. package/dist/types/openapi/references.d.ts +30 -4
  160. package/dist/types/ratelimit/cloudflare.d.ts +39 -0
  161. package/dist/types/ratelimit/index.d.ts +27 -0
  162. package/dist/types/ratelimit/memory.d.ts +36 -0
  163. package/dist/types/ratelimit/schema.d.ts +119 -0
  164. package/dist/types/ratelimit/upstash.d.ts +46 -0
  165. package/dist/types/ratelimit/wrangler.d.ts +23 -0
  166. package/dist/types/reference/asyncapi.d.ts +10 -10
  167. package/dist/types/reference/graphql.d.ts +89 -11
  168. package/dist/types/reference/index.d.ts +1 -1
  169. package/dist/types/reference/openapi.d.ts +33 -12
  170. package/dist/types/reference/options.d.ts +82 -6
  171. package/dist/types/reference/scalar.d.ts +13 -0
  172. package/dist/types/reference/schema.d.ts +144 -36
  173. package/dist/types/search/adapters/registry.d.ts +3 -0
  174. package/dist/types/search/adapters/typesense.d.ts +14 -4
  175. package/dist/types/search/build.d.ts +3 -1
  176. package/dist/types/search/documents.d.ts +12 -1
  177. package/dist/types/search/orama-index.d.ts +13 -9
  178. package/dist/types/search/sync/algolia.d.ts +5 -2
  179. package/dist/types/search/sync/index.d.ts +3 -1
  180. package/dist/types/search/sync/typesense.d.ts +11 -7
  181. package/dist/types/seo/metatags.d.ts +20 -0
  182. package/dist/types/sources/contentful.d.ts +16 -0
  183. package/dist/types/sources/github-releases.d.ts +8 -1
  184. package/dist/types/sources/payload.d.ts +2 -0
  185. package/dist/types/sources/registry.d.ts +54 -0
  186. package/dist/types/sources/shared.d.ts +3 -0
  187. package/dist/types/sources/strapi.d.ts +2 -0
  188. package/docs/01-quickstart.mdx +4 -2
  189. package/docs/02-deployment.mdx +71 -17
  190. package/docs/03-upgrading.mdx +4 -4
  191. package/docs/04-migrating.mdx +1 -1
  192. package/docs/advanced/changelog.mdx +13 -2
  193. package/docs/advanced/skills.mdx +15 -1
  194. package/docs/cli/audit.mdx +51 -8
  195. package/docs/cli/doctor.mdx +5 -1
  196. package/docs/cli/evals.mdx +4 -4
  197. package/docs/cli/index.mdx +1 -0
  198. package/docs/cli/translate.mdx +1 -0
  199. package/docs/cli/validate.mdx +4 -3
  200. package/docs/configuration/analytics.mdx +3 -1
  201. package/docs/configuration/assistant.mdx +194 -38
  202. package/docs/configuration/consent.mdx +100 -0
  203. package/docs/configuration/customization.mdx +8 -6
  204. package/docs/configuration/export.mdx +5 -3
  205. package/docs/configuration/index.mdx +64 -5
  206. package/docs/configuration/meta.ts +3 -0
  207. package/docs/configuration/narration.mdx +119 -0
  208. package/docs/configuration/rate-limiting.mdx +90 -0
  209. package/docs/configuration/search.mdx +63 -12
  210. package/docs/configuration/theming.mdx +3 -1
  211. package/docs/content/components.mdx +108 -2
  212. package/docs/content/frontmatter.mdx +80 -2
  213. package/docs/content/i18n.mdx +39 -4
  214. package/docs/content/includes.mdx +16 -0
  215. package/docs/content/meta.mdx +1 -0
  216. package/docs/content/meta.ts +1 -0
  217. package/docs/content/navigation.mdx +32 -10
  218. package/docs/content/sources/contentful.mdx +35 -0
  219. package/docs/content/sources/custom.mdx +43 -0
  220. package/docs/content/sources/github-releases.mdx +39 -0
  221. package/docs/content/sources/index.mdx +91 -0
  222. package/docs/content/sources/meta.ts +16 -0
  223. package/docs/content/sources/notion.mdx +32 -0
  224. package/docs/content/sources/obsidian.mdx +37 -0
  225. package/docs/content/sources/payload.mdx +31 -0
  226. package/docs/content/sources/remote-mdx.mdx +44 -0
  227. package/docs/content/sources/sanity.mdx +29 -0
  228. package/docs/content/sources/strapi.mdx +32 -0
  229. package/docs/content/syntax.mdx +69 -4
  230. package/docs/content/variables.mdx +41 -0
  231. package/docs/discoverability/agent-discovery.mdx +29 -2
  232. package/docs/discoverability/index.mdx +2 -2
  233. package/docs/discoverability/json-api.mdx +2 -0
  234. package/docs/discoverability/llms-txt.mdx +2 -2
  235. package/docs/discoverability/markdown.mdx +3 -1
  236. package/docs/discoverability/mcp.mdx +4 -0
  237. package/docs/discoverability/metadata.mdx +17 -0
  238. package/docs/discoverability/sitemap-and-robots.mdx +1 -1
  239. package/docs/references/api-pages.mdx +74 -0
  240. package/docs/references/asyncapi.mdx +4 -2
  241. package/docs/references/graphql.mdx +14 -2
  242. package/docs/references/meta.ts +1 -1
  243. package/docs/references/openapi.mdx +148 -7
  244. package/docs/references/scalar.mdx +3 -1
  245. package/package.json +43 -8
  246. package/skills/blume/SKILL.md +3 -0
  247. package/skills/blume-migrate/SKILL.md +6 -6
  248. package/skills/blume-migrate/references/docusaurus.md +7 -7
  249. package/skills/blume-migrate/references/fumadocs.md +17 -13
  250. package/skills/blume-migrate/references/mintlify.md +37 -19
  251. package/skills/blume-migrate/references/nextra.md +10 -7
  252. package/skills/blume-migrate/references/starlight.md +11 -8
  253. package/skills/blume-migrate/scripts/mintlify-codemod.mjs +60 -14
  254. package/skills/blume-write-skill/SKILL.md +82 -0
  255. package/src/ai/agent-readability.ts +2 -1
  256. package/src/ai/agent-surface.ts +9 -7
  257. package/src/ai/ai-catalog.ts +6 -3
  258. package/src/ai/ask-context.ts +103 -20
  259. package/src/ai/ask-data.ts +6 -0
  260. package/src/ai/ask-limits.ts +13 -0
  261. package/src/ai/ask-tools.ts +142 -0
  262. package/src/ai/ask.ts +354 -76
  263. package/src/ai/changelog-markdown.ts +11 -6
  264. package/src/ai/component-markdown.ts +76 -1
  265. package/src/ai/cors.ts +14 -3
  266. package/src/ai/index.ts +12 -0
  267. package/src/ai/llms.ts +6 -2
  268. package/src/ai/markdown.ts +2 -1
  269. package/src/ai/mcp/data.ts +6 -0
  270. package/src/ai/mcp/query.ts +14 -6
  271. package/src/ai/openapi-components.ts +36 -6
  272. package/src/ai/relative-links.ts +4 -38
  273. package/src/ai/site-skill.ts +274 -0
  274. package/src/ai/skills.ts +35 -2
  275. package/src/ai/write-skill.ts +97 -0
  276. package/src/astro/generate.ts +210 -66
  277. package/src/astro/index.ts +1 -0
  278. package/src/astro/integration.ts +42 -4
  279. package/src/astro/markdown-negotiation.ts +3 -0
  280. package/src/astro/module-types.ts +9 -1
  281. package/src/astro/pages.ts +23 -0
  282. package/src/astro/templates.ts +406 -108
  283. package/src/astro/variables.ts +31 -0
  284. package/src/audit/catalog.ts +38 -3
  285. package/src/audit/checks/contrast.ts +183 -0
  286. package/src/audit/checks/i18n.ts +2 -3
  287. package/src/audit/checks/indexability.ts +2 -3
  288. package/src/audit/checks/links.ts +19 -13
  289. package/src/audit/checks/network.ts +110 -6
  290. package/src/audit/checks/redirects.ts +6 -4
  291. package/src/audit/checks/robots.ts +58 -2
  292. package/src/audit/checks/sitemap.ts +2 -3
  293. package/src/audit/redirects.ts +46 -15
  294. package/src/audit/run.ts +5 -0
  295. package/src/audit/types.ts +4 -1
  296. package/src/audit/url.ts +14 -5
  297. package/src/blume-modules.d.ts +12 -1
  298. package/src/captcha/client.ts +152 -0
  299. package/src/captcha/hcaptcha.ts +36 -0
  300. package/src/captcha/index.ts +28 -0
  301. package/src/captcha/schema.ts +32 -0
  302. package/src/captcha/turnstile.ts +38 -0
  303. package/src/captcha/verify.ts +71 -0
  304. package/src/cli/args.ts +51 -0
  305. package/src/cli/build-failure.ts +23 -0
  306. package/src/cli/command-meta.ts +5 -0
  307. package/src/cli/commands/audit.ts +10 -1
  308. package/src/cli/commands/dev.ts +18 -2
  309. package/src/cli/commands/doctor.ts +67 -28
  310. package/src/cli/commands/eval.ts +36 -4
  311. package/src/cli/commands/init.ts +6 -4
  312. package/src/cli/commands/skill.ts +96 -0
  313. package/src/cli/commands/validate.ts +10 -1
  314. package/src/cli/index.ts +5 -0
  315. package/src/cli/init/scaffold.ts +3 -3
  316. package/src/cli/prepare.ts +6 -3
  317. package/src/cli/required-secrets.ts +72 -30
  318. package/src/components/BlumePage.astro +2 -0
  319. package/src/components/content/ApiEndpoint.astro +18 -0
  320. package/src/components/content/ApiField.astro +79 -0
  321. package/src/components/content/ApiPlayground.astro +66 -0
  322. package/src/components/content/ApiRail.astro +19 -0
  323. package/src/components/content/Color.astro +1 -0
  324. package/src/components/content/FileTree.astro +1 -0
  325. package/src/components/content/GithubInfo.astro +1 -0
  326. package/src/components/content/Panel.astro +1 -0
  327. package/src/components/content/ParamField.astro +25 -0
  328. package/src/components/content/RequestExample.astro +17 -0
  329. package/src/components/content/ResponseExample.astro +17 -0
  330. package/src/components/content/ResponseField.astro +32 -0
  331. package/src/components/content/Step.astro +1 -0
  332. package/src/components/content/Tile.astro +1 -0
  333. package/src/components/content/Tooltip.astro +1 -0
  334. package/src/components/content/Tree.astro +1 -0
  335. package/src/components/content/TypeTable.astro +4 -1
  336. package/src/components/content/View.astro +21 -0
  337. package/src/components/content/ViewSwitcher.astro +168 -0
  338. package/src/components/content/api-field.ts +103 -0
  339. package/src/components/content/api-page.ts +248 -0
  340. package/src/components/content/mermaid-element.ts +15 -1
  341. package/src/components/content/view-switcher.ts +71 -0
  342. package/src/components/index.ts +2 -0
  343. package/src/components/islands/Assistant.astro +10 -1
  344. package/src/components/islands/assistant.tsx +104 -14
  345. package/src/components/islands/code-question.ts +62 -0
  346. package/src/components/islands/hooks.ts +136 -21
  347. package/src/components/islands/support-link.ts +68 -0
  348. package/src/components/layout/Analytics.astro +11 -2
  349. package/src/components/layout/Banner.astro +14 -10
  350. package/src/components/layout/Breadcrumbs.astro +6 -2
  351. package/src/components/layout/ConsentBanner.astro +62 -0
  352. package/src/components/layout/ConsentHead.astro +38 -0
  353. package/src/components/layout/Directory.astro +94 -0
  354. package/src/components/layout/DrawerSwitchers.astro +51 -0
  355. package/src/components/layout/Empty.astro +2 -2
  356. package/src/components/layout/Header.astro +122 -80
  357. package/src/components/layout/LanguageSwitcher.astro +48 -15
  358. package/src/components/layout/NarrationPlayer.astro +1085 -0
  359. package/src/components/layout/NavSelector.astro +33 -7
  360. package/src/components/layout/PageActions.astro +139 -24
  361. package/src/components/layout/PageFeedback.astro +54 -49
  362. package/src/components/layout/PageLayout.astro +71 -3
  363. package/src/components/layout/ReferenceLayout.astro +17 -6
  364. package/src/components/layout/RelatedPages.astro +42 -0
  365. package/src/components/layout/RootLayout.astro +318 -47
  366. package/src/components/layout/Search.astro +47 -19
  367. package/src/components/layout/SiteFooter.astro +124 -0
  368. package/src/components/layout/SiteMetatags.astro +16 -0
  369. package/src/components/layout/TableOfContents.astro +24 -17
  370. package/src/components/layout/consent/native.ts +79 -0
  371. package/src/components/layout/consent/types.ts +37 -0
  372. package/src/components/layout/footer-links.ts +53 -0
  373. package/src/components/layout/footer-socials.ts +87 -0
  374. package/src/components/layout/head-scripts.ts +6 -1
  375. package/src/components/layout/locale-redirect.ts +107 -0
  376. package/src/components/layout/nav-utils.ts +83 -0
  377. package/src/components/layout/page-feedback.ts +96 -0
  378. package/src/components/layout/search/algolia.ts +10 -1
  379. package/src/components/layout/search/analytics.ts +126 -0
  380. package/src/components/layout/search/endpoint.ts +48 -5
  381. package/src/components/layout/search/flexsearch.ts +17 -5
  382. package/src/components/layout/search/orama-cloud.ts +26 -14
  383. package/src/components/layout/search/pagefind.ts +114 -5
  384. package/src/components/layout/search/types.ts +32 -0
  385. package/src/components/layout/search/typesense.ts +19 -3
  386. package/src/components/openapi/AsyncApiOperation.astro +14 -7
  387. package/src/components/openapi/Authorization.astro +10 -0
  388. package/src/components/openapi/GraphqlOperation.astro +5 -0
  389. package/src/components/openapi/Operation.astro +184 -13
  390. package/src/components/openapi/ParametersTable.astro +8 -4
  391. package/src/components/openapi/Playground.astro +7 -1
  392. package/src/components/openapi/RequestBody.astro +24 -5
  393. package/src/components/openapi/RequestPanel.astro +59 -7
  394. package/src/components/openapi/Responses.astro +7 -4
  395. package/src/components/openapi/SchemaTable.astro +15 -3
  396. package/src/components/openapi/async-snippets.ts +65 -9
  397. package/src/components/openapi/async.ts +123 -29
  398. package/src/components/openapi/code-samples.ts +97 -0
  399. package/src/components/openapi/graphql-helpers.ts +35 -12
  400. package/src/components/openapi/helpers.ts +36 -2
  401. package/src/components/openapi/message-model.ts +54 -11
  402. package/src/components/openapi/message.ts +14 -4
  403. package/src/components/openapi/operation-model.ts +16 -7
  404. package/src/components/openapi/playground-client.ts +24 -5
  405. package/src/components/openapi/playground-schema.ts +1 -1
  406. package/src/components/openapi/schema-tree.ts +44 -14
  407. package/src/components/openapi/security.ts +16 -3
  408. package/src/components/openapi/snippets.ts +873 -10
  409. package/src/components/openapi/validate-json.ts +4 -3
  410. package/src/components/props.ts +6 -0
  411. package/src/consent/client.ts +95 -0
  412. package/src/consent/clients.ts +19 -0
  413. package/src/consent/ethyca.ts +71 -0
  414. package/src/consent/head.ts +24 -0
  415. package/src/consent/index.ts +37 -0
  416. package/src/consent/init.ts +29 -0
  417. package/src/consent/native.ts +36 -0
  418. package/src/consent/osano.ts +59 -0
  419. package/src/consent/schema.ts +30 -0
  420. package/src/core/builtin-tags.ts +5 -0
  421. package/src/core/changelog-index.ts +31 -1
  422. package/src/core/client-address.ts +19 -0
  423. package/src/core/component-diagnostics.ts +53 -0
  424. package/src/core/component-overrides.ts +32 -0
  425. package/src/core/config-input.ts +225 -23
  426. package/src/core/data.ts +46 -4
  427. package/src/core/diagnostics.ts +8 -2
  428. package/src/core/footer.ts +26 -0
  429. package/src/core/graph.ts +39 -36
  430. package/src/core/i18n-ui.ts +84 -2
  431. package/src/core/includes.ts +21 -4
  432. package/src/core/links.ts +63 -19
  433. package/src/core/localizable.ts +26 -0
  434. package/src/core/navigation.ts +43 -12
  435. package/src/core/page-modes.ts +56 -0
  436. package/src/core/probe.ts +26 -6
  437. package/src/core/project-graph.ts +163 -16
  438. package/src/core/redirect-patterns.ts +520 -0
  439. package/src/core/related.ts +131 -0
  440. package/src/core/schema.ts +339 -66
  441. package/src/core/sources/cache.ts +6 -0
  442. package/src/core/sources/contentful-rich-text.ts +29 -6
  443. package/src/core/sources/contentful.ts +135 -25
  444. package/src/core/sources/filesystem.ts +26 -4
  445. package/src/core/sources/github-releases.ts +12 -1
  446. package/src/core/sources/lexical.ts +15 -10
  447. package/src/core/sources/mdx-remote.ts +62 -10
  448. package/src/core/sources/normalize.ts +228 -41
  449. package/src/core/sources/notion.ts +7 -1
  450. package/src/core/sources/obsidian.ts +16 -3
  451. package/src/core/sources/payload.ts +38 -15
  452. package/src/core/sources/read.ts +8 -3
  453. package/src/core/sources/remote.ts +72 -15
  454. package/src/core/sources/resolve.ts +11 -1
  455. package/src/core/sources/sanity.ts +43 -25
  456. package/src/core/sources/strapi.ts +2 -2
  457. package/src/core/types.ts +19 -1
  458. package/src/core/ui-packs/ar.ts +44 -0
  459. package/src/core/ui-packs/bg.ts +46 -0
  460. package/src/core/ui-packs/bn.ts +44 -0
  461. package/src/core/ui-packs/ca.ts +47 -0
  462. package/src/core/ui-packs/cs.ts +45 -0
  463. package/src/core/ui-packs/da.ts +46 -0
  464. package/src/core/ui-packs/de.ts +47 -0
  465. package/src/core/ui-packs/el.ts +46 -0
  466. package/src/core/ui-packs/es.ts +47 -0
  467. package/src/core/ui-packs/fa.ts +46 -0
  468. package/src/core/ui-packs/fi.ts +46 -0
  469. package/src/core/ui-packs/fr.ts +47 -0
  470. package/src/core/ui-packs/he.ts +44 -0
  471. package/src/core/ui-packs/hi.ts +45 -0
  472. package/src/core/ui-packs/hr.ts +46 -0
  473. package/src/core/ui-packs/hu.ts +45 -0
  474. package/src/core/ui-packs/id.ts +46 -0
  475. package/src/core/ui-packs/it.ts +46 -0
  476. package/src/core/ui-packs/ja.ts +46 -0
  477. package/src/core/ui-packs/ko.ts +45 -0
  478. package/src/core/ui-packs/nl.ts +47 -0
  479. package/src/core/ui-packs/no.ts +45 -0
  480. package/src/core/ui-packs/pl.ts +46 -0
  481. package/src/core/ui-packs/pt-br.ts +47 -0
  482. package/src/core/ui-packs/pt.ts +46 -0
  483. package/src/core/ui-packs/ro.ts +46 -0
  484. package/src/core/ui-packs/ru.ts +46 -0
  485. package/src/core/ui-packs/sk.ts +45 -0
  486. package/src/core/ui-packs/sr.ts +46 -0
  487. package/src/core/ui-packs/sv.ts +45 -0
  488. package/src/core/ui-packs/th.ts +44 -0
  489. package/src/core/ui-packs/tr.ts +46 -0
  490. package/src/core/ui-packs/uk.ts +46 -0
  491. package/src/core/ui-packs/vi.ts +46 -0
  492. package/src/core/ui-packs/zh-tw.ts +44 -0
  493. package/src/core/ui-packs/zh.ts +44 -0
  494. package/src/core/variables.ts +103 -0
  495. package/src/deploy/adapter-output.ts +7 -2
  496. package/src/deploy/adapters/cloudflare.ts +7 -5
  497. package/src/deploy/adapters/node.ts +47 -9
  498. package/src/deploy/adapters/types.ts +7 -3
  499. package/src/deploy/artifacts.ts +79 -9
  500. package/src/deploy/cloudflare-negotiation.ts +35 -5
  501. package/src/deploy/headers.ts +2 -1
  502. package/src/deploy/node-headers.ts +63 -9
  503. package/src/deploy/platforms/cloudflare.ts +45 -9
  504. package/src/deploy/platforms/netlify.ts +35 -2
  505. package/src/deploy/platforms/node.ts +12 -2
  506. package/src/deploy/platforms/types.ts +21 -7
  507. package/src/deploy/platforms/vercel.ts +149 -17
  508. package/src/deploy/redirects.ts +151 -24
  509. package/src/deploy/rss.ts +3 -1
  510. package/src/deploy/sitemap.ts +39 -5
  511. package/src/deploy/vercel-negotiation.ts +326 -102
  512. package/src/eval/agents.ts +18 -1
  513. package/src/eval/prompts.ts +6 -3
  514. package/src/eval/report.ts +34 -13
  515. package/src/eval/run.ts +5 -0
  516. package/src/markdown/api-rail.ts +259 -0
  517. package/src/markdown/code-title.ts +35 -9
  518. package/src/markdown/features.ts +3 -2
  519. package/src/markdown/fence-meta.ts +2 -0
  520. package/src/markdown/include.ts +11 -1
  521. package/src/markdown/index.ts +28 -5
  522. package/src/markdown/math.ts +4 -4
  523. package/src/markdown/relative-links.ts +3 -2
  524. package/src/markdown/variables.ts +56 -0
  525. package/src/markdown/views.ts +82 -0
  526. package/src/narration/build.ts +412 -0
  527. package/src/narration/player.ts +249 -0
  528. package/src/narration/provider.ts +29 -0
  529. package/src/narration/script.ts +592 -0
  530. package/src/og/card.ts +7 -6
  531. package/src/openapi/checks.ts +273 -0
  532. package/src/openapi/graphql-build.ts +21 -0
  533. package/src/openapi/graphql.ts +11 -0
  534. package/src/openapi/model.ts +72 -27
  535. package/src/openapi/overlay.ts +256 -0
  536. package/src/openapi/parse.ts +182 -4
  537. package/src/openapi/references.ts +64 -8
  538. package/src/openapi/render-mdx.ts +5 -1
  539. package/src/openapi/scalar.ts +40 -6
  540. package/src/openapi/source.ts +40 -15
  541. package/src/ratelimit/cloudflare.ts +54 -0
  542. package/src/ratelimit/index.ts +30 -0
  543. package/src/ratelimit/memory.ts +45 -0
  544. package/src/ratelimit/runtime.ts +247 -0
  545. package/src/ratelimit/schema.ts +28 -0
  546. package/src/ratelimit/upstash.ts +60 -0
  547. package/src/ratelimit/wrangler.ts +70 -0
  548. package/src/reference/asyncapi.ts +2 -2
  549. package/src/reference/graphql.ts +20 -3
  550. package/src/reference/index.ts +1 -0
  551. package/src/reference/openapi.ts +21 -7
  552. package/src/reference/options.ts +80 -8
  553. package/src/reference/scalar.ts +33 -8
  554. package/src/registry/eject.ts +23 -5
  555. package/src/registry/registry.ts +7 -0
  556. package/src/search/adapters/locale-scope.ts +30 -0
  557. package/src/search/adapters/typesense.ts +14 -4
  558. package/src/search/build.ts +21 -3
  559. package/src/search/documents.ts +26 -2
  560. package/src/search/orama-index.ts +191 -34
  561. package/src/search/pagefind-ranking.ts +39 -0
  562. package/src/search/source-pages.ts +29 -0
  563. package/src/search/sync/algolia.ts +111 -10
  564. package/src/search/sync/index.ts +52 -13
  565. package/src/search/sync/orama-cloud.ts +3 -0
  566. package/src/search/sync/typesense.ts +76 -26
  567. package/src/seo/metatags.ts +67 -0
  568. package/src/sources/contentful.ts +12 -0
  569. package/src/sources/github-releases.ts +7 -1
  570. package/src/sources/shared.ts +3 -0
  571. package/src/theme/chrome-icons.ts +1 -0
  572. package/src/theme/color.ts +500 -0
  573. package/src/theme/entry.ts +85 -6
  574. package/src/theme/palette.ts +121 -45
  575. package/src/translate/prompts.ts +1 -0
  576. package/src/translate/validate.ts +31 -3
  577. package/src/upgrade/upgrade.ts +4 -171
  578. package/dist/cli/chunk-1d7ve1dm.js.map +0 -12
  579. package/dist/cli/chunk-2hsdwb9n.js.map +0 -10
  580. package/dist/cli/chunk-35d4wj9f.js.map +0 -15
  581. package/dist/cli/chunk-364znk6q.js +0 -75
  582. package/dist/cli/chunk-364znk6q.js.map +0 -11
  583. package/dist/cli/chunk-5m5nmvyq.js.map +0 -13
  584. package/dist/cli/chunk-8ktnccpt.js.map +0 -10
  585. package/dist/cli/chunk-9t7a85s3.js.map +0 -10
  586. package/dist/cli/chunk-b07cmahc.js +0 -145
  587. package/dist/cli/chunk-b07cmahc.js.map +0 -11
  588. package/dist/cli/chunk-c8chx29p.js.map +0 -36
  589. package/dist/cli/chunk-e04dxsz1.js +0 -39
  590. package/dist/cli/chunk-e04dxsz1.js.map +0 -10
  591. package/dist/cli/chunk-j85vccga.js.map +0 -36
  592. package/dist/cli/chunk-p3v96n38.js.map +0 -10
  593. package/dist/cli/chunk-pehfxfta.js.map +0 -10
  594. package/dist/cli/chunk-pv29h0wf.js.map +0 -190
  595. package/dist/cli/chunk-q58y5e6a.js.map +0 -10
  596. package/dist/cli/chunk-vg9r4eb9.js.map +0 -14
  597. package/dist/cli/chunk-wrr3j9w9.js.map +0 -35
  598. package/dist/cli/chunk-yfyb25rh.js.map +0 -14
  599. package/docs/content/sources.mdx +0 -334
  600. /package/dist/cli/{chunk-tkacnehg.js.map → chunk-3v3ffbrj.js.map} +0 -0
  601. /package/dist/cli/{chunk-r9rcc4w7.js.map → chunk-8y2kmjv8.js.map} +0 -0
  602. /package/dist/cli/{chunk-2eytanqx.js.map → chunk-c887fj0s.js.map} +0 -0
  603. /package/dist/cli/{chunk-akbpwfxc.js.map → chunk-dzwehpy7.js.map} +0 -0
  604. /package/dist/cli/{chunk-2z928egk.js.map → chunk-ft9vfys6.js.map} +0 -0
  605. /package/dist/cli/{chunk-a58773jm.js.map → chunk-kma9f6sf.js.map} +0 -0
  606. /package/dist/cli/{chunk-crgn1q09.js.map → chunk-sds923fa.js.map} +0 -0
  607. /package/dist/cli/{chunk-acanzt5p.js.map → chunk-tdqhyxfz.js.map} +0 -0
@@ -2,6 +2,7 @@ import { normalizeBasePath, stripBasePath } from "../../core/base-path.ts";
2
2
  import type { Diagnostic } from "../../core/types.ts";
3
3
  import { finding } from "../catalog.ts";
4
4
  import { pageSite } from "../locate.ts";
5
+ import { redirectAt } from "../redirects.ts";
5
6
  import type { AuditContext, CheckModule } from "../types.ts";
6
7
  import { decodePath, normalizePath, siteOrigin } from "../url.ts";
7
8
 
@@ -100,9 +101,7 @@ const checkListedUrl = (
100
101
  );
101
102
  const page = context.byUrl.get(path);
102
103
  if (!page) {
103
- const redirect = context.redirects.find(
104
- (entry) => normalizePath(entry.from) === path
105
- );
104
+ const redirect = redirectAt(context.redirects, path);
106
105
  return [
107
106
  finding(
108
107
  "BLUME_AUDIT_SITEMAP_BAD_URL",
@@ -1,3 +1,7 @@
1
+ import {
2
+ isPatternPath,
3
+ patternDestination,
4
+ } from "../core/redirect-patterns.ts";
1
5
  import type { RedirectResolution } from "./types.ts";
2
6
  import { normalizePath } from "./url.ts";
3
7
 
@@ -7,18 +11,6 @@ interface ConfiguredRedirect {
7
11
  status: number;
8
12
  }
9
13
 
10
- /**
11
- * Follow every configured redirect through to its destination, classifying what
12
- * it lands on.
13
- *
14
- * - `loop` — the chain revisits a hop it has already been to. Never resolves.
15
- * - `broken` — the chain ends somewhere the build does not serve.
16
- * - `chain` — it resolves, but through at least one intermediate redirect.
17
- * - `ok` — one hop, straight to a real page.
18
- *
19
- * An external destination (`https://…`) is always `ok`: it's outside the site,
20
- * so there's no local page to check it against.
21
- */
22
14
  /**
23
15
  * A destination's page path: the part before any query string or fragment. A
24
16
  * redirect to `/guide#setup` or `/search?q=x` lands on the `/guide` / `/search`
@@ -30,6 +22,22 @@ const pathOnly = (value: string): string => {
30
22
  return cut === -1 ? value : value.slice(0, cut);
31
23
  };
32
24
 
25
+ /**
26
+ * Follow every configured redirect through to its destination, classifying what
27
+ * it lands on.
28
+ *
29
+ * - `loop` — the chain revisits a hop it has already been to. Never resolves.
30
+ * - `broken` — the chain ends somewhere the build does not serve.
31
+ * - `chain` — it resolves, but through at least one intermediate redirect.
32
+ * - `ok` — one hop, straight to a real page.
33
+ * - `pattern` — a pattern (`/beta/:slug*`), which covers paths rather than
34
+ * naming one, so there is no single chain to walk; {@link redirectAt}
35
+ * resolves a path it covers.
36
+ *
37
+ * An external destination (`https://…`) is always `ok`: it's outside the site,
38
+ * so there's no local page to check it against. A hop onto a path a pattern
39
+ * covers continues through that pattern.
40
+ */
33
41
  export const resolveRedirects = (
34
42
  redirects: readonly ConfiguredRedirect[],
35
43
  /** Whether the build serves a normalized path — see `isServed`. */
@@ -39,9 +47,14 @@ export const resolveRedirects = (
39
47
  for (const redirect of redirects) {
40
48
  byFrom.set(normalizePath(redirect.from), redirect);
41
49
  }
50
+ const hopFrom = (path: string): string | undefined =>
51
+ byFrom.get(path)?.to ?? patternDestination(redirects, path);
42
52
 
43
53
  return redirects.map((redirect) => {
44
54
  const from = normalizePath(redirect.from);
55
+ if (isPatternPath(redirect.from)) {
56
+ return { ...redirect, chain: [from], outcome: "pattern" as const };
57
+ }
45
58
  const chain: string[] = [from];
46
59
  const seen = new Set<string>([from]);
47
60
  let current = redirect.to;
@@ -63,11 +76,11 @@ export const resolveRedirects = (
63
76
  }
64
77
  chain.push(next);
65
78
  seen.add(next);
66
- const hop = byFrom.get(next);
67
- if (!hop) {
79
+ const hop = hopFrom(next);
80
+ if (hop === undefined) {
68
81
  break;
69
82
  }
70
- current = hop.to;
83
+ current = hop;
71
84
  }
72
85
 
73
86
  const destination = chain.at(-1) ?? from;
@@ -84,3 +97,21 @@ export const resolveRedirects = (
84
97
  };
85
98
  });
86
99
  };
100
+
101
+ /**
102
+ * The configured redirect a path takes, for the checks that report a link,
103
+ * canonical, sitemap entry, or `hreflang` pointing through one: the exact
104
+ * redirect from that path, else the pattern covering it, its `to` filled in
105
+ * for this path.
106
+ */
107
+ export const redirectAt = (
108
+ redirects: readonly RedirectResolution[],
109
+ path: string
110
+ ): { to: string } | undefined => {
111
+ const exact = redirects.find((entry) => normalizePath(entry.from) === path);
112
+ if (exact) {
113
+ return exact;
114
+ }
115
+ const to = patternDestination(redirects, path);
116
+ return to === undefined ? undefined : { to };
117
+ };
package/src/audit/run.ts CHANGED
@@ -12,6 +12,7 @@ import { CHECKS } from "./catalog.ts";
12
12
  import type { CheckId } from "./catalog.ts";
13
13
  import { assetChecks } from "./checks/assets.ts";
14
14
  import { contentChecks } from "./checks/content.ts";
15
+ import { contrastChecks } from "./checks/contrast.ts";
15
16
  import { dnsAidChecks } from "./checks/dns-aid.ts";
16
17
  import { duplicateChecks } from "./checks/duplicates.ts";
17
18
  import { i18nChecks } from "./checks/i18n.ts";
@@ -51,6 +52,7 @@ const MODULES: CheckModule[] = [
51
52
  ogImageChecks,
52
53
  i18nChecks,
53
54
  assetChecks,
55
+ contrastChecks,
54
56
  sitemapChecks,
55
57
  robotsChecks,
56
58
  llmsChecks,
@@ -67,6 +69,8 @@ export interface AuditOptions {
67
69
  origin?: string;
68
70
  /** Probe outbound links (`--external`). */
69
71
  external?: boolean;
72
+ /** Outbound URLs not to probe (`--ignore`). */
73
+ ignore?: (url: string) => boolean;
70
74
  /** Only report these check ids or categories. */
71
75
  only?: string[];
72
76
  /** Suppress these check ids or categories. */
@@ -157,6 +161,7 @@ export const runAudit = async (options: AuditOptions): Promise<AuditResult> => {
157
161
  siteOrigin(project.config.deployment.options.site),
158
162
  normalizeBasePath(project.config.deployment.options.base)
159
163
  ),
164
+ ignore: options.ignore ?? (() => false),
160
165
  llms: crawl.llms,
161
166
  origin,
162
167
  pages: crawl.pages,
@@ -13,6 +13,7 @@ import type {
13
13
  export type AuditTier = "static" | "network" | "external";
14
14
 
15
15
  export type AuditCategory =
16
+ | "accessibility"
16
17
  | "content"
17
18
  | "duplicates"
18
19
  | "indexability"
@@ -110,7 +111,7 @@ export interface RedirectResolution {
110
111
  status: number;
111
112
  /** Every hop from `from` to the final target, inclusive. */
112
113
  chain: string[];
113
- outcome: "ok" | "loop" | "broken" | "chain";
114
+ outcome: "ok" | "loop" | "broken" | "chain" | "pattern";
114
115
  }
115
116
 
116
117
  /** One urlset file behind a sitemap, for the per-file size limits. */
@@ -248,6 +249,8 @@ export interface AuditContext {
248
249
  robots: RobotsDoc | null;
249
250
  llms: LlmsDoc | null;
250
251
  thresholds: AuditThresholds;
252
+ /** Outbound URLs the external tier doesn't probe (`--ignore`). */
253
+ ignore: (url: string) => boolean;
251
254
  }
252
255
 
253
256
  /** One category's checks. Modules, not per-check closures — see catalog.ts. */
package/src/audit/url.ts CHANGED
@@ -45,10 +45,11 @@ export type ResolvedHref =
45
45
  | { kind: "self-origin"; path: string; hash: string }
46
46
  /**
47
47
  * A path on this origin that lacks `deployment.base`. The host serves the
48
- * whole build under the base, so nothing answers here — `path` is the path
49
- * as requested, base missing.
48
+ * whole build under the base, so nothing in the build answers here — `path`
49
+ * is the path as requested, base missing. `absolute` when the href spelled
50
+ * out the origin: a full URL can name another app on the same host.
50
51
  */
51
- | { kind: "outside-base"; path: string }
52
+ | { kind: "outside-base"; path: string; absolute: boolean }
52
53
  /** An absolute URL on another origin. */
53
54
  | { kind: "external"; url: string }
54
55
  /** In-page anchor, `mailto:`, `tel:`, `javascript:`, data URI — not a page link. */
@@ -139,7 +140,11 @@ export const resolveHref = (
139
140
  if (origin && parsed.origin === origin) {
140
141
  const served = decodePath(parsed.pathname);
141
142
  if (!underBase(deployBase, served)) {
142
- return { kind: "outside-base", path: normalizePath(served) };
143
+ return {
144
+ absolute: true,
145
+ kind: "outside-base",
146
+ path: normalizePath(served),
147
+ };
143
148
  }
144
149
  return {
145
150
  hash: parsed.hash.slice(1),
@@ -171,7 +176,11 @@ export const resolveHref = (
171
176
  // (`./auth`) was resolved against the base-less page URL above, so it
172
177
  // inherits the page's base by construction.
173
178
  if (target.startsWith("/") && !underBase(deployBase, served)) {
174
- return { kind: "outside-base", path: normalizePath(served) };
179
+ return {
180
+ absolute: false,
181
+ kind: "outside-base",
182
+ path: normalizePath(served),
183
+ };
175
184
  }
176
185
  return {
177
186
  hash: resolved.hash.slice(1),
@@ -12,7 +12,18 @@ declare module "blume:search-client" {
12
12
  // biome-ignore lint/style/useImportType: ambient module must stay a global script
13
13
  // oxlint-disable-next-line typescript/consistent-type-imports
14
14
  type Fn = import("./components/layout/search/types.ts").SearchFn;
15
- export const createSearch: () => Fn | Promise<Fn>;
15
+ type Options =
16
+ // biome-ignore lint/style/useImportType: ambient module must stay a global script
17
+ // oxlint-disable-next-line typescript/consistent-type-imports
18
+ import("./components/layout/search/types.ts").SearchClientOptions;
19
+ export const createSearch: (options?: Options) => Fn | Promise<Fn>;
20
+ }
21
+
22
+ declare module "blume:consent-client" {
23
+ /** Start the configured consent adapter's browser module (see `consentClientTemplate`); a no-op for one without. */
24
+ // oxlint-disable-next-line typescript/consistent-type-imports
25
+ type Consent = import("./components/layout/consent/types.ts").BlumeConsent;
26
+ export const startConsentClient: (consent: Consent) => void;
16
27
  }
17
28
 
18
29
  declare module "blume:ask" {
@@ -0,0 +1,152 @@
1
+ /**
2
+ * The browser half of the bot check: a fresh token for each question the
3
+ * assistant sends (both providers' tokens are single-use). The provider's
4
+ * script loads with the first question rather than with the page, and one
5
+ * widget serves every question after it: an invisible hCaptcha, or a
6
+ * Turnstile that only shows itself when it needs the reader to interact,
7
+ * in a small container at the foot of the viewport.
8
+ *
9
+ * The client router swaps `<body>` on every navigation, taking the container
10
+ * and its widget with it, so a question after a swap mounts a new one.
11
+ */
12
+ import type { CaptchaSettings } from "./schema.ts";
13
+
14
+ /** Each provider's script, rendering only when asked to. */
15
+ export const CAPTCHA_SCRIPTS = {
16
+ hcaptcha: "https://js.hcaptcha.com/1/api.js?render=explicit",
17
+ turnstile:
18
+ "https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit",
19
+ } as const;
20
+
21
+ /** What Blume asks a Turnstile widget for. */
22
+ interface TurnstileRenderOptions {
23
+ appearance: "interaction-only";
24
+ callback: (token: string) => void;
25
+ "error-callback": () => void;
26
+ execution: "execute";
27
+ sitekey: string;
28
+ }
29
+
30
+ /** The slice of `window.turnstile` the check uses. */
31
+ export interface TurnstileApi {
32
+ execute: (widget: string) => void;
33
+ render: (container: HTMLElement, options: TurnstileRenderOptions) => string;
34
+ reset: (widget: string) => void;
35
+ }
36
+
37
+ /** What Blume asks an hCaptcha widget for. */
38
+ interface HcaptchaRenderOptions {
39
+ sitekey: string;
40
+ size: "invisible";
41
+ }
42
+
43
+ /** The slice of `window.hcaptcha` the check uses. */
44
+ export interface HcaptchaApi {
45
+ execute: (
46
+ widget: string,
47
+ options: { async: true }
48
+ ) => Promise<{ response: string }>;
49
+ render: (container: HTMLElement, options: HcaptchaRenderOptions) => string;
50
+ reset: (widget: string) => void;
51
+ }
52
+
53
+ /** The window, with the providers' globals once their scripts ran. */
54
+ type CaptchaWindow = Window & {
55
+ hcaptcha?: HcaptchaApi;
56
+ turnstile?: TurnstileApi;
57
+ };
58
+
59
+ /** A Turnstile token on its way. */
60
+ interface PendingToken {
61
+ reject: (error: Error) => void;
62
+ resolve: (token: string) => void;
63
+ }
64
+
65
+ /** Get a token for the next question. */
66
+ export type CaptchaTokenGetter = (settings: CaptchaSettings) => Promise<string>;
67
+
68
+ /** A token getter with its own script, container, and widget. */
69
+ export const createCaptchaClient = (): CaptchaTokenGetter => {
70
+ let loaded: Promise<void> | undefined;
71
+ let host: HTMLElement | undefined;
72
+ let widget: string | undefined;
73
+ let pending: PendingToken | undefined;
74
+
75
+ const load = (kind: CaptchaSettings["kind"]): Promise<void> => {
76
+ // oxlint-disable-next-line promise/avoid-new -- adapt the script tag's load events
77
+ loaded ??= new Promise<void>((resolve, reject) => {
78
+ const tag = document.createElement("script");
79
+ tag.async = true;
80
+ tag.src = CAPTCHA_SCRIPTS[kind];
81
+ tag.addEventListener("load", () => resolve());
82
+ tag.addEventListener("error", () => {
83
+ // Let the next question try again.
84
+ loaded = undefined;
85
+ reject(new Error(`The ${kind} script didn't load.`));
86
+ });
87
+ document.head.append(tag);
88
+ });
89
+ return loaded;
90
+ };
91
+
92
+ // The widget's container, mounted again when a swap took the last one.
93
+ const mount = (): HTMLElement => {
94
+ if (host?.isConnected) {
95
+ return host;
96
+ }
97
+ host = document.createElement("div");
98
+ host.className = "fixed bottom-4 left-1/2 z-[60] -translate-x-1/2";
99
+ host.dataset.blumeCaptcha = "";
100
+ document.body.append(host);
101
+ widget = undefined;
102
+ return host;
103
+ };
104
+
105
+ const turnstileToken = (api: TurnstileApi, siteKey: string) =>
106
+ // oxlint-disable-next-line promise/avoid-new -- adapt Turnstile's token callbacks
107
+ new Promise<string>((resolve, reject) => {
108
+ pending = { reject, resolve };
109
+ const target = mount();
110
+ if (widget === undefined) {
111
+ widget = api.render(target, {
112
+ appearance: "interaction-only",
113
+ callback: (token) => pending?.resolve(token),
114
+ "error-callback": () =>
115
+ pending?.reject(new Error("The Turnstile check failed.")),
116
+ execution: "execute",
117
+ sitekey: siteKey,
118
+ });
119
+ } else {
120
+ api.reset(widget);
121
+ }
122
+ api.execute(widget);
123
+ });
124
+
125
+ const hcaptchaToken = async (api: HcaptchaApi, siteKey: string) => {
126
+ const target = mount();
127
+ if (widget === undefined) {
128
+ widget = api.render(target, { sitekey: siteKey, size: "invisible" });
129
+ } else {
130
+ api.reset(widget);
131
+ }
132
+ const { response } = await api.execute(widget, { async: true });
133
+ return response;
134
+ };
135
+
136
+ return async (settings) => {
137
+ await load(settings.kind);
138
+ // SAFETY: CaptchaWindow only adds the providers' optional globals, each
139
+ // checked before use.
140
+ const { hcaptcha, turnstile } = window as CaptchaWindow;
141
+ if (settings.kind === "hcaptcha" && hcaptcha) {
142
+ return hcaptchaToken(hcaptcha, settings.siteKey);
143
+ }
144
+ if (settings.kind === "turnstile" && turnstile) {
145
+ return turnstileToken(turnstile, settings.siteKey);
146
+ }
147
+ throw new Error(`The ${settings.kind} script loaded without its API.`);
148
+ };
149
+ };
150
+
151
+ /** The page's token getter: one script and one widget per page load. */
152
+ export const captchaToken = createCaptchaClient();
@@ -0,0 +1,36 @@
1
+ import { z } from "zod";
2
+
3
+ import type { AdapterDescriptor } from "../core/adapter.ts";
4
+ import { adapterDescriptorSchema } from "../core/adapter.ts";
5
+
6
+ /** Options for {@link hcaptcha}. */
7
+ export interface HcaptchaOptions {
8
+ /** The site key, from the hCaptcha dashboard. */
9
+ siteKey: string;
10
+ }
11
+
12
+ export const hcaptchaOptionsSchema = z.strictObject({
13
+ siteKey: z.string().min(1),
14
+ });
15
+
16
+ export type HcaptchaAdapter = AdapterDescriptor<"hcaptcha", HcaptchaOptions>;
17
+
18
+ export const hcaptchaAdapterSchema = adapterDescriptorSchema(
19
+ "hcaptcha",
20
+ hcaptchaOptionsSchema
21
+ );
22
+
23
+ /** Where the assistant's route checks an hCaptcha token. */
24
+ export const HCAPTCHA_VERIFY_URL = "https://api.hcaptcha.com/siteverify";
25
+
26
+ /**
27
+ * hCaptcha, as an invisible check: most readers never see a challenge. The
28
+ * site key is public and goes to the browser; the secret the route checks
29
+ * tokens with comes from `HCAPTCHA_SECRET_KEY`.
30
+ */
31
+ export const hcaptcha = (options: HcaptchaOptions): HcaptchaAdapter => ({
32
+ kind: "hcaptcha",
33
+ options,
34
+ requiredSecrets: ["HCAPTCHA_SECRET_KEY"],
35
+ runtimeDeps: [],
36
+ });
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Bot checks for the assistant (`ai.assistant.captcha` in `blume.config.ts`).
3
+ * The assistant gets a token from the check before each question and the
4
+ * generated route verifies it before calling the model:
5
+ *
6
+ * ```ts
7
+ * import { defineConfig } from "blume";
8
+ * import { turnstile } from "blume/captcha";
9
+ *
10
+ * export default defineConfig({
11
+ * ai: {
12
+ * assistant: {
13
+ * enabled: true,
14
+ * captcha: turnstile({ siteKey: "0x4AAAAAAA…" }),
15
+ * },
16
+ * },
17
+ * });
18
+ * ```
19
+ *
20
+ * Each factory returns a plain descriptor (see `core/adapter.ts`); the browser
21
+ * half is `client.ts`, the server half `verify.ts`.
22
+ */
23
+ export type { AdapterDescriptor, JsonValue } from "../core/adapter.ts";
24
+ export { hcaptcha } from "./hcaptcha.ts";
25
+ export type { HcaptchaAdapter, HcaptchaOptions } from "./hcaptcha.ts";
26
+ export type { CaptchaAdapter, CaptchaSettings } from "./schema.ts";
27
+ export { turnstile } from "./turnstile.ts";
28
+ export type { TurnstileAdapter, TurnstileOptions } from "./turnstile.ts";
@@ -0,0 +1,32 @@
1
+ import { z } from "zod";
2
+
3
+ import { hcaptchaAdapterSchema } from "./hcaptcha.ts";
4
+ import { turnstileAdapterSchema } from "./turnstile.ts";
5
+
6
+ /**
7
+ * The configured bot check, as its factory returned it. Anything that isn't
8
+ * one fails with the adapter hint; an adapter's own option errors keep their
9
+ * messages.
10
+ */
11
+ export const captchaAdapterSchema = z.discriminatedUnion(
12
+ "kind",
13
+ [hcaptchaAdapterSchema, turnstileAdapterSchema],
14
+ {
15
+ error:
16
+ 'captcha takes one adapter — e.g. `captcha: turnstile({ siteKey })` or `captcha: hcaptcha({ siteKey })`, imported from "blume/captcha".',
17
+ }
18
+ );
19
+
20
+ export type CaptchaAdapter = z.output<typeof captchaAdapterSchema>;
21
+
22
+ /** What the browser needs to run the check: never the secret. */
23
+ export interface CaptchaSettings {
24
+ kind: CaptchaAdapter["kind"];
25
+ siteKey: string;
26
+ }
27
+
28
+ /** The public half of an adapter, for the data snapshot. */
29
+ export const captchaSettings = (adapter: CaptchaAdapter): CaptchaSettings => ({
30
+ kind: adapter.kind,
31
+ siteKey: adapter.options.siteKey,
32
+ });
@@ -0,0 +1,38 @@
1
+ import { z } from "zod";
2
+
3
+ import type { AdapterDescriptor } from "../core/adapter.ts";
4
+ import { adapterDescriptorSchema } from "../core/adapter.ts";
5
+
6
+ /** Options for {@link turnstile}. */
7
+ export interface TurnstileOptions {
8
+ /** The widget's site key, from the Cloudflare dashboard. */
9
+ siteKey: string;
10
+ }
11
+
12
+ export const turnstileOptionsSchema = z.strictObject({
13
+ siteKey: z.string().min(1),
14
+ });
15
+
16
+ export type TurnstileAdapter = AdapterDescriptor<"turnstile", TurnstileOptions>;
17
+
18
+ export const turnstileAdapterSchema = adapterDescriptorSchema(
19
+ "turnstile",
20
+ turnstileOptionsSchema
21
+ );
22
+
23
+ /** Where the assistant's route checks a Turnstile token. */
24
+ export const TURNSTILE_VERIFY_URL =
25
+ "https://challenges.cloudflare.com/turnstile/v0/siteverify";
26
+
27
+ /**
28
+ * Cloudflare Turnstile. The site key is public and goes to the browser; the
29
+ * secret key the route checks tokens with comes from `TURNSTILE_SECRET_KEY`.
30
+ * Set the widget to Invisible or Managed in the Cloudflare dashboard: most
31
+ * readers never see a challenge.
32
+ */
33
+ export const turnstile = (options: TurnstileOptions): TurnstileAdapter => ({
34
+ kind: "turnstile",
35
+ options,
36
+ requiredSecrets: ["TURNSTILE_SECRET_KEY"],
37
+ runtimeDeps: [],
38
+ });
@@ -0,0 +1,71 @@
1
+ /**
2
+ * The server half of the bot check: the generated assistant route verifies
3
+ * the token the browser sent with the question against the provider before
4
+ * it calls the model. A failed or unreachable check refuses the question
5
+ * (and logs why), so a server that can't reach the provider shows up as a
6
+ * refused question rather than a silently unprotected route.
7
+ */
8
+ import { clientAddressOf } from "../core/client-address.ts";
9
+ import type { ClientContext } from "../core/client-address.ts";
10
+ import { HCAPTCHA_VERIFY_URL } from "./hcaptcha.ts";
11
+ import type { CaptchaAdapter } from "./schema.ts";
12
+ import { TURNSTILE_VERIFY_URL } from "./turnstile.ts";
13
+
14
+ /** The longest token either provider issues, with room to spare. */
15
+ const MAX_TOKEN_LENGTH = 4096;
16
+
17
+ /** What the provider answers about a token. */
18
+ interface VerifyReply {
19
+ "error-codes"?: string[];
20
+ success?: boolean;
21
+ }
22
+
23
+ /** What the route hands the check from its runtime. */
24
+ export interface VerifyOptions {
25
+ /** For tests; defaults to the global `fetch`. */
26
+ fetch?: typeof fetch;
27
+ /** The provider's secret key. */
28
+ secret: string;
29
+ }
30
+
31
+ /**
32
+ * Whether the provider confirms `token` came from a person on this site. The
33
+ * route passes the request body's `captcha` when it's a string.
34
+ */
35
+ export const verifyCaptcha = async (
36
+ adapter: CaptchaAdapter,
37
+ token: string | undefined,
38
+ context: ClientContext,
39
+ options: VerifyOptions
40
+ ): Promise<boolean> => {
41
+ if (!token || token.length > MAX_TOKEN_LENGTH) {
42
+ return false;
43
+ }
44
+ const form = new URLSearchParams({ response: token, secret: options.secret });
45
+ const address = clientAddressOf(context);
46
+ if (address) {
47
+ form.set("remoteip", address);
48
+ }
49
+ if (adapter.kind === "hcaptcha") {
50
+ form.set("sitekey", adapter.options.siteKey);
51
+ }
52
+ try {
53
+ const response = await (options.fetch ?? fetch)(
54
+ adapter.kind === "hcaptcha" ? HCAPTCHA_VERIFY_URL : TURNSTILE_VERIFY_URL,
55
+ { body: form, method: "POST" }
56
+ );
57
+ // SAFETY: both providers answer JSON with a `success` flag; anything
58
+ // else reads as a failure below.
59
+ const reply = (await response.json()) as VerifyReply;
60
+ if (reply.success === true) {
61
+ return true;
62
+ }
63
+ console.warn(
64
+ `The ${adapter.kind} check refused a question:`,
65
+ reply["error-codes"] ?? response.status
66
+ );
67
+ } catch (error) {
68
+ console.error(`The ${adapter.kind} check could not be reached:`, error);
69
+ }
70
+ return false;
71
+ };
package/src/cli/args.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import picomatch from "picomatch";
2
+
1
3
  import { logger } from "./log.ts";
2
4
 
3
5
  const MAX_PORT = 65_535;
@@ -22,6 +24,55 @@ export const parsePort = (value?: string): number | undefined => {
22
24
  return port;
23
25
  };
24
26
 
27
+ /**
28
+ * Every value a repeatable string flag was given (`--ignore a --ignore b`), in
29
+ * order. citty 0.2 parses with `node:util.parseArgs` without its `multiple`
30
+ * option, so the parsed `args` keep only the last value; this reads the raw
31
+ * arguments the way that parser does instead: `--name=value`, or `--name`
32
+ * followed by the next token whatever it looks like (`""` when there is
33
+ * none), and nothing after a bare `--`.
34
+ */
35
+ export const repeatedFlag = (
36
+ rawArgs: readonly string[],
37
+ name: string
38
+ ): string[] => {
39
+ const flag = `--${name}`;
40
+ const values: string[] = [];
41
+ for (let index = 0; index < rawArgs.length; index += 1) {
42
+ const arg = rawArgs[index];
43
+ if (arg === "--") {
44
+ break;
45
+ }
46
+ if (arg === flag) {
47
+ values.push(rawArgs[index + 1] ?? "");
48
+ index += 1;
49
+ } else if (arg?.startsWith(`${flag}=`)) {
50
+ values.push(arg.slice(flag.length + 1));
51
+ }
52
+ }
53
+ return values;
54
+ };
55
+
56
+ /**
57
+ * The `--ignore` globs of `blume validate` and `blume audit`, as a test for
58
+ * the external URLs they skip: any glob matching the full URL skips it, with
59
+ * `dot` set so `**` also crosses a segment like `/.well-known/`. No globs skip
60
+ * nothing. An empty one (`--ignore` with nothing after it) exits with an
61
+ * error: it names no URL, and picomatch throws on it.
62
+ */
63
+ export const parseIgnoreFlag = (
64
+ rawArgs: readonly string[]
65
+ ): ((url: string) => boolean) => {
66
+ const globs = repeatedFlag(rawArgs, "ignore");
67
+ if (globs.includes("")) {
68
+ logger.error(
69
+ 'Invalid --ignore "": pass a glob for the URLs to skip (e.g. --ignore "https://example.com/**").'
70
+ );
71
+ process.exit(1);
72
+ }
73
+ return picomatch(globs, { dot: true });
74
+ };
75
+
25
76
  /**
26
77
  * The longest `--timeout` a timer can hold, in seconds. Node clamps a
27
78
  * `setTimeout` delay above 2^31−1 ms (about 24.8 days) to 1 ms, so a larger