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/core/diagnostics.ts
CHANGED
|
@@ -98,50 +98,237 @@ const isKeySegment = (segment: string | number): segment is string =>
|
|
|
98
98
|
typeof segment === "string";
|
|
99
99
|
|
|
100
100
|
/**
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
* or undefined when nothing matches.
|
|
101
|
+
* Where a located value's children are searched. `object` and `array` are the
|
|
102
|
+
* inside of a `{…}`/`[…]` literal, searched entry by entry so a key or an index
|
|
103
|
+
* only matches a direct child. `block` has no brackets — the whole file at the
|
|
104
|
+
* top, or a YAML key's indented lines — and is searched at any depth.
|
|
106
105
|
*/
|
|
107
|
-
|
|
106
|
+
interface Scope {
|
|
107
|
+
end: number;
|
|
108
|
+
/** A literal's direct entries (keys or elements), in source order. */
|
|
109
|
+
entries: number[];
|
|
110
|
+
kind: "array" | "block" | "object";
|
|
111
|
+
start: number;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** A located key or element: where it starts, and where its value starts. */
|
|
115
|
+
interface Step {
|
|
116
|
+
index: number;
|
|
117
|
+
next: number;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const QUOTES = new Set(['"', "'", "`"]);
|
|
121
|
+
const OPENERS = new Set(["(", "[", "{"]);
|
|
122
|
+
const CLOSERS = new Set([")", "]", "}"]);
|
|
123
|
+
const WHITESPACE = /\s/u;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* The index just past a comment starting at `index`, or `index` when none does.
|
|
127
|
+
* A `//` after a `:` is a URL scheme in YAML (`href: https://…`), not a comment.
|
|
128
|
+
*/
|
|
129
|
+
const skipComment = (source: string, index: number): number => {
|
|
130
|
+
if (source.startsWith("//", index) && source.charAt(index - 1) !== ":") {
|
|
131
|
+
const end = source.indexOf("\n", index);
|
|
132
|
+
return end === -1 ? source.length : end;
|
|
133
|
+
}
|
|
134
|
+
if (source.startsWith("/*", index)) {
|
|
135
|
+
const end = source.indexOf("*/", index + 2);
|
|
136
|
+
return end === -1 ? source.length : end + 2;
|
|
137
|
+
}
|
|
138
|
+
return index;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* The index just past a string literal starting at `index`, or `index` when
|
|
143
|
+
* none does. A `'`/`"` that doesn't close on its own line is an apostrophe in
|
|
144
|
+
* plain YAML text, not a string.
|
|
145
|
+
*/
|
|
146
|
+
const skipString = (source: string, index: number): number => {
|
|
147
|
+
const quote = source.charAt(index);
|
|
148
|
+
if (!QUOTES.has(quote)) {
|
|
149
|
+
return index;
|
|
150
|
+
}
|
|
151
|
+
let cursor = index + 1;
|
|
152
|
+
while (cursor < source.length) {
|
|
153
|
+
const char = source.charAt(cursor);
|
|
154
|
+
if (char === quote) {
|
|
155
|
+
return cursor + 1;
|
|
156
|
+
}
|
|
157
|
+
if (char === "\n" && quote !== "`") {
|
|
158
|
+
return index;
|
|
159
|
+
}
|
|
160
|
+
cursor += char === "\\" ? 2 : 1;
|
|
161
|
+
}
|
|
162
|
+
return index;
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
/** Where a literal closes, and where each of its direct entries starts. */
|
|
166
|
+
interface LiteralScan {
|
|
167
|
+
end: number;
|
|
168
|
+
entries: number[];
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Walk the `{…}`/`[…]` literal opened at `open`: where each direct entry starts
|
|
173
|
+
* (a key, or an array element) and where the literal closes. Nested brackets,
|
|
174
|
+
* strings, and comments are stepped over, so only the literal's own commas
|
|
175
|
+
* separate its entries.
|
|
176
|
+
*/
|
|
177
|
+
const scanLiteral = (source: string, open: number): LiteralScan => {
|
|
178
|
+
const entries: number[] = [];
|
|
179
|
+
let depth = 0;
|
|
180
|
+
let pending = true;
|
|
181
|
+
let index = open + 1;
|
|
182
|
+
while (index < source.length) {
|
|
183
|
+
const afterComment = skipComment(source, index);
|
|
184
|
+
const char = source.charAt(index);
|
|
185
|
+
if (afterComment !== index || WHITESPACE.test(char)) {
|
|
186
|
+
index = Math.max(afterComment, index + 1);
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (depth === 0 && CLOSERS.has(char)) {
|
|
190
|
+
return { end: index, entries };
|
|
191
|
+
}
|
|
192
|
+
if (depth === 0 && pending) {
|
|
193
|
+
entries.push(index);
|
|
194
|
+
pending = false;
|
|
195
|
+
}
|
|
196
|
+
const afterString = skipString(source, index);
|
|
197
|
+
if (afterString !== index) {
|
|
198
|
+
index = afterString;
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
if (OPENERS.has(char)) {
|
|
202
|
+
depth += 1;
|
|
203
|
+
} else if (CLOSERS.has(char)) {
|
|
204
|
+
depth -= 1;
|
|
205
|
+
} else if (char === "," && depth === 0) {
|
|
206
|
+
pending = true;
|
|
207
|
+
}
|
|
208
|
+
index += 1;
|
|
209
|
+
}
|
|
210
|
+
return { end: source.length, entries };
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* The lines indented deeper than the key at `anchor` — a YAML mapping's
|
|
215
|
+
* children — up to the first line that isn't, or `limit`.
|
|
216
|
+
*/
|
|
217
|
+
const indentedBlock = (
|
|
108
218
|
source: string,
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
) => {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
219
|
+
anchor: number,
|
|
220
|
+
limit: number
|
|
221
|
+
): Scope => {
|
|
222
|
+
const column = anchor - (source.lastIndexOf("\n", anchor - 1) + 1);
|
|
223
|
+
const lineEnd = source.indexOf("\n", anchor);
|
|
224
|
+
const start = lineEnd === -1 ? limit : Math.min(lineEnd + 1, limit);
|
|
225
|
+
let end = start;
|
|
226
|
+
while (end < limit) {
|
|
227
|
+
const next = source.indexOf("\n", end);
|
|
228
|
+
const after = next === -1 || next >= limit ? limit : next + 1;
|
|
229
|
+
const line = source.slice(end, after);
|
|
230
|
+
if (line.trim() !== "" && line.length - line.trimStart().length <= column) {
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
end = after;
|
|
115
234
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
235
|
+
return { end, entries: [], kind: "block", start };
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
/** A value's lead-in before its literal: spaces, or an adapter call's `vercel(`. */
|
|
239
|
+
const VALUE_LEAD = /[\s\w$.(]*/uy;
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* The scope holding the children of the key or element at `anchor`, whose
|
|
243
|
+
* value starts at `from`. A `{…}`/`[…]` literal — written directly, or as the
|
|
244
|
+
* first argument of a call like `vercel({…})` — is walked entry by entry;
|
|
245
|
+
* anything else falls back to the lines indented under `anchor` (YAML's
|
|
246
|
+
* nesting), within the enclosing scope's `limit`.
|
|
247
|
+
*/
|
|
248
|
+
const valueScope = (source: string, step: Step, limit: number): Scope => {
|
|
249
|
+
VALUE_LEAD.lastIndex = step.next;
|
|
250
|
+
VALUE_LEAD.exec(source);
|
|
251
|
+
const open = VALUE_LEAD.lastIndex;
|
|
252
|
+
const char = source.charAt(open);
|
|
253
|
+
if (open < limit && (char === "{" || char === "[")) {
|
|
254
|
+
const literal = scanLiteral(source, open);
|
|
255
|
+
return {
|
|
256
|
+
end: literal.end,
|
|
257
|
+
entries: literal.entries,
|
|
258
|
+
kind: char === "{" ? "object" : "array",
|
|
259
|
+
start: open,
|
|
260
|
+
};
|
|
126
261
|
}
|
|
127
|
-
return
|
|
262
|
+
return indentedBlock(source, step.index, limit);
|
|
128
263
|
};
|
|
129
264
|
|
|
265
|
+
/**
|
|
266
|
+
* Find `segment` as a `key:`/`key =` (bare or quoted) in `scope`: among an
|
|
267
|
+
* object literal's own entries, or anywhere in a block. The negative lookbehind
|
|
268
|
+
* keeps `title` from matching the tail of an unrelated key such as `subtitle:`.
|
|
269
|
+
*/
|
|
270
|
+
const findKey = (
|
|
271
|
+
source: string,
|
|
272
|
+
scope: Scope,
|
|
273
|
+
segment: string
|
|
274
|
+
): Step | undefined => {
|
|
275
|
+
const key = escapeRegExp(segment);
|
|
276
|
+
const pattern = `(?<![\\w$])(?:${key}|"${key}"|'${key}')\\s*[:=]`;
|
|
277
|
+
if (scope.kind === "block") {
|
|
278
|
+
const matcher = new RegExp(pattern, "gu");
|
|
279
|
+
matcher.lastIndex = scope.start;
|
|
280
|
+
const match = matcher.exec(source);
|
|
281
|
+
return match && match.index < scope.end
|
|
282
|
+
? { index: match.index, next: matcher.lastIndex }
|
|
283
|
+
: undefined;
|
|
284
|
+
}
|
|
285
|
+
// Sticky: the key must start exactly where the entry does.
|
|
286
|
+
const matcher = new RegExp(pattern, "uy");
|
|
287
|
+
for (const entry of scope.kind === "object" ? scope.entries : []) {
|
|
288
|
+
matcher.lastIndex = entry;
|
|
289
|
+
if (matcher.test(source)) {
|
|
290
|
+
return { index: entry, next: matcher.lastIndex };
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return undefined;
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
/** The element at `position` of an array literal; a block can't be indexed. */
|
|
297
|
+
const findElement = (scope: Scope, position: number): Step | undefined => {
|
|
298
|
+
const index = scope.kind === "array" ? scope.entries[position] : undefined;
|
|
299
|
+
return index === undefined ? undefined : { index, next: index };
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Best-effort source position for a Zod issue path (e.g. `["seo", "title"]`,
|
|
304
|
+
* `["redirects", 2, "status"]`) in the raw config / frontmatter text. Narrows
|
|
305
|
+
* segment by segment: a key is found among its parent's own entries (or, where
|
|
306
|
+
* there are no brackets, in the parent's indented lines), an index picks that
|
|
307
|
+
* element of an array literal. A segment that isn't there — a missing key —
|
|
308
|
+
* stops the walk at its parent, so the position never escapes into a sibling.
|
|
309
|
+
* Returns 1-based line/column, or undefined when not even the first segment
|
|
310
|
+
* matches.
|
|
311
|
+
*/
|
|
130
312
|
const locatePath = (
|
|
131
313
|
source: string,
|
|
132
314
|
path: readonly (string | number)[]
|
|
133
315
|
): { column: number; line: number } | undefined => {
|
|
134
|
-
let
|
|
316
|
+
let scope: Scope = {
|
|
317
|
+
end: source.length,
|
|
318
|
+
entries: [],
|
|
319
|
+
kind: "block",
|
|
320
|
+
start: 0,
|
|
321
|
+
};
|
|
135
322
|
let found = -1;
|
|
136
323
|
for (const segment of path) {
|
|
137
|
-
const step =
|
|
138
|
-
|
|
324
|
+
const step = isKeySegment(segment)
|
|
325
|
+
? findKey(source, scope, segment)
|
|
326
|
+
: findElement(scope, segment);
|
|
327
|
+
if (!step) {
|
|
139
328
|
break;
|
|
140
329
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
found = step.index;
|
|
144
|
-
}
|
|
330
|
+
found = step.index;
|
|
331
|
+
scope = valueScope(source, step, scope.end);
|
|
145
332
|
}
|
|
146
333
|
if (found < 0) {
|
|
147
334
|
return;
|
package/src/core/graph.ts
CHANGED
|
@@ -1,17 +1,22 @@
|
|
|
1
|
-
import { withBasePath } from "./base-path.ts";
|
|
1
|
+
import { mountBasePath, withBasePath } from "./base-path.ts";
|
|
2
2
|
import {
|
|
3
3
|
localizeInternalPath,
|
|
4
4
|
localizeRoute,
|
|
5
5
|
resolveFallbackLocale,
|
|
6
6
|
} from "./i18n.ts";
|
|
7
|
-
import {
|
|
8
|
-
|
|
7
|
+
import {
|
|
8
|
+
validateNavIcons,
|
|
9
|
+
validateNavStructure,
|
|
10
|
+
validateSidebarItems,
|
|
11
|
+
} from "./nav-diagnostics.ts";
|
|
12
|
+
import { buildNavigation, isPageRef, pagesByRef } from "./navigation.ts";
|
|
9
13
|
import type {
|
|
10
14
|
FolderMeta,
|
|
11
15
|
LocalizableLabel,
|
|
12
16
|
ResolvedConfig,
|
|
13
17
|
ResolvedI18nConfig,
|
|
14
18
|
ResolvedVersionsConfig,
|
|
19
|
+
SidebarItemConfig,
|
|
15
20
|
} from "./schema.ts";
|
|
16
21
|
import type {
|
|
17
22
|
ContentGraph,
|
|
@@ -23,6 +28,8 @@ import type {
|
|
|
23
28
|
import { versionizeRoute } from "./versions.ts";
|
|
24
29
|
|
|
25
30
|
interface BuildContentGraphOptions {
|
|
31
|
+
/** The banner's link target as configured (`banner.link.href`), if any. */
|
|
32
|
+
bannerHref?: string;
|
|
26
33
|
/** Site-wide route mount point (`""` or `/seg`); invisible to the nav tree. */
|
|
27
34
|
basePath?: string;
|
|
28
35
|
/** The header brand link as configured (`logo.href`, default `/`). */
|
|
@@ -83,7 +90,7 @@ const localePagesFor = (
|
|
|
83
90
|
...source,
|
|
84
91
|
fallback: true,
|
|
85
92
|
locale: code,
|
|
86
|
-
route:
|
|
93
|
+
route: mountBasePath(basePath, localizeRoute(key, code, i18n)),
|
|
87
94
|
});
|
|
88
95
|
}
|
|
89
96
|
}
|
|
@@ -115,6 +122,28 @@ const resolveLabel = (
|
|
|
115
122
|
);
|
|
116
123
|
};
|
|
117
124
|
|
|
125
|
+
/**
|
|
126
|
+
* An explicit sidebar with every item's `href` passed through `localize` (page
|
|
127
|
+
* refs and `root`s resolve against each locale's own pages already).
|
|
128
|
+
*/
|
|
129
|
+
const localizeSidebarHrefs = (
|
|
130
|
+
items: SidebarItemConfig[],
|
|
131
|
+
localize: (href: string) => string
|
|
132
|
+
): SidebarItemConfig[] =>
|
|
133
|
+
items.map((item) => {
|
|
134
|
+
if (isPageRef(item)) {
|
|
135
|
+
return item;
|
|
136
|
+
}
|
|
137
|
+
const localized = { ...item };
|
|
138
|
+
if (item.href !== undefined) {
|
|
139
|
+
localized.href = localize(item.href);
|
|
140
|
+
}
|
|
141
|
+
if (item.items) {
|
|
142
|
+
localized.items = localizeSidebarHrefs(item.items, localize);
|
|
143
|
+
}
|
|
144
|
+
return localized;
|
|
145
|
+
});
|
|
146
|
+
|
|
118
147
|
/** Resolve every localizable label in the configured tabs for one locale. */
|
|
119
148
|
const resolveTabLabels = (
|
|
120
149
|
tabs: BuildContentGraphOptions["navigation"]["tabs"],
|
|
@@ -130,6 +159,18 @@ const resolveTabLabels = (
|
|
|
130
159
|
label: resolveLabel(tab.label, locale, defaultLocale),
|
|
131
160
|
}));
|
|
132
161
|
|
|
162
|
+
/**
|
|
163
|
+
* Mount a configured link authored as if the site were served at root (the
|
|
164
|
+
* banner's): under `basePath`, except a route served outside the content tree
|
|
165
|
+
* — a custom page, the generated changelog index — which answers only at its
|
|
166
|
+
* own path.
|
|
167
|
+
*/
|
|
168
|
+
const mountConfiguredLink = (
|
|
169
|
+
path: string,
|
|
170
|
+
basePath: string,
|
|
171
|
+
extraRoutes: ReadonlySet<string>
|
|
172
|
+
): string => (extraRoutes.has(path) ? path : withBasePath(basePath, path));
|
|
173
|
+
|
|
133
174
|
/**
|
|
134
175
|
* Build one locale's navigation tree from its own pages and folder meta.
|
|
135
176
|
* `version` is the archived version id when building a snapshot's tree
|
|
@@ -217,8 +258,15 @@ const buildLocaleNavigation = (
|
|
|
217
258
|
// next to the originals and `guides/meta.ts` applies to every locale —
|
|
218
259
|
// prefixing would look up keys that can never exist. Inside a snapshot the
|
|
219
260
|
// version dir is hoisted in front (`v1.0/fr`), matching `discoverFolderMeta`.
|
|
220
|
-
const
|
|
221
|
-
i18n.parser === "dir" &&
|
|
261
|
+
const localeDirOf = (locale: string): string =>
|
|
262
|
+
i18n.parser === "dir" && locale !== i18n.defaultLocale ? locale : "";
|
|
263
|
+
const localeDir = localeDirOf(code);
|
|
264
|
+
// A locale padded from the fallback locale mirrors that locale's folder meta
|
|
265
|
+
// for any folder it has no meta of its own for.
|
|
266
|
+
const fallbackMetaPrefix =
|
|
267
|
+
fallback && code !== fallback
|
|
268
|
+
? [version, localeDirOf(fallback)].filter(Boolean).join("/")
|
|
269
|
+
: undefined;
|
|
222
270
|
// Internal featured and header hrefs are localized like tab paths — a pinned
|
|
223
271
|
// `/changelog` link rendered on `/fr/…` pages must stay inside the reader's
|
|
224
272
|
// locale, not kick them back to the default one.
|
|
@@ -227,6 +275,7 @@ const buildLocaleNavigation = (
|
|
|
227
275
|
href: localizeServed(item.href, false),
|
|
228
276
|
});
|
|
229
277
|
const { actions, cta, featured } = options.navigation;
|
|
278
|
+
const sidebarItems = options.navigation.sidebar.items;
|
|
230
279
|
|
|
231
280
|
const navigation = buildNavigation(localePages, {
|
|
232
281
|
actions: actions?.map(localizeHref),
|
|
@@ -235,6 +284,7 @@ const buildLocaleNavigation = (
|
|
|
235
284
|
diagnostics,
|
|
236
285
|
display: options.navigation.sidebar.display,
|
|
237
286
|
extraRoutes: options.extraRoutes,
|
|
287
|
+
fallbackMetaPrefix,
|
|
238
288
|
featured: featured?.map(localizeHref),
|
|
239
289
|
folderMeta: options.folderMeta,
|
|
240
290
|
// The localized tree root ("/" for the hidden default, "/fr" otherwise;
|
|
@@ -250,13 +300,43 @@ const buildLocaleNavigation = (
|
|
|
250
300
|
sharedMetaPrefix: version,
|
|
251
301
|
// A configured explicit sidebar describes the current docs; a frozen
|
|
252
302
|
// snapshot's structure comes from the snapshot itself, so archived trees
|
|
253
|
-
// always build from the filesystem.
|
|
254
|
-
|
|
303
|
+
// always build from the filesystem. Its `href` links are localized like
|
|
304
|
+
// featured links.
|
|
305
|
+
sidebar:
|
|
306
|
+
version || !sidebarItems
|
|
307
|
+
? undefined
|
|
308
|
+
: localizeSidebarHrefs(sidebarItems, (href) =>
|
|
309
|
+
localizeServed(href, false)
|
|
310
|
+
),
|
|
311
|
+
// Tab paths are written against the current docs' root, which a
|
|
312
|
+
// snapshot's versionized `localizedRoot` no longer is.
|
|
313
|
+
tabRoot: localizeRoute("/", code, i18n),
|
|
255
314
|
tabs,
|
|
256
315
|
});
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
316
|
+
// `serves` checked the localized brand link against based routes, and
|
|
317
|
+
// unlike tab and header links it isn't rebased later, so a localized one
|
|
318
|
+
// takes the base here (`/fr` is served at `/docs/fr`).
|
|
319
|
+
const localizedBrand = (href: string): string => {
|
|
320
|
+
const localized = localizeServed(href, false);
|
|
321
|
+
return localized === href ? localized : withBasePath(basePath, localized);
|
|
322
|
+
};
|
|
323
|
+
const branded =
|
|
324
|
+
options.brandHref === undefined
|
|
325
|
+
? navigation
|
|
326
|
+
: { ...navigation, brandHref: localizedBrand(options.brandHref) };
|
|
327
|
+
// The banner link is authored like a featured href — in the default
|
|
328
|
+
// locale's path space, as if mounted at root — so it is localized the same
|
|
329
|
+
// way before it is mounted.
|
|
330
|
+
return options.bannerHref === undefined
|
|
331
|
+
? branded
|
|
332
|
+
: {
|
|
333
|
+
...branded,
|
|
334
|
+
bannerHref: mountConfiguredLink(
|
|
335
|
+
localizeServed(options.bannerHref, false),
|
|
336
|
+
basePath,
|
|
337
|
+
extraRoutes
|
|
338
|
+
),
|
|
339
|
+
};
|
|
260
340
|
};
|
|
261
341
|
|
|
262
342
|
/**
|
|
@@ -348,6 +428,8 @@ const buildVersionNavigation = (
|
|
|
348
428
|
selectors: options.navigation.selectors,
|
|
349
429
|
sharedFolderMeta: options.sharedFolderMeta,
|
|
350
430
|
sharedMetaPrefix: id,
|
|
431
|
+
// Tab paths are written against the current docs' root.
|
|
432
|
+
tabRoot: "/",
|
|
351
433
|
// A configured explicit sidebar describes the current docs; a snapshot's
|
|
352
434
|
// structure comes from the snapshot itself.
|
|
353
435
|
tabs: resolveTabLabels(options.navigation.tabs, ""),
|
|
@@ -398,6 +480,16 @@ export const buildContentGraph = (
|
|
|
398
480
|
// entry on a single-locale site.
|
|
399
481
|
tabs: resolveTabLabels(options.navigation.tabs, ""),
|
|
400
482
|
});
|
|
483
|
+
if (options.bannerHref !== undefined) {
|
|
484
|
+
navigation = {
|
|
485
|
+
...navigation,
|
|
486
|
+
bannerHref: mountConfiguredLink(
|
|
487
|
+
options.bannerHref,
|
|
488
|
+
options.basePath ?? "",
|
|
489
|
+
options.extraRoutes ?? new Set<string>()
|
|
490
|
+
),
|
|
491
|
+
};
|
|
492
|
+
}
|
|
401
493
|
}
|
|
402
494
|
|
|
403
495
|
const navigationByVersion: Record<string, Record<string, Navigation>> = {};
|
|
@@ -417,6 +509,23 @@ export const buildContentGraph = (
|
|
|
417
509
|
...validateNavIcons(navigation),
|
|
418
510
|
...validateNavStructure(navigation, currentPages)
|
|
419
511
|
);
|
|
512
|
+
// Checked once against every locale's pages, not per locale tree: a page
|
|
513
|
+
// missing from one locale's sidebar is an untranslated page, not a typo.
|
|
514
|
+
const sidebarItems = options.navigation.sidebar.items;
|
|
515
|
+
if (sidebarItems) {
|
|
516
|
+
const byRef = pagesByRef(
|
|
517
|
+
currentPages,
|
|
518
|
+
options.basePath ?? "",
|
|
519
|
+
Boolean(i18n)
|
|
520
|
+
);
|
|
521
|
+
const extraRoutes = options.extraRoutes ?? new Set<string>();
|
|
522
|
+
diagnostics.push(
|
|
523
|
+
...validateSidebarItems(
|
|
524
|
+
sidebarItems,
|
|
525
|
+
(route) => byRef.has(route) || extraRoutes.has(route)
|
|
526
|
+
)
|
|
527
|
+
);
|
|
528
|
+
}
|
|
420
529
|
|
|
421
530
|
return {
|
|
422
531
|
diagnostics,
|
package/src/core/i18n-ui.ts
CHANGED
|
@@ -15,7 +15,6 @@ const uiStringsObject = z.object({
|
|
|
15
15
|
.object({
|
|
16
16
|
addToCursor: z.string().default("Add to Cursor"),
|
|
17
17
|
addToVscode: z.string().default("Add to VS Code"),
|
|
18
|
-
askAI: z.string().default("Ask AI about this page"),
|
|
19
18
|
connectMcp: z.string().default("Connect to MCP"),
|
|
20
19
|
copied: z.string().default("Copied!"),
|
|
21
20
|
copyClaudeCode: z.string().default("Copy Claude Code command"),
|
|
@@ -40,7 +39,7 @@ const uiStringsObject = z.object({
|
|
|
40
39
|
scrollToTop: z.string().default("Scroll to top"),
|
|
41
40
|
})
|
|
42
41
|
.prefault({}),
|
|
43
|
-
|
|
42
|
+
assistant: z
|
|
44
43
|
.object({
|
|
45
44
|
ai: z.string().default("AI"),
|
|
46
45
|
clear: z.string().default("Clear conversation"),
|
|
@@ -52,7 +51,7 @@ const uiStringsObject = z.object({
|
|
|
52
51
|
placeholder: z.string().default("Ask a question…"),
|
|
53
52
|
send: z.string().default("Send"),
|
|
54
53
|
tip: z.string().default("Tip: You can open and close chat with"),
|
|
55
|
-
title: z.string().default("
|
|
54
|
+
title: z.string().default("Assistant"),
|
|
56
55
|
you: z.string().default("You"),
|
|
57
56
|
})
|
|
58
57
|
.prefault({}),
|
|
@@ -73,11 +72,32 @@ const uiStringsObject = z.object({
|
|
|
73
72
|
.prefault({}),
|
|
74
73
|
content: z
|
|
75
74
|
.object({
|
|
75
|
+
// `<Component>`'s source tab.
|
|
76
|
+
code: z.string().default("Code"),
|
|
76
77
|
// A `<Color.Item>` swatch's accessible name; `{name}` and `{value}` are
|
|
77
78
|
// replaced with the color's name and the value it shows.
|
|
78
79
|
copyColor: z.string().default("Copy {name} color {value}"),
|
|
79
80
|
copyPrompt: z.string().default("Copy prompt"),
|
|
81
|
+
// `<TypeTable>`: a row's default-value label.
|
|
82
|
+
default: z.string().default("Default"),
|
|
80
83
|
diagramError: z.string().default("Could not render this diagram."),
|
|
84
|
+
// `<GithubInfo>`: the screen-reader labels of the fork and star counts.
|
|
85
|
+
forks: z.string().default("Forks"),
|
|
86
|
+
// `<Component>`'s live-preview tab, also its frame's title.
|
|
87
|
+
preview: z.string().default("Preview"),
|
|
88
|
+
// `<TypeTable>`: the property-name column.
|
|
89
|
+
prop: z.string().default("Prop"),
|
|
90
|
+
// `<Tabs dropdown>`: the accessible name of the tab picker.
|
|
91
|
+
selectTab: z.string().default("Select tab"),
|
|
92
|
+
// `<Expandable>`'s toggle when it sets no `title`.
|
|
93
|
+
showMore: z.string().default("Show more"),
|
|
94
|
+
stars: z.string().default("Stars"),
|
|
95
|
+
// An untitled tab's label; `{n}` is replaced with its position.
|
|
96
|
+
tab: z.string().default("Tab {n}"),
|
|
97
|
+
// `<TypeTable>`: the type column and a row's type label.
|
|
98
|
+
type: z.string().default("Type"),
|
|
99
|
+
// `<Update>`'s heading when it sets no `label` or `title`.
|
|
100
|
+
update: z.string().default("Update"),
|
|
81
101
|
})
|
|
82
102
|
.prefault({}),
|
|
83
103
|
feedback: z
|
|
@@ -142,14 +162,19 @@ const uiStringsObject = z.object({
|
|
|
142
162
|
all: z.string().default("All"),
|
|
143
163
|
allLanguages: z.string().default("All languages"),
|
|
144
164
|
allVersions: z.string().default("All versions"),
|
|
145
|
-
|
|
146
|
-
|
|
165
|
+
assistant: z.string().default("Assistant"),
|
|
166
|
+
assistantHint: z.string().default("Get an instant answer from AI"),
|
|
147
167
|
button: z.string().default("Search"),
|
|
148
168
|
devOnly: z
|
|
149
169
|
.string()
|
|
150
170
|
.default("Search is available in the production build."),
|
|
171
|
+
// The section a hit falls under when its page sits in no sidebar group.
|
|
172
|
+
docs: z.string().default("Docs"),
|
|
151
173
|
error: z.string().default("Something went wrong. Please try again."),
|
|
152
174
|
label: z.string().default("Search docs"),
|
|
175
|
+
// The version tag on a result from the current docs in an all-versions
|
|
176
|
+
// search (a result from an archived version shows its version id).
|
|
177
|
+
latest: z.string().default("latest"),
|
|
153
178
|
navigate: z.string().default("navigate"),
|
|
154
179
|
noResults: z.string().default("No results found."),
|
|
155
180
|
open: z.string().default("open"),
|
|
@@ -204,11 +229,38 @@ export const uiStringsOverrideSchema = z.record(
|
|
|
204
229
|
|
|
205
230
|
export type UIStringsOverride = z.infer<typeof uiStringsOverrideSchema>;
|
|
206
231
|
|
|
232
|
+
/**
|
|
233
|
+
* UI string keys renamed when Ask AI became the assistant, as `group` or
|
|
234
|
+
* `group.key`, each with its new name. Unknown keys merge harmlessly, so an
|
|
235
|
+
* override under an old name would otherwise be dropped without a word.
|
|
236
|
+
*/
|
|
237
|
+
const RENAMED_UI_KEYS = new Map([
|
|
238
|
+
["ask", "assistant"],
|
|
239
|
+
["search.askAi", "search.assistant"],
|
|
240
|
+
["search.askAiHint", "search.assistantHint"],
|
|
241
|
+
]);
|
|
242
|
+
|
|
207
243
|
/** Per-locale UI overrides supplied in `i18n.ui`. */
|
|
208
|
-
export const uiLocaleOverridesSchema = z
|
|
209
|
-
z.string(),
|
|
210
|
-
|
|
211
|
-
)
|
|
244
|
+
export const uiLocaleOverridesSchema = z
|
|
245
|
+
.record(z.string(), uiStringsOverrideSchema)
|
|
246
|
+
.superRefine((value, ctx) => {
|
|
247
|
+
for (const [locale, override] of Object.entries(value)) {
|
|
248
|
+
const keys = Object.entries(override).flatMap(([group, strings]) => [
|
|
249
|
+
group,
|
|
250
|
+
...Object.keys(strings).map((key) => `${group}.${key}`),
|
|
251
|
+
]);
|
|
252
|
+
for (const key of keys) {
|
|
253
|
+
const renamed = RENAMED_UI_KEYS.get(key);
|
|
254
|
+
if (renamed) {
|
|
255
|
+
ctx.addIssue({
|
|
256
|
+
code: z.ZodIssueCode.custom,
|
|
257
|
+
message: `i18n.ui.${locale}.${key} was renamed to i18n.ui.${locale}.${renamed}.`,
|
|
258
|
+
path: [locale, ...key.split(".")],
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
});
|
|
212
264
|
|
|
213
265
|
/** Merge an override's string leaves onto a base dictionary (two levels deep). */
|
|
214
266
|
const mergeUI = (base: UIStrings, override?: UIStringsOverride): UIStrings => {
|
|
@@ -255,11 +307,20 @@ const packFor = (code: string): UIStringsOverride | undefined => {
|
|
|
255
307
|
);
|
|
256
308
|
};
|
|
257
309
|
|
|
310
|
+
/** Whether a locale code is English (`en`, `en-GB`, `en_US`), in any case. */
|
|
311
|
+
const isEnglish = (code: string): boolean =>
|
|
312
|
+
code.toLowerCase().split(/[-_]/u)[0] === "en";
|
|
313
|
+
|
|
258
314
|
/**
|
|
259
315
|
* Resolve the active dictionary for a locale. Layers, in order:
|
|
260
316
|
* English baseline ← default-locale pack ← default-locale override ←
|
|
261
317
|
* locale pack ← locale override. So a missing key falls back to the default
|
|
262
318
|
* locale's translation, then to English.
|
|
319
|
+
*
|
|
320
|
+
* English has no pack because the baseline is its complete translation, so an
|
|
321
|
+
* English locale (`en`, `en-GB`) skips the default-locale layers: on a site
|
|
322
|
+
* whose default is German, English pages keep English chrome instead of
|
|
323
|
+
* inheriting the German strings.
|
|
263
324
|
*/
|
|
264
325
|
export const resolveUIStrings = (
|
|
265
326
|
locale: string,
|
|
@@ -270,8 +331,10 @@ export const resolveUIStrings = (
|
|
|
270
331
|
): UIStrings => {
|
|
271
332
|
const { defaultLocale, overrides } = options;
|
|
272
333
|
let dict = EN_UI;
|
|
273
|
-
|
|
274
|
-
|
|
334
|
+
if (!isEnglish(locale) || isEnglish(defaultLocale)) {
|
|
335
|
+
dict = mergeUI(dict, packFor(defaultLocale));
|
|
336
|
+
dict = mergeUI(dict, overrides?.[defaultLocale]);
|
|
337
|
+
}
|
|
275
338
|
if (locale !== defaultLocale) {
|
|
276
339
|
dict = mergeUI(dict, packFor(locale));
|
|
277
340
|
dict = mergeUI(dict, overrides?.[locale]);
|
package/src/core/i18n.ts
CHANGED
|
@@ -187,12 +187,19 @@ export const localePlacement = (
|
|
|
187
187
|
* (`x.en.mdx`) swaps it for the target's (`x.fr.mdx`) — same case-insensitive
|
|
188
188
|
* last-dot-inside-filename logic as `localePlacement`, so both spellings of a
|
|
189
189
|
* default-locale file resolve to one canonical target.
|
|
190
|
+
*
|
|
191
|
+
* Under `dir`, `folders` are the content root's existing top-level folders: one
|
|
192
|
+
* that names the locale in another casing (`pt-br/` for a configured `pt-BR`)
|
|
193
|
+
* is the locale's folder already, since {@link detectLocale} matches it case-
|
|
194
|
+
* insensitively. Writing beside it into a second `pt-BR/` would, on a case-
|
|
195
|
+
* sensitive filesystem, publish the page twice.
|
|
190
196
|
*/
|
|
191
197
|
export const localeTargetPath = (
|
|
192
198
|
rel: string,
|
|
193
199
|
ext: string,
|
|
194
200
|
locale: string,
|
|
195
|
-
i18n: ResolvedI18nConfig
|
|
201
|
+
i18n: ResolvedI18nConfig,
|
|
202
|
+
folders: readonly string[] = []
|
|
196
203
|
): string => {
|
|
197
204
|
if (i18n.parser === "dot") {
|
|
198
205
|
let base = rel.slice(0, rel.length - ext.length);
|
|
@@ -205,7 +212,11 @@ export const localeTargetPath = (
|
|
|
205
212
|
}
|
|
206
213
|
return `${base}.${locale}${ext}`;
|
|
207
214
|
}
|
|
208
|
-
|
|
215
|
+
const folder = folders.includes(locale)
|
|
216
|
+
? locale
|
|
217
|
+
: (folders.find((name) => name.toLowerCase() === locale.toLowerCase()) ??
|
|
218
|
+
locale);
|
|
219
|
+
return `${folder}/${rel}`;
|
|
209
220
|
};
|
|
210
221
|
|
|
211
222
|
/**
|