blume 2.0.0 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (454) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +110 -0
  3. package/README.md +2 -2
  4. package/dist/cli/{chunk-f2972sbt.js → chunk-00gs3wqs.js} +1 -1
  5. package/dist/cli/{chunk-fa25z98p.js → chunk-273ygyr4.js} +4 -3
  6. package/dist/cli/chunk-273ygyr4.js.map +11 -0
  7. package/dist/cli/{chunk-pat2zzwc.js → chunk-3yce002v.js} +21 -8
  8. package/dist/cli/{chunk-pat2zzwc.js.map → chunk-3yce002v.js.map} +4 -4
  9. package/dist/cli/{chunk-d1tadaw7.js → chunk-4e9b9ra6.js} +17 -7
  10. package/dist/cli/chunk-4e9b9ra6.js.map +10 -0
  11. package/dist/cli/{chunk-zg2gtj10.js → chunk-5r8g91qn.js} +352 -676
  12. package/dist/cli/chunk-5r8g91qn.js.map +34 -0
  13. package/dist/cli/{chunk-11j0384y.js → chunk-6dtt0zfn.js} +14 -14
  14. package/dist/cli/{chunk-11j0384y.js.map → chunk-6dtt0zfn.js.map} +3 -3
  15. package/dist/cli/{chunk-bw22s759.js → chunk-6k4ftwze.js} +38 -19
  16. package/dist/cli/{chunk-bw22s759.js.map → chunk-6k4ftwze.js.map} +4 -4
  17. package/dist/cli/{chunk-sqn5t4q0.js → chunk-7mbqtmgb.js} +16 -7
  18. package/dist/cli/chunk-7mbqtmgb.js.map +10 -0
  19. package/dist/cli/{chunk-79njf86q.js → chunk-7vtckvaw.js} +21 -19
  20. package/dist/cli/chunk-7vtckvaw.js.map +11 -0
  21. package/dist/cli/{chunk-fh5hj5jt.js → chunk-8g8ytmgx.js} +17 -18
  22. package/dist/cli/{chunk-fh5hj5jt.js.map → chunk-8g8ytmgx.js.map} +2 -2
  23. package/dist/cli/{chunk-1w8dp3qb.js → chunk-91ws1n6j.js} +18 -15
  24. package/dist/cli/chunk-91ws1n6j.js.map +10 -0
  25. package/dist/cli/{chunk-7ez8ny0t.js → chunk-bbnwccaz.js} +2 -2
  26. package/dist/cli/{chunk-5a2z0198.js → chunk-bfwp9vp6.js} +18 -10
  27. package/dist/cli/chunk-bfwp9vp6.js.map +10 -0
  28. package/dist/cli/{chunk-ernrthtr.js → chunk-d1v5rhy0.js} +14 -15
  29. package/dist/cli/{chunk-ernrthtr.js.map → chunk-d1v5rhy0.js.map} +2 -2
  30. package/dist/cli/{chunk-a9kptbw5.js → chunk-ddndchfr.js} +21 -6
  31. package/dist/cli/chunk-ddndchfr.js.map +14 -0
  32. package/dist/cli/{chunk-zxccj738.js → chunk-esh98wmb.js} +1 -1
  33. package/dist/cli/{chunk-88cpgt6h.js → chunk-fsmrqk8a.js} +1 -1
  34. package/dist/cli/{chunk-b5aj94ah.js → chunk-g698a744.js} +5 -5
  35. package/dist/cli/{chunk-jts8mvcz.js → chunk-gs7r695n.js} +9 -3
  36. package/dist/cli/{chunk-jts8mvcz.js.map → chunk-gs7r695n.js.map} +3 -3
  37. package/dist/cli/{chunk-41za066z.js → chunk-h2ez8dzb.js} +4 -4
  38. package/dist/cli/{chunk-by2290sx.js → chunk-h7k3nq3v.js} +2 -2
  39. package/dist/cli/{chunk-j8mw0za6.js → chunk-hqp2ajnh.js} +252 -123
  40. package/dist/cli/chunk-hqp2ajnh.js.map +35 -0
  41. package/dist/cli/{chunk-mnqj32sj.js → chunk-hr8ne106.js} +119 -50
  42. package/dist/cli/chunk-hr8ne106.js.map +13 -0
  43. package/dist/cli/{chunk-mwt1k8n7.js → chunk-j85scx15.js} +74 -30
  44. package/dist/cli/chunk-j85scx15.js.map +10 -0
  45. package/dist/cli/{chunk-z01ze5c1.js → chunk-k7pj68a8.js} +63 -22
  46. package/dist/cli/chunk-k7pj68a8.js.map +11 -0
  47. package/dist/cli/{chunk-2q1dwty4.js → chunk-mqc662a6.js} +8 -8
  48. package/dist/cli/{chunk-2q1dwty4.js.map → chunk-mqc662a6.js.map} +4 -4
  49. package/dist/cli/{chunk-y3e45rc8.js → chunk-n1yg3tj3.js} +4 -4
  50. package/dist/cli/{chunk-bctazmbk.js → chunk-nfcyttvj.js} +17 -6
  51. package/dist/cli/chunk-nfcyttvj.js.map +10 -0
  52. package/dist/cli/{chunk-6k8vp3ta.js → chunk-pbg5a4s3.js} +60 -28
  53. package/dist/cli/chunk-pbg5a4s3.js.map +19 -0
  54. package/dist/cli/{chunk-d80hr03s.js → chunk-ppzjqwx2.js} +24 -17
  55. package/dist/cli/{chunk-d80hr03s.js.map → chunk-ppzjqwx2.js.map} +4 -4
  56. package/dist/cli/{chunk-beat36xx.js → chunk-qkb5a8sa.js} +25 -10
  57. package/dist/cli/chunk-qkb5a8sa.js.map +10 -0
  58. package/dist/cli/{chunk-bnbmcwfb.js → chunk-sqw4ekg1.js} +5 -5
  59. package/dist/cli/{chunk-bnbmcwfb.js.map → chunk-sqw4ekg1.js.map} +3 -3
  60. package/dist/cli/{chunk-xaz13gwg.js → chunk-v6ya5kcb.js} +3268 -1150
  61. package/dist/cli/chunk-v6ya5kcb.js.map +189 -0
  62. package/dist/cli/{chunk-tzne8qfq.js → chunk-w4bxdvsa.js} +18 -15
  63. package/dist/cli/chunk-w4bxdvsa.js.map +10 -0
  64. package/dist/cli/{chunk-f7t03s3g.js → chunk-wdrt2k2v.js} +2 -2
  65. package/dist/cli/{chunk-z1f5arsg.js → chunk-xh43dwgw.js} +170 -55
  66. package/dist/cli/chunk-xh43dwgw.js.map +36 -0
  67. package/dist/cli/{chunk-pnnvybbk.js → chunk-zp79m0ts.js} +5 -5
  68. package/dist/cli/{chunk-pnnvybbk.js.map → chunk-zp79m0ts.js.map} +2 -2
  69. package/dist/cli/index.js +160 -35
  70. package/dist/cli/index.js.map +4 -4
  71. package/dist/types/ai/agent-readability.d.ts +1 -1
  72. package/dist/types/ai/agent-surface.d.ts +32 -0
  73. package/dist/types/ai/api/paths.d.ts +1 -1
  74. package/dist/types/ai/api-catalog.d.ts +7 -1
  75. package/dist/types/ai/ask-context.d.ts +14 -7
  76. package/dist/types/ai/ask.d.ts +43 -43
  77. package/dist/types/ai/component-markdown.d.ts +4 -4
  78. package/dist/types/ai/index.d.ts +3 -3
  79. package/dist/types/ai/openapi-components.d.ts +1 -1
  80. package/dist/types/ai/relative-links.d.ts +9 -4
  81. package/dist/types/ai/serializers.d.ts +1 -1
  82. package/dist/types/ai/static-expression.d.ts +28 -0
  83. package/dist/types/ai/visibility.d.ts +1 -1
  84. package/dist/types/analytics/databuddy.d.ts +43 -0
  85. package/dist/types/analytics/index.d.ts +2 -0
  86. package/dist/types/analytics/schema.d.ts +14 -0
  87. package/dist/types/cli/env.d.ts +5 -0
  88. package/dist/types/cli/init/scaffold.d.ts +19 -3
  89. package/dist/types/cli/init/starter-spec.d.ts +11 -0
  90. package/dist/types/core/base-path.d.ts +9 -0
  91. package/dist/types/core/config-input.d.ts +21 -21
  92. package/dist/types/core/config.d.ts +5 -5
  93. package/dist/types/core/data.d.ts +3 -3
  94. package/dist/types/core/graph.d.ts +2 -0
  95. package/dist/types/core/i18n-ui.d.ts +37 -8
  96. package/dist/types/core/i18n.d.ts +7 -1
  97. package/dist/types/core/links.d.ts +3 -1
  98. package/dist/types/core/load-module.d.ts +10 -0
  99. package/dist/types/core/locale-links.d.ts +12 -2
  100. package/dist/types/core/meta.d.ts +5 -1
  101. package/dist/types/core/nav-diagnostics.d.ts +10 -0
  102. package/dist/types/core/navigation.d.ts +38 -0
  103. package/dist/types/core/ordering-prefix.d.ts +4 -0
  104. package/dist/types/core/safe-links.d.ts +3 -1
  105. package/dist/types/core/schema.d.ts +57 -18
  106. package/dist/types/core/sources/github-releases.d.ts +5 -0
  107. package/dist/types/core/sources/lower.d.ts +14 -2
  108. package/dist/types/core/sources/normalize.d.ts +10 -1
  109. package/dist/types/core/sources/remote.d.ts +11 -1
  110. package/dist/types/core/sources/resolve.d.ts +12 -0
  111. package/dist/types/core/sources/types.d.ts +31 -0
  112. package/dist/types/core/types.d.ts +9 -0
  113. package/dist/types/core/unrecognized-keys.d.ts +1 -1
  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/cloudflare-negotiation.d.ts +3 -2
  117. package/dist/types/deploy/headers.d.ts +31 -7
  118. package/dist/types/deploy/node-headers.d.ts +43 -8
  119. package/dist/types/deploy/platforms/netlify.d.ts +27 -2
  120. package/dist/types/deploy/platforms/node.d.ts +6 -5
  121. package/dist/types/deploy/platforms/types.d.ts +7 -0
  122. package/dist/types/deploy/platforms/vercel.d.ts +3 -2
  123. package/dist/types/deploy/redirects.d.ts +7 -2
  124. package/dist/types/deploy/vercel-negotiation.d.ts +3 -2
  125. package/dist/types/openapi/model.d.ts +20 -6
  126. package/dist/types/search/documents.d.ts +1 -1
  127. package/dist/types/search/orama-index.d.ts +1 -1
  128. package/dist/types/search/sync/algolia.d.ts +3 -1
  129. package/docs/01-quickstart.mdx +3 -2
  130. package/docs/02-deployment.mdx +21 -12
  131. package/docs/03-upgrading.mdx +22 -9
  132. package/docs/04-migrating.mdx +4 -4
  133. package/docs/08-faq.mdx +12 -5
  134. package/docs/advanced/changelog.mdx +1 -1
  135. package/docs/advanced/custom-pages.mdx +5 -3
  136. package/docs/advanced/skills.mdx +1 -1
  137. package/docs/cli/audit.mdx +5 -5
  138. package/docs/cli/doctor.mdx +4 -4
  139. package/docs/cli/evals.mdx +9 -9
  140. package/docs/cli/index.mdx +6 -3
  141. package/docs/cli/translate.mdx +8 -8
  142. package/docs/configuration/analytics.mdx +23 -2
  143. package/docs/configuration/{ask-ai.mdx → assistant.mdx} +20 -20
  144. package/docs/configuration/customization.mdx +5 -4
  145. package/docs/configuration/index.mdx +7 -5
  146. package/docs/configuration/meta.ts +1 -1
  147. package/docs/configuration/search.mdx +2 -2
  148. package/docs/content/components.mdx +1 -1
  149. package/docs/content/frontmatter.mdx +5 -1
  150. package/docs/content/i18n.mdx +3 -3
  151. package/docs/content/index.mdx +4 -2
  152. package/docs/content/islands.mdx +1 -1
  153. package/docs/content/meta.mdx +5 -3
  154. package/docs/content/navigation.mdx +29 -4
  155. package/docs/content/sources.mdx +18 -12
  156. package/docs/content/versioning.mdx +1 -0
  157. package/docs/discoverability/agent-discovery.mdx +22 -8
  158. package/docs/discoverability/index.mdx +3 -3
  159. package/docs/discoverability/llms-txt.mdx +2 -5
  160. package/docs/discoverability/markdown.mdx +5 -3
  161. package/docs/discoverability/mcp.mdx +4 -0
  162. package/docs/discoverability/metadata.mdx +3 -2
  163. package/docs/discoverability/open-graph.mdx +6 -4
  164. package/docs/discoverability/rss.mdx +3 -1
  165. package/docs/index.mdx +2 -2
  166. package/docs/references/asyncapi.mdx +1 -1
  167. package/docs/references/graphql.mdx +2 -2
  168. package/docs/references/openapi.mdx +3 -3
  169. package/package.json +1 -1
  170. package/skills/blume/SKILL.md +5 -5
  171. package/skills/blume-migrate/SKILL.md +6 -6
  172. package/skills/blume-migrate/references/docusaurus.md +6 -6
  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 +1 -1
  176. package/skills/blume-migrate/references/starlight.md +5 -5
  177. package/skills/blume-migrate/scripts/mintlify-codemod.mjs +2 -2
  178. package/skills/blume-update-docs/references/audit-checklist.md +1 -1
  179. package/src/ai/agent-readability.ts +4 -4
  180. package/src/ai/agent-surface.ts +56 -0
  181. package/src/ai/api/paths.ts +1 -1
  182. package/src/ai/api/spec.ts +15 -2
  183. package/src/ai/api-catalog.ts +7 -1
  184. package/src/ai/ask-context.ts +21 -9
  185. package/src/ai/ask-data.ts +28 -14
  186. package/src/ai/ask.ts +84 -71
  187. package/src/ai/component-markdown.ts +43 -37
  188. package/src/ai/cors.ts +3 -3
  189. package/src/ai/index.ts +16 -16
  190. package/src/ai/link-headers.ts +8 -3
  191. package/src/ai/llms.ts +4 -3
  192. package/src/ai/markdown.ts +23 -11
  193. package/src/ai/mcp/server.ts +78 -7
  194. package/src/ai/openapi-components.ts +1 -1
  195. package/src/ai/relative-links.ts +78 -10
  196. package/src/ai/serializers.ts +1 -1
  197. package/src/ai/static-expression.ts +416 -0
  198. package/src/ai/visibility.ts +46 -15
  199. package/src/analytics/databuddy.ts +67 -0
  200. package/src/analytics/head.ts +4 -0
  201. package/src/analytics/index.ts +2 -0
  202. package/src/analytics/posthog.ts +21 -3
  203. package/src/analytics/schema.ts +2 -0
  204. package/src/astro/generate.ts +90 -45
  205. package/src/astro/module-types.ts +1 -1
  206. package/src/astro/runtime-deps.ts +6 -6
  207. package/src/astro/templates.ts +87 -49
  208. package/src/audit/catalog.ts +2 -2
  209. package/src/audit/checks/assets.ts +25 -5
  210. package/src/audit/checks/content.ts +20 -2
  211. package/src/audit/checks/i18n.ts +46 -8
  212. package/src/audit/checks/indexability.ts +39 -19
  213. package/src/audit/checks/links.ts +14 -0
  214. package/src/audit/checks/llms.ts +6 -3
  215. package/src/audit/checks/network.ts +3 -1
  216. package/src/audit/checks/og-image.ts +10 -0
  217. package/src/audit/checks/robots.ts +6 -1
  218. package/src/audit/checks/sitemap.ts +56 -31
  219. package/src/audit/checks/social.ts +21 -2
  220. package/src/audit/crawl.ts +88 -15
  221. package/src/audit/report.ts +54 -17
  222. package/src/audit/run.ts +1 -0
  223. package/src/audit/types.ts +18 -0
  224. package/src/audit/url.ts +37 -6
  225. package/src/blume-modules.d.ts +2 -2
  226. package/src/cli/build-failure.ts +50 -0
  227. package/src/cli/commands/audit.ts +8 -1
  228. package/src/cli/commands/build.ts +19 -5
  229. package/src/cli/commands/check.ts +2 -0
  230. package/src/cli/commands/doctor.ts +7 -5
  231. package/src/cli/commands/eval.ts +4 -10
  232. package/src/cli/commands/init.ts +15 -5
  233. package/src/cli/commands/migrate.ts +2 -2
  234. package/src/cli/commands/preview.ts +15 -0
  235. package/src/cli/commands/sync.ts +2 -0
  236. package/src/cli/commands/translate.ts +12 -9
  237. package/src/cli/commands/upgrade.ts +13 -2
  238. package/src/cli/eject-scripts.ts +32 -7
  239. package/src/cli/env.ts +12 -1
  240. package/src/cli/init/scaffold.ts +117 -11
  241. package/src/cli/init/starter-spec.ts +235 -0
  242. package/src/cli/required-secrets.ts +3 -3
  243. package/src/components/content/AccordionItem.astro +26 -22
  244. package/src/components/content/Badge.astro +2 -9
  245. package/src/components/content/Card.astro +2 -2
  246. package/src/components/content/Component.astro +9 -3
  247. package/src/components/content/Expandable.astro +5 -1
  248. package/src/components/content/Frame.astro +2 -7
  249. package/src/components/content/GithubInfo.astro +12 -2
  250. package/src/components/content/Prompt.astro +2 -7
  251. package/src/components/content/Tabs.astro +54 -5
  252. package/src/components/content/Tile.astro +1 -1
  253. package/src/components/content/Tooltip.astro +69 -7
  254. package/src/components/content/Tree.astro +7 -2
  255. package/src/components/content/TypeTable.astro +10 -5
  256. package/src/components/content/Update.astro +8 -2
  257. package/src/components/content/auto-type-table.ts +4 -1
  258. package/src/components/content/badge-color.ts +17 -0
  259. package/src/components/content/base-href.ts +18 -3
  260. package/src/components/content/inline-markdown.ts +27 -7
  261. package/src/components/copy-feedback.ts +36 -9
  262. package/src/components/islands/{AskAI.astro → Assistant.astro} +10 -10
  263. package/src/components/islands/{ask-ai.tsx → assistant.tsx} +39 -25
  264. package/src/components/islands/hooks.ts +21 -14
  265. package/src/components/islands/webmcp.ts +12 -8
  266. package/src/components/layout/Banner.astro +23 -4
  267. package/src/components/layout/Header.astro +32 -19
  268. package/src/components/layout/Logo.astro +5 -0
  269. package/src/components/layout/NavSelector.astro +6 -1
  270. package/src/components/layout/NavTabMenu.astro +133 -0
  271. package/src/components/layout/NavTree.astro +15 -6
  272. package/src/components/layout/NavTreeCache.astro +5 -2
  273. package/src/components/layout/NavTreeScript.astro +45 -5
  274. package/src/components/layout/PageLayout.astro +28 -14
  275. package/src/components/layout/Pagination.astro +7 -7
  276. package/src/components/layout/ReferenceLayout.astro +5 -2
  277. package/src/components/layout/RootLayout.astro +27 -14
  278. package/src/components/layout/Search.astro +18 -14
  279. package/src/components/layout/analytics-client.ts +3 -1
  280. package/src/components/layout/drawer-inert.ts +1 -1
  281. package/src/components/openapi/GraphqlType.astro +11 -3
  282. package/src/components/openapi/MessageComposer.astro +1 -1
  283. package/src/components/openapi/Operation.astro +21 -5
  284. package/src/components/openapi/PanelTabs.astro +4 -1
  285. package/src/components/openapi/Playground.astro +8 -2
  286. package/src/components/openapi/RequestPanel.astro +9 -5
  287. package/src/components/openapi/SchemaProperty.astro +11 -33
  288. package/src/components/openapi/SchemaTable.astro +27 -52
  289. package/src/components/openapi/description.ts +2 -2
  290. package/src/components/openapi/helpers.ts +90 -12
  291. package/src/components/openapi/message-composer.ts +8 -0
  292. package/src/components/openapi/message-model.ts +12 -2
  293. package/src/components/openapi/message.ts +4 -1
  294. package/src/components/openapi/operation-model.ts +40 -8
  295. package/src/components/openapi/panel.ts +29 -5
  296. package/src/components/openapi/playground-client.ts +37 -7
  297. package/src/components/openapi/playground-schema.ts +25 -6
  298. package/src/components/openapi/request.ts +2 -0
  299. package/src/components/openapi/schema-tree.ts +203 -0
  300. package/src/components/openapi/snippets.ts +30 -15
  301. package/src/components/openapi/validate-json.ts +1 -1
  302. package/src/core/base-path.ts +15 -0
  303. package/src/core/code-fences.ts +1 -1
  304. package/src/core/config-input.ts +23 -23
  305. package/src/core/config.ts +21 -7
  306. package/src/core/data.ts +3 -3
  307. package/src/core/diagnostics.ts +217 -30
  308. package/src/core/graph.ts +120 -11
  309. package/src/core/i18n-ui.ts +74 -11
  310. package/src/core/i18n.ts +13 -2
  311. package/src/core/links.ts +7 -4
  312. package/src/core/load-module.ts +20 -0
  313. package/src/core/locale-links.ts +17 -17
  314. package/src/core/manifest.ts +3 -2
  315. package/src/core/meta.ts +69 -6
  316. package/src/core/nav-diagnostics.ts +56 -1
  317. package/src/core/navigation.ts +245 -76
  318. package/src/core/ordering-prefix.ts +27 -0
  319. package/src/core/project-graph.ts +23 -1
  320. package/src/core/request-body.ts +1 -1
  321. package/src/core/safe-href.ts +53 -1
  322. package/src/core/safe-links.ts +11 -2
  323. package/src/core/schema.ts +95 -34
  324. package/src/core/server-features.ts +2 -2
  325. package/src/core/sources/assets.ts +83 -41
  326. package/src/core/sources/contentful-rich-text.ts +3 -2
  327. package/src/core/sources/contentful.ts +25 -12
  328. package/src/core/sources/filesystem.ts +5 -1
  329. package/src/core/sources/github-releases.ts +106 -5
  330. package/src/core/sources/lexical.ts +9 -4
  331. package/src/core/sources/lower.ts +79 -26
  332. package/src/core/sources/mdx-remote.ts +1 -0
  333. package/src/core/sources/normalize.ts +132 -30
  334. package/src/core/sources/notion.ts +26 -10
  335. package/src/core/sources/obsidian.ts +17 -3
  336. package/src/core/sources/payload.ts +1 -0
  337. package/src/core/sources/portable-text.ts +63 -44
  338. package/src/core/sources/remote.ts +18 -2
  339. package/src/core/sources/resolve.ts +52 -33
  340. package/src/core/sources/sanity.ts +1 -0
  341. package/src/core/sources/strapi-blocks.ts +9 -4
  342. package/src/core/sources/strapi.ts +1 -0
  343. package/src/core/sources/types.ts +31 -0
  344. package/src/core/types.ts +9 -0
  345. package/src/core/ui-packs/ar.ts +17 -5
  346. package/src/core/ui-packs/bg.ts +17 -5
  347. package/src/core/ui-packs/bn.ts +17 -5
  348. package/src/core/ui-packs/ca.ts +17 -5
  349. package/src/core/ui-packs/cs.ts +17 -5
  350. package/src/core/ui-packs/da.ts +17 -5
  351. package/src/core/ui-packs/de.ts +17 -5
  352. package/src/core/ui-packs/el.ts +17 -5
  353. package/src/core/ui-packs/es.ts +17 -5
  354. package/src/core/ui-packs/fa.ts +17 -5
  355. package/src/core/ui-packs/fi.ts +17 -5
  356. package/src/core/ui-packs/fr.ts +17 -5
  357. package/src/core/ui-packs/he.ts +17 -5
  358. package/src/core/ui-packs/hi.ts +17 -5
  359. package/src/core/ui-packs/hr.ts +17 -5
  360. package/src/core/ui-packs/hu.ts +17 -5
  361. package/src/core/ui-packs/id.ts +17 -5
  362. package/src/core/ui-packs/it.ts +17 -5
  363. package/src/core/ui-packs/ja.ts +17 -5
  364. package/src/core/ui-packs/ko.ts +17 -5
  365. package/src/core/ui-packs/nl.ts +17 -5
  366. package/src/core/ui-packs/no.ts +17 -5
  367. package/src/core/ui-packs/pl.ts +17 -5
  368. package/src/core/ui-packs/pt-br.ts +17 -5
  369. package/src/core/ui-packs/pt.ts +17 -5
  370. package/src/core/ui-packs/ro.ts +17 -5
  371. package/src/core/ui-packs/ru.ts +17 -5
  372. package/src/core/ui-packs/sk.ts +17 -5
  373. package/src/core/ui-packs/sr.ts +17 -5
  374. package/src/core/ui-packs/sv.ts +17 -5
  375. package/src/core/ui-packs/th.ts +17 -5
  376. package/src/core/ui-packs/tr.ts +17 -5
  377. package/src/core/ui-packs/uk.ts +17 -5
  378. package/src/core/ui-packs/vi.ts +17 -5
  379. package/src/core/ui-packs/zh-tw.ts +17 -5
  380. package/src/core/ui-packs/zh.ts +17 -5
  381. package/src/core/unrecognized-keys.ts +1 -1
  382. package/src/core/version-cut.ts +17 -2
  383. package/src/core/versions.ts +4 -1
  384. package/src/deploy/adapters/node.ts +5 -2
  385. package/src/deploy/adapters/registry.ts +2 -1
  386. package/src/deploy/adapters/types.ts +13 -1
  387. package/src/deploy/artifacts.ts +25 -5
  388. package/src/deploy/cloudflare-negotiation.ts +23 -4
  389. package/src/deploy/headers.ts +67 -51
  390. package/src/deploy/node-headers.ts +148 -27
  391. package/src/deploy/platforms/cloudflare.ts +1 -0
  392. package/src/deploy/platforms/netlify.ts +82 -5
  393. package/src/deploy/platforms/node.ts +7 -5
  394. package/src/deploy/platforms/static.ts +1 -0
  395. package/src/deploy/platforms/types.ts +7 -0
  396. package/src/deploy/platforms/vercel.ts +9 -3
  397. package/src/deploy/redirects.ts +14 -3
  398. package/src/deploy/vercel-negotiation.ts +35 -3
  399. package/src/eval/agents.ts +10 -2
  400. package/src/eval/run.ts +25 -0
  401. package/src/markdown/base-links.ts +55 -8
  402. package/src/markdown/index.ts +6 -3
  403. package/src/markdown/relative-links.ts +3 -23
  404. package/src/markdown/route-snapshot.ts +37 -0
  405. package/src/og/card.ts +20 -4
  406. package/src/og/derive.ts +145 -4
  407. package/src/openapi/graphql-build.ts +28 -2
  408. package/src/openapi/model.ts +77 -13
  409. package/src/openapi/render-mdx.ts +10 -1
  410. package/src/registry/eject.ts +135 -37
  411. package/src/search/documents.ts +24 -3
  412. package/src/search/orama-index.ts +1 -1
  413. package/src/search/sync/algolia.ts +36 -2
  414. package/src/sources/registry.ts +5 -0
  415. package/src/theme/entry.ts +11 -4
  416. package/src/translate/agents.ts +6 -1
  417. package/src/translate/ledger.ts +26 -3
  418. package/src/translate/meta.ts +11 -3
  419. package/src/translate/report.ts +1 -1
  420. package/src/translate/run.ts +11 -5
  421. package/src/translate/validate.ts +10 -1
  422. package/src/translate/work-list.ts +36 -3
  423. package/src/upgrade/upgrade.ts +37 -5
  424. package/dist/cli/chunk-1w8dp3qb.js.map +0 -10
  425. package/dist/cli/chunk-5a2z0198.js.map +0 -10
  426. package/dist/cli/chunk-6k8vp3ta.js.map +0 -19
  427. package/dist/cli/chunk-79njf86q.js.map +0 -11
  428. package/dist/cli/chunk-a9kptbw5.js.map +0 -14
  429. package/dist/cli/chunk-abh8yjkn.js +0 -31
  430. package/dist/cli/chunk-abh8yjkn.js.map +0 -10
  431. package/dist/cli/chunk-bctazmbk.js.map +0 -10
  432. package/dist/cli/chunk-beat36xx.js.map +0 -10
  433. package/dist/cli/chunk-d1tadaw7.js.map +0 -10
  434. package/dist/cli/chunk-fa25z98p.js.map +0 -11
  435. package/dist/cli/chunk-j8mw0za6.js.map +0 -35
  436. package/dist/cli/chunk-mnqj32sj.js.map +0 -12
  437. package/dist/cli/chunk-mwt1k8n7.js.map +0 -10
  438. package/dist/cli/chunk-nk3ts2xk.js +0 -51
  439. package/dist/cli/chunk-nk3ts2xk.js.map +0 -10
  440. package/dist/cli/chunk-sqn5t4q0.js.map +0 -10
  441. package/dist/cli/chunk-tzne8qfq.js.map +0 -10
  442. package/dist/cli/chunk-xaz13gwg.js.map +0 -182
  443. package/dist/cli/chunk-z01ze5c1.js.map +0 -10
  444. package/dist/cli/chunk-z1f5arsg.js.map +0 -36
  445. package/dist/cli/chunk-zg2gtj10.js.map +0 -35
  446. /package/dist/cli/{chunk-f2972sbt.js.map → chunk-00gs3wqs.js.map} +0 -0
  447. /package/dist/cli/{chunk-7ez8ny0t.js.map → chunk-bbnwccaz.js.map} +0 -0
  448. /package/dist/cli/{chunk-zxccj738.js.map → chunk-esh98wmb.js.map} +0 -0
  449. /package/dist/cli/{chunk-88cpgt6h.js.map → chunk-fsmrqk8a.js.map} +0 -0
  450. /package/dist/cli/{chunk-b5aj94ah.js.map → chunk-g698a744.js.map} +0 -0
  451. /package/dist/cli/{chunk-41za066z.js.map → chunk-h2ez8dzb.js.map} +0 -0
  452. /package/dist/cli/{chunk-by2290sx.js.map → chunk-h7k3nq3v.js.map} +0 -0
  453. /package/dist/cli/{chunk-y3e45rc8.js.map → chunk-n1yg3tj3.js.map} +0 -0
  454. /package/dist/cli/{chunk-f7t03s3g.js.map → chunk-wdrt2k2v.js.map} +0 -0
@@ -19,28 +19,26 @@ import {
19
19
  writeAgentReport,
20
20
  fixPrompt,
21
21
  launchInstalledAgent
22
- } from "./chunk-bw22s759.js";
22
+ } from "./chunk-6k4ftwze.js";
23
23
  import {
24
24
  frontmatter_default,
25
25
  gradeExternal,
26
26
  probeAll,
27
27
  deployPlatform
28
- } from "./chunk-xaz13gwg.js";
28
+ } from "./chunk-v6ya5kcb.js";
29
29
  import {
30
30
  normalizeBasePath,
31
31
  normalizePath,
32
32
  withBasePath,
33
33
  stripBasePath
34
- } from "./chunk-jts8mvcz.js";
35
- import {
36
- deployStaticDir
37
- } from "./chunk-nk3ts2xk.js";
34
+ } from "./chunk-gs7r695n.js";
38
35
  import {
36
+ deployStaticDir,
39
37
  examplesRouteBase
40
- } from "./chunk-mnqj32sj.js";
38
+ } from "./chunk-hr8ne106.js";
41
39
  import {
42
40
  scanProject
43
- } from "./chunk-zg2gtj10.js";
41
+ } from "./chunk-5r8g91qn.js";
44
42
 
45
43
  // src/cli/commands/audit.ts
46
44
  import { defineCommand } from "citty";
@@ -71,6 +69,7 @@ var serverRoutes = (context) => {
71
69
  return mcp?.enabled ? [normalizePath(mcp.route)] : [];
72
70
  };
73
71
  var isServed = (context, path) => context.byUrl.has(path) || context.files.has(path) || context.files.has(`${path}/index.html`) || serverRoutes(context).includes(path);
72
+ var underBase = (deployBase, path) => !deployBase || path === deployBase || path.startsWith(`${deployBase}/`);
74
73
  var NON_HTTP_SCHEME = /^(?!https?:)[a-z][a-z0-9+.-]*:/iu;
75
74
  var decodePath = (path) => {
76
75
  try {
@@ -79,6 +78,14 @@ var decodePath = (path) => {
79
78
  return path;
80
79
  }
81
80
  };
81
+ var isHttpUrl = (input) => {
82
+ try {
83
+ const { protocol } = new URL(input);
84
+ return protocol === "http:" || protocol === "https:";
85
+ } catch {
86
+ return false;
87
+ }
88
+ };
82
89
  var siteOrigin = (site) => {
83
90
  if (!site) {
84
91
  return null;
@@ -106,10 +113,14 @@ var resolveHref = (pageUrl, href, origin, deployBase = "") => {
106
113
  return { kind: "ignored" };
107
114
  }
108
115
  if (origin && parsed.origin === origin) {
116
+ const served = decodePath(parsed.pathname);
117
+ if (!underBase(deployBase, served)) {
118
+ return { kind: "outside-base", path: normalizePath(served) };
119
+ }
109
120
  return {
110
121
  hash: parsed.hash.slice(1),
111
122
  kind: "self-origin",
112
- path: normalizePath(stripBasePath(deployBase, decodePath(parsed.pathname)))
123
+ path: normalizePath(stripBasePath(deployBase, served))
113
124
  };
114
125
  }
115
126
  return { kind: "external", url: parsed.toString() };
@@ -121,14 +132,19 @@ var resolveHref = (pageUrl, href, origin, deployBase = "") => {
121
132
  } catch {
122
133
  return { kind: "ignored" };
123
134
  }
135
+ const served = decodePath(resolved.pathname);
136
+ if (target.startsWith("/") && !underBase(deployBase, served)) {
137
+ return { kind: "outside-base", path: normalizePath(served) };
138
+ }
124
139
  return {
125
140
  hash: resolved.hash.slice(1),
126
141
  kind: "internal",
127
- path: normalizePath(stripBasePath(deployBase, decodePath(resolved.pathname)))
142
+ path: normalizePath(stripBasePath(deployBase, served))
128
143
  };
129
144
  };
130
145
 
131
146
  // src/audit/checks/assets.ts
147
+ var VERCEL_IMAGE_ENDPOINT = "/_vercel/image";
132
148
  var formatBytes = (bytes) => bytes < 1024 * 1024 ? `${Math.round(bytes / 1024)} kB` : `${(bytes / 1024 / 1024).toFixed(1)} MB`;
133
149
  var resolveAsset = (context, page, asset, missingId, kind, found) => {
134
150
  if (asset.src.startsWith("http://")) {
@@ -139,10 +155,18 @@ var resolveAsset = (context, page, asset, missingId, kind, found) => {
139
155
  return null;
140
156
  }
141
157
  const origin = siteOrigin(context.project.config.deployment.options.site);
142
- const resolved = resolveHref(page.url, asset.src, origin, normalizeBasePath(context.project.config.deployment.options.base));
158
+ const deployBase = normalizeBasePath(context.project.config.deployment.options.base);
159
+ const resolved = resolveHref(page.url, asset.src, origin, deployBase);
143
160
  if (resolved.kind === "external" || resolved.kind === "ignored") {
144
161
  return null;
145
162
  }
163
+ if (resolved.path === VERCEL_IMAGE_ENDPOINT) {
164
+ return null;
165
+ }
166
+ if (resolved.kind === "outside-base") {
167
+ found.push(finding(missingId, pageSite(context, page), `Page references ${asset.src}, which is missing deployment.base (${deployBase}), so the deployed site does not serve it.`));
168
+ return null;
169
+ }
146
170
  const bytes = context.files.get(resolved.path);
147
171
  if (bytes === undefined) {
148
172
  found.push(finding(missingId, pageSite(context, page), `Page references ${asset.src}, which is not in the build.`));
@@ -214,6 +238,9 @@ var frontmatterDate = (source) => {
214
238
  return null;
215
239
  }
216
240
  };
241
+ var isGeneratedReference = (page) => page.route?.source.name === "openapi";
242
+ var GENERATED_TITLE_FIX = "This page is generated from an API spec, which titles it with the operation's `summary` (the spec's own title on the overview page) — rewrite it there.";
243
+ var GENERATED_DESCRIPTION_FIX = "This page is generated from an API spec, which describes it with the operation's `description` or `summary` (the spec's own description on the overview page) — rewrite it there.";
217
244
  var titleChecks = (context, page) => {
218
245
  const { titleMin, titleMax } = context.thresholds;
219
246
  const [title] = page.titles;
@@ -229,7 +256,7 @@ var titleChecks = (context, page) => {
229
256
  const width = stringWidth(title.replaceAll(WHITESPACE, " "));
230
257
  if (width > titleMax || width < titleMin) {
231
258
  const direction = width > titleMax ? "long" : "short";
232
- found.push(finding("BLUME_AUDIT_TITLE_LENGTH", pageSite(context, page, ["title"]), `Title renders ${width} columns wide — too ${direction} (aim for ${titleMin}–${titleMax}).`));
259
+ found.push(finding("BLUME_AUDIT_TITLE_LENGTH", pageSite(context, page, ["title"]), `Title renders ${width} columns wide — too ${direction} (aim for ${titleMin}–${titleMax}).`, isGeneratedReference(page) ? GENERATED_TITLE_FIX : undefined));
233
260
  }
234
261
  return found;
235
262
  };
@@ -251,7 +278,7 @@ var descriptionChecks = (context, page) => {
251
278
  const width = stringWidth(description.replaceAll(WHITESPACE, " "));
252
279
  if (width > descriptionMax || width < descriptionMin) {
253
280
  const direction = width > descriptionMax ? "long" : "short";
254
- found.push(finding("BLUME_AUDIT_DESCRIPTION_LENGTH", pageSite(context, page, ["description"]), `Meta description renders ${width} columns wide — too ${direction} (aim for ${descriptionMin}–${descriptionMax}).`));
281
+ found.push(finding("BLUME_AUDIT_DESCRIPTION_LENGTH", pageSite(context, page, ["description"]), `Meta description renders ${width} columns wide — too ${direction} (aim for ${descriptionMin}–${descriptionMax}).`, isGeneratedReference(page) ? GENERATED_DESCRIPTION_FIX : undefined));
255
282
  }
256
283
  return found;
257
284
  };
@@ -467,6 +494,94 @@ var duplicateChecks = {
467
494
  tier: "static"
468
495
  };
469
496
 
497
+ // src/audit/checks/indexability.ts
498
+ var parseCanonical = (canonical) => {
499
+ try {
500
+ return new URL(canonical);
501
+ } catch {
502
+ return null;
503
+ }
504
+ };
505
+ var isArchivedLatestCanonical = (context, page, target) => {
506
+ const version = page.route?.version;
507
+ const archived = version ? context.project.config.versions?.archived.find((entry) => entry.id === version) : undefined;
508
+ const latestAlternate = page.route?.versionAlternates.find((alternate) => alternate.version === "");
509
+ return archived?.canonical === "latest" && latestAlternate !== undefined && normalizePath(latestAlternate.path) === target;
510
+ };
511
+ var canonicalChecks = (context, page) => {
512
+ const { site } = context.project.config.deployment.options;
513
+ const origin = siteOrigin(site);
514
+ if (ERROR_ROUTES.has(page.url)) {
515
+ return [];
516
+ }
517
+ if (!page.canonical) {
518
+ return site && page.indexable ? [
519
+ finding("BLUME_AUDIT_CANONICAL_MISSING", pageSite(context, page), "Page has no canonical URL.")
520
+ ] : [];
521
+ }
522
+ const canonical = parseCanonical(page.canonical);
523
+ if (!canonical) {
524
+ return [
525
+ finding("BLUME_AUDIT_CANONICAL_BAD_TARGET", pageSite(context, page, ["seo", "canonical"]), `Canonical "${page.canonical}" is not a valid absolute URL.`)
526
+ ];
527
+ }
528
+ const found = [];
529
+ if (origin && canonical.origin !== origin) {
530
+ const sameHost = canonical.host === new URL(origin).host;
531
+ if (sameHost) {
532
+ found.push(finding("BLUME_AUDIT_CANONICAL_PROTOCOL_MISMATCH", pageSite(context, page, ["seo", "canonical"]), `Canonical uses ${canonical.protocol}// but the site is ${new URL(origin).protocol}//.`));
533
+ }
534
+ return found;
535
+ }
536
+ const target = normalizePath(stripBasePath(normalizeBasePath(context.project.config.deployment.options.base), decodePath(canonical.pathname)));
537
+ if (target === normalizePath(page.url)) {
538
+ return found;
539
+ }
540
+ const redirect = context.redirects.find((entry) => normalizePath(entry.from) === target);
541
+ if (redirect) {
542
+ found.push(finding("BLUME_AUDIT_CANONICAL_BAD_TARGET", pageSite(context, page, ["seo", "canonical"]), `Canonical points at ${target}, which is a redirect to ${redirect.to}.`));
543
+ } else if (context.byUrl.has(target)) {
544
+ if (!(page.route?.fallback === true || isArchivedLatestCanonical(context, page, target))) {
545
+ found.push(finding("BLUME_AUDIT_CANONICAL_NOT_SELF", pageSite(context, page, ["seo", "canonical"]), `Page declares ${target} as its canonical, so it will not be indexed itself.`));
546
+ }
547
+ } else {
548
+ found.push(finding("BLUME_AUDIT_CANONICAL_BAD_TARGET", pageSite(context, page, ["seo", "canonical"]), `Canonical points at ${target}, which is not a page on this site.`));
549
+ }
550
+ return found;
551
+ };
552
+ var indexabilityChecks = {
553
+ category: "indexability",
554
+ run(context) {
555
+ const found = [];
556
+ if (!context.project.config.deployment.options.site) {
557
+ const { deployment } = context.project.config;
558
+ const site = {
559
+ file: context.project.context.configFile ?? undefined,
560
+ url: "/"
561
+ };
562
+ found.push(deployPlatform(deployment).env ? finding("BLUME_AUDIT_SITE_INFERRED_AT_DEPLOY", site, `deployment.site is not set in this build — on ${deployment.kind} it is inferred from the platform env at deploy time, so canonical URLs, Open Graph images, and the sitemap are only missing from this local artifact.`) : finding("BLUME_AUDIT_SITE_NOT_SET", site, "deployment.site is not set, so no canonical URLs, Open Graph images, or sitemap can be generated."));
563
+ }
564
+ for (const page of context.pages) {
565
+ found.push(...canonicalChecks(context, page));
566
+ if (page.robots && !page.indexable && !ERROR_ROUTES.has(page.url)) {
567
+ found.push(finding("BLUME_AUDIT_ROBOTS_META_UNEXPECTED", pageSite(context, page, ["noindex"]), `Page declares robots "${page.robots}" and will not be indexed.`));
568
+ if (!page.indexable && page.canonical) {
569
+ found.push(finding("BLUME_AUDIT_CANONICAL_ON_NOINDEX", pageSite(context, page, ["noindex"]), `Page is noindex but declares ${page.canonical} as its canonical.`));
570
+ }
571
+ }
572
+ if (page.route?.draft) {
573
+ found.push(finding("BLUME_AUDIT_DRAFT_PAGE_PUBLISHED", pageSite(context, page, ["draft"]), `${page.url} is marked draft in its front matter but is in the build.`));
574
+ }
575
+ if (page.bytes > context.thresholds.maxHtmlBytes) {
576
+ const mb = (page.bytes / 1024 / 1024).toFixed(1);
577
+ found.push(finding("BLUME_AUDIT_HTML_TOO_LARGE", pageSite(context, page), `Page HTML is ${mb} MB — past Googlebot's 2 MB limit, the rest is not crawled.`));
578
+ }
579
+ }
580
+ return found;
581
+ },
582
+ tier: "static"
583
+ };
584
+
470
585
  // src/audit/checks/i18n.ts
471
586
  var deployBaseOf = (context) => normalizeBasePath(context.project.config.deployment.options.base);
472
587
  var X_DEFAULT = "x-default";
@@ -480,11 +595,22 @@ var isValidBcp47 = (tag) => {
480
595
  };
481
596
  var alternatePath = (href, deployBase) => {
482
597
  try {
483
- return normalizePath(stripBasePath(deployBase, new URL(href).pathname));
598
+ return normalizePath(stripBasePath(deployBase, decodePath(new URL(href).pathname)));
484
599
  } catch {
485
600
  return null;
486
601
  }
487
602
  };
603
+ var isFallbackCopy = (page) => page.route?.fallback === true;
604
+ var canonicalizesElsewhere = (context, target, deployBase) => {
605
+ if (!target.canonical) {
606
+ return false;
607
+ }
608
+ const canonical = alternatePath(target.canonical, deployBase);
609
+ if (canonical === normalizePath(target.url)) {
610
+ return false;
611
+ }
612
+ return !(canonical !== null && isArchivedLatestCanonical(context, target, canonical));
613
+ };
488
614
  var langChecks = (context, page) => {
489
615
  if (!page.lang) {
490
616
  return [
@@ -521,7 +647,7 @@ var hreflangChecks = (context, page) => {
521
647
  found.push(finding("BLUME_AUDIT_HREFLANG_BAD_TARGET", pageSite(context, page), `hreflang="${alternate.lang}" points at ${path}, which redirects to ${redirect.to}.`));
522
648
  } else if (!target) {
523
649
  found.push(finding("BLUME_AUDIT_HREFLANG_BAD_TARGET", pageSite(context, page), `hreflang="${alternate.lang}" points at ${path}, which the build does not serve.`));
524
- } else if (target.canonical && alternatePath(target.canonical, deployBase) !== normalizePath(target.url)) {
650
+ } else if (canonicalizesElsewhere(context, target, deployBase)) {
525
651
  found.push(finding("BLUME_AUDIT_HREFLANG_BAD_TARGET", pageSite(context, page), `hreflang="${alternate.lang}" points at ${path}, which canonicalizes elsewhere.`));
526
652
  }
527
653
  }
@@ -547,6 +673,9 @@ var returnTagChecks = (context) => {
547
673
  const found = [];
548
674
  const deployBase = deployBaseOf(context);
549
675
  for (const page of context.pages) {
676
+ if (isFallbackCopy(page)) {
677
+ continue;
678
+ }
550
679
  for (const alternate of page.hreflang) {
551
680
  if (alternate.lang === X_DEFAULT) {
552
681
  continue;
@@ -573,7 +702,7 @@ var i18nChecks = {
573
702
  const found = [];
574
703
  for (const page of context.pages) {
575
704
  found.push(...langChecks(context, page));
576
- if (page.hreflang.length > 0) {
705
+ if (page.hreflang.length > 0 && !isFallbackCopy(page)) {
577
706
  found.push(...hreflangChecks(context, page));
578
707
  }
579
708
  }
@@ -583,92 +712,6 @@ var i18nChecks = {
583
712
  tier: "static"
584
713
  };
585
714
 
586
- // src/audit/checks/indexability.ts
587
- var parseCanonical = (canonical) => {
588
- try {
589
- return new URL(canonical);
590
- } catch {
591
- return null;
592
- }
593
- };
594
- var canonicalChecks = (context, page) => {
595
- const { site } = context.project.config.deployment.options;
596
- const origin = siteOrigin(site);
597
- if (ERROR_ROUTES.has(page.url)) {
598
- return [];
599
- }
600
- if (!page.canonical) {
601
- return site ? [
602
- finding("BLUME_AUDIT_CANONICAL_MISSING", pageSite(context, page), "Page has no canonical URL.")
603
- ] : [];
604
- }
605
- const canonical = parseCanonical(page.canonical);
606
- if (!canonical) {
607
- return [
608
- finding("BLUME_AUDIT_CANONICAL_BAD_TARGET", pageSite(context, page, ["seo", "canonical"]), `Canonical "${page.canonical}" is not a valid absolute URL.`)
609
- ];
610
- }
611
- const found = [];
612
- if (origin && canonical.origin !== origin) {
613
- const sameHost = canonical.host === new URL(origin).host;
614
- if (sameHost) {
615
- found.push(finding("BLUME_AUDIT_CANONICAL_PROTOCOL_MISMATCH", pageSite(context, page, ["seo", "canonical"]), `Canonical uses ${canonical.protocol}// but the site is ${new URL(origin).protocol}//.`));
616
- }
617
- return found;
618
- }
619
- const target = normalizePath(stripBasePath(normalizeBasePath(context.project.config.deployment.options.base), decodePath(canonical.pathname)));
620
- if (target === normalizePath(page.url)) {
621
- return found;
622
- }
623
- const redirect = context.redirects.find((entry) => normalizePath(entry.from) === target);
624
- if (redirect) {
625
- found.push(finding("BLUME_AUDIT_CANONICAL_BAD_TARGET", pageSite(context, page, ["seo", "canonical"]), `Canonical points at ${target}, which is a redirect to ${redirect.to}.`));
626
- } else if (context.byUrl.has(target)) {
627
- const version = page.route?.version;
628
- const archived = version ? context.project.config.versions?.archived.find((entry) => entry.id === version) : undefined;
629
- const latestAlternate = page.route?.versionAlternates.find((alternate) => alternate.version === "");
630
- const intentional = archived?.canonical === "latest" && latestAlternate !== undefined && normalizePath(latestAlternate.path) === target;
631
- if (!intentional) {
632
- found.push(finding("BLUME_AUDIT_CANONICAL_NOT_SELF", pageSite(context, page, ["seo", "canonical"]), `Page declares ${target} as its canonical, so it will not be indexed itself.`));
633
- }
634
- } else {
635
- found.push(finding("BLUME_AUDIT_CANONICAL_BAD_TARGET", pageSite(context, page, ["seo", "canonical"]), `Canonical points at ${target}, which is not a page on this site.`));
636
- }
637
- return found;
638
- };
639
- var indexabilityChecks = {
640
- category: "indexability",
641
- run(context) {
642
- const found = [];
643
- if (!context.project.config.deployment.options.site) {
644
- const { deployment } = context.project.config;
645
- const site = {
646
- file: context.project.context.configFile ?? undefined,
647
- url: "/"
648
- };
649
- found.push(deployPlatform(deployment).env ? finding("BLUME_AUDIT_SITE_INFERRED_AT_DEPLOY", site, `deployment.site is not set in this build — on ${deployment.kind} it is inferred from the platform env at deploy time, so canonical URLs, Open Graph images, and the sitemap are only missing from this local artifact.`) : finding("BLUME_AUDIT_SITE_NOT_SET", site, "deployment.site is not set, so no canonical URLs, Open Graph images, or sitemap can be generated."));
650
- }
651
- for (const page of context.pages) {
652
- found.push(...canonicalChecks(context, page));
653
- if (page.robots && !page.indexable && !ERROR_ROUTES.has(page.url)) {
654
- found.push(finding("BLUME_AUDIT_ROBOTS_META_UNEXPECTED", pageSite(context, page, ["noindex"]), `Page declares robots "${page.robots}" and will not be indexed.`));
655
- if (!page.indexable && page.canonical) {
656
- found.push(finding("BLUME_AUDIT_CANONICAL_ON_NOINDEX", pageSite(context, page, ["noindex"]), `Page is noindex but declares ${page.canonical} as its canonical.`));
657
- }
658
- }
659
- if (page.route?.draft) {
660
- found.push(finding("BLUME_AUDIT_DRAFT_PAGE_PUBLISHED", pageSite(context, page, ["draft"]), `${page.url} is marked draft in its front matter but is in the build.`));
661
- }
662
- if (page.bytes > context.thresholds.maxHtmlBytes) {
663
- const mb = (page.bytes / 1024 / 1024).toFixed(1);
664
- found.push(finding("BLUME_AUDIT_HTML_TOO_LARGE", pageSite(context, page), `Page HTML is ${mb} MB — past Googlebot's 2 MB limit, the rest is not crawled.`));
665
- }
666
- }
667
- return found;
668
- },
669
- tier: "static"
670
- };
671
-
672
715
  // src/audit/graph.ts
673
716
  var add = (map, key, value) => {
674
717
  const existing = map.get(key);
@@ -746,6 +789,14 @@ var linkChecks = {
746
789
  if (resolved.kind === "external" || resolved.kind === "ignored") {
747
790
  return;
748
791
  }
792
+ if (resolved.kind === "outside-base") {
793
+ if (link.content) {
794
+ found.push(finding("BLUME_AUDIT_LINK_TO_BROKEN", pageSite(context, page), `Link to ${link.href} is missing deployment.base (${deployBase}), so the deployed site does not serve it.`));
795
+ } else if (!brokenChrome.has(resolved.path)) {
796
+ brokenChrome.set(resolved.path, page);
797
+ }
798
+ return;
799
+ }
749
800
  const anchorTarget = resolved.hash ? context.byUrl.get(resolved.path) : undefined;
750
801
  if (anchorTarget) {
751
802
  checkAnchor(page, link, anchorTarget, resolved.hash);
@@ -858,7 +909,7 @@ var llmsChecks = {
858
909
  }
859
910
  for (const page of context.pages) {
860
911
  const { route } = page;
861
- if (!route || route.hidden || route.draft || !page.indexable || ERROR_ROUTES.has(page.url) || !openapi && route.source.name === "openapi" || listed.has(normalizePath(page.url))) {
912
+ if (!route || route.hidden || route.draft || route.aiExclude || route.fallback || !page.indexable || ERROR_ROUTES.has(page.url) || !openapi && route.source.name === "openapi" || listed.has(normalizePath(page.url))) {
862
913
  continue;
863
914
  }
864
915
  found.push(finding("BLUME_AUDIT_LLMS_TXT_PAGE_MISSING", pageSite(context, page), `${page.url} is built and indexable but is not listed in llms.txt.`));
@@ -934,7 +985,7 @@ var networkChecks = {
934
985
  found.push(...servedPageChecks(context, page, result, origin));
935
986
  }
936
987
  const robots = results.get(robotsUrl);
937
- if (robots && !robots.ok) {
988
+ if (context.project.config.seo.robots && robots && !robots.ok) {
938
989
  found.push(finding("BLUME_AUDIT_ROBOTS_NOT_ACCESSIBLE", { url: "/robots.txt" }, `robots.txt is not reachable at ${robotsUrl}.`));
939
990
  }
940
991
  const sitemap = results.get(sitemapUrl);
@@ -1046,6 +1097,10 @@ var ogImageChecks = {
1046
1097
  if (resolved.kind === "external" || resolved.kind === "ignored") {
1047
1098
  continue;
1048
1099
  }
1100
+ if (resolved.kind === "outside-base") {
1101
+ found.push(finding("BLUME_AUDIT_OG_IMAGE_BROKEN", pageSite(context, page, ["seo", "image"]), `og:image points at ${resolved.path}, which is missing deployment.base (${deployBase}), so the deployed site does not serve it.`));
1102
+ continue;
1103
+ }
1049
1104
  const { path } = resolved;
1050
1105
  if (!(context.files.has(path) || context.files.has(`${path}/index.html`))) {
1051
1106
  found.push(finding("BLUME_AUDIT_OG_IMAGE_BROKEN", pageSite(context, page, ["seo", "image"]), `og:image points at ${path}, which is not in the build.`));
@@ -1117,7 +1172,7 @@ var robotsChecks = {
1117
1172
  ];
1118
1173
  }
1119
1174
  const found = robots.invalid.map((line) => finding("BLUME_AUDIT_ROBOTS_INVALID", { file: robots.file, line: line.line, url: "/robots.txt" }, `robots.txt line ${line.line} is not a directive: "${line.text}"`));
1120
- if (site && robots.sitemaps.length === 0) {
1175
+ if (site && context.project.config.seo.sitemap && robots.sitemaps.length === 0) {
1121
1176
  found.push(finding("BLUME_AUDIT_ROBOTS_SITEMAP_MISSING", { file: robots.file, url: "/robots.txt" }, "robots.txt does not declare a Sitemap."));
1122
1177
  }
1123
1178
  const parser = robotsParser(`${MATCH_ORIGIN}/robots.txt`, robots.raw);
@@ -1195,6 +1250,15 @@ var checkListedUrl = (context, loc, origin, file, deployBase) => {
1195
1250
  }
1196
1251
  return found;
1197
1252
  };
1253
+ var unlistedPages = (context, listed, deployBase) => context.pages.flatMap((page) => {
1254
+ const canonical = page.canonical ? canonicalPath(page.canonical, deployBase) : null;
1255
+ if (!page.indexable || page.route?.fallback || canonical && canonical !== normalizePath(page.url) || ERROR_ROUTES2.has(page.url) || listed.has(normalizePath(page.url))) {
1256
+ return [];
1257
+ }
1258
+ return [
1259
+ finding("BLUME_AUDIT_INDEXABLE_PAGE_NOT_IN_SITEMAP", pageSite(context, page), `${page.url} is built and indexable but is not listed in sitemap.xml.`)
1260
+ ];
1261
+ });
1198
1262
  var sitemapChecks = {
1199
1263
  category: "sitemap",
1200
1264
  run(context) {
@@ -1210,11 +1274,21 @@ var sitemapChecks = {
1210
1274
  }
1211
1275
  const found = [];
1212
1276
  if (sitemap.error) {
1213
- found.push(finding("BLUME_AUDIT_SITEMAP_INVALID", { file: sitemap.file, url: "/sitemap.xml" }, `sitemap.xml is not a valid urlset: ${sitemap.error}`));
1277
+ found.push(finding("BLUME_AUDIT_SITEMAP_INVALID", { file: sitemap.file, url: "/sitemap.xml" }, `sitemap.xml is not a valid urlset or sitemap index: ${sitemap.error}`));
1214
1278
  return found;
1215
1279
  }
1216
- if (sitemap.bytes > MAX_SITEMAP_BYTES || sitemap.urls.length > MAX_SITEMAP_URLS) {
1217
- found.push(finding("BLUME_AUDIT_SITEMAP_TOO_LARGE", { file: sitemap.file, url: "/sitemap.xml" }, `sitemap.xml holds ${sitemap.urls.length} URLs in ${Math.round(sitemap.bytes / 1024 / 1024)} MB.`));
1280
+ const parts = sitemap.parts ?? [
1281
+ {
1282
+ bytes: sitemap.bytes,
1283
+ file: sitemap.file,
1284
+ url: "/sitemap.xml",
1285
+ urls: sitemap.urls.length
1286
+ }
1287
+ ];
1288
+ for (const part of parts) {
1289
+ if (part.bytes > MAX_SITEMAP_BYTES || part.urls > MAX_SITEMAP_URLS) {
1290
+ found.push(finding("BLUME_AUDIT_SITEMAP_TOO_LARGE", { file: part.file, url: part.url }, `${part.url.slice(1)} holds ${part.urls} URLs in ${Math.round(part.bytes / 1024 / 1024)} MB.`));
1291
+ }
1218
1292
  }
1219
1293
  for (const [loc, lastmod] of sitemap.lastmod ?? []) {
1220
1294
  const time = Date.parse(lastmod);
@@ -1230,12 +1304,7 @@ var sitemapChecks = {
1230
1304
  for (const loc of sitemap.urls) {
1231
1305
  found.push(...checkListedUrl(context, loc, origin, sitemap.file, deployBase));
1232
1306
  }
1233
- for (const page of context.pages) {
1234
- if (!page.indexable || ERROR_ROUTES2.has(page.url) || listed.has(normalizePath(page.url))) {
1235
- continue;
1236
- }
1237
- found.push(finding("BLUME_AUDIT_INDEXABLE_PAGE_NOT_IN_SITEMAP", pageSite(context, page), `${page.url} is built and indexable but is not listed in sitemap.xml.`));
1238
- }
1307
+ found.push(...unlistedPages(context, listed, deployBase));
1239
1308
  return found;
1240
1309
  },
1241
1310
  tier: "static"
@@ -1317,6 +1386,16 @@ var URL_STYLE = [
1317
1386
  { name: "underscores", test: /_/u },
1318
1387
  { name: "spaces", test: /%20| /u }
1319
1388
  ];
1389
+ var authoredPath = (context, url) => {
1390
+ const { basePath, i18n } = context.project.config;
1391
+ if (!i18n) {
1392
+ return url;
1393
+ }
1394
+ const base = normalizeBasePath(basePath);
1395
+ const [, first = "", ...rest] = stripBasePath(base, url).split("/");
1396
+ const isLocale = i18n.locales.some((locale) => locale.code === first);
1397
+ return isLocale ? `${base}/${rest.join("/")}` : url;
1398
+ };
1320
1399
  var DOTTED_HOST = /^\/\/[^/]*\./u;
1321
1400
  var doubledSlash = (href) => {
1322
1401
  if (href.startsWith("//")) {
@@ -1343,7 +1422,8 @@ var urlChecks = {
1343
1422
  doubled.set(href, page);
1344
1423
  }
1345
1424
  }
1346
- const untidy = URL_STYLE.filter((style) => style.test.test(page.url));
1425
+ const authored = authoredPath(context, page.url);
1426
+ const untidy = URL_STYLE.filter((style) => style.test.test(authored));
1347
1427
  if (untidy.length > 0) {
1348
1428
  found.push(finding("BLUME_AUDIT_URL_STYLE", pageSite(context, page), `URL ${page.url} contains ${untidy.map((style) => style.name).join(" and ")}.`));
1349
1429
  }
@@ -1538,6 +1618,7 @@ var sitemapParser = new XMLParser({
1538
1618
  removeNSPrefix: true
1539
1619
  });
1540
1620
  var isUrlsetElement = (value) => typeof value === "object" && value !== null;
1621
+ var isSitemapIndexElement = (value) => typeof value === "object" && value !== null;
1541
1622
  var isUrlEntry = (value) => typeof value === "object" && value !== null;
1542
1623
  var isText = (value) => typeof value === "string";
1543
1624
  var parseSitemap = (file, xml, bytes) => {
@@ -1549,8 +1630,14 @@ var parseSitemap = (file, xml, bytes) => {
1549
1630
  doc.error = "no <urlset> element";
1550
1631
  return doc;
1551
1632
  }
1633
+ if (Object.hasOwn(parsed, "sitemapindex")) {
1634
+ const { sitemapindex } = parsed;
1635
+ const children = isSitemapIndexElement(sitemapindex) ? [sitemapindex.sitemap].flat() : [];
1636
+ doc.sitemaps = children.flatMap((child) => isUrlEntry(child) && isText(child.loc) && child.loc.trim() ? [child.loc.trim()] : []);
1637
+ return doc;
1638
+ }
1552
1639
  if (!Object.hasOwn(parsed, "urlset")) {
1553
- doc.error = Object.hasOwn(parsed, "sitemapindex") ? "sitemap is an index, not a urlset" : "no <urlset> element";
1640
+ doc.error = "no <urlset> element";
1554
1641
  return doc;
1555
1642
  }
1556
1643
  const { urlset } = parsed;
@@ -1615,8 +1702,47 @@ var readIfPresent = async (file) => {
1615
1702
  return null;
1616
1703
  }
1617
1704
  };
1705
+ var readSitemap = async (staticDir, files, deployBase) => {
1706
+ const file = join2(staticDir, "sitemap.xml");
1707
+ const xml = await readIfPresent(file);
1708
+ if (xml === null) {
1709
+ return null;
1710
+ }
1711
+ const doc = parseSitemap(file, xml, files.get("/sitemap.xml") ?? Buffer.byteLength(xml, "utf-8"));
1712
+ if (!doc.sitemaps) {
1713
+ return doc;
1714
+ }
1715
+ doc.parts = [];
1716
+ for (const loc of doc.sitemaps) {
1717
+ let url;
1718
+ try {
1719
+ url = stripBasePath(deployBase, decodeURI(new URL(loc).pathname));
1720
+ } catch {
1721
+ doc.error = `the sitemap index lists "${loc}", which is not a valid absolute URL`;
1722
+ return doc;
1723
+ }
1724
+ const childFile = join2(staticDir, url);
1725
+ const childXml = await readIfPresent(childFile);
1726
+ const child = childXml === null ? null : parseSitemap(childFile, childXml, files.get(url) ?? Buffer.byteLength(childXml, "utf-8"));
1727
+ if (!child || child.error || child.sitemaps) {
1728
+ doc.error = `the sitemap index lists ${loc}, which is not a urlset in the build`;
1729
+ return doc;
1730
+ }
1731
+ doc.urls.push(...child.urls);
1732
+ for (const [key, value] of child.lastmod ?? []) {
1733
+ doc.lastmod?.set(key, value);
1734
+ }
1735
+ doc.parts.push({
1736
+ bytes: child.bytes,
1737
+ file: childFile,
1738
+ url,
1739
+ urls: child.urls.length
1740
+ });
1741
+ }
1742
+ return doc;
1743
+ };
1618
1744
  var crawlStaticDir = async (options) => {
1619
- const { staticDir, manifest, basePath } = options;
1745
+ const { staticDir, manifest, basePath, deployBase = "" } = options;
1620
1746
  const routes = routeIndex(manifest, basePath);
1621
1747
  const htmlFiles = await glob("**/*.html", { absolute: true, cwd: staticDir });
1622
1748
  const snapshots = await pMap(htmlFiles.toSorted(), async (file) => {
@@ -1636,8 +1762,6 @@ var crawlStaticDir = async (options) => {
1636
1762
  });
1637
1763
  }, { concurrency: CRAWL_CONCURRENCY });
1638
1764
  const pages = snapshots.filter((page) => page !== null);
1639
- const sitemapFile = join2(staticDir, "sitemap.xml");
1640
- const sitemapXml = await readIfPresent(sitemapFile);
1641
1765
  const robotsFile = join2(staticDir, "robots.txt");
1642
1766
  const robotsTxt = await readIfPresent(robotsFile);
1643
1767
  const llmsFile = join2(staticDir, "llms.txt");
@@ -1648,7 +1772,7 @@ var crawlStaticDir = async (options) => {
1648
1772
  llms: llmsText === null ? null : parseLlms(llmsFile, llmsText),
1649
1773
  pages,
1650
1774
  robots: robotsTxt === null ? null : parseRobots(robotsFile, robotsTxt),
1651
- sitemap: sitemapXml === null ? null : parseSitemap(sitemapFile, sitemapXml, files.get("/sitemap.xml") ?? Buffer.byteLength(sitemapXml, "utf-8"))
1775
+ sitemap: await readSitemap(staticDir, files, deployBase)
1652
1776
  };
1653
1777
  };
1654
1778
 
@@ -1775,6 +1899,7 @@ var runAudit = async (options) => {
1775
1899
  const basePath = normalizeBasePath(project.config.basePath);
1776
1900
  const crawl = await crawlStaticDir({
1777
1901
  basePath,
1902
+ deployBase: normalizeBasePath(project.config.deployment.options.base),
1778
1903
  manifest: project.manifest,
1779
1904
  staticDir
1780
1905
  });
@@ -1916,7 +2041,7 @@ var auditCommand = defineCommand({
1916
2041
  }
1917
2042
  const agents = Object.keys(AGENTS).filter((kind) => args[kind]);
1918
2043
  if (agents.length > 1) {
1919
- logger.error("Pass at most one of --claude or --codex.");
2044
+ logger.error("Pass at most one of --codex or --claude.");
1920
2045
  process.exit(1);
1921
2046
  }
1922
2047
  const [agent] = agents;
@@ -1924,6 +2049,10 @@ var auditCommand = defineCommand({
1924
2049
  logger.error(`--json and --${agent} are mutually exclusive.`);
1925
2050
  process.exit(1);
1926
2051
  }
2052
+ if (args.url !== undefined && !isHttpUrl(args.url)) {
2053
+ logger.error(`Invalid --url "${args.url}": pass the deployment's full URL, scheme included (e.g. https://${args.url}).`);
2054
+ process.exit(1);
2055
+ }
1927
2056
  const only = splitTerms(args.only);
1928
2057
  const skip = splitTerms(args.skip);
1929
2058
  rejectUnknownTerms([...only, ...skip]);
@@ -1985,5 +2114,5 @@ export {
1985
2114
  shouldFail
1986
2115
  };
1987
2116
 
1988
- //# debugId=938D6EDE48FC146464756E2164756E21
1989
- //# sourceMappingURL=chunk-j8mw0za6.js.map
2117
+ //# debugId=B39D0173C92838E764756E2164756E21
2118
+ //# sourceMappingURL=chunk-hqp2ajnh.js.map