blume 2.0.1 → 2.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +147 -0
- package/dist/cli/{chunk-qwsrynx5.js → chunk-1d7ve1dm.js} +38 -19
- package/dist/cli/{chunk-qwsrynx5.js.map → chunk-1d7ve1dm.js.map} +4 -4
- package/dist/cli/{chunk-s6jhgk0q.js → chunk-2eytanqx.js} +2 -2
- package/dist/cli/{chunk-kdp5q7ke.js → chunk-2hsdwb9n.js} +18 -19
- package/dist/cli/{chunk-kdp5q7ke.js.map → chunk-2hsdwb9n.js.map} +2 -2
- package/dist/cli/{chunk-hdpx1tax.js → chunk-2z928egk.js} +5 -5
- package/dist/cli/{chunk-jwyddg7y.js → chunk-35d4wj9f.js} +30 -24
- package/dist/cli/chunk-35d4wj9f.js.map +15 -0
- package/dist/cli/{chunk-ah61y8py.js → chunk-364znk6q.js} +2 -2
- package/dist/cli/{chunk-jts8mvcz.js → chunk-3em5wd2y.js} +25 -6
- package/dist/cli/{chunk-jts8mvcz.js.map → chunk-3em5wd2y.js.map} +3 -3
- package/dist/cli/{chunk-s1p84fyh.js → chunk-5f86nr5m.js} +63 -22
- package/dist/cli/chunk-5f86nr5m.js.map +11 -0
- package/dist/cli/{chunk-2hn4b8z7.js → chunk-5m5nmvyq.js} +170 -76
- package/dist/cli/chunk-5m5nmvyq.js.map +13 -0
- package/dist/cli/{chunk-epjnccmv.js → chunk-5xvm6tfj.js} +18 -15
- package/dist/cli/chunk-5xvm6tfj.js.map +10 -0
- package/dist/cli/{chunk-fz5wtpmh.js → chunk-6dsbexzp.js} +18 -15
- package/dist/cli/chunk-6dsbexzp.js.map +10 -0
- package/dist/cli/{chunk-vtk4a6dg.js → chunk-8ktnccpt.js} +1 -1
- package/dist/cli/{chunk-6hsn950k.js → chunk-9t7a85s3.js} +178 -48
- package/dist/cli/chunk-9t7a85s3.js.map +10 -0
- package/dist/cli/{chunk-fxypxtvm.js → chunk-a58773jm.js} +2 -2
- package/dist/cli/{chunk-mb2919y2.js → chunk-acanzt5p.js} +17 -6
- package/dist/cli/chunk-acanzt5p.js.map +10 -0
- package/dist/cli/{chunk-27g6wdth.js → chunk-akbpwfxc.js} +90 -26
- package/dist/cli/chunk-akbpwfxc.js.map +10 -0
- package/dist/cli/{chunk-wm7js3j9.js → chunk-b07cmahc.js} +2 -2
- package/dist/cli/{chunk-wgm7m9qk.js → chunk-c8chx29p.js} +179 -51
- package/dist/cli/chunk-c8chx29p.js.map +36 -0
- package/dist/cli/{chunk-qs4q5p4e.js → chunk-crgn1q09.js} +32 -14
- package/dist/cli/chunk-crgn1q09.js.map +10 -0
- package/dist/cli/chunk-e04dxsz1.js +39 -0
- package/dist/cli/chunk-e04dxsz1.js.map +10 -0
- package/dist/cli/{chunk-yt5n7ppj.js → chunk-ey84smr6.js} +17 -7
- package/dist/cli/chunk-ey84smr6.js.map +10 -0
- package/dist/cli/{chunk-f2z5v128.js → chunk-g4hq16wv.js} +14 -15
- package/dist/cli/{chunk-f2z5v128.js.map → chunk-g4hq16wv.js.map} +2 -2
- package/dist/cli/{chunk-fa25z98p.js → chunk-ga0pf4aj.js} +9 -8
- package/dist/cli/chunk-ga0pf4aj.js.map +11 -0
- package/dist/cli/{chunk-kpf8rrjc.js → chunk-hm3vjy5s.js} +109 -58
- package/dist/cli/chunk-hm3vjy5s.js.map +19 -0
- package/dist/cli/{chunk-fs23ddbb.js → chunk-j85vccga.js} +625 -750
- package/dist/cli/chunk-j85vccga.js.map +36 -0
- package/dist/cli/{chunk-ch6g3ar0.js → chunk-p3v96n38.js} +6 -6
- package/dist/cli/{chunk-ch6g3ar0.js.map → chunk-p3v96n38.js.map} +3 -3
- package/dist/cli/{chunk-q5163e60.js → chunk-p73c0m7w.js} +21 -19
- package/dist/cli/chunk-p73c0m7w.js.map +11 -0
- package/dist/cli/{chunk-dh8cwk36.js → chunk-pehfxfta.js} +24 -9
- package/dist/cli/chunk-pehfxfta.js.map +10 -0
- package/dist/cli/{chunk-qkqwkpte.js → chunk-pv29h0wf.js} +3688 -1313
- package/dist/cli/chunk-pv29h0wf.js.map +190 -0
- package/dist/cli/{chunk-6vm74dry.js → chunk-q58y5e6a.js} +10 -10
- package/dist/cli/{chunk-6vm74dry.js.map → chunk-q58y5e6a.js.map} +3 -3
- package/dist/cli/{chunk-zxcczpyx.js → chunk-r20tn01b.js} +1 -1
- package/dist/cli/{chunk-m3vmjgmq.js → chunk-r9rcc4w7.js} +16 -8
- package/dist/cli/chunk-r9rcc4w7.js.map +10 -0
- package/dist/cli/{chunk-5shv93fd.js → chunk-tkacnehg.js} +2 -2
- package/dist/cli/{chunk-zxh4d9vy.js → chunk-tzmab476.js} +4 -4
- package/dist/cli/{chunk-yw7dm696.js → chunk-vg9r4eb9.js} +24 -11
- package/dist/cli/chunk-vg9r4eb9.js.map +14 -0
- package/dist/cli/{chunk-79jhk4py.js → chunk-wrr3j9w9.js} +260 -132
- package/dist/cli/chunk-wrr3j9w9.js.map +35 -0
- package/dist/cli/{chunk-6crbhc3x.js → chunk-yfyb25rh.js} +54 -27
- package/dist/cli/chunk-yfyb25rh.js.map +14 -0
- package/dist/cli/index.js +162 -35
- package/dist/cli/index.js.map +4 -4
- package/dist/types/ai/agent-surface.d.ts +32 -0
- package/dist/types/ai/api-catalog.d.ts +7 -1
- package/dist/types/ai/ask-context.d.ts +7 -0
- package/dist/types/ai/component-markdown.d.ts +4 -4
- package/dist/types/ai/link-headers.d.ts +8 -1
- package/dist/types/ai/openapi-components.d.ts +5 -2
- package/dist/types/ai/relative-links.d.ts +11 -4
- package/dist/types/ai/skills.d.ts +4 -1
- package/dist/types/ai/static-expression.d.ts +28 -0
- package/dist/types/ai/tar.d.ts +1 -3
- package/dist/types/analytics/databuddy.d.ts +43 -0
- package/dist/types/analytics/index.d.ts +4 -0
- package/dist/types/analytics/one-dollar-stats.d.ts +48 -0
- package/dist/types/analytics/schema.d.ts +28 -0
- package/dist/types/astro/integration.d.ts +3 -2
- 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 +24 -1
- package/dist/types/core/config-input.d.ts +13 -11
- package/dist/types/core/config.d.ts +2 -2
- package/dist/types/core/directive-diagnostics.d.ts +12 -0
- package/dist/types/core/graph.d.ts +2 -0
- package/dist/types/core/heading-markers.d.ts +5 -7
- package/dist/types/core/i18n-ui.d.ts +31 -0
- package/dist/types/core/i18n.d.ts +9 -1
- package/dist/types/core/last-modified.d.ts +10 -0
- 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 +13 -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 +59 -13
- package/dist/types/core/sources/github-releases.d.ts +5 -0
- package/dist/types/core/sources/lower.d.ts +38 -13
- package/dist/types/core/sources/normalize.d.ts +23 -2
- 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/sources/watch.d.ts +12 -5
- package/dist/types/core/standard-schema.d.ts +5 -0
- package/dist/types/core/types.d.ts +9 -0
- package/dist/types/deploy/adapters/node.d.ts +5 -2
- package/dist/types/deploy/adapters/types.d.ts +7 -0
- package/dist/types/deploy/artifacts.d.ts +6 -4
- package/dist/types/deploy/cloudflare-negotiation.d.ts +3 -2
- package/dist/types/deploy/headers.d.ts +36 -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 +15 -0
- package/dist/types/deploy/platforms/vercel.d.ts +3 -2
- package/dist/types/deploy/redirects.d.ts +31 -15
- package/dist/types/deploy/vercel-negotiation.d.ts +3 -2
- package/dist/types/markdown/directives.d.ts +62 -0
- package/dist/types/markdown/features.d.ts +21 -0
- package/dist/types/markdown/mdast.d.ts +63 -0
- package/dist/types/openapi/asyncapi.d.ts +4 -2
- package/dist/types/openapi/model.d.ts +20 -6
- package/dist/types/search/sync/algolia.d.ts +3 -1
- package/docs/01-quickstart.mdx +3 -2
- package/docs/02-deployment.mdx +20 -9
- package/docs/08-faq.mdx +10 -3
- package/docs/advanced/changelog.mdx +1 -1
- package/docs/advanced/custom-pages.mdx +7 -5
- package/docs/cli/audit.mdx +19 -3
- package/docs/cli/doctor.mdx +2 -2
- package/docs/cli/evals.mdx +4 -4
- package/docs/cli/index.mdx +4 -1
- package/docs/cli/translate.mdx +4 -4
- package/docs/cli/version.mdx +1 -1
- package/docs/configuration/analytics.mdx +42 -2
- package/docs/configuration/assistant.mdx +1 -1
- package/docs/configuration/customization.mdx +6 -3
- package/docs/configuration/index.mdx +5 -3
- package/docs/configuration/search.mdx +2 -2
- package/docs/content/components.mdx +2 -2
- package/docs/content/frontmatter.mdx +5 -1
- package/docs/content/i18n.mdx +3 -1
- package/docs/content/index.mdx +8 -4
- package/docs/content/islands.mdx +1 -1
- package/docs/content/meta.mdx +7 -3
- package/docs/content/navigation.mdx +29 -4
- package/docs/content/sources.mdx +18 -16
- package/docs/content/syntax.mdx +14 -0
- package/docs/content/versioning.mdx +2 -1
- package/docs/discoverability/agent-discovery.mdx +22 -8
- package/docs/discoverability/index.mdx +4 -3
- package/docs/discoverability/llms-txt.mdx +2 -5
- package/docs/discoverability/markdown.mdx +6 -4
- package/docs/discoverability/mcp.mdx +4 -0
- package/docs/discoverability/metadata.mdx +3 -2
- package/docs/discoverability/open-graph.mdx +8 -4
- package/docs/discoverability/rss.mdx +4 -2
- package/docs/references/asyncapi.mdx +2 -2
- package/docs/references/graphql.mdx +2 -2
- package/docs/references/openapi.mdx +6 -4
- package/package.json +1 -1
- package/skills/blume-migrate/SKILL.md +6 -6
- package/skills/blume-migrate/references/docusaurus.md +6 -6
- package/skills/blume-migrate/references/fumadocs.md +1 -1
- 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 +2 -2
- package/skills/blume-migrate/references/starlight.md +5 -5
- package/skills/blume-migrate/scripts/mintlify-codemod.mjs +9 -4
- package/skills/blume-update-docs/references/audit-checklist.md +1 -1
- package/src/ai/agent-readability.ts +2 -2
- package/src/ai/agent-surface.ts +56 -0
- package/src/ai/ai-catalog.ts +6 -2
- package/src/ai/api/handlers.ts +2 -2
- package/src/ai/api/spec.ts +17 -4
- package/src/ai/api-catalog.ts +13 -3
- package/src/ai/ask-context.ts +14 -2
- package/src/ai/ask-data.ts +26 -12
- package/src/ai/changelog-markdown.ts +2 -2
- package/src/ai/component-markdown.ts +43 -37
- package/src/ai/link-headers.ts +22 -8
- package/src/ai/llms.ts +22 -10
- package/src/ai/markdown.ts +23 -11
- package/src/ai/mcp/discovery.ts +2 -2
- package/src/ai/mcp/query.ts +2 -2
- package/src/ai/mcp/server.ts +80 -9
- package/src/ai/openapi-components.ts +22 -6
- package/src/ai/relative-links.ts +99 -15
- package/src/ai/serializers.ts +2 -1
- package/src/ai/skills.ts +14 -1
- package/src/ai/static-expression.ts +416 -0
- package/src/ai/tar.ts +139 -12
- package/src/ai/visibility.ts +45 -14
- package/src/analytics/databuddy.ts +67 -0
- package/src/analytics/head.ts +8 -0
- package/src/analytics/index.ts +7 -0
- package/src/analytics/one-dollar-stats.ts +86 -0
- package/src/analytics/posthog.ts +21 -3
- package/src/analytics/schema.ts +4 -0
- package/src/astro/generate.ts +72 -28
- package/src/astro/include-hmr.ts +31 -12
- package/src/astro/include-refresh.ts +19 -8
- package/src/astro/integration.ts +108 -14
- package/src/astro/runtime-modules.ts +28 -13
- package/src/astro/templates.ts +139 -57
- 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 +22 -1
- 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/graph.ts +3 -1
- package/src/audit/report.ts +54 -17
- package/src/audit/run.ts +13 -9
- package/src/audit/snapshot.ts +5 -0
- package/src/audit/types.ts +18 -0
- package/src/audit/url.ts +37 -6
- package/src/cli/args.ts +32 -0
- package/src/cli/build-failure.ts +50 -0
- package/src/cli/commands/audit.ts +11 -1
- package/src/cli/commands/build.ts +19 -5
- package/src/cli/commands/check.ts +2 -0
- package/src/cli/commands/doctor.ts +3 -1
- package/src/cli/commands/eval.ts +20 -18
- package/src/cli/commands/init.ts +15 -5
- package/src/cli/commands/preview.ts +15 -0
- package/src/cli/commands/sync.ts +2 -0
- package/src/cli/commands/translate.ts +18 -18
- package/src/cli/commands/upgrade.ts +11 -0
- package/src/cli/commands/validate.ts +3 -1
- package/src/cli/dev-lock.ts +157 -37
- 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/report-format.ts +11 -6
- package/src/components/colors.ts +19 -0
- package/src/components/content/AccordionItem.astro +26 -22
- package/src/components/content/Badge.astro +7 -12
- package/src/components/content/Card.astro +2 -2
- package/src/components/content/Component.astro +11 -5
- 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/Tab.astro +0 -1
- package/src/components/content/Tabs.astro +58 -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 +20 -0
- package/src/components/content/base-href.ts +14 -22
- package/src/components/content/inline-markdown.ts +27 -7
- package/src/components/copy-feedback.ts +35 -8
- package/src/components/islands/assistant.tsx +37 -6
- package/src/components/islands/base-path.ts +47 -10
- package/src/components/islands/hooks.ts +42 -21
- package/src/components/islands/webmcp.ts +16 -10
- package/src/components/layout/Banner.astro +23 -4
- package/src/components/layout/Breadcrumbs.astro +5 -2
- package/src/components/layout/DiscoveryLinks.astro +9 -5
- package/src/components/layout/Header.astro +23 -10
- package/src/components/layout/LanguageSwitcher.astro +2 -2
- package/src/components/layout/Logo.astro +5 -0
- package/src/components/layout/NavSelector.astro +8 -3
- package/src/components/layout/NavTabMenu.astro +133 -0
- package/src/components/layout/NavTree.astro +31 -12
- package/src/components/layout/NavTreeCache.astro +5 -2
- package/src/components/layout/NavTreeScript.astro +45 -5
- package/src/components/layout/PageActions.astro +4 -3
- package/src/components/layout/PageLayout.astro +28 -13
- package/src/components/layout/Pagination.astro +3 -3
- package/src/components/layout/ReferenceLayout.astro +4 -1
- package/src/components/layout/RootLayout.astro +25 -12
- package/src/components/layout/Search.astro +38 -14
- package/src/components/layout/VersionBanner.astro +2 -2
- package/src/components/layout/analytics-client.ts +14 -0
- package/src/components/layout/toc-active.ts +41 -0
- package/src/components/layout/toc-element.ts +8 -14
- package/src/components/openapi/ApiTagOperations.astro +2 -2
- package/src/components/openapi/AsyncApiOperation.astro +7 -4
- package/src/components/openapi/GraphqlChip.astro +2 -2
- package/src/components/openapi/GraphqlType.astro +11 -3
- package/src/components/openapi/MessageComposer.astro +1 -1
- package/src/components/openapi/Operation.astro +23 -5
- package/src/components/openapi/PanelTabs.astro +4 -1
- package/src/components/openapi/Playground.astro +8 -2
- package/src/components/openapi/RequestPanel.astro +13 -6
- package/src/components/openapi/SchemaProperty.astro +11 -33
- package/src/components/openapi/SchemaTable.astro +34 -52
- package/src/components/openapi/async.ts +38 -6
- package/src/components/openapi/helpers.ts +100 -12
- package/src/components/openapi/message-composer.ts +14 -1
- package/src/components/openapi/message-model.ts +12 -2
- package/src/components/openapi/message.ts +21 -3
- package/src/components/openapi/operation-model.ts +119 -17
- package/src/components/openapi/panel.ts +31 -5
- package/src/components/openapi/param-style.ts +181 -0
- package/src/components/openapi/playground-client.ts +110 -23
- package/src/components/openapi/playground-schema.ts +25 -6
- package/src/components/openapi/request.ts +192 -26
- package/src/components/openapi/schema-tree.ts +209 -0
- package/src/components/openapi/snippets.ts +113 -19
- package/src/components/openapi/validate-json.ts +1 -1
- package/src/components/openapi/ws-client.ts +18 -2
- package/src/core/base-path.ts +69 -8
- package/src/core/config-input.ts +13 -11
- package/src/core/config.ts +18 -4
- package/src/core/diagnostics.ts +219 -30
- package/src/core/directive-diagnostics.ts +99 -0
- package/src/core/frontmatter.ts +21 -18
- package/src/core/graph.ts +120 -11
- package/src/core/heading-markers.ts +5 -18
- package/src/core/i18n-ui.ts +39 -2
- package/src/core/i18n.ts +44 -5
- package/src/core/last-modified.ts +25 -3
- package/src/core/links.ts +7 -4
- package/src/core/load-module.ts +20 -0
- package/src/core/locale-links.ts +22 -18
- package/src/core/manifest.ts +3 -2
- package/src/core/meta.ts +89 -12
- package/src/core/nav-diagnostics.ts +56 -1
- package/src/core/navigation.ts +272 -83
- package/src/core/ordering-prefix.ts +27 -0
- package/src/core/project-graph.ts +50 -6
- package/src/core/safe-href.ts +53 -1
- package/src/core/safe-links.ts +11 -2
- package/src/core/schema.ts +94 -18
- package/src/core/sources/assets.ts +83 -41
- package/src/core/sources/contentful-rich-text.ts +28 -20
- package/src/core/sources/contentful.ts +25 -12
- package/src/core/sources/filesystem.ts +25 -3
- package/src/core/sources/github-releases.ts +131 -11
- package/src/core/sources/lexical.ts +30 -20
- package/src/core/sources/lower.ts +276 -56
- package/src/core/sources/mdx-remote.ts +52 -16
- package/src/core/sources/normalize.ts +456 -119
- package/src/core/sources/notion.ts +77 -34
- package/src/core/sources/obsidian.ts +40 -9
- package/src/core/sources/payload.ts +1 -0
- package/src/core/sources/portable-text.ts +85 -49
- 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 +28 -19
- package/src/core/sources/strapi.ts +1 -0
- package/src/core/sources/types.ts +31 -0
- package/src/core/sources/watch.ts +20 -7
- package/src/core/standard-schema.ts +10 -6
- package/src/core/types.ts +9 -0
- package/src/core/ui-packs/ar.ts +13 -0
- package/src/core/ui-packs/bg.ts +13 -0
- package/src/core/ui-packs/bn.ts +13 -0
- package/src/core/ui-packs/ca.ts +13 -0
- package/src/core/ui-packs/cs.ts +13 -0
- package/src/core/ui-packs/da.ts +13 -0
- package/src/core/ui-packs/de.ts +13 -0
- package/src/core/ui-packs/el.ts +13 -0
- package/src/core/ui-packs/es.ts +13 -0
- package/src/core/ui-packs/fa.ts +13 -0
- package/src/core/ui-packs/fi.ts +13 -0
- package/src/core/ui-packs/fr.ts +13 -0
- package/src/core/ui-packs/he.ts +13 -0
- package/src/core/ui-packs/hi.ts +13 -0
- package/src/core/ui-packs/hr.ts +13 -0
- package/src/core/ui-packs/hu.ts +13 -0
- package/src/core/ui-packs/id.ts +13 -0
- package/src/core/ui-packs/it.ts +13 -0
- package/src/core/ui-packs/ja.ts +13 -0
- package/src/core/ui-packs/ko.ts +13 -0
- package/src/core/ui-packs/nl.ts +13 -0
- package/src/core/ui-packs/no.ts +13 -0
- package/src/core/ui-packs/pl.ts +13 -0
- package/src/core/ui-packs/pt-br.ts +13 -0
- package/src/core/ui-packs/pt.ts +13 -0
- package/src/core/ui-packs/ro.ts +13 -0
- package/src/core/ui-packs/ru.ts +13 -0
- package/src/core/ui-packs/sk.ts +13 -0
- package/src/core/ui-packs/sr.ts +13 -0
- package/src/core/ui-packs/sv.ts +13 -0
- package/src/core/ui-packs/th.ts +13 -0
- package/src/core/ui-packs/tr.ts +13 -0
- package/src/core/ui-packs/uk.ts +13 -0
- package/src/core/ui-packs/vi.ts +13 -0
- package/src/core/ui-packs/zh-tw.ts +13 -0
- package/src/core/ui-packs/zh.ts +13 -0
- package/src/core/version-cut.ts +69 -17
- 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 +52 -11
- package/src/deploy/cloudflare-negotiation.ts +27 -16
- package/src/deploy/headers.ts +75 -55
- package/src/deploy/node-headers.ts +148 -27
- package/src/deploy/platforms/cloudflare.ts +13 -3
- package/src/deploy/platforms/netlify.ts +83 -5
- package/src/deploy/platforms/node.ts +8 -5
- package/src/deploy/platforms/static.ts +2 -0
- package/src/deploy/platforms/types.ts +15 -0
- package/src/deploy/platforms/vercel.ts +10 -3
- package/src/deploy/redirects.ts +68 -21
- package/src/deploy/robots.ts +2 -2
- package/src/deploy/rss.ts +4 -3
- package/src/deploy/sitemap.ts +7 -5
- 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 +74 -29
- package/src/markdown/directives.ts +242 -36
- package/src/markdown/features.ts +17 -0
- package/src/markdown/index.ts +13 -9
- package/src/markdown/mdast.ts +5 -2
- package/src/markdown/relative-links.ts +15 -27
- package/src/markdown/route-snapshot.ts +37 -0
- package/src/og/card.ts +149 -9
- package/src/og/derive.ts +156 -5
- package/src/og/index.ts +1 -0
- package/src/openapi/asyncapi.ts +4 -2
- package/src/openapi/graphql-build.ts +28 -2
- package/src/openapi/model.ts +99 -27
- package/src/openapi/proxy.ts +63 -10
- package/src/openapi/render-mdx.ts +51 -3
- package/src/registry/eject.ts +297 -43
- package/src/search/adapters/version-scope.ts +30 -0
- package/src/search/documents.ts +89 -27
- package/src/search/popular.ts +2 -1
- package/src/search/sync/algolia.ts +36 -2
- package/src/seo/jsonld.ts +7 -3
- 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 +76 -24
- package/src/translate/run.ts +14 -8
- package/src/translate/validate.ts +14 -2
- package/src/translate/work-list.ts +91 -19
- package/src/upgrade/upgrade.ts +36 -4
- package/dist/cli/chunk-27g6wdth.js.map +0 -10
- package/dist/cli/chunk-2hn4b8z7.js.map +0 -12
- package/dist/cli/chunk-6crbhc3x.js.map +0 -14
- package/dist/cli/chunk-6hsn950k.js.map +0 -10
- package/dist/cli/chunk-79jhk4py.js.map +0 -35
- package/dist/cli/chunk-82bbrxdn.js +0 -51
- package/dist/cli/chunk-82bbrxdn.js.map +0 -10
- package/dist/cli/chunk-abh8yjkn.js +0 -31
- package/dist/cli/chunk-abh8yjkn.js.map +0 -10
- package/dist/cli/chunk-ce574jw2.js +0 -23
- package/dist/cli/chunk-ce574jw2.js.map +0 -10
- package/dist/cli/chunk-dh8cwk36.js.map +0 -10
- package/dist/cli/chunk-epjnccmv.js.map +0 -10
- package/dist/cli/chunk-fa25z98p.js.map +0 -11
- package/dist/cli/chunk-fs23ddbb.js.map +0 -35
- package/dist/cli/chunk-fz5wtpmh.js.map +0 -10
- package/dist/cli/chunk-jwyddg7y.js.map +0 -15
- package/dist/cli/chunk-kpf8rrjc.js.map +0 -19
- package/dist/cli/chunk-m3vmjgmq.js.map +0 -10
- package/dist/cli/chunk-mb2919y2.js.map +0 -10
- package/dist/cli/chunk-q5163e60.js.map +0 -11
- package/dist/cli/chunk-qkqwkpte.js.map +0 -182
- package/dist/cli/chunk-qs4q5p4e.js.map +0 -10
- package/dist/cli/chunk-s1p84fyh.js.map +0 -10
- package/dist/cli/chunk-wgm7m9qk.js.map +0 -36
- package/dist/cli/chunk-yt5n7ppj.js.map +0 -10
- package/dist/cli/chunk-yw7dm696.js.map +0 -14
- /package/dist/cli/{chunk-s6jhgk0q.js.map → chunk-2eytanqx.js.map} +0 -0
- /package/dist/cli/{chunk-hdpx1tax.js.map → chunk-2z928egk.js.map} +0 -0
- /package/dist/cli/{chunk-ah61y8py.js.map → chunk-364znk6q.js.map} +0 -0
- /package/dist/cli/{chunk-vtk4a6dg.js.map → chunk-8ktnccpt.js.map} +0 -0
- /package/dist/cli/{chunk-fxypxtvm.js.map → chunk-a58773jm.js.map} +0 -0
- /package/dist/cli/{chunk-wm7js3j9.js.map → chunk-b07cmahc.js.map} +0 -0
- /package/dist/cli/{chunk-zxcczpyx.js.map → chunk-r20tn01b.js.map} +0 -0
- /package/dist/cli/{chunk-5shv93fd.js.map → chunk-tkacnehg.js.map} +0 -0
- /package/dist/cli/{chunk-zxh4d9vy.js.map → chunk-tzmab476.js.map} +0 -0
|
@@ -55,17 +55,12 @@ export interface HeadingMarkers {
|
|
|
55
55
|
* the heading count (mid-text brackets are ordinary prose); when a marker kind
|
|
56
56
|
* repeats, the last one written wins.
|
|
57
57
|
*
|
|
58
|
-
*
|
|
59
|
-
* bracket
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* The render-time hast plugin never passes it (resolved links are already
|
|
63
|
-
* elements by the time it runs); the raw-source scanner does.
|
|
58
|
+
* Callers pass the heading's trailing text node after a Markdown parse, so a
|
|
59
|
+
* bracket that a link-reference definition turns into a CommonMark shortcut
|
|
60
|
+
* link (`[toc]` with a `[toc]: …` definition) is already an element there and
|
|
61
|
+
* never reaches this parse.
|
|
64
62
|
*/
|
|
65
|
-
export const parseHeadingMarkers = (
|
|
66
|
-
text: string,
|
|
67
|
-
isRefDefined?: (label: string) => boolean
|
|
68
|
-
): HeadingMarkers => {
|
|
63
|
+
export const parseHeadingMarkers = (text: string): HeadingMarkers => {
|
|
69
64
|
let remaining = text;
|
|
70
65
|
let id: string | undefined;
|
|
71
66
|
let toc: HeadingMarkers["toc"];
|
|
@@ -75,14 +70,6 @@ export const parseHeadingMarkers = (
|
|
|
75
70
|
match = MARKER.exec(remaining)
|
|
76
71
|
) {
|
|
77
72
|
const explicitId = match.groups.curlyId ?? match.groups.id;
|
|
78
|
-
const label =
|
|
79
|
-
explicitId === undefined
|
|
80
|
-
? `${match.groups.hide ?? ""}toc`
|
|
81
|
-
: `#${explicitId}`;
|
|
82
|
-
// A `{#id}` is never a shortcut link, whatever the definitions say.
|
|
83
|
-
if (match.groups.curlyId === undefined && isRefDefined?.(label)) {
|
|
84
|
-
break;
|
|
85
|
-
}
|
|
86
73
|
remaining = remaining.slice(0, match.index);
|
|
87
74
|
if (explicitId === undefined) {
|
|
88
75
|
// Stripping runs right-to-left, so keeping the first capture of each
|
package/src/core/i18n-ui.ts
CHANGED
|
@@ -72,11 +72,32 @@ const uiStringsObject = z.object({
|
|
|
72
72
|
.prefault({}),
|
|
73
73
|
content: z
|
|
74
74
|
.object({
|
|
75
|
+
// `<Component>`'s source tab.
|
|
76
|
+
code: z.string().default("Code"),
|
|
75
77
|
// A `<Color.Item>` swatch's accessible name; `{name}` and `{value}` are
|
|
76
78
|
// replaced with the color's name and the value it shows.
|
|
77
79
|
copyColor: z.string().default("Copy {name} color {value}"),
|
|
78
80
|
copyPrompt: z.string().default("Copy prompt"),
|
|
81
|
+
// `<TypeTable>`: a row's default-value label.
|
|
82
|
+
default: z.string().default("Default"),
|
|
79
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"),
|
|
80
101
|
})
|
|
81
102
|
.prefault({}),
|
|
82
103
|
feedback: z
|
|
@@ -147,8 +168,13 @@ const uiStringsObject = z.object({
|
|
|
147
168
|
devOnly: z
|
|
148
169
|
.string()
|
|
149
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"),
|
|
150
173
|
error: z.string().default("Something went wrong. Please try again."),
|
|
151
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"),
|
|
152
178
|
navigate: z.string().default("navigate"),
|
|
153
179
|
noResults: z.string().default("No results found."),
|
|
154
180
|
open: z.string().default("open"),
|
|
@@ -281,11 +307,20 @@ const packFor = (code: string): UIStringsOverride | undefined => {
|
|
|
281
307
|
);
|
|
282
308
|
};
|
|
283
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
|
+
|
|
284
314
|
/**
|
|
285
315
|
* Resolve the active dictionary for a locale. Layers, in order:
|
|
286
316
|
* English baseline ← default-locale pack ← default-locale override ←
|
|
287
317
|
* locale pack ← locale override. So a missing key falls back to the default
|
|
288
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.
|
|
289
324
|
*/
|
|
290
325
|
export const resolveUIStrings = (
|
|
291
326
|
locale: string,
|
|
@@ -296,8 +331,10 @@ export const resolveUIStrings = (
|
|
|
296
331
|
): UIStrings => {
|
|
297
332
|
const { defaultLocale, overrides } = options;
|
|
298
333
|
let dict = EN_UI;
|
|
299
|
-
|
|
300
|
-
|
|
334
|
+
if (!isEnglish(locale) || isEnglish(defaultLocale)) {
|
|
335
|
+
dict = mergeUI(dict, packFor(defaultLocale));
|
|
336
|
+
dict = mergeUI(dict, overrides?.[defaultLocale]);
|
|
337
|
+
}
|
|
301
338
|
if (locale !== defaultLocale) {
|
|
302
339
|
dict = mergeUI(dict, packFor(locale));
|
|
303
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,14 +212,38 @@ 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
|
|
|
222
|
+
/**
|
|
223
|
+
* The warning for a top-level folder named after the default locale
|
|
224
|
+
* (`docs/en/` when `defaultLocale` is `en`). Only non-default codes are
|
|
225
|
+
* locale folders — the default locale lives at the content root — so the
|
|
226
|
+
* folder is ordinary default-locale content, published one level down at
|
|
227
|
+
* `/en/…` (`/en/en/…` with the default prefix shown) and filled into every
|
|
228
|
+
* other locale at `/fr/en/…`.
|
|
229
|
+
*/
|
|
230
|
+
const defaultLocaleFolderDiagnostic = (
|
|
231
|
+
folder: string,
|
|
232
|
+
i18n: ResolvedI18nConfig
|
|
233
|
+
): Diagnostic => ({
|
|
234
|
+
code: "BLUME_I18N_DEFAULT_LOCALE_FOLDER",
|
|
235
|
+
message: `Folder "${folder}/" is named after the default locale, but "${i18n.defaultLocale}" pages live at the content root — its pages publish at ${localizeRoute(`/${folder}`, i18n.defaultLocale, i18n)}/… instead.`,
|
|
236
|
+
severity: "warning",
|
|
237
|
+
suggestion: `Move the contents of "${folder}/" up to the content root; only the other locales get a folder.`,
|
|
238
|
+
});
|
|
239
|
+
|
|
211
240
|
/**
|
|
212
241
|
* Warn about top-level content folders that look like a locale (a code Blume
|
|
213
242
|
* recognizes) but aren't declared in `i18n.locales`. Without this they're
|
|
214
243
|
* silently treated as default-locale content under a `/<code>/…` route, which
|
|
215
244
|
* is almost never intended — usually a translation that wasn't registered.
|
|
245
|
+
* A folder named after the default locale gets the same treatment (see
|
|
246
|
+
* {@link defaultLocaleFolderDiagnostic}).
|
|
216
247
|
*/
|
|
217
248
|
export const i18nDiagnostics = (
|
|
218
249
|
pages: PageRecord[],
|
|
@@ -231,9 +262,17 @@ export const i18nDiagnostics = (
|
|
|
231
262
|
// Inside a version snapshot the locale dir sits one level deeper
|
|
232
263
|
// (`v1.0/fr/guide.md`), so a configured version segment is skipped first.
|
|
233
264
|
const parts = page.source.ref.split("/");
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
265
|
+
const folder = parts[0] && versionDirs.has(parts[0]) ? parts[1] : parts[0];
|
|
266
|
+
const first = folder?.toLowerCase();
|
|
267
|
+
if (
|
|
268
|
+
folder !== undefined &&
|
|
269
|
+
first === i18n.defaultLocale.toLowerCase() &&
|
|
270
|
+
!seen.has(first)
|
|
271
|
+
) {
|
|
272
|
+
seen.add(first);
|
|
273
|
+
diagnostics.push(defaultLocaleFolderDiagnostic(folder, i18n));
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
237
276
|
if (
|
|
238
277
|
first &&
|
|
239
278
|
!seen.has(first) &&
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { realpathSync } from "node:fs";
|
|
2
3
|
|
|
3
|
-
import { relative } from "pathe";
|
|
4
|
+
import { normalize, relative } from "pathe";
|
|
4
5
|
|
|
5
6
|
import type { Diagnostic } from "./types.ts";
|
|
6
7
|
|
|
@@ -58,6 +59,23 @@ export const parseGitLog = (output: string): Map<string, string> => {
|
|
|
58
59
|
return times;
|
|
59
60
|
};
|
|
60
61
|
|
|
62
|
+
/**
|
|
63
|
+
* `path` with every symlink resolved, the spelling git prints paths in: a
|
|
64
|
+
* project opened through a link (`/tmp/site` on macOS, where `/tmp` links to
|
|
65
|
+
* `/private/tmp`) gets `/private/tmp/site` from `rev-parse --show-toplevel`,
|
|
66
|
+
* so the project's own spelling would compare as outside the repository and
|
|
67
|
+
* every page would go undated. The native realpath also expands a Windows
|
|
68
|
+
* 8.3 short name (`RUNNER~1`), which git does too. A path that doesn't exist
|
|
69
|
+
* stays as written.
|
|
70
|
+
*/
|
|
71
|
+
export const realPath = (path: string): string => {
|
|
72
|
+
try {
|
|
73
|
+
return normalize(realpathSync.native(path));
|
|
74
|
+
} catch {
|
|
75
|
+
return path;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
61
79
|
/**
|
|
62
80
|
* The toplevel of the repository containing `root`, or null when git is
|
|
63
81
|
* unavailable or the project isn't a repo. Callers use it to decide which
|
|
@@ -108,6 +126,10 @@ export const gitLastModifiedTimes = (
|
|
|
108
126
|
if (gitRoot === null) {
|
|
109
127
|
return new Map();
|
|
110
128
|
}
|
|
129
|
+
// Git names paths by their real location, so the pathspecs and the paths
|
|
130
|
+
// read back against the log are compared in that spelling too (see
|
|
131
|
+
// `realPath`).
|
|
132
|
+
const top = realPath(gitRoot);
|
|
111
133
|
try {
|
|
112
134
|
const output = execFileSync(
|
|
113
135
|
// oxlint-disable-next-line sonarjs/no-os-command-from-path -- git is a required dev-tool dependency resolved from PATH
|
|
@@ -121,14 +143,14 @@ export const gitLastModifiedTimes = (
|
|
|
121
143
|
"--format=%x00%cI",
|
|
122
144
|
"--name-only",
|
|
123
145
|
"--",
|
|
124
|
-
...contentRoots,
|
|
146
|
+
...contentRoots.map(realPath),
|
|
125
147
|
],
|
|
126
148
|
{ encoding: "utf-8", env: gitEnv(), maxBuffer: 256 * 1024 * 1024 }
|
|
127
149
|
);
|
|
128
150
|
const byRepoPath = parseGitLog(output);
|
|
129
151
|
const result = new Map<string, string>();
|
|
130
152
|
for (const sourcePath of sourcePaths) {
|
|
131
|
-
const iso = byRepoPath.get(relative(
|
|
153
|
+
const iso = byRepoPath.get(relative(top, realPath(sourcePath)));
|
|
132
154
|
if (iso) {
|
|
133
155
|
result.set(sourcePath, iso);
|
|
134
156
|
}
|
package/src/core/links.ts
CHANGED
|
@@ -109,10 +109,11 @@ const NUMERIC_PREFIX = /^\d+[-_.]/u;
|
|
|
109
109
|
* falsely flagged as broken. Tested against `navPath` — the locale-stripped
|
|
110
110
|
* path — so a dot-parser localized index (`index.fr.mdx`) and a shared
|
|
111
111
|
* locale-agnostic one (`index.$.mdx`) count too, matching how route mapping
|
|
112
|
-
* recognizes them.
|
|
112
|
+
* recognizes them. Only a lowercase `index` counts, as in route mapping:
|
|
113
|
+
* `Index.md` publishes at `…/Index`, a page of its own.
|
|
113
114
|
*/
|
|
114
115
|
const isIndexPage = (page: PageRecord): boolean =>
|
|
115
|
-
/^index\.
|
|
116
|
+
/^index\.[Mm][Dd][Xx]?$/u.test(
|
|
116
117
|
basename(page.navPath).replace(NUMERIC_PREFIX, "")
|
|
117
118
|
);
|
|
118
119
|
|
|
@@ -218,14 +219,16 @@ export const resolveRelativeHref = (
|
|
|
218
219
|
* Whether a content file is its folder's index, from its name as written on
|
|
219
220
|
* disk: `index.md(x)` after an ordering prefix, optionally carrying one of
|
|
220
221
|
* `localeTokens` before the extension (`index.fr.mdx` under the `dot` locale
|
|
221
|
-
* parser, `index.$.mdx` for a file shared by every locale).
|
|
222
|
+
* parser, `index.$.mdx` for a file shared by every locale). The `index` itself
|
|
223
|
+
* is case-sensitive, as route mapping reads it: `Index.md` is a page named
|
|
224
|
+
* `Index`, not its folder's index.
|
|
222
225
|
*/
|
|
223
226
|
export const isIndexFileName = (
|
|
224
227
|
name: string,
|
|
225
228
|
localeTokens: readonly string[] = []
|
|
226
229
|
): boolean => {
|
|
227
230
|
const stem = basename(name).replace(NUMERIC_PREFIX, "");
|
|
228
|
-
const match = /^index(?:\.(?<token>[^./]+))?\.
|
|
231
|
+
const match = /^index(?:\.(?<token>[^./]+))?\.[Mm][Dd][Xx]?$/u.exec(stem);
|
|
229
232
|
if (!match) {
|
|
230
233
|
return false;
|
|
231
234
|
}
|
package/src/core/load-module.ts
CHANGED
|
@@ -14,3 +14,23 @@ export const createModuleLoader = (): ((file: string) => Promise<unknown>) => {
|
|
|
14
14
|
return loaded?.default ?? loaded;
|
|
15
15
|
};
|
|
16
16
|
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Create a loader for a module whose default export is its whole value
|
|
20
|
+
* (`blume.config.ts`). Unlike {@link createModuleLoader}, it never falls back
|
|
21
|
+
* to the module's namespace: a module with no default export, or one that
|
|
22
|
+
* exports `null`/`undefined`, resolves to `undefined` so the caller can say
|
|
23
|
+
* so, instead of validating `{}` (a bare `defineConfig({…})` call) or
|
|
24
|
+
* `{ config }` (a named export). jiti's default interop is off because it
|
|
25
|
+
* throws on `export default null`.
|
|
26
|
+
*/
|
|
27
|
+
export const createDefaultExportLoader: typeof createModuleLoader = () => {
|
|
28
|
+
const jiti = createJiti(import.meta.url, {
|
|
29
|
+
interopDefault: false,
|
|
30
|
+
moduleCache: false,
|
|
31
|
+
});
|
|
32
|
+
return async (file: string) => {
|
|
33
|
+
const loaded = await jiti.import<{ default?: unknown }>(file);
|
|
34
|
+
return loaded.default ?? undefined;
|
|
35
|
+
};
|
|
36
|
+
};
|
package/src/core/locale-links.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
isInternalPath,
|
|
3
|
+
mountBasePath,
|
|
3
4
|
normalizePath,
|
|
4
5
|
stripBasePath,
|
|
5
6
|
withBasePath,
|
|
@@ -44,13 +45,6 @@ export interface LocalizeLinkOptions {
|
|
|
44
45
|
routes: RouteSet;
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
/**
|
|
48
|
-
* Mirrors the asset heuristic in `markdown/base-links.ts`: a path whose final
|
|
49
|
-
* segment carries a file extension is a `public/` asset (or a raw `.md` twin),
|
|
50
|
-
* served at the site root and never localized.
|
|
51
|
-
*/
|
|
52
|
-
const ASSET_PATH = /\.[a-z0-9]+$/iu;
|
|
53
|
-
|
|
54
48
|
/** Decode a percent-encoded path for a route lookup; leave junk as-is. */
|
|
55
49
|
const decodePercent = (value: string): string => {
|
|
56
50
|
try {
|
|
@@ -60,6 +54,13 @@ const decodePercent = (value: string): string => {
|
|
|
60
54
|
}
|
|
61
55
|
};
|
|
62
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Whether `routes` serves `route` (base-prefixed, fragment-less). Routes are
|
|
59
|
+
* stored decoded, so a browser-copied `/caf%C3%A9` still finds its page.
|
|
60
|
+
*/
|
|
61
|
+
export const servesRoute = (routes: RouteSet, route: string): boolean =>
|
|
62
|
+
routes.has(route) || routes.has(decodePercent(route));
|
|
63
|
+
|
|
63
64
|
/** Whether `path` (base-stripped) already sits under some locale's prefix. */
|
|
64
65
|
const hasLocalePrefix = (path: string, i18n: LocaleRouting): boolean =>
|
|
65
66
|
i18n.locales.some((locale) => {
|
|
@@ -85,11 +86,9 @@ export const localizeLinkPath = (
|
|
|
85
86
|
return path;
|
|
86
87
|
}
|
|
87
88
|
const localized = withBasePath(basePath, localizeRoute(rest, locale, i18n));
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
return routes
|
|
91
|
-
? localized
|
|
92
|
-
: path;
|
|
89
|
+
// A browser-copied `/caf%C3%A9` must still find its translation, but the
|
|
90
|
+
// emitted href keeps the author's encoding.
|
|
91
|
+
return servesRoute(routes, localized) ? localized : path;
|
|
93
92
|
};
|
|
94
93
|
|
|
95
94
|
export interface LocalizeHrefOptions extends LocalizeLinkOptions {
|
|
@@ -98,9 +97,14 @@ export interface LocalizeHrefOptions extends LocalizeLinkOptions {
|
|
|
98
97
|
}
|
|
99
98
|
|
|
100
99
|
/**
|
|
101
|
-
* Localize a rendered `href`: external URLs, relative paths, bare
|
|
102
|
-
*
|
|
100
|
+
* Localize a rendered `href`: external URLs, relative paths, and bare
|
|
101
|
+
* fragments pass through; a root-relative page link keeps its `?query` and
|
|
103
102
|
* `#fragment` and its `deployment.base` layer around the localized path.
|
|
103
|
+
*
|
|
104
|
+
* Asset links (`/logo.png`, a raw `.md` twin) pass through too, because no
|
|
105
|
+
* page is served at their localized path — the same served-route test the
|
|
106
|
+
* link checker applies. A file extension alone doesn't mark an asset: a
|
|
107
|
+
* dotted page route (`/releases/v1.2`) is localized like any other page.
|
|
104
108
|
*/
|
|
105
109
|
export const localizeHref = (
|
|
106
110
|
href: string,
|
|
@@ -112,15 +116,15 @@ export const localizeHref = (
|
|
|
112
116
|
const suffixAt = href.search(/[#?]/u);
|
|
113
117
|
const path = suffixAt === -1 ? href : href.slice(0, suffixAt);
|
|
114
118
|
const suffix = suffixAt === -1 ? "" : href.slice(suffixAt);
|
|
115
|
-
if (ASSET_PATH.test(path)) {
|
|
116
|
-
return href;
|
|
117
|
-
}
|
|
118
119
|
const based = stripBasePath(options.deployBase, path);
|
|
119
120
|
const localized = localizeLinkPath(based, options);
|
|
120
121
|
if (localized === based) {
|
|
121
122
|
return href;
|
|
122
123
|
}
|
|
123
|
-
|
|
124
|
+
// The base came off above, so it goes back on unconditionally: a localized
|
|
125
|
+
// path that starts with the base's own name (`/ja/…` under base `/ja`) is
|
|
126
|
+
// still served under it.
|
|
127
|
+
return `${mountBasePath(options.deployBase, localized)}${suffix}`;
|
|
124
128
|
};
|
|
125
129
|
|
|
126
130
|
/** Every `<a …>` opening tag; `\s` keeps `<abbr>`/`<astro-island>` out. */
|
package/src/core/manifest.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mountBasePath } from "./base-path.ts";
|
|
2
2
|
import { localizeRoute, resolveFallbackLocale } from "./i18n.ts";
|
|
3
3
|
import type { ResolvedConfig } from "./schema.ts";
|
|
4
4
|
import type {
|
|
@@ -106,7 +106,7 @@ const buildFallbackRoutes = (
|
|
|
106
106
|
if (present.has(key)) {
|
|
107
107
|
continue;
|
|
108
108
|
}
|
|
109
|
-
const path =
|
|
109
|
+
const path = mountBasePath(basePath, localizeRoute(key, code, i18n));
|
|
110
110
|
// A fallback route is a real prerendered page, so it registers as a
|
|
111
111
|
// version alternate too — the switcher on a sibling version's page
|
|
112
112
|
// lands here instead of bouncing to the version root. Its own path is
|
|
@@ -183,6 +183,7 @@ export const buildManifest = (options: {
|
|
|
183
183
|
}
|
|
184
184
|
|
|
185
185
|
const routes: RouteManifestEntry[] = graph.pages.map((page) => ({
|
|
186
|
+
aiExclude: page.meta.ai.exclude,
|
|
186
187
|
alternates: alternatesByKey.get(page.translationKey) ?? [],
|
|
187
188
|
collection: page.collection ?? "docs",
|
|
188
189
|
contentType: page.contentType,
|
package/src/core/meta.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { basename, dirname, relative } from "pathe";
|
|
2
|
-
import { glob } from "tinyglobby";
|
|
2
|
+
import { glob, isDynamicPattern } from "tinyglobby";
|
|
3
3
|
|
|
4
4
|
import { diagnosticsFromZod } from "./diagnostics.ts";
|
|
5
5
|
import { createModuleLoader } from "./load-module.ts";
|
|
@@ -38,12 +38,89 @@ const resolveMeta = async (mod: MetaModuleExport) =>
|
|
|
38
38
|
|
|
39
39
|
/** A filesystem content source to scan for folder meta: its on-disk root and
|
|
40
40
|
* optional route prefix. The prefix is folded into every key so meta lines up
|
|
41
|
-
* with the sidebar group path, which carries the same prefix.
|
|
41
|
+
* with the sidebar group path, which carries the same prefix. `include` and
|
|
42
|
+
* `exclude` are the source's content globs, which scope the scan to the
|
|
43
|
+
* folders the source reads. */
|
|
42
44
|
export interface FolderMetaSource {
|
|
43
45
|
root: string;
|
|
44
46
|
prefix?: string;
|
|
47
|
+
include?: readonly string[];
|
|
48
|
+
exclude?: readonly string[];
|
|
45
49
|
}
|
|
46
50
|
|
|
51
|
+
/**
|
|
52
|
+
* The literal directory a glob starts from (`docs/**\/*.md` → `docs`, `""`
|
|
53
|
+
* when it matches from the root): its leading segments up to the first one
|
|
54
|
+
* with glob syntax, the file-name segment excluded.
|
|
55
|
+
*/
|
|
56
|
+
const globBase = (pattern: string): string => {
|
|
57
|
+
const literal: string[] = [];
|
|
58
|
+
for (const segment of pattern.split("/").slice(0, -1)) {
|
|
59
|
+
if (isDynamicPattern(segment)) {
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
if (segment !== "" && segment !== ".") {
|
|
63
|
+
literal.push(segment);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return literal.join("/");
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/** Whether `dir` is `base`, or nested beneath or above it (`""` is the root). */
|
|
70
|
+
const onPathOf = (dir: string, base: string): boolean =>
|
|
71
|
+
dir === base ||
|
|
72
|
+
base === "" ||
|
|
73
|
+
dir === "" ||
|
|
74
|
+
dir.startsWith(`${base}/`) ||
|
|
75
|
+
base.startsWith(`${dir}/`);
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Whether a meta file's directory can configure a sidebar group of the
|
|
79
|
+
* source: it lies on the path of some `include` glob's base — the folders
|
|
80
|
+
* that glob reads, or the ancestors whose groups hold them. Negated globs
|
|
81
|
+
* only narrow what a source reads, so they never qualify a folder.
|
|
82
|
+
*/
|
|
83
|
+
const withinInclude = (
|
|
84
|
+
dir: string,
|
|
85
|
+
include: readonly string[] | undefined
|
|
86
|
+
): boolean =>
|
|
87
|
+
include === undefined ||
|
|
88
|
+
include.some(
|
|
89
|
+
(pattern) => !pattern.startsWith("!") && onPathOf(dir, globBase(pattern))
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The folder-meta files a filesystem source contributes: `patterns` (every
|
|
94
|
+
* meta module name, shared `meta.$.*` included, by default) under the source's
|
|
95
|
+
* root, minus its `exclude` globs, dependencies, and build output, and only in
|
|
96
|
+
* folders its `include` globs reach. `blume translate` finds the meta files it
|
|
97
|
+
* translates through this too, so it covers exactly the ones the scan reads.
|
|
98
|
+
*/
|
|
99
|
+
export const findFolderMetaFiles = async (
|
|
100
|
+
source: FolderMetaSource,
|
|
101
|
+
patterns: readonly string[] = META_FILES
|
|
102
|
+
): Promise<string[]> => {
|
|
103
|
+
const found = await glob([...patterns], {
|
|
104
|
+
absolute: true,
|
|
105
|
+
cwd: source.root,
|
|
106
|
+
// Never descend into dependencies or build output — relevant when the
|
|
107
|
+
// root is the project root (e.g. a `.`-rooted or all-staged project).
|
|
108
|
+
// The source's own `exclude` applies too: a `meta.ts` under an excluded
|
|
109
|
+
// folder (`src/lib/meta.ts` beside `exclude: ["src/**"]`) is application
|
|
110
|
+
// code, not folder meta, and importing it would fail.
|
|
111
|
+
ignore: [
|
|
112
|
+
...(source.exclude ?? []),
|
|
113
|
+
"**/node_modules/**",
|
|
114
|
+
"**/.blume/**",
|
|
115
|
+
"**/dist/**",
|
|
116
|
+
],
|
|
117
|
+
onlyFiles: true,
|
|
118
|
+
});
|
|
119
|
+
return found.filter((file) =>
|
|
120
|
+
withinInclude(relative(source.root, dirname(file)), source.include)
|
|
121
|
+
);
|
|
122
|
+
};
|
|
123
|
+
|
|
47
124
|
/**
|
|
48
125
|
* The folder-meta key for a directory. Mirrors the sidebar group path: the
|
|
49
126
|
* source's route prefix (`docs`) followed by the directory relative to the
|
|
@@ -93,7 +170,12 @@ export const discoverFolderMeta = async (
|
|
|
93
170
|
const list: FolderMetaSource[] = Array.isArray(sources)
|
|
94
171
|
? sources
|
|
95
172
|
: [{ root: sources }];
|
|
96
|
-
|
|
173
|
+
// Locale folders match case-insensitively, like content does (`pt-br/` for
|
|
174
|
+
// a configured `pt-BR`), and key under the configured casing, which is what
|
|
175
|
+
// navigation looks meta up by.
|
|
176
|
+
const localeDirs = new Map(
|
|
177
|
+
(options.localeDirs ?? []).map((code) => [code.toLowerCase(), code])
|
|
178
|
+
);
|
|
97
179
|
const versionDirs = new Set(options.versionDirs);
|
|
98
180
|
|
|
99
181
|
const load = createModuleLoader();
|
|
@@ -105,14 +187,7 @@ export const discoverFolderMeta = async (
|
|
|
105
187
|
// `content.root` still contributes its folder meta.
|
|
106
188
|
const perSource = await Promise.all(
|
|
107
189
|
list.map(async (source) => {
|
|
108
|
-
const files = await
|
|
109
|
-
absolute: true,
|
|
110
|
-
cwd: source.root,
|
|
111
|
-
// Never descend into dependencies or build output — relevant when the
|
|
112
|
-
// root is the project root (e.g. a `.`-rooted or all-staged project).
|
|
113
|
-
ignore: ["**/node_modules/**", "**/.blume/**", "**/dist/**"],
|
|
114
|
-
onlyFiles: true,
|
|
115
|
-
});
|
|
190
|
+
const files = await findFolderMetaFiles(source);
|
|
116
191
|
const loaded = await Promise.all(
|
|
117
192
|
files.map(
|
|
118
193
|
async (
|
|
@@ -149,7 +224,9 @@ export const discoverFolderMeta = async (
|
|
|
149
224
|
const version = head && versionDirs.has(head) ? head : "";
|
|
150
225
|
const afterVersion = version ? tail : [head ?? "", ...tail];
|
|
151
226
|
const [localeHead, ...localeTail] = afterVersion;
|
|
152
|
-
const locale = localeHead
|
|
227
|
+
const locale = localeHead
|
|
228
|
+
? (localeDirs.get(localeHead.toLowerCase()) ?? "")
|
|
229
|
+
: "";
|
|
153
230
|
const rest = (locale ? localeTail : afterVersion)
|
|
154
231
|
.filter(Boolean)
|
|
155
232
|
.join("/");
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { isAssetIcon } from "../theme/icon-kind.ts";
|
|
2
2
|
import { hasIcon } from "../theme/icons.ts";
|
|
3
3
|
import { isInternalPath } from "./base-path.ts";
|
|
4
|
+
import { isPageRef, normalizeRef } from "./navigation.ts";
|
|
5
|
+
import type { SidebarItemConfig } from "./schema.ts";
|
|
4
6
|
import type { Diagnostic, NavNode, Navigation, PageRecord } from "./types.ts";
|
|
5
7
|
|
|
6
8
|
/**
|
|
@@ -137,7 +139,9 @@ export const validateNavTargets = (
|
|
|
137
139
|
if (!isInternalPath(path) || path.startsWith("/#") || seen.has(path)) {
|
|
138
140
|
continue;
|
|
139
141
|
}
|
|
140
|
-
|
|
142
|
+
// A query or fragment (`/guides?tab=cli`) rides on the route; only the
|
|
143
|
+
// path names a page.
|
|
144
|
+
if (!resolvesToPages(routes, path.split(/[?#]/u)[0] ?? path)) {
|
|
141
145
|
seen.add(path);
|
|
142
146
|
diagnostics.push({
|
|
143
147
|
code: "BLUME_NAV_MISSING_PAGE",
|
|
@@ -150,6 +154,57 @@ export const validateNavTargets = (
|
|
|
150
154
|
return diagnostics;
|
|
151
155
|
};
|
|
152
156
|
|
|
157
|
+
/**
|
|
158
|
+
* Warn about explicit `navigation.sidebar` items that can't render as written,
|
|
159
|
+
* instead of dropping them without a word: a page ref that matches no page
|
|
160
|
+
* (left out of the sidebar), a `root` that matches no page (its link leads
|
|
161
|
+
* nowhere), and an item with no page, `href`, `root`, or `items` (left out).
|
|
162
|
+
* `resolves` answers whether a normalized ref (`/guides`) names a page — in
|
|
163
|
+
* any locale — or a route served outside the content tree.
|
|
164
|
+
*/
|
|
165
|
+
export const validateSidebarItems = (
|
|
166
|
+
items: SidebarItemConfig[],
|
|
167
|
+
resolves: (route: string) => boolean
|
|
168
|
+
): Diagnostic[] => {
|
|
169
|
+
const diagnostics: Diagnostic[] = [];
|
|
170
|
+
const walk = (list: SidebarItemConfig[]): void => {
|
|
171
|
+
for (const item of list) {
|
|
172
|
+
if (isPageRef(item)) {
|
|
173
|
+
if (!resolves(normalizeRef(item))) {
|
|
174
|
+
diagnostics.push({
|
|
175
|
+
code: "BLUME_NAV_MISSING_PAGE",
|
|
176
|
+
message: `Sidebar entry "${item}" matches no page, so it's left out of the sidebar.`,
|
|
177
|
+
severity: "warning",
|
|
178
|
+
suggestion: "Fix the route, or add a page at it.",
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (item.root && !resolves(normalizeRef(item.root))) {
|
|
184
|
+
diagnostics.push({
|
|
185
|
+
code: "BLUME_NAV_MISSING_PAGE",
|
|
186
|
+
message: `Sidebar entry "${item.label}" has root "${item.root}", but no page matches it, so its link leads nowhere.`,
|
|
187
|
+
severity: "warning",
|
|
188
|
+
suggestion: "Fix the root, or add a page at that route.",
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
if (item.items) {
|
|
192
|
+
walk(item.items);
|
|
193
|
+
} else if (!(item.root || item.href)) {
|
|
194
|
+
diagnostics.push({
|
|
195
|
+
code: "BLUME_NAV_EMPTY_ITEM",
|
|
196
|
+
message: `Sidebar entry "${item.label}" has no page, href, root, or items, so it's left out of the sidebar.`,
|
|
197
|
+
severity: "warning",
|
|
198
|
+
suggestion:
|
|
199
|
+
"Give it an href or a root to link somewhere, or items to make it a group.",
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
walk(items);
|
|
205
|
+
return diagnostics;
|
|
206
|
+
};
|
|
207
|
+
|
|
153
208
|
/** Warn about two nav items sharing a label at the same sidebar level. */
|
|
154
209
|
const duplicateLabelDiagnostics = (navigation: Navigation): Diagnostic[] => {
|
|
155
210
|
const diagnostics: Diagnostic[] = [];
|