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.
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +110 -0
- package/README.md +2 -2
- package/dist/cli/{chunk-f2972sbt.js → chunk-00gs3wqs.js} +1 -1
- package/dist/cli/{chunk-fa25z98p.js → chunk-273ygyr4.js} +4 -3
- package/dist/cli/chunk-273ygyr4.js.map +11 -0
- package/dist/cli/{chunk-pat2zzwc.js → chunk-3yce002v.js} +21 -8
- package/dist/cli/{chunk-pat2zzwc.js.map → chunk-3yce002v.js.map} +4 -4
- package/dist/cli/{chunk-d1tadaw7.js → chunk-4e9b9ra6.js} +17 -7
- package/dist/cli/chunk-4e9b9ra6.js.map +10 -0
- package/dist/cli/{chunk-zg2gtj10.js → chunk-5r8g91qn.js} +352 -676
- package/dist/cli/chunk-5r8g91qn.js.map +34 -0
- package/dist/cli/{chunk-11j0384y.js → chunk-6dtt0zfn.js} +14 -14
- package/dist/cli/{chunk-11j0384y.js.map → chunk-6dtt0zfn.js.map} +3 -3
- package/dist/cli/{chunk-bw22s759.js → chunk-6k4ftwze.js} +38 -19
- package/dist/cli/{chunk-bw22s759.js.map → chunk-6k4ftwze.js.map} +4 -4
- package/dist/cli/{chunk-sqn5t4q0.js → chunk-7mbqtmgb.js} +16 -7
- package/dist/cli/chunk-7mbqtmgb.js.map +10 -0
- package/dist/cli/{chunk-79njf86q.js → chunk-7vtckvaw.js} +21 -19
- package/dist/cli/chunk-7vtckvaw.js.map +11 -0
- package/dist/cli/{chunk-fh5hj5jt.js → chunk-8g8ytmgx.js} +17 -18
- package/dist/cli/{chunk-fh5hj5jt.js.map → chunk-8g8ytmgx.js.map} +2 -2
- package/dist/cli/{chunk-1w8dp3qb.js → chunk-91ws1n6j.js} +18 -15
- package/dist/cli/chunk-91ws1n6j.js.map +10 -0
- package/dist/cli/{chunk-7ez8ny0t.js → chunk-bbnwccaz.js} +2 -2
- package/dist/cli/{chunk-5a2z0198.js → chunk-bfwp9vp6.js} +18 -10
- package/dist/cli/chunk-bfwp9vp6.js.map +10 -0
- package/dist/cli/{chunk-ernrthtr.js → chunk-d1v5rhy0.js} +14 -15
- package/dist/cli/{chunk-ernrthtr.js.map → chunk-d1v5rhy0.js.map} +2 -2
- package/dist/cli/{chunk-a9kptbw5.js → chunk-ddndchfr.js} +21 -6
- package/dist/cli/chunk-ddndchfr.js.map +14 -0
- package/dist/cli/{chunk-zxccj738.js → chunk-esh98wmb.js} +1 -1
- package/dist/cli/{chunk-88cpgt6h.js → chunk-fsmrqk8a.js} +1 -1
- package/dist/cli/{chunk-b5aj94ah.js → chunk-g698a744.js} +5 -5
- package/dist/cli/{chunk-jts8mvcz.js → chunk-gs7r695n.js} +9 -3
- package/dist/cli/{chunk-jts8mvcz.js.map → chunk-gs7r695n.js.map} +3 -3
- package/dist/cli/{chunk-41za066z.js → chunk-h2ez8dzb.js} +4 -4
- package/dist/cli/{chunk-by2290sx.js → chunk-h7k3nq3v.js} +2 -2
- package/dist/cli/{chunk-j8mw0za6.js → chunk-hqp2ajnh.js} +252 -123
- package/dist/cli/chunk-hqp2ajnh.js.map +35 -0
- package/dist/cli/{chunk-mnqj32sj.js → chunk-hr8ne106.js} +119 -50
- package/dist/cli/chunk-hr8ne106.js.map +13 -0
- package/dist/cli/{chunk-mwt1k8n7.js → chunk-j85scx15.js} +74 -30
- package/dist/cli/chunk-j85scx15.js.map +10 -0
- package/dist/cli/{chunk-z01ze5c1.js → chunk-k7pj68a8.js} +63 -22
- package/dist/cli/chunk-k7pj68a8.js.map +11 -0
- package/dist/cli/{chunk-2q1dwty4.js → chunk-mqc662a6.js} +8 -8
- package/dist/cli/{chunk-2q1dwty4.js.map → chunk-mqc662a6.js.map} +4 -4
- package/dist/cli/{chunk-y3e45rc8.js → chunk-n1yg3tj3.js} +4 -4
- package/dist/cli/{chunk-bctazmbk.js → chunk-nfcyttvj.js} +17 -6
- package/dist/cli/chunk-nfcyttvj.js.map +10 -0
- package/dist/cli/{chunk-6k8vp3ta.js → chunk-pbg5a4s3.js} +60 -28
- package/dist/cli/chunk-pbg5a4s3.js.map +19 -0
- package/dist/cli/{chunk-d80hr03s.js → chunk-ppzjqwx2.js} +24 -17
- package/dist/cli/{chunk-d80hr03s.js.map → chunk-ppzjqwx2.js.map} +4 -4
- package/dist/cli/{chunk-beat36xx.js → chunk-qkb5a8sa.js} +25 -10
- package/dist/cli/chunk-qkb5a8sa.js.map +10 -0
- package/dist/cli/{chunk-bnbmcwfb.js → chunk-sqw4ekg1.js} +5 -5
- package/dist/cli/{chunk-bnbmcwfb.js.map → chunk-sqw4ekg1.js.map} +3 -3
- package/dist/cli/{chunk-xaz13gwg.js → chunk-v6ya5kcb.js} +3268 -1150
- package/dist/cli/chunk-v6ya5kcb.js.map +189 -0
- package/dist/cli/{chunk-tzne8qfq.js → chunk-w4bxdvsa.js} +18 -15
- package/dist/cli/chunk-w4bxdvsa.js.map +10 -0
- package/dist/cli/{chunk-f7t03s3g.js → chunk-wdrt2k2v.js} +2 -2
- package/dist/cli/{chunk-z1f5arsg.js → chunk-xh43dwgw.js} +170 -55
- package/dist/cli/chunk-xh43dwgw.js.map +36 -0
- package/dist/cli/{chunk-pnnvybbk.js → chunk-zp79m0ts.js} +5 -5
- package/dist/cli/{chunk-pnnvybbk.js.map → chunk-zp79m0ts.js.map} +2 -2
- package/dist/cli/index.js +160 -35
- package/dist/cli/index.js.map +4 -4
- package/dist/types/ai/agent-readability.d.ts +1 -1
- package/dist/types/ai/agent-surface.d.ts +32 -0
- package/dist/types/ai/api/paths.d.ts +1 -1
- package/dist/types/ai/api-catalog.d.ts +7 -1
- package/dist/types/ai/ask-context.d.ts +14 -7
- package/dist/types/ai/ask.d.ts +43 -43
- package/dist/types/ai/component-markdown.d.ts +4 -4
- package/dist/types/ai/index.d.ts +3 -3
- package/dist/types/ai/openapi-components.d.ts +1 -1
- package/dist/types/ai/relative-links.d.ts +9 -4
- package/dist/types/ai/serializers.d.ts +1 -1
- package/dist/types/ai/static-expression.d.ts +28 -0
- package/dist/types/ai/visibility.d.ts +1 -1
- package/dist/types/analytics/databuddy.d.ts +43 -0
- package/dist/types/analytics/index.d.ts +2 -0
- package/dist/types/analytics/schema.d.ts +14 -0
- package/dist/types/cli/env.d.ts +5 -0
- package/dist/types/cli/init/scaffold.d.ts +19 -3
- package/dist/types/cli/init/starter-spec.d.ts +11 -0
- package/dist/types/core/base-path.d.ts +9 -0
- package/dist/types/core/config-input.d.ts +21 -21
- package/dist/types/core/config.d.ts +5 -5
- package/dist/types/core/data.d.ts +3 -3
- package/dist/types/core/graph.d.ts +2 -0
- package/dist/types/core/i18n-ui.d.ts +37 -8
- package/dist/types/core/i18n.d.ts +7 -1
- package/dist/types/core/links.d.ts +3 -1
- package/dist/types/core/load-module.d.ts +10 -0
- package/dist/types/core/locale-links.d.ts +12 -2
- package/dist/types/core/meta.d.ts +5 -1
- package/dist/types/core/nav-diagnostics.d.ts +10 -0
- package/dist/types/core/navigation.d.ts +38 -0
- package/dist/types/core/ordering-prefix.d.ts +4 -0
- package/dist/types/core/safe-links.d.ts +3 -1
- package/dist/types/core/schema.d.ts +57 -18
- package/dist/types/core/sources/github-releases.d.ts +5 -0
- package/dist/types/core/sources/lower.d.ts +14 -2
- package/dist/types/core/sources/normalize.d.ts +10 -1
- package/dist/types/core/sources/remote.d.ts +11 -1
- package/dist/types/core/sources/resolve.d.ts +12 -0
- package/dist/types/core/sources/types.d.ts +31 -0
- package/dist/types/core/types.d.ts +9 -0
- package/dist/types/core/unrecognized-keys.d.ts +1 -1
- package/dist/types/deploy/adapters/node.d.ts +5 -2
- package/dist/types/deploy/adapters/types.d.ts +7 -0
- package/dist/types/deploy/cloudflare-negotiation.d.ts +3 -2
- package/dist/types/deploy/headers.d.ts +31 -7
- package/dist/types/deploy/node-headers.d.ts +43 -8
- package/dist/types/deploy/platforms/netlify.d.ts +27 -2
- package/dist/types/deploy/platforms/node.d.ts +6 -5
- package/dist/types/deploy/platforms/types.d.ts +7 -0
- package/dist/types/deploy/platforms/vercel.d.ts +3 -2
- package/dist/types/deploy/redirects.d.ts +7 -2
- package/dist/types/deploy/vercel-negotiation.d.ts +3 -2
- package/dist/types/openapi/model.d.ts +20 -6
- package/dist/types/search/documents.d.ts +1 -1
- package/dist/types/search/orama-index.d.ts +1 -1
- package/dist/types/search/sync/algolia.d.ts +3 -1
- package/docs/01-quickstart.mdx +3 -2
- package/docs/02-deployment.mdx +21 -12
- package/docs/03-upgrading.mdx +22 -9
- package/docs/04-migrating.mdx +4 -4
- package/docs/08-faq.mdx +12 -5
- package/docs/advanced/changelog.mdx +1 -1
- package/docs/advanced/custom-pages.mdx +5 -3
- package/docs/advanced/skills.mdx +1 -1
- package/docs/cli/audit.mdx +5 -5
- package/docs/cli/doctor.mdx +4 -4
- package/docs/cli/evals.mdx +9 -9
- package/docs/cli/index.mdx +6 -3
- package/docs/cli/translate.mdx +8 -8
- package/docs/configuration/analytics.mdx +23 -2
- package/docs/configuration/{ask-ai.mdx → assistant.mdx} +20 -20
- package/docs/configuration/customization.mdx +5 -4
- package/docs/configuration/index.mdx +7 -5
- package/docs/configuration/meta.ts +1 -1
- package/docs/configuration/search.mdx +2 -2
- package/docs/content/components.mdx +1 -1
- package/docs/content/frontmatter.mdx +5 -1
- package/docs/content/i18n.mdx +3 -3
- package/docs/content/index.mdx +4 -2
- package/docs/content/islands.mdx +1 -1
- package/docs/content/meta.mdx +5 -3
- package/docs/content/navigation.mdx +29 -4
- package/docs/content/sources.mdx +18 -12
- package/docs/content/versioning.mdx +1 -0
- package/docs/discoverability/agent-discovery.mdx +22 -8
- package/docs/discoverability/index.mdx +3 -3
- package/docs/discoverability/llms-txt.mdx +2 -5
- package/docs/discoverability/markdown.mdx +5 -3
- package/docs/discoverability/mcp.mdx +4 -0
- package/docs/discoverability/metadata.mdx +3 -2
- package/docs/discoverability/open-graph.mdx +6 -4
- package/docs/discoverability/rss.mdx +3 -1
- package/docs/index.mdx +2 -2
- package/docs/references/asyncapi.mdx +1 -1
- package/docs/references/graphql.mdx +2 -2
- package/docs/references/openapi.mdx +3 -3
- package/package.json +1 -1
- package/skills/blume/SKILL.md +5 -5
- package/skills/blume-migrate/SKILL.md +6 -6
- package/skills/blume-migrate/references/docusaurus.md +6 -6
- package/skills/blume-migrate/references/mintlify.md +3 -3
- package/skills/blume-migrate/references/monorepo.md +1 -1
- package/skills/blume-migrate/references/nextra.md +1 -1
- package/skills/blume-migrate/references/starlight.md +5 -5
- package/skills/blume-migrate/scripts/mintlify-codemod.mjs +2 -2
- package/skills/blume-update-docs/references/audit-checklist.md +1 -1
- package/src/ai/agent-readability.ts +4 -4
- package/src/ai/agent-surface.ts +56 -0
- package/src/ai/api/paths.ts +1 -1
- package/src/ai/api/spec.ts +15 -2
- package/src/ai/api-catalog.ts +7 -1
- package/src/ai/ask-context.ts +21 -9
- package/src/ai/ask-data.ts +28 -14
- package/src/ai/ask.ts +84 -71
- package/src/ai/component-markdown.ts +43 -37
- package/src/ai/cors.ts +3 -3
- package/src/ai/index.ts +16 -16
- package/src/ai/link-headers.ts +8 -3
- package/src/ai/llms.ts +4 -3
- package/src/ai/markdown.ts +23 -11
- package/src/ai/mcp/server.ts +78 -7
- package/src/ai/openapi-components.ts +1 -1
- package/src/ai/relative-links.ts +78 -10
- package/src/ai/serializers.ts +1 -1
- package/src/ai/static-expression.ts +416 -0
- package/src/ai/visibility.ts +46 -15
- package/src/analytics/databuddy.ts +67 -0
- package/src/analytics/head.ts +4 -0
- package/src/analytics/index.ts +2 -0
- package/src/analytics/posthog.ts +21 -3
- package/src/analytics/schema.ts +2 -0
- package/src/astro/generate.ts +90 -45
- package/src/astro/module-types.ts +1 -1
- package/src/astro/runtime-deps.ts +6 -6
- package/src/astro/templates.ts +87 -49
- package/src/audit/catalog.ts +2 -2
- package/src/audit/checks/assets.ts +25 -5
- package/src/audit/checks/content.ts +20 -2
- package/src/audit/checks/i18n.ts +46 -8
- package/src/audit/checks/indexability.ts +39 -19
- package/src/audit/checks/links.ts +14 -0
- package/src/audit/checks/llms.ts +6 -3
- package/src/audit/checks/network.ts +3 -1
- package/src/audit/checks/og-image.ts +10 -0
- package/src/audit/checks/robots.ts +6 -1
- package/src/audit/checks/sitemap.ts +56 -31
- package/src/audit/checks/social.ts +21 -2
- package/src/audit/crawl.ts +88 -15
- package/src/audit/report.ts +54 -17
- package/src/audit/run.ts +1 -0
- package/src/audit/types.ts +18 -0
- package/src/audit/url.ts +37 -6
- package/src/blume-modules.d.ts +2 -2
- package/src/cli/build-failure.ts +50 -0
- package/src/cli/commands/audit.ts +8 -1
- package/src/cli/commands/build.ts +19 -5
- package/src/cli/commands/check.ts +2 -0
- package/src/cli/commands/doctor.ts +7 -5
- package/src/cli/commands/eval.ts +4 -10
- package/src/cli/commands/init.ts +15 -5
- package/src/cli/commands/migrate.ts +2 -2
- package/src/cli/commands/preview.ts +15 -0
- package/src/cli/commands/sync.ts +2 -0
- package/src/cli/commands/translate.ts +12 -9
- package/src/cli/commands/upgrade.ts +13 -2
- package/src/cli/eject-scripts.ts +32 -7
- package/src/cli/env.ts +12 -1
- package/src/cli/init/scaffold.ts +117 -11
- package/src/cli/init/starter-spec.ts +235 -0
- package/src/cli/required-secrets.ts +3 -3
- package/src/components/content/AccordionItem.astro +26 -22
- package/src/components/content/Badge.astro +2 -9
- package/src/components/content/Card.astro +2 -2
- package/src/components/content/Component.astro +9 -3
- package/src/components/content/Expandable.astro +5 -1
- package/src/components/content/Frame.astro +2 -7
- package/src/components/content/GithubInfo.astro +12 -2
- package/src/components/content/Prompt.astro +2 -7
- package/src/components/content/Tabs.astro +54 -5
- package/src/components/content/Tile.astro +1 -1
- package/src/components/content/Tooltip.astro +69 -7
- package/src/components/content/Tree.astro +7 -2
- package/src/components/content/TypeTable.astro +10 -5
- package/src/components/content/Update.astro +8 -2
- package/src/components/content/auto-type-table.ts +4 -1
- package/src/components/content/badge-color.ts +17 -0
- package/src/components/content/base-href.ts +18 -3
- package/src/components/content/inline-markdown.ts +27 -7
- package/src/components/copy-feedback.ts +36 -9
- package/src/components/islands/{AskAI.astro → Assistant.astro} +10 -10
- package/src/components/islands/{ask-ai.tsx → assistant.tsx} +39 -25
- package/src/components/islands/hooks.ts +21 -14
- package/src/components/islands/webmcp.ts +12 -8
- package/src/components/layout/Banner.astro +23 -4
- package/src/components/layout/Header.astro +32 -19
- package/src/components/layout/Logo.astro +5 -0
- package/src/components/layout/NavSelector.astro +6 -1
- package/src/components/layout/NavTabMenu.astro +133 -0
- package/src/components/layout/NavTree.astro +15 -6
- package/src/components/layout/NavTreeCache.astro +5 -2
- package/src/components/layout/NavTreeScript.astro +45 -5
- package/src/components/layout/PageLayout.astro +28 -14
- package/src/components/layout/Pagination.astro +7 -7
- package/src/components/layout/ReferenceLayout.astro +5 -2
- package/src/components/layout/RootLayout.astro +27 -14
- package/src/components/layout/Search.astro +18 -14
- package/src/components/layout/analytics-client.ts +3 -1
- package/src/components/layout/drawer-inert.ts +1 -1
- package/src/components/openapi/GraphqlType.astro +11 -3
- package/src/components/openapi/MessageComposer.astro +1 -1
- package/src/components/openapi/Operation.astro +21 -5
- package/src/components/openapi/PanelTabs.astro +4 -1
- package/src/components/openapi/Playground.astro +8 -2
- package/src/components/openapi/RequestPanel.astro +9 -5
- package/src/components/openapi/SchemaProperty.astro +11 -33
- package/src/components/openapi/SchemaTable.astro +27 -52
- package/src/components/openapi/description.ts +2 -2
- package/src/components/openapi/helpers.ts +90 -12
- package/src/components/openapi/message-composer.ts +8 -0
- package/src/components/openapi/message-model.ts +12 -2
- package/src/components/openapi/message.ts +4 -1
- package/src/components/openapi/operation-model.ts +40 -8
- package/src/components/openapi/panel.ts +29 -5
- package/src/components/openapi/playground-client.ts +37 -7
- package/src/components/openapi/playground-schema.ts +25 -6
- package/src/components/openapi/request.ts +2 -0
- package/src/components/openapi/schema-tree.ts +203 -0
- package/src/components/openapi/snippets.ts +30 -15
- package/src/components/openapi/validate-json.ts +1 -1
- package/src/core/base-path.ts +15 -0
- package/src/core/code-fences.ts +1 -1
- package/src/core/config-input.ts +23 -23
- package/src/core/config.ts +21 -7
- package/src/core/data.ts +3 -3
- package/src/core/diagnostics.ts +217 -30
- package/src/core/graph.ts +120 -11
- package/src/core/i18n-ui.ts +74 -11
- package/src/core/i18n.ts +13 -2
- package/src/core/links.ts +7 -4
- package/src/core/load-module.ts +20 -0
- package/src/core/locale-links.ts +17 -17
- package/src/core/manifest.ts +3 -2
- package/src/core/meta.ts +69 -6
- package/src/core/nav-diagnostics.ts +56 -1
- package/src/core/navigation.ts +245 -76
- package/src/core/ordering-prefix.ts +27 -0
- package/src/core/project-graph.ts +23 -1
- package/src/core/request-body.ts +1 -1
- package/src/core/safe-href.ts +53 -1
- package/src/core/safe-links.ts +11 -2
- package/src/core/schema.ts +95 -34
- package/src/core/server-features.ts +2 -2
- package/src/core/sources/assets.ts +83 -41
- package/src/core/sources/contentful-rich-text.ts +3 -2
- package/src/core/sources/contentful.ts +25 -12
- package/src/core/sources/filesystem.ts +5 -1
- package/src/core/sources/github-releases.ts +106 -5
- package/src/core/sources/lexical.ts +9 -4
- package/src/core/sources/lower.ts +79 -26
- package/src/core/sources/mdx-remote.ts +1 -0
- package/src/core/sources/normalize.ts +132 -30
- package/src/core/sources/notion.ts +26 -10
- package/src/core/sources/obsidian.ts +17 -3
- package/src/core/sources/payload.ts +1 -0
- package/src/core/sources/portable-text.ts +63 -44
- package/src/core/sources/remote.ts +18 -2
- package/src/core/sources/resolve.ts +52 -33
- package/src/core/sources/sanity.ts +1 -0
- package/src/core/sources/strapi-blocks.ts +9 -4
- package/src/core/sources/strapi.ts +1 -0
- package/src/core/sources/types.ts +31 -0
- package/src/core/types.ts +9 -0
- package/src/core/ui-packs/ar.ts +17 -5
- package/src/core/ui-packs/bg.ts +17 -5
- package/src/core/ui-packs/bn.ts +17 -5
- package/src/core/ui-packs/ca.ts +17 -5
- package/src/core/ui-packs/cs.ts +17 -5
- package/src/core/ui-packs/da.ts +17 -5
- package/src/core/ui-packs/de.ts +17 -5
- package/src/core/ui-packs/el.ts +17 -5
- package/src/core/ui-packs/es.ts +17 -5
- package/src/core/ui-packs/fa.ts +17 -5
- package/src/core/ui-packs/fi.ts +17 -5
- package/src/core/ui-packs/fr.ts +17 -5
- package/src/core/ui-packs/he.ts +17 -5
- package/src/core/ui-packs/hi.ts +17 -5
- package/src/core/ui-packs/hr.ts +17 -5
- package/src/core/ui-packs/hu.ts +17 -5
- package/src/core/ui-packs/id.ts +17 -5
- package/src/core/ui-packs/it.ts +17 -5
- package/src/core/ui-packs/ja.ts +17 -5
- package/src/core/ui-packs/ko.ts +17 -5
- package/src/core/ui-packs/nl.ts +17 -5
- package/src/core/ui-packs/no.ts +17 -5
- package/src/core/ui-packs/pl.ts +17 -5
- package/src/core/ui-packs/pt-br.ts +17 -5
- package/src/core/ui-packs/pt.ts +17 -5
- package/src/core/ui-packs/ro.ts +17 -5
- package/src/core/ui-packs/ru.ts +17 -5
- package/src/core/ui-packs/sk.ts +17 -5
- package/src/core/ui-packs/sr.ts +17 -5
- package/src/core/ui-packs/sv.ts +17 -5
- package/src/core/ui-packs/th.ts +17 -5
- package/src/core/ui-packs/tr.ts +17 -5
- package/src/core/ui-packs/uk.ts +17 -5
- package/src/core/ui-packs/vi.ts +17 -5
- package/src/core/ui-packs/zh-tw.ts +17 -5
- package/src/core/ui-packs/zh.ts +17 -5
- package/src/core/unrecognized-keys.ts +1 -1
- package/src/core/version-cut.ts +17 -2
- package/src/core/versions.ts +4 -1
- package/src/deploy/adapters/node.ts +5 -2
- package/src/deploy/adapters/registry.ts +2 -1
- package/src/deploy/adapters/types.ts +13 -1
- package/src/deploy/artifacts.ts +25 -5
- package/src/deploy/cloudflare-negotiation.ts +23 -4
- package/src/deploy/headers.ts +67 -51
- package/src/deploy/node-headers.ts +148 -27
- package/src/deploy/platforms/cloudflare.ts +1 -0
- package/src/deploy/platforms/netlify.ts +82 -5
- package/src/deploy/platforms/node.ts +7 -5
- package/src/deploy/platforms/static.ts +1 -0
- package/src/deploy/platforms/types.ts +7 -0
- package/src/deploy/platforms/vercel.ts +9 -3
- package/src/deploy/redirects.ts +14 -3
- package/src/deploy/vercel-negotiation.ts +35 -3
- package/src/eval/agents.ts +10 -2
- package/src/eval/run.ts +25 -0
- package/src/markdown/base-links.ts +55 -8
- package/src/markdown/index.ts +6 -3
- package/src/markdown/relative-links.ts +3 -23
- package/src/markdown/route-snapshot.ts +37 -0
- package/src/og/card.ts +20 -4
- package/src/og/derive.ts +145 -4
- package/src/openapi/graphql-build.ts +28 -2
- package/src/openapi/model.ts +77 -13
- package/src/openapi/render-mdx.ts +10 -1
- package/src/registry/eject.ts +135 -37
- package/src/search/documents.ts +24 -3
- package/src/search/orama-index.ts +1 -1
- package/src/search/sync/algolia.ts +36 -2
- package/src/sources/registry.ts +5 -0
- package/src/theme/entry.ts +11 -4
- package/src/translate/agents.ts +6 -1
- package/src/translate/ledger.ts +26 -3
- package/src/translate/meta.ts +11 -3
- package/src/translate/report.ts +1 -1
- package/src/translate/run.ts +11 -5
- package/src/translate/validate.ts +10 -1
- package/src/translate/work-list.ts +36 -3
- package/src/upgrade/upgrade.ts +37 -5
- package/dist/cli/chunk-1w8dp3qb.js.map +0 -10
- package/dist/cli/chunk-5a2z0198.js.map +0 -10
- package/dist/cli/chunk-6k8vp3ta.js.map +0 -19
- package/dist/cli/chunk-79njf86q.js.map +0 -11
- package/dist/cli/chunk-a9kptbw5.js.map +0 -14
- package/dist/cli/chunk-abh8yjkn.js +0 -31
- package/dist/cli/chunk-abh8yjkn.js.map +0 -10
- package/dist/cli/chunk-bctazmbk.js.map +0 -10
- package/dist/cli/chunk-beat36xx.js.map +0 -10
- package/dist/cli/chunk-d1tadaw7.js.map +0 -10
- package/dist/cli/chunk-fa25z98p.js.map +0 -11
- package/dist/cli/chunk-j8mw0za6.js.map +0 -35
- package/dist/cli/chunk-mnqj32sj.js.map +0 -12
- package/dist/cli/chunk-mwt1k8n7.js.map +0 -10
- package/dist/cli/chunk-nk3ts2xk.js +0 -51
- package/dist/cli/chunk-nk3ts2xk.js.map +0 -10
- package/dist/cli/chunk-sqn5t4q0.js.map +0 -10
- package/dist/cli/chunk-tzne8qfq.js.map +0 -10
- package/dist/cli/chunk-xaz13gwg.js.map +0 -182
- package/dist/cli/chunk-z01ze5c1.js.map +0 -10
- package/dist/cli/chunk-z1f5arsg.js.map +0 -36
- package/dist/cli/chunk-zg2gtj10.js.map +0 -35
- /package/dist/cli/{chunk-f2972sbt.js.map → chunk-00gs3wqs.js.map} +0 -0
- /package/dist/cli/{chunk-7ez8ny0t.js.map → chunk-bbnwccaz.js.map} +0 -0
- /package/dist/cli/{chunk-zxccj738.js.map → chunk-esh98wmb.js.map} +0 -0
- /package/dist/cli/{chunk-88cpgt6h.js.map → chunk-fsmrqk8a.js.map} +0 -0
- /package/dist/cli/{chunk-b5aj94ah.js.map → chunk-g698a744.js.map} +0 -0
- /package/dist/cli/{chunk-41za066z.js.map → chunk-h2ez8dzb.js.map} +0 -0
- /package/dist/cli/{chunk-by2290sx.js.map → chunk-h7k3nq3v.js.map} +0 -0
- /package/dist/cli/{chunk-y3e45rc8.js.map → chunk-n1yg3tj3.js.map} +0 -0
- /package/dist/cli/{chunk-f7t03s3g.js.map → chunk-wdrt2k2v.js.map} +0 -0
package/src/astro/templates.ts
CHANGED
|
@@ -14,7 +14,7 @@ import { BLUME_IGNORE_DIRS } from "../core/sources/watch.ts";
|
|
|
14
14
|
import { trimChar } from "../core/trim.ts";
|
|
15
15
|
import type { ProjectContext } from "../core/types.ts";
|
|
16
16
|
import { deployPassthrough } from "../deploy/adapters/types.ts";
|
|
17
|
-
import {
|
|
17
|
+
import { SVG_ASSET_HEADERS } from "../deploy/headers.ts";
|
|
18
18
|
import { deployPlatform } from "../deploy/platforms/index.ts";
|
|
19
19
|
import { adapterRoot, distDir } from "../deploy/platforms/paths.ts";
|
|
20
20
|
import { applyBaseToAstroRedirects } from "../deploy/redirects.ts";
|
|
@@ -196,10 +196,10 @@ export const runtimeDependencies = (options: {
|
|
|
196
196
|
}
|
|
197
197
|
}
|
|
198
198
|
}
|
|
199
|
-
//
|
|
199
|
+
// The assistant's provider SDK, as its adapter declares it (the gateway needs
|
|
200
200
|
// nothing beyond core `ai`, so it declares none).
|
|
201
|
-
if (config.ai.
|
|
202
|
-
deps.push(...config.ai.
|
|
201
|
+
if (config.ai.assistant?.enabled && !config.ai.assistant.endpoint) {
|
|
202
|
+
deps.push(...config.ai.assistant.provider.runtimeDeps);
|
|
203
203
|
}
|
|
204
204
|
// The deployment adapter's `@astrojs/*` package, for a server build; the
|
|
205
205
|
// descriptor declares it (and nothing for a static build).
|
|
@@ -727,7 +727,7 @@ export const astroConfigTemplate = (options: {
|
|
|
727
727
|
hasFonts: fontEntries.length > 0,
|
|
728
728
|
});
|
|
729
729
|
|
|
730
|
-
// Framework renderers are only wired in when an island (or
|
|
730
|
+
// Framework renderers are only wired in when an island (or the assistant, for React)
|
|
731
731
|
// needs them. The core theme is Astro-first and ships no client JS.
|
|
732
732
|
const reactImport = needsReact ? `import react from "@astrojs/react";\n` : "";
|
|
733
733
|
const vueImport = needsVue ? `import vue from "@astrojs/vue";\n` : "";
|
|
@@ -1064,17 +1064,17 @@ export const collections = { docs${options.staged ? ", staged" : ""} };
|
|
|
1064
1064
|
const ASK_FALLBACK_PROMPT =
|
|
1065
1065
|
"You are a helpful documentation assistant. Answer using the project's documentation.";
|
|
1066
1066
|
|
|
1067
|
-
/** The `ai.
|
|
1067
|
+
/** The `ai.assistant` values the generated endpoint has to carry with it. */
|
|
1068
1068
|
export interface AskEndpointOptions {
|
|
1069
|
-
/** `ai.
|
|
1069
|
+
/** `ai.assistant.cors` — origins allowed to call the route from another site. */
|
|
1070
1070
|
cors?: string[];
|
|
1071
|
-
/** `ai.
|
|
1071
|
+
/** `ai.assistant.instructions` — extra system-prompt text. */
|
|
1072
1072
|
instructions?: string;
|
|
1073
|
-
/** `ai.
|
|
1073
|
+
/** `ai.assistant.retrieval` — how much documentation each question carries. */
|
|
1074
1074
|
retrieval?: AskRetrievalOptions;
|
|
1075
1075
|
}
|
|
1076
1076
|
|
|
1077
|
-
/** The pieces `askEndpointTemplate` splices in for `ai.
|
|
1077
|
+
/** The pieces `askEndpointTemplate` splices in for `ai.assistant.cors`. */
|
|
1078
1078
|
interface AskCorsTemplate {
|
|
1079
1079
|
/** The route's closing token: `});` when the POST is wrapped, `};` otherwise. */
|
|
1080
1080
|
close: string;
|
|
@@ -1087,7 +1087,7 @@ interface AskCorsTemplate {
|
|
|
1087
1087
|
}
|
|
1088
1088
|
|
|
1089
1089
|
/**
|
|
1090
|
-
* `ai.
|
|
1090
|
+
* `ai.assistant.cors`: a browser only lets another origin read the stream when the
|
|
1091
1091
|
* response names that origin, and a JSON POST preflights first, so the route
|
|
1092
1092
|
* answers `OPTIONS` and wraps the `POST` in `withCors`, which stamps a listed
|
|
1093
1093
|
* origin on every response — errors included, so a cross-origin caller can
|
|
@@ -1114,14 +1114,14 @@ export const OPTIONS: APIRoute = ({ request }) =>
|
|
|
1114
1114
|
: { close: "};", imports: [], open: "async ({ request }) => {", setup: "" };
|
|
1115
1115
|
|
|
1116
1116
|
/**
|
|
1117
|
-
* Largest request body the
|
|
1117
|
+
* Largest request body the assistant route reads: 64 KB, well above the
|
|
1118
1118
|
* 24,000-character message budget it validates next, so a real conversation
|
|
1119
1119
|
* never meets it.
|
|
1120
1120
|
*/
|
|
1121
1121
|
const ASK_BODY_LIMIT_BYTES = 65_536;
|
|
1122
1122
|
|
|
1123
1123
|
/**
|
|
1124
|
-
* Generate the
|
|
1124
|
+
* Generate the assistant server endpoint (`.blume/src/pages/api/ask.ts`).
|
|
1125
1125
|
*
|
|
1126
1126
|
* The provider-specific pieces — imports, the provider factory call, the model
|
|
1127
1127
|
* expression, the credential guard, and the adapter's reasoning mapping and
|
|
@@ -1130,11 +1130,11 @@ const ASK_BODY_LIMIT_BYTES = 65_536;
|
|
|
1130
1130
|
* `blume.config.ts`. `backend.grounded` decides whether answers are grounded
|
|
1131
1131
|
* in the retrieved docs (every adapter but Inkeep, which retrieves itself).
|
|
1132
1132
|
*
|
|
1133
|
-
* `options.instructions` (the `ai.
|
|
1133
|
+
* `options.instructions` (the `ai.assistant.instructions` config) is appended to the
|
|
1134
1134
|
* built-in prompt on every path: the grounded prompt via `createAskContext`,
|
|
1135
|
-
* and the plain fallback here. `options.retrieval` (the `ai.
|
|
1135
|
+
* and the plain fallback here. `options.retrieval` (the `ai.assistant.retrieval`
|
|
1136
1136
|
* config) is forwarded to `createAskContext` on the grounded path, where it
|
|
1137
|
-
* sizes retrieval. `options.cors` (the `ai.
|
|
1137
|
+
* sizes retrieval. `options.cors` (the `ai.assistant.cors` config) adds a preflight
|
|
1138
1138
|
* handler and wraps the `POST` so every response names a listed origin. All
|
|
1139
1139
|
* three travel in one options object so a new call site can't silently drop
|
|
1140
1140
|
* one of them.
|
|
@@ -1232,7 +1232,7 @@ export const askEndpointTemplate = (
|
|
|
1232
1232
|
// Provider errors surface mid-stream, after the 200 is committed; this is
|
|
1233
1233
|
// the only place they can be observed server-side.
|
|
1234
1234
|
const onError = ` onError({ error }) {
|
|
1235
|
-
console.error("
|
|
1235
|
+
console.error("Assistant provider error:", error);
|
|
1236
1236
|
},`;
|
|
1237
1237
|
// The `streamText` argument list, built once so the grounded and plain
|
|
1238
1238
|
// paths can't drift: they differ only in where the instructions come from.
|
|
@@ -1281,9 +1281,9 @@ ${handler}
|
|
|
1281
1281
|
* Generate `.blume/src/generated/Ask.astro` — the component behind the
|
|
1282
1282
|
* `blume:ask` alias that the shared header renders in place of a per-page slot.
|
|
1283
1283
|
*
|
|
1284
|
-
* The header can't import the
|
|
1284
|
+
* The header can't import the assistant island directly: it's a React component, so
|
|
1285
1285
|
* the import alone would drag the JSX renderer into the module graph of every
|
|
1286
|
-
* project — including the ones that never enable
|
|
1286
|
+
* project — including the ones that never enable the assistant and therefore have no
|
|
1287
1287
|
* React integration wired into their generated Astro config (see `needsReact`).
|
|
1288
1288
|
* Routing the import through a generated component keeps that dependency behind
|
|
1289
1289
|
* the config switch: enabled projects get the island, disabled ones get a
|
|
@@ -1293,25 +1293,25 @@ ${handler}
|
|
|
1293
1293
|
* state suggestions are read straight from the data snapshot, which is why no
|
|
1294
1294
|
* page has to pass them.
|
|
1295
1295
|
*/
|
|
1296
|
-
export const askComponentTemplate = (
|
|
1297
|
-
|
|
1296
|
+
export const askComponentTemplate = (assistantEnabled: boolean): string =>
|
|
1297
|
+
assistantEnabled
|
|
1298
1298
|
? `---
|
|
1299
1299
|
// Generated by Blume. Do not edit.
|
|
1300
|
-
import
|
|
1300
|
+
import Assistant from "blume/components/islands/Assistant.astro";
|
|
1301
1301
|
import data from "blume:data";
|
|
1302
1302
|
|
|
1303
1303
|
const { strings } = Astro.props;
|
|
1304
1304
|
---
|
|
1305
1305
|
|
|
1306
|
-
<
|
|
1307
|
-
endpoint={data.config.
|
|
1308
|
-
strings={strings ?? data.ui.
|
|
1309
|
-
suggestions={data.config.
|
|
1306
|
+
<Assistant
|
|
1307
|
+
endpoint={data.config.assistant?.endpoint ?? undefined}
|
|
1308
|
+
strings={strings ?? data.ui.assistant}
|
|
1309
|
+
suggestions={data.config.assistant?.suggestions ?? []}
|
|
1310
1310
|
/>
|
|
1311
1311
|
`
|
|
1312
1312
|
: `---
|
|
1313
1313
|
// Generated by Blume. Do not edit.
|
|
1314
|
-
//
|
|
1314
|
+
// The assistant is off (\`ai.assistant.enabled\`), so the header's Ask trigger renders nothing.
|
|
1315
1315
|
// Deliberately imports no React island, keeping the JSX renderer out of projects
|
|
1316
1316
|
// that don't need it.
|
|
1317
1317
|
---
|
|
@@ -1377,7 +1377,10 @@ export const createSearch = () => create(${JSON.stringify(provider.options)});
|
|
|
1377
1377
|
* site uses the feature and `null` otherwise, so an unused library is absent
|
|
1378
1378
|
* from the module graph (never bundled, never pre-bundled in dev) rather than
|
|
1379
1379
|
* merely lazy. Regenerated on every pass: a page that gains a mermaid fence
|
|
1380
|
-
* turns the loader on.
|
|
1380
|
+
* turns the loader on. The EPUB module is named — even in a type — only when
|
|
1381
|
+
* `export.epub` is on: an ejected app depends on `epub-gen-memory` only then,
|
|
1382
|
+
* and a type import of a package it can't resolve fails its type check under
|
|
1383
|
+
* a strict linker such as pnpm.
|
|
1381
1384
|
*/
|
|
1382
1385
|
export const featuresTemplate = (features: ClientFeatures): string =>
|
|
1383
1386
|
`// Generated by Blume. Do not edit.
|
|
@@ -1394,9 +1397,13 @@ export const loadMermaid: (() => Promise<unknown>) | null = ${
|
|
|
1394
1397
|
};
|
|
1395
1398
|
|
|
1396
1399
|
/** The EPUB generator's browser bundle; null when export.epub is off. */
|
|
1397
|
-
|
|
1400
|
+
${
|
|
1401
|
+
features.epub
|
|
1402
|
+
? `export const loadEpub:
|
|
1398
1403
|
| (() => Promise<typeof import("epub-gen-memory/bundle")>)
|
|
1399
|
-
| null =
|
|
1404
|
+
| null = () => import("epub-gen-memory/bundle");`
|
|
1405
|
+
: "export const loadEpub: (() => Promise<unknown>) | null = null;"
|
|
1406
|
+
}
|
|
1400
1407
|
`;
|
|
1401
1408
|
|
|
1402
1409
|
/**
|
|
@@ -1561,12 +1568,13 @@ export function GET({ props }: { props: { route: string } }) {
|
|
|
1561
1568
|
* materializes under `.blume/public/blume-assets` (see
|
|
1562
1569
|
* `core/sources/assets.ts`). That directory is NOT Astro's `publicDir`
|
|
1563
1570
|
* (which points at the user project's `public/`), so without this endpoint
|
|
1564
|
-
* those references 404.
|
|
1571
|
+
* those references 404. `null` for an ejected app, which copies those
|
|
1572
|
+
* files into its own `public/blume-assets` instead.
|
|
1565
1573
|
* Prerendered: every asset becomes a static file in the build output; the dev
|
|
1566
1574
|
* server renders on demand, so new images appear without a restart.
|
|
1567
1575
|
*/
|
|
1568
1576
|
export const contentAssetsEndpointTemplate = (
|
|
1569
|
-
stagedAssetsDir: string
|
|
1577
|
+
stagedAssetsDir: string | null
|
|
1570
1578
|
): string =>
|
|
1571
1579
|
`// Generated by Blume. Do not edit.
|
|
1572
1580
|
import { existsSync } from "node:fs";
|
|
@@ -1588,18 +1596,29 @@ const TYPES: Record<string, string> = {
|
|
|
1588
1596
|
".ico": "image/x-icon",
|
|
1589
1597
|
".jpeg": "image/jpeg",
|
|
1590
1598
|
".jpg": "image/jpeg",
|
|
1599
|
+
".mov": "video/quicktime",
|
|
1600
|
+
".mp4": "video/mp4",
|
|
1601
|
+
".ogv": "video/ogg",
|
|
1591
1602
|
".png": "image/png",
|
|
1592
1603
|
".svg": "image/svg+xml",
|
|
1593
1604
|
".tiff": "image/tiff",
|
|
1605
|
+
".webm": "video/webm",
|
|
1594
1606
|
".webp": "image/webp",
|
|
1595
1607
|
};
|
|
1596
1608
|
|
|
1609
|
+
const extensionOf = (path: string): string =>
|
|
1610
|
+
path.slice(path.lastIndexOf(".")).toLowerCase();
|
|
1611
|
+
|
|
1597
1612
|
const contentType = (path: string): string =>
|
|
1598
|
-
TYPES[
|
|
1599
|
-
|
|
1613
|
+
TYPES[extensionOf(path)] ?? "application/octet-stream";
|
|
1614
|
+
|
|
1615
|
+
// A source downloads only images and videos; anything else in the staged
|
|
1616
|
+
// directory (a page an older build saved under its URL's extension) is never
|
|
1617
|
+
// published from the docs origin.
|
|
1618
|
+
const isMedia = (path: string): boolean => Object.hasOwn(TYPES, extensionOf(path));
|
|
1600
1619
|
|
|
1601
1620
|
const stagedParams = async (): Promise<string[]> => {
|
|
1602
|
-
if (!existsSync(STAGED_DIR)) {
|
|
1621
|
+
if (STAGED_DIR === null || !existsSync(STAGED_DIR)) {
|
|
1603
1622
|
return [];
|
|
1604
1623
|
}
|
|
1605
1624
|
const entries = await readdir(STAGED_DIR, {
|
|
@@ -1607,7 +1626,7 @@ const stagedParams = async (): Promise<string[]> => {
|
|
|
1607
1626
|
withFileTypes: true,
|
|
1608
1627
|
});
|
|
1609
1628
|
return entries
|
|
1610
|
-
.filter((entry) => entry.isFile())
|
|
1629
|
+
.filter((entry) => entry.isFile() && isMedia(entry.name))
|
|
1611
1630
|
.map((entry) =>
|
|
1612
1631
|
relative(STAGED_DIR, join(entry.parentPath, entry.name)).replaceAll(
|
|
1613
1632
|
"\\\\",
|
|
@@ -1628,6 +1647,9 @@ const resolveAsset = (asset: string): string | null => {
|
|
|
1628
1647
|
if (asset.startsWith("content/")) {
|
|
1629
1648
|
return files[asset.slice("content/".length)] ?? null;
|
|
1630
1649
|
}
|
|
1650
|
+
if (STAGED_DIR === null) {
|
|
1651
|
+
return null;
|
|
1652
|
+
}
|
|
1631
1653
|
const abs = resolve(STAGED_DIR, asset);
|
|
1632
1654
|
// Traversal guard: the dev server renders on demand, so the param is
|
|
1633
1655
|
// attacker-controlled there — never step outside the staged directory.
|
|
@@ -1637,7 +1659,7 @@ const resolveAsset = (asset: string): string | null => {
|
|
|
1637
1659
|
// asset — and a bare prefix also admits a sibling directory that merely
|
|
1638
1660
|
// shares the name.
|
|
1639
1661
|
const rel = relative(STAGED_DIR, abs);
|
|
1640
|
-
if (rel === "" || rel.startsWith("..") || isAbsolute(rel)) {
|
|
1662
|
+
if (rel === "" || rel.startsWith("..") || isAbsolute(rel) || !isMedia(rel)) {
|
|
1641
1663
|
return null;
|
|
1642
1664
|
}
|
|
1643
1665
|
return existsSync(abs) ? abs : null;
|
|
@@ -1651,11 +1673,12 @@ export const GET: APIRoute = async ({ params }) => {
|
|
|
1651
1673
|
const body = await readFile(path);
|
|
1652
1674
|
const type = contentType(path);
|
|
1653
1675
|
// An SVG opened directly is a document that can run script; the sandbox
|
|
1654
|
-
// keeps an uploaded one inert on the docs origin.
|
|
1655
|
-
//
|
|
1676
|
+
// keeps an uploaded one inert on the docs origin. This endpoint
|
|
1677
|
+
// prerenders, so these headers reach dev only: a build serves the files
|
|
1678
|
+
// with the same headers from its host's rules (see deploy/headers.ts).
|
|
1656
1679
|
const headers: Record<string, string> =
|
|
1657
1680
|
type === "image/svg+xml"
|
|
1658
|
-
? {
|
|
1681
|
+
? { ...${JSON.stringify(SVG_ASSET_HEADERS)}, "Content-Type": type }
|
|
1659
1682
|
: { "Content-Type": type };
|
|
1660
1683
|
return new Response(new Uint8Array(body), { headers });
|
|
1661
1684
|
};
|
|
@@ -2758,6 +2781,7 @@ export const notFoundPageTemplate = (): string => `---
|
|
|
2758
2781
|
import Icon from "blume/components/Icon.astro";
|
|
2759
2782
|
import PageLayout from "blume/components/layout/PageLayout.astro";
|
|
2760
2783
|
import { withBase } from "blume/components/islands/base-path.ts";
|
|
2784
|
+
import { withBasePath } from "blume/core/base-path.ts";
|
|
2761
2785
|
import data from "blume:data";
|
|
2762
2786
|
|
|
2763
2787
|
export const prerender = true;
|
|
@@ -2795,8 +2819,13 @@ const suggestions = [
|
|
|
2795
2819
|
// Every host serves this one page for any missing URL, so a reader who
|
|
2796
2820
|
// mistyped \`/ar/…\` would get the default locale's message. The other locales'
|
|
2797
2821
|
// copy rides along, and the script below swaps it in when the URL's first
|
|
2798
|
-
// segment names one of them — the message, its language
|
|
2799
|
-
// tab title, and the home link (to that locale's root).
|
|
2822
|
+
// segment past the site's base names one of them — the message, its language
|
|
2823
|
+
// and direction, the tab title, and the home link (to that locale's root).
|
|
2824
|
+
// Localized routes sit under both bases (\`{deployment.base}/{basePath}/ar/…\`),
|
|
2825
|
+
// so the script strips the two together, trailing slash included.
|
|
2826
|
+
const { basePath } = data.config;
|
|
2827
|
+
const siteRoot = withBase(withBasePath(basePath, "/"));
|
|
2828
|
+
const localeBase = siteRoot.endsWith("/") ? siteRoot : siteRoot + "/";
|
|
2800
2829
|
const localized = Object.fromEntries(
|
|
2801
2830
|
(i18n?.locales ?? [])
|
|
2802
2831
|
.filter((l) => l.code !== i18n?.defaultLocale && data.uiByLocale[l.code])
|
|
@@ -2808,7 +2837,7 @@ const localized = Object.fromEntries(
|
|
|
2808
2837
|
description: strings.description,
|
|
2809
2838
|
dir: l.dir ?? "ltr",
|
|
2810
2839
|
home: strings.home,
|
|
2811
|
-
homeHref: withBase("/" + l.code),
|
|
2840
|
+
homeHref: withBase(withBasePath(basePath, "/" + l.code)),
|
|
2812
2841
|
suggestions: strings.suggestions,
|
|
2813
2842
|
title: strings.title,
|
|
2814
2843
|
},
|
|
@@ -2837,7 +2866,7 @@ const localizedJson = JSON.stringify(localized).replaceAll("<", "\\\\u003c");
|
|
|
2837
2866
|
>
|
|
2838
2867
|
<div
|
|
2839
2868
|
class="mx-auto grid w-full max-w-5xl gap-12 px-6 py-20 sm:py-28 md:grid-cols-[3fr_2fr] md:gap-16 lg:gap-24 lg:py-36"
|
|
2840
|
-
data-base={
|
|
2869
|
+
data-base={localeBase}
|
|
2841
2870
|
data-blume-not-found
|
|
2842
2871
|
>
|
|
2843
2872
|
<div class="flex flex-col items-start">
|
|
@@ -3300,11 +3329,20 @@ const Example = entry.Component;
|
|
|
3300
3329
|
root.dataset.theme = theme;
|
|
3301
3330
|
root.classList.toggle("dark", theme === "dark");
|
|
3302
3331
|
};
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3332
|
+
// Blocked storage (a sandboxed or privacy-locked frame) throws on
|
|
3333
|
+
// access; it falls through to the OS setting like an unset preference.
|
|
3334
|
+
const stored = () => {
|
|
3335
|
+
let theme = null;
|
|
3336
|
+
try {
|
|
3337
|
+
theme = localStorage.getItem("blume-theme");
|
|
3338
|
+
} catch {}
|
|
3339
|
+
return (
|
|
3340
|
+
theme ??
|
|
3341
|
+
(matchMedia("(prefers-color-scheme: dark)").matches
|
|
3342
|
+
? "dark"
|
|
3343
|
+
: "light")
|
|
3344
|
+
);
|
|
3345
|
+
};
|
|
3308
3346
|
try {
|
|
3309
3347
|
const host = window.parent.document.documentElement;
|
|
3310
3348
|
apply(host.dataset.theme ?? stored());
|
package/src/audit/catalog.ts
CHANGED
|
@@ -583,7 +583,7 @@ export const CHECKS = [
|
|
|
583
583
|
},
|
|
584
584
|
{
|
|
585
585
|
category: "sitemap",
|
|
586
|
-
fix: "Sitemaps must be valid XML in the sitemaps.org urlset format.",
|
|
586
|
+
fix: "Sitemaps must be valid XML in the sitemaps.org urlset or sitemap index format.",
|
|
587
587
|
id: "BLUME_AUDIT_SITEMAP_INVALID",
|
|
588
588
|
severity: "error",
|
|
589
589
|
tier: "static",
|
|
@@ -684,7 +684,7 @@ export const CHECKS = [
|
|
|
684
684
|
},
|
|
685
685
|
{
|
|
686
686
|
category: "ai",
|
|
687
|
-
fix: "Rebuild so llms.txt matches the site; if the page is deliberately excluded,
|
|
687
|
+
fix: "Rebuild so llms.txt matches the site; if the page is deliberately excluded, set `ai.exclude: true` in its front matter.",
|
|
688
688
|
id: "BLUME_AUDIT_LLMS_TXT_PAGE_MISSING",
|
|
689
689
|
severity: "warning",
|
|
690
690
|
tier: "static",
|
|
@@ -11,6 +11,13 @@ import type {
|
|
|
11
11
|
} from "../types.ts";
|
|
12
12
|
import { resolveHref, siteOrigin } from "../url.ts";
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Vercel's image optimization route (`/_vercel/image?url=…&w=…`), which
|
|
16
|
+
* `imageService` on the Vercel adapter points `<img>` at. The platform
|
|
17
|
+
* serves it; the build never writes a file there.
|
|
18
|
+
*/
|
|
19
|
+
const VERCEL_IMAGE_ENDPOINT = "/_vercel/image";
|
|
20
|
+
|
|
14
21
|
const formatBytes = (bytes: number): string =>
|
|
15
22
|
bytes < 1024 * 1024
|
|
16
23
|
? `${Math.round(bytes / 1024)} kB`
|
|
@@ -44,17 +51,30 @@ const resolveAsset = (
|
|
|
44
51
|
}
|
|
45
52
|
|
|
46
53
|
const origin = siteOrigin(context.project.config.deployment.options.site);
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
asset.src,
|
|
50
|
-
origin,
|
|
51
|
-
normalizeBasePath(context.project.config.deployment.options.base)
|
|
54
|
+
const deployBase = normalizeBasePath(
|
|
55
|
+
context.project.config.deployment.options.base
|
|
52
56
|
);
|
|
57
|
+
const resolved = resolveHref(page.url, asset.src, origin, deployBase);
|
|
53
58
|
// A subresource on another origin (a CDN, an analytics script) is outside the
|
|
54
59
|
// build; we can't check whether it exists without the network.
|
|
55
60
|
if (resolved.kind === "external" || resolved.kind === "ignored") {
|
|
56
61
|
return null;
|
|
57
62
|
}
|
|
63
|
+
// Vercel's image optimization endpoint answers these at request time, at the
|
|
64
|
+
// host root whatever the base.
|
|
65
|
+
if (resolved.path === VERCEL_IMAGE_ENDPOINT) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
if (resolved.kind === "outside-base") {
|
|
69
|
+
found.push(
|
|
70
|
+
finding(
|
|
71
|
+
missingId,
|
|
72
|
+
pageSite(context, page),
|
|
73
|
+
`Page references ${asset.src}, which is missing deployment.base (${deployBase}), so the deployed site does not serve it.`
|
|
74
|
+
)
|
|
75
|
+
);
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
58
78
|
|
|
59
79
|
const bytes = context.files.get(resolved.path);
|
|
60
80
|
if (bytes === undefined) {
|
|
@@ -38,6 +38,22 @@ const frontmatterDate = (source: string): Date | null => {
|
|
|
38
38
|
}
|
|
39
39
|
};
|
|
40
40
|
|
|
41
|
+
/**
|
|
42
|
+
* API reference pages (OpenAPI, AsyncAPI, GraphQL) are generated from a spec
|
|
43
|
+
* and have no front matter to edit: their title comes from the operation's
|
|
44
|
+
* summary (the spec's own title on the overview page) and their description
|
|
45
|
+
* from its description or summary. A fix naming front matter would send the
|
|
46
|
+
* reader to a file that doesn't exist.
|
|
47
|
+
*/
|
|
48
|
+
const isGeneratedReference = (page: PageSnapshot): boolean =>
|
|
49
|
+
page.route?.source.name === "openapi";
|
|
50
|
+
|
|
51
|
+
const GENERATED_TITLE_FIX =
|
|
52
|
+
"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.";
|
|
53
|
+
|
|
54
|
+
const GENERATED_DESCRIPTION_FIX =
|
|
55
|
+
"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.";
|
|
56
|
+
|
|
41
57
|
const titleChecks = (
|
|
42
58
|
context: AuditContext,
|
|
43
59
|
page: PageSnapshot
|
|
@@ -74,7 +90,8 @@ const titleChecks = (
|
|
|
74
90
|
finding(
|
|
75
91
|
"BLUME_AUDIT_TITLE_LENGTH",
|
|
76
92
|
pageSite(context, page, ["title"]),
|
|
77
|
-
`Title renders ${width} columns wide — too ${direction} (aim for ${titleMin}–${titleMax})
|
|
93
|
+
`Title renders ${width} columns wide — too ${direction} (aim for ${titleMin}–${titleMax}).`,
|
|
94
|
+
isGeneratedReference(page) ? GENERATED_TITLE_FIX : undefined
|
|
78
95
|
)
|
|
79
96
|
);
|
|
80
97
|
}
|
|
@@ -123,7 +140,8 @@ const descriptionChecks = (
|
|
|
123
140
|
finding(
|
|
124
141
|
"BLUME_AUDIT_DESCRIPTION_LENGTH",
|
|
125
142
|
pageSite(context, page, ["description"]),
|
|
126
|
-
`Meta description renders ${width} columns wide — too ${direction} (aim for ${descriptionMin}–${descriptionMax})
|
|
143
|
+
`Meta description renders ${width} columns wide — too ${direction} (aim for ${descriptionMin}–${descriptionMax}).`,
|
|
144
|
+
isGeneratedReference(page) ? GENERATED_DESCRIPTION_FIX : undefined
|
|
127
145
|
)
|
|
128
146
|
);
|
|
129
147
|
}
|
package/src/audit/checks/i18n.ts
CHANGED
|
@@ -3,7 +3,8 @@ import type { Diagnostic } from "../../core/types.ts";
|
|
|
3
3
|
import { finding } from "../catalog.ts";
|
|
4
4
|
import { pageSite } from "../locate.ts";
|
|
5
5
|
import type { AuditContext, CheckModule, PageSnapshot } from "../types.ts";
|
|
6
|
-
import { normalizePath } from "../url.ts";
|
|
6
|
+
import { decodePath, normalizePath } from "../url.ts";
|
|
7
|
+
import { isArchivedLatestCanonical } from "./indexability.ts";
|
|
7
8
|
|
|
8
9
|
/** The normalized `deployment.base` — hreflang hrefs carry it, page URLs don't. */
|
|
9
10
|
const deployBaseOf = (context: AuditContext): string =>
|
|
@@ -21,15 +22,52 @@ const isValidBcp47 = (tag: string): boolean => {
|
|
|
21
22
|
}
|
|
22
23
|
};
|
|
23
24
|
|
|
24
|
-
/**
|
|
25
|
+
/**
|
|
26
|
+
* The site path an hreflang `href` points at, or null when it isn't parseable.
|
|
27
|
+
* Percent-decoded like every other URL the audit compares, since page URLs
|
|
28
|
+
* come from raw on-disk names (`/ガイド`) and `URL#pathname` is encoded.
|
|
29
|
+
*/
|
|
25
30
|
const alternatePath = (href: string, deployBase: string): string | null => {
|
|
26
31
|
try {
|
|
27
|
-
return normalizePath(
|
|
32
|
+
return normalizePath(
|
|
33
|
+
stripBasePath(deployBase, decodePath(new URL(href).pathname))
|
|
34
|
+
);
|
|
28
35
|
} catch {
|
|
29
36
|
return null;
|
|
30
37
|
}
|
|
31
38
|
};
|
|
32
39
|
|
|
40
|
+
/**
|
|
41
|
+
* An i18n fallback copy renders another locale's page at this locale's URL.
|
|
42
|
+
* Blume gives it hreflang links to the real translations, which by design
|
|
43
|
+
* never link back to it (it isn't one), and leaves it out of the sitemap —
|
|
44
|
+
* so the hreflang cluster checks don't apply to it.
|
|
45
|
+
*/
|
|
46
|
+
const isFallbackCopy = (page: PageSnapshot): boolean =>
|
|
47
|
+
page.route?.fallback === true;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Whether `target` names a URL other than its own as canonical — unless that
|
|
51
|
+
* is Blume's archived-version default pointing at the latest equivalent,
|
|
52
|
+
* which the canonical checks already accept.
|
|
53
|
+
*/
|
|
54
|
+
const canonicalizesElsewhere = (
|
|
55
|
+
context: AuditContext,
|
|
56
|
+
target: PageSnapshot,
|
|
57
|
+
deployBase: string
|
|
58
|
+
): boolean => {
|
|
59
|
+
if (!target.canonical) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
const canonical = alternatePath(target.canonical, deployBase);
|
|
63
|
+
if (canonical === normalizePath(target.url)) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
return !(
|
|
67
|
+
canonical !== null && isArchivedLatestCanonical(context, target, canonical)
|
|
68
|
+
);
|
|
69
|
+
};
|
|
70
|
+
|
|
33
71
|
const langChecks = (
|
|
34
72
|
context: AuditContext,
|
|
35
73
|
page: PageSnapshot
|
|
@@ -115,10 +153,7 @@ const hreflangChecks = (
|
|
|
115
153
|
`hreflang="${alternate.lang}" points at ${path}, which the build does not serve.`
|
|
116
154
|
)
|
|
117
155
|
);
|
|
118
|
-
} else if (
|
|
119
|
-
target.canonical &&
|
|
120
|
-
alternatePath(target.canonical, deployBase) !== normalizePath(target.url)
|
|
121
|
-
) {
|
|
156
|
+
} else if (canonicalizesElsewhere(context, target, deployBase)) {
|
|
122
157
|
found.push(
|
|
123
158
|
finding(
|
|
124
159
|
"BLUME_AUDIT_HREFLANG_BAD_TARGET",
|
|
@@ -190,6 +225,9 @@ const returnTagChecks = (context: AuditContext): Diagnostic[] => {
|
|
|
190
225
|
const found: Diagnostic[] = [];
|
|
191
226
|
const deployBase = deployBaseOf(context);
|
|
192
227
|
for (const page of context.pages) {
|
|
228
|
+
if (isFallbackCopy(page)) {
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
193
231
|
for (const alternate of page.hreflang) {
|
|
194
232
|
if (alternate.lang === X_DEFAULT) {
|
|
195
233
|
continue;
|
|
@@ -235,7 +273,7 @@ export const i18nChecks: CheckModule = {
|
|
|
235
273
|
const found: Diagnostic[] = [];
|
|
236
274
|
for (const page of context.pages) {
|
|
237
275
|
found.push(...langChecks(context, page));
|
|
238
|
-
if (page.hreflang.length > 0) {
|
|
276
|
+
if (page.hreflang.length > 0 && !isFallbackCopy(page)) {
|
|
239
277
|
found.push(...hreflangChecks(context, page));
|
|
240
278
|
}
|
|
241
279
|
}
|
|
@@ -16,6 +16,33 @@ const parseCanonical = (canonical: string): URL | null => {
|
|
|
16
16
|
}
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Whether `page` naming `target` as its canonical is Blume's own default for
|
|
21
|
+
* an archived-version page (`versions.archived[].canonical: "latest"`)
|
|
22
|
+
* pointing at its latest equivalent — the live page is the authoritative one
|
|
23
|
+
* by design, not a defect. `target` is a base-less, normalized site path.
|
|
24
|
+
*/
|
|
25
|
+
export const isArchivedLatestCanonical = (
|
|
26
|
+
context: AuditContext,
|
|
27
|
+
page: PageSnapshot,
|
|
28
|
+
target: string
|
|
29
|
+
): boolean => {
|
|
30
|
+
const version = page.route?.version;
|
|
31
|
+
const archived = version
|
|
32
|
+
? context.project.config.versions?.archived.find(
|
|
33
|
+
(entry) => entry.id === version
|
|
34
|
+
)
|
|
35
|
+
: undefined;
|
|
36
|
+
const latestAlternate = page.route?.versionAlternates.find(
|
|
37
|
+
(alternate) => alternate.version === ""
|
|
38
|
+
);
|
|
39
|
+
return (
|
|
40
|
+
archived?.canonical === "latest" &&
|
|
41
|
+
latestAlternate !== undefined &&
|
|
42
|
+
normalizePath(latestAlternate.path) === target
|
|
43
|
+
);
|
|
44
|
+
};
|
|
45
|
+
|
|
19
46
|
const canonicalChecks = (
|
|
20
47
|
context: AuditContext,
|
|
21
48
|
page: PageSnapshot
|
|
@@ -33,8 +60,10 @@ const canonicalChecks = (
|
|
|
33
60
|
if (!page.canonical) {
|
|
34
61
|
// Without `deployment.site` Blume has no absolute URL to canonicalize to, so
|
|
35
62
|
// report the root cause once (in the sitemap/robots checks) rather than
|
|
36
|
-
// flagging every page for a config field they can't fix individually.
|
|
37
|
-
|
|
63
|
+
// flagging every page for a config field they can't fix individually. A
|
|
64
|
+
// noindex page omits its canonical on purpose — the layouts drop it, since
|
|
65
|
+
// Google treats the pairing as contradictory (see CANONICAL_ON_NOINDEX).
|
|
66
|
+
return site && page.indexable
|
|
38
67
|
? [
|
|
39
68
|
finding(
|
|
40
69
|
"BLUME_AUDIT_CANONICAL_MISSING",
|
|
@@ -101,23 +130,14 @@ const canonicalChecks = (
|
|
|
101
130
|
)
|
|
102
131
|
);
|
|
103
132
|
} else if (context.byUrl.has(target)) {
|
|
104
|
-
// An
|
|
105
|
-
//
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
: undefined;
|
|
113
|
-
const latestAlternate = page.route?.versionAlternates.find(
|
|
114
|
-
(alternate) => alternate.version === ""
|
|
115
|
-
);
|
|
116
|
-
const intentional =
|
|
117
|
-
archived?.canonical === "latest" &&
|
|
118
|
-
latestAlternate !== undefined &&
|
|
119
|
-
normalizePath(latestAlternate.path) === target;
|
|
120
|
-
if (!intentional) {
|
|
133
|
+
// An i18n fallback copy canonicalizes to the page it copies on purpose,
|
|
134
|
+
// like an archived page to its latest equivalent.
|
|
135
|
+
if (
|
|
136
|
+
!(
|
|
137
|
+
page.route?.fallback === true ||
|
|
138
|
+
isArchivedLatestCanonical(context, page, target)
|
|
139
|
+
)
|
|
140
|
+
) {
|
|
121
141
|
found.push(
|
|
122
142
|
finding(
|
|
123
143
|
"BLUME_AUDIT_CANONICAL_NOT_SELF",
|
|
@@ -100,6 +100,20 @@ export const linkChecks: CheckModule = {
|
|
|
100
100
|
if (resolved.kind === "external" || resolved.kind === "ignored") {
|
|
101
101
|
return;
|
|
102
102
|
}
|
|
103
|
+
if (resolved.kind === "outside-base") {
|
|
104
|
+
if (link.content) {
|
|
105
|
+
found.push(
|
|
106
|
+
finding(
|
|
107
|
+
"BLUME_AUDIT_LINK_TO_BROKEN",
|
|
108
|
+
pageSite(context, page),
|
|
109
|
+
`Link to ${link.href} is missing deployment.base (${deployBase}), so the deployed site does not serve it.`
|
|
110
|
+
)
|
|
111
|
+
);
|
|
112
|
+
} else if (!brokenChrome.has(resolved.path)) {
|
|
113
|
+
brokenChrome.set(resolved.path, page);
|
|
114
|
+
}
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
103
117
|
|
|
104
118
|
const anchorTarget = resolved.hash
|
|
105
119
|
? context.byUrl.get(resolved.path)
|
package/src/audit/checks/llms.ts
CHANGED
|
@@ -113,15 +113,18 @@ export const llmsChecks: CheckModule = {
|
|
|
113
113
|
|
|
114
114
|
// The reverse direction, mirroring INDEXABLE_PAGE_NOT_IN_SITEMAP: a page
|
|
115
115
|
// that is built, indexable, and in the nav belongs in the index. Pages the
|
|
116
|
-
// generator deliberately skips — hidden, drafts,
|
|
117
|
-
//
|
|
118
|
-
//
|
|
116
|
+
// generator deliberately skips — hidden, drafts, `ai.exclude`, i18n
|
|
117
|
+
// fallback copies, error routes, API reference when
|
|
118
|
+
// `agents.llmsTxt.openapi` is off, custom pages with no manifest route —
|
|
119
|
+
// are skipped here for the same reasons.
|
|
119
120
|
for (const page of context.pages) {
|
|
120
121
|
const { route } = page;
|
|
121
122
|
if (
|
|
122
123
|
!route ||
|
|
123
124
|
route.hidden ||
|
|
124
125
|
route.draft ||
|
|
126
|
+
route.aiExclude ||
|
|
127
|
+
route.fallback ||
|
|
125
128
|
!page.indexable ||
|
|
126
129
|
ERROR_ROUTES.has(page.url) ||
|
|
127
130
|
(!openapi && route.source.name === "openapi") ||
|