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
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
isInternalPath,
|
|
3
|
+
withBasePath,
|
|
4
|
+
withComposedBasePath,
|
|
5
|
+
} from "../core/base-path.ts";
|
|
6
|
+
import { servesRoute } from "../core/locale-links.ts";
|
|
2
7
|
import type { MdastNode } from "./mdast.ts";
|
|
8
|
+
import { routeSnapshotReader } from "./route-snapshot.ts";
|
|
3
9
|
|
|
4
10
|
interface UrlNode extends MdastNode {
|
|
5
11
|
url?: string | null;
|
|
@@ -16,12 +22,12 @@ interface MdastUrlContext {
|
|
|
16
22
|
|
|
17
23
|
/**
|
|
18
24
|
* A path whose final segment carries a file extension (`/spec.pdf`, `/logo.svg`)
|
|
19
|
-
* —
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
25
|
+
* — a public asset, which Blume serves from `public/` at the site root and
|
|
26
|
+
* does *not* move under `basePath`, unless a page is served there: a dotted
|
|
27
|
+
* route (`/releases/v1.2`) is based like any other page link, the same
|
|
28
|
+
* served-route test the link checker and the locale rewrite apply.
|
|
23
29
|
*/
|
|
24
|
-
const
|
|
30
|
+
const DOTTED_PATH = /\.[a-z0-9]+$/iu;
|
|
25
31
|
|
|
26
32
|
/** Strip any `#fragment`/`?query` so only the path is extension-tested. */
|
|
27
33
|
const pathOf = (url: string): string => url.replace(/[#?].*$/u, "");
|
|
@@ -30,6 +36,14 @@ const pathOf = (url: string): string => url.replace(/[#?].*$/u, "");
|
|
|
30
36
|
const isUrl = (url: string | null | undefined): url is string =>
|
|
31
37
|
typeof url === "string";
|
|
32
38
|
|
|
39
|
+
export interface BaseLinksPluginOptions {
|
|
40
|
+
/**
|
|
41
|
+
* The `blume:data` JSON file, for an ejected app: with no CLI in the process
|
|
42
|
+
* to publish the snapshot, the plugin reads the file eject writes instead.
|
|
43
|
+
*/
|
|
44
|
+
dataFile?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
33
47
|
/**
|
|
34
48
|
* Satteri MDAST plugin that prepends the served-URL base — `deployment.base`
|
|
35
49
|
* layered over the site-wide `basePath` — to root-relative internal page links
|
|
@@ -39,10 +53,43 @@ const isUrl = (url: string | null | undefined): url is string =>
|
|
|
39
53
|
* relative paths, images, and asset links. Only constructed when a base is set
|
|
40
54
|
* (see `markdown/index.ts`).
|
|
41
55
|
*/
|
|
42
|
-
export const baseLinksPlugin = (
|
|
56
|
+
export const baseLinksPlugin = (
|
|
57
|
+
deployBase: string,
|
|
58
|
+
basePath: string,
|
|
59
|
+
options: BaseLinksPluginOptions = {}
|
|
60
|
+
) => {
|
|
61
|
+
const readSnapshot = routeSnapshotReader(options.dataFile);
|
|
62
|
+
// Parsed once per published snapshot, like the relative-links index.
|
|
63
|
+
let cached: { routes: Set<string>; text: string } | undefined;
|
|
64
|
+
|
|
65
|
+
/** Every route the site serves (base-prefixed), from the route snapshot. */
|
|
66
|
+
const servedRoutes = (): ReadonlySet<string> => {
|
|
67
|
+
const text = readSnapshot();
|
|
68
|
+
if (text === undefined) {
|
|
69
|
+
return new Set();
|
|
70
|
+
}
|
|
71
|
+
if (cached?.text !== text) {
|
|
72
|
+
const data: { routes: { path: string }[] } = JSON.parse(text);
|
|
73
|
+
cached = {
|
|
74
|
+
routes: new Set(data.routes.map((route) => route.path)),
|
|
75
|
+
text,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return cached.routes;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/** Whether an internal `url` links a page rather than a public asset. */
|
|
82
|
+
const isPageLink = (url: string): boolean => {
|
|
83
|
+
const path = pathOf(url);
|
|
84
|
+
return (
|
|
85
|
+
!DOTTED_PATH.test(path) ||
|
|
86
|
+
servesRoute(servedRoutes(), withBasePath(basePath, path))
|
|
87
|
+
);
|
|
88
|
+
};
|
|
89
|
+
|
|
43
90
|
const rebase = (node: UrlNode, ctx: MdastUrlContext): void => {
|
|
44
91
|
const { url } = node;
|
|
45
|
-
if (isUrl(url) && isInternalPath(url) &&
|
|
92
|
+
if (isUrl(url) && isInternalPath(url) && isPageLink(url)) {
|
|
46
93
|
const next = withComposedBasePath(deployBase, basePath, url);
|
|
47
94
|
if (next !== url) {
|
|
48
95
|
ctx.setProperty(node, "url", next);
|
package/src/markdown/index.ts
CHANGED
|
@@ -286,8 +286,9 @@ export interface BlumeMarkdownOptions {
|
|
|
286
286
|
contentRoot?: string;
|
|
287
287
|
/**
|
|
288
288
|
* The `blume:data` snapshot file an ejected app aliases. Relative page links
|
|
289
|
-
*
|
|
290
|
-
* process, they read this
|
|
289
|
+
* (and the base-path rewrite of dotted page routes) resolve through the
|
|
290
|
+
* snapshot the CLI publishes; with no CLI in the process, they read this
|
|
291
|
+
* file instead.
|
|
291
292
|
*/
|
|
292
293
|
dataFile?: string;
|
|
293
294
|
}
|
|
@@ -310,7 +311,9 @@ const blumeSharedMdastPlugins = (
|
|
|
310
311
|
...(options.basePath || options.deployBase
|
|
311
312
|
? [
|
|
312
313
|
asMdastPlugin(
|
|
313
|
-
baseLinksPlugin(options.deployBase ?? "", options.basePath ?? ""
|
|
314
|
+
baseLinksPlugin(options.deployBase ?? "", options.basePath ?? "", {
|
|
315
|
+
dataFile: options.dataFile,
|
|
316
|
+
})
|
|
314
317
|
),
|
|
315
318
|
]
|
|
316
319
|
: []),
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import { readFileSync, statSync } from "node:fs";
|
|
2
1
|
import { fileURLToPath } from "node:url";
|
|
3
2
|
|
|
4
3
|
import { dirname, normalize, relative, resolve } from "pathe";
|
|
5
4
|
|
|
6
|
-
import { readRuntimeModule } from "../astro/runtime-modules.ts";
|
|
7
5
|
import { isIndexFileName, resolveRelativeHref } from "../core/links.ts";
|
|
8
6
|
import type { RelativeLinkBase } from "../core/links.ts";
|
|
9
7
|
import type { MdastNode, MdastValue } from "./mdast.ts";
|
|
8
|
+
import { routeSnapshotReader } from "./route-snapshot.ts";
|
|
10
9
|
|
|
11
10
|
interface UrlNode extends MdastNode {
|
|
12
11
|
url?: string | null;
|
|
@@ -156,34 +155,15 @@ export const relativeLinksPlugin = (
|
|
|
156
155
|
const contentRoot = options.contentRoot
|
|
157
156
|
? resolve(options.contentRoot)
|
|
158
157
|
: undefined;
|
|
159
|
-
const
|
|
158
|
+
const readSnapshot = routeSnapshotReader(options.dataFile);
|
|
160
159
|
|
|
161
160
|
// Parsed once per published snapshot: the CLI republishes on regeneration,
|
|
162
161
|
// and an unchanged snapshot is the same string, so every page compiled
|
|
163
162
|
// between regenerations reuses one index.
|
|
164
163
|
let cached: { index: RouteIndex; text: string } | undefined;
|
|
165
|
-
let fileStamp: number | undefined;
|
|
166
|
-
let fileText: string | undefined;
|
|
167
|
-
|
|
168
|
-
/** The ejected snapshot file's text, re-read only when it changes. */
|
|
169
|
-
const readDataFile = (path: string): string | undefined => {
|
|
170
|
-
let stamp: number;
|
|
171
|
-
try {
|
|
172
|
-
stamp = statSync(path).mtimeMs;
|
|
173
|
-
} catch {
|
|
174
|
-
return undefined;
|
|
175
|
-
}
|
|
176
|
-
if (stamp !== fileStamp) {
|
|
177
|
-
fileStamp = stamp;
|
|
178
|
-
fileText = readFileSync(path, "utf-8");
|
|
179
|
-
}
|
|
180
|
-
return fileText;
|
|
181
|
-
};
|
|
182
164
|
|
|
183
165
|
const routeIndex = (): RouteIndex | undefined => {
|
|
184
|
-
const text =
|
|
185
|
-
readRuntimeModule("blume:data") ??
|
|
186
|
-
(dataFile ? readDataFile(dataFile) : undefined);
|
|
166
|
+
const text = readSnapshot();
|
|
187
167
|
if (text === undefined) {
|
|
188
168
|
return undefined;
|
|
189
169
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { readFileSync, statSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
import { resolve } from "pathe";
|
|
4
|
+
|
|
5
|
+
import { readRuntimeModule } from "../astro/runtime-modules.ts";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A reader for the `blume:data` snapshot the Markdown link plugins consult for
|
|
9
|
+
* the site's routes: the text the CLI publishes, or — in an ejected app, with
|
|
10
|
+
* no CLI in the process — the snapshot file eject writes (`dataFile`), re-read
|
|
11
|
+
* only when it changes. Resolves to `undefined` when neither is available.
|
|
12
|
+
*/
|
|
13
|
+
export const routeSnapshotReader = (
|
|
14
|
+
dataFile?: string
|
|
15
|
+
): (() => string | undefined) => {
|
|
16
|
+
const file = dataFile ? resolve(dataFile) : undefined;
|
|
17
|
+
let fileStamp: number | undefined;
|
|
18
|
+
let fileText: string | undefined;
|
|
19
|
+
|
|
20
|
+
/** The ejected snapshot file's text, re-read only when it changes. */
|
|
21
|
+
const readDataFile = (path: string): string | undefined => {
|
|
22
|
+
let stamp: number;
|
|
23
|
+
try {
|
|
24
|
+
stamp = statSync(path).mtimeMs;
|
|
25
|
+
} catch {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
if (stamp !== fileStamp) {
|
|
29
|
+
fileStamp = stamp;
|
|
30
|
+
fileText = readFileSync(path, "utf-8");
|
|
31
|
+
}
|
|
32
|
+
return fileText;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return () =>
|
|
36
|
+
readRuntimeModule("blume:data") ?? (file ? readDataFile(file) : undefined);
|
|
37
|
+
};
|
package/src/og/card.ts
CHANGED
|
@@ -261,11 +261,23 @@ const logoMark = (svg: string, foreground: string): Node => {
|
|
|
261
261
|
};
|
|
262
262
|
|
|
263
263
|
// Fallback mark when no SVG logo is configured: an accent tile with the brand's
|
|
264
|
-
// initial, matching the docs favicon aesthetic.
|
|
265
|
-
|
|
264
|
+
// initial, matching the docs favicon aesthetic. The initial takes the headline's
|
|
265
|
+
// family, so it matches the title rather than whichever loaded font comes first.
|
|
266
|
+
const initialMark = (
|
|
267
|
+
accent: string,
|
|
268
|
+
initial: string,
|
|
269
|
+
family: { fontFamily?: string }
|
|
270
|
+
): Node =>
|
|
266
271
|
container({
|
|
267
272
|
children: initial
|
|
268
|
-
? [
|
|
273
|
+
? [
|
|
274
|
+
text(initial, {
|
|
275
|
+
color: "#ffffff",
|
|
276
|
+
fontSize: 32,
|
|
277
|
+
fontWeight: 600,
|
|
278
|
+
...family,
|
|
279
|
+
}),
|
|
280
|
+
]
|
|
269
281
|
: [],
|
|
270
282
|
style: {
|
|
271
283
|
alignItems: "center",
|
|
@@ -319,7 +331,11 @@ export const renderOgImage = async (
|
|
|
319
331
|
if (logo === false) {
|
|
320
332
|
return [];
|
|
321
333
|
}
|
|
322
|
-
return [
|
|
334
|
+
return [
|
|
335
|
+
logo
|
|
336
|
+
? logoMark(logo, foreground)
|
|
337
|
+
: initialMark(accent, initial, titleFamily),
|
|
338
|
+
];
|
|
323
339
|
};
|
|
324
340
|
const header = container({
|
|
325
341
|
children: mark(),
|
package/src/og/derive.ts
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
* Bridges `theme.fonts` into the OG card renderer. A site that explicitly
|
|
3
3
|
* picks its typefaces gets matching cards (and non-Latin coverage) without
|
|
4
4
|
* configuring `seo.og.fonts`; untouched defaults derive nothing, so plain
|
|
5
|
-
* sites keep Takumi's built-in font and gain no build-time font fetch.
|
|
5
|
+
* sites keep Takumi's built-in font and gain no build-time font fetch. Locales
|
|
6
|
+
* in scripts that font can't draw (Japanese, Hindi, Russian, …) add a Noto
|
|
7
|
+
* fallback for their script either way.
|
|
6
8
|
*/
|
|
7
9
|
|
|
8
10
|
import { existsSync } from "node:fs";
|
|
@@ -14,7 +16,7 @@ import type {
|
|
|
14
16
|
FontValue,
|
|
15
17
|
LocalFontConfig,
|
|
16
18
|
} from "../theme/fonts.ts";
|
|
17
|
-
import { GOOGLE_FONTS, isFontSlug } from "../theme/fonts.ts";
|
|
19
|
+
import { GOOGLE_FONTS, isFontSlug, localeFontSubsets } from "../theme/fonts.ts";
|
|
18
20
|
import type { OgFont, OgFontFamilies, OgLocalFont } from "./card.ts";
|
|
19
21
|
|
|
20
22
|
/** Fonts plus per-role families for the generated OG endpoint. */
|
|
@@ -160,6 +162,140 @@ export const deriveOgFonts = (
|
|
|
160
162
|
return result;
|
|
161
163
|
};
|
|
162
164
|
|
|
165
|
+
/**
|
|
166
|
+
* The family Takumi renders a card in when no font is loaded: its embedded
|
|
167
|
+
* Geist, which covers Latin only. Naming it keeps a card's Latin text in that
|
|
168
|
+
* face once locale fallbacks are loaded, since Takumi otherwise tries loaded
|
|
169
|
+
* fonts first; a glyph Geist lacks still falls back to them. Were Takumi to
|
|
170
|
+
* rename it, the name would resolve to nothing and cards would fall back to
|
|
171
|
+
* the loaded fonts, never to tofu.
|
|
172
|
+
*/
|
|
173
|
+
const BUILT_IN_FAMILY = "Geist";
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* The Google Noto family that draws each language's script, keyed by BCP 47
|
|
177
|
+
* language subtag, for scripts beyond the Latin, Cyrillic, Greek, and
|
|
178
|
+
* Vietnamese that `Noto Sans` covers (see {@link localeOgFonts}). Keep keys
|
|
179
|
+
* alphabetical.
|
|
180
|
+
*/
|
|
181
|
+
const SCRIPT_FAMILIES = {
|
|
182
|
+
am: "Noto Sans Ethiopic",
|
|
183
|
+
ar: "Noto Sans Arabic",
|
|
184
|
+
as: "Noto Sans Bengali",
|
|
185
|
+
bn: "Noto Sans Bengali",
|
|
186
|
+
ckb: "Noto Sans Arabic",
|
|
187
|
+
fa: "Noto Sans Arabic",
|
|
188
|
+
gu: "Noto Sans Gujarati",
|
|
189
|
+
he: "Noto Sans Hebrew",
|
|
190
|
+
hi: "Noto Sans Devanagari",
|
|
191
|
+
hy: "Noto Sans Armenian",
|
|
192
|
+
ja: "Noto Sans JP",
|
|
193
|
+
ka: "Noto Sans Georgian",
|
|
194
|
+
km: "Noto Sans Khmer",
|
|
195
|
+
kn: "Noto Sans Kannada",
|
|
196
|
+
ko: "Noto Sans KR",
|
|
197
|
+
lo: "Noto Sans Lao",
|
|
198
|
+
ml: "Noto Sans Malayalam",
|
|
199
|
+
mr: "Noto Sans Devanagari",
|
|
200
|
+
my: "Noto Sans Myanmar",
|
|
201
|
+
ne: "Noto Sans Devanagari",
|
|
202
|
+
or: "Noto Sans Oriya",
|
|
203
|
+
pa: "Noto Sans Gurmukhi",
|
|
204
|
+
ps: "Noto Sans Arabic",
|
|
205
|
+
sa: "Noto Sans Devanagari",
|
|
206
|
+
si: "Noto Sans Sinhala",
|
|
207
|
+
ta: "Noto Sans Tamil",
|
|
208
|
+
te: "Noto Sans Telugu",
|
|
209
|
+
th: "Noto Sans Thai",
|
|
210
|
+
ti: "Noto Sans Ethiopic",
|
|
211
|
+
ur: "Noto Sans Arabic",
|
|
212
|
+
yi: "Noto Sans Hebrew",
|
|
213
|
+
zh: "Noto Sans SC",
|
|
214
|
+
} satisfies Record<string, string>;
|
|
215
|
+
|
|
216
|
+
/** Type guard: does `language` have an entry in the script table? */
|
|
217
|
+
const isScriptLanguage = (
|
|
218
|
+
language: string
|
|
219
|
+
): language is keyof typeof SCRIPT_FAMILIES =>
|
|
220
|
+
Object.hasOwn(SCRIPT_FAMILIES, language);
|
|
221
|
+
|
|
222
|
+
/** A locale fallback: a Google family at the weights the card renders. */
|
|
223
|
+
export interface LocaleOgFont {
|
|
224
|
+
name: string;
|
|
225
|
+
weight: number[];
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** Subtags that write Chinese in traditional characters (`zh-Hant`, `zh-TW`). */
|
|
229
|
+
const TRADITIONAL_CHINESE = new Set(["hant", "hk", "mo", "tw"]);
|
|
230
|
+
|
|
231
|
+
/** The Noto family for one locale's script, or null when Geist covers it. */
|
|
232
|
+
const localeFamily = (locale: string): string | null => {
|
|
233
|
+
const [language = "", ...subtags] = locale.toLowerCase().split("-");
|
|
234
|
+
if (
|
|
235
|
+
language === "zh" &&
|
|
236
|
+
subtags.some((tag) => TRADITIONAL_CHINESE.has(tag))
|
|
237
|
+
) {
|
|
238
|
+
return "Noto Sans TC";
|
|
239
|
+
}
|
|
240
|
+
if (isScriptLanguage(language)) {
|
|
241
|
+
return SCRIPT_FAMILIES[language];
|
|
242
|
+
}
|
|
243
|
+
// Anything past plain `latin` in the theme's subset table (Polish, Russian,
|
|
244
|
+
// Greek, Vietnamese, …) needs glyphs Geist lacks and `Noto Sans` has.
|
|
245
|
+
return localeFontSubsets([locale]).length > 1 ? "Noto Sans" : null;
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* The fallback card fonts the configured locales' scripts need, at the card's
|
|
250
|
+
* weights. Takumi's built-in font covers only basic Latin, so without these a
|
|
251
|
+
* Japanese or Hindi page's card renders every glyph as tofu. The renderer
|
|
252
|
+
* fetches only the glyph subsets a card's text uses, so an English card on
|
|
253
|
+
* the same site pulls nothing extra.
|
|
254
|
+
*/
|
|
255
|
+
export const localeOgFonts = (locales: string[]): LocaleOgFont[] => {
|
|
256
|
+
const names = new Set<string>();
|
|
257
|
+
for (const locale of locales) {
|
|
258
|
+
const family = localeFamily(locale);
|
|
259
|
+
if (family) {
|
|
260
|
+
names.add(family);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return [...names].map((name) => ({ name, weight: CARD_WEIGHTS }));
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
/** A bare Google family name entry (as opposed to the object forms). */
|
|
267
|
+
const isGoogleFamilyName = (font: OgFont): font is string =>
|
|
268
|
+
typeof font === "string";
|
|
269
|
+
|
|
270
|
+
/** The family an OG font entry registers under. */
|
|
271
|
+
const ogFontName = (font: OgFont): string =>
|
|
272
|
+
isGoogleFamilyName(font) ? font : font.name;
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* `derived` plus the locale fallbacks it doesn't already load. A card that
|
|
276
|
+
* names no family is pinned to {@link BUILT_IN_FAMILY}, so its Latin text
|
|
277
|
+
* keeps the face it had before any fallback was loaded.
|
|
278
|
+
*/
|
|
279
|
+
const withLocaleFonts = (
|
|
280
|
+
derived: DerivedOgFonts,
|
|
281
|
+
locales: string[]
|
|
282
|
+
): DerivedOgFonts => {
|
|
283
|
+
const loaded = new Set(derived.fonts.map(ogFontName));
|
|
284
|
+
const fallbacks = localeOgFonts(locales).filter(
|
|
285
|
+
(font) => !loaded.has(ogFontName(font))
|
|
286
|
+
);
|
|
287
|
+
if (fallbacks.length === 0) {
|
|
288
|
+
return derived;
|
|
289
|
+
}
|
|
290
|
+
return {
|
|
291
|
+
families: derived.families ?? {
|
|
292
|
+
body: BUILT_IN_FAMILY,
|
|
293
|
+
title: BUILT_IN_FAMILY,
|
|
294
|
+
},
|
|
295
|
+
fonts: [...derived.fonts, ...fallbacks],
|
|
296
|
+
};
|
|
297
|
+
};
|
|
298
|
+
|
|
163
299
|
/** Explicit `seo.og.fonts` with local `src` paths resolved to absolute. */
|
|
164
300
|
export const resolveOgFontSources = (fonts: OgFont[], root: string): OgFont[] =>
|
|
165
301
|
fonts.map((font) =>
|
|
@@ -170,10 +306,14 @@ export const resolveOgFontSources = (fonts: OgFont[], root: string): OgFont[] =>
|
|
|
170
306
|
* The fonts baked into the generated OG endpoint. An explicit `seo.og.fonts`
|
|
171
307
|
* always wins (including `[]` to opt out, keeping the card's role styling
|
|
172
308
|
* untouched); otherwise a site that explicitly set `theme.fonts` gets its
|
|
173
|
-
* display/body fonts derived so cards match the site without extra config
|
|
309
|
+
* display/body fonts derived so cards match the site without extra config,
|
|
310
|
+
* and either way the configured locales add a fallback for each script the
|
|
311
|
+
* built-in font can't draw.
|
|
174
312
|
*/
|
|
175
313
|
export const resolveOgFonts = (
|
|
176
314
|
options: {
|
|
315
|
+
/** The site's configured locale codes (none without `i18n`). */
|
|
316
|
+
locales?: string[];
|
|
177
317
|
/** Explicit `seo.og.fonts`, or undefined when unset. */
|
|
178
318
|
ogFonts: OgFont[] | undefined;
|
|
179
319
|
themeFonts: FontsConfig;
|
|
@@ -185,9 +325,10 @@ export const resolveOgFonts = (
|
|
|
185
325
|
if (options.ogFonts) {
|
|
186
326
|
return { fonts: resolveOgFontSources(options.ogFonts, root) };
|
|
187
327
|
}
|
|
188
|
-
|
|
328
|
+
const derived = options.themeFontsConfigured
|
|
189
329
|
? deriveOgFonts(options.themeFonts, root)
|
|
190
330
|
: { fonts: [] };
|
|
331
|
+
return withLocaleFonts(derived, options.locales ?? []);
|
|
191
332
|
};
|
|
192
333
|
|
|
193
334
|
/**
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
DocumentNode,
|
|
2
3
|
GraphQLArgument,
|
|
3
4
|
GraphQLEnumType,
|
|
4
5
|
GraphQLField,
|
|
@@ -12,8 +13,8 @@ import type {
|
|
|
12
13
|
GraphQLUnionType,
|
|
13
14
|
} from "graphql";
|
|
14
15
|
import {
|
|
16
|
+
buildASTSchema,
|
|
15
17
|
buildClientSchema,
|
|
16
|
-
buildSchema,
|
|
17
18
|
getNamedType,
|
|
18
19
|
isEnumType,
|
|
19
20
|
isInputObjectType,
|
|
@@ -21,7 +22,11 @@ import {
|
|
|
21
22
|
isObjectType,
|
|
22
23
|
isSpecifiedScalarType,
|
|
23
24
|
isUnionType,
|
|
25
|
+
Kind,
|
|
26
|
+
parse,
|
|
24
27
|
print,
|
|
28
|
+
specifiedDirectives,
|
|
29
|
+
visit,
|
|
25
30
|
} from "graphql";
|
|
26
31
|
|
|
27
32
|
import type {
|
|
@@ -259,6 +264,27 @@ const introspectionOf = <Value>(parsed: Value): object | undefined => {
|
|
|
259
264
|
: undefined;
|
|
260
265
|
};
|
|
261
266
|
|
|
267
|
+
/**
|
|
268
|
+
* SDL with every use of an undeclared directive removed. Subgraph and platform
|
|
269
|
+
* schemas lean on directives their server defines out of band — Apollo
|
|
270
|
+
* Federation's `@key` and `@link`, AppSync's `@aws_*` — and strict SDL
|
|
271
|
+
* validation rejects each one as `Unknown directive`. The reference renders
|
|
272
|
+
* none of them, so they are dropped rather than declared; every other check
|
|
273
|
+
* (syntax, unknown types, duplicates) still runs.
|
|
274
|
+
*/
|
|
275
|
+
const withoutUnknownDirectives = (document: DocumentNode): DocumentNode => {
|
|
276
|
+
const known = new Set(specifiedDirectives.map((directive) => directive.name));
|
|
277
|
+
for (const definition of document.definitions) {
|
|
278
|
+
if (definition.kind === Kind.DIRECTIVE_DEFINITION) {
|
|
279
|
+
known.add(definition.name.value);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return visit(document, {
|
|
283
|
+
// `null` deletes the node; `undefined` leaves it in place.
|
|
284
|
+
Directive: (node) => (known.has(node.name.value) ? undefined : null),
|
|
285
|
+
});
|
|
286
|
+
};
|
|
287
|
+
|
|
262
288
|
/**
|
|
263
289
|
* Build the document from schema text: an introspection JSON result (the raw
|
|
264
290
|
* `{ __schema }` shape or a `{ data: { __schema } }` response envelope) or
|
|
@@ -289,5 +315,5 @@ export const buildGraphqlDocument = (text: string): GraphqlDocument => {
|
|
|
289
315
|
)
|
|
290
316
|
);
|
|
291
317
|
}
|
|
292
|
-
return documentOf(
|
|
318
|
+
return documentOf(buildASTSchema(withoutUnknownDirectives(parse(text))));
|
|
293
319
|
};
|
package/src/openapi/model.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { Document, OperationObject } from "@scalar/openapi-types/3.1";
|
|
2
2
|
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
AsyncApiAction,
|
|
5
|
+
AsyncApiDocument,
|
|
6
|
+
AsyncApiSpecValue,
|
|
7
|
+
} from "./asyncapi.ts";
|
|
4
8
|
// Type-only, so the import can't cycle at runtime (graphql.ts imports the
|
|
5
9
|
// collector from here).
|
|
6
10
|
import type { GraphqlDocument, GraphqlMember } from "./graphql.ts";
|
|
@@ -99,7 +103,10 @@ export interface ApiOperationRef {
|
|
|
99
103
|
channelId?: string;
|
|
100
104
|
}
|
|
101
105
|
|
|
102
|
-
/**
|
|
106
|
+
/**
|
|
107
|
+
* A tag/section: the spec's declared tags in their declared order, then any
|
|
108
|
+
* undeclared tag an operation uses, in first-seen order.
|
|
109
|
+
*/
|
|
103
110
|
export interface ApiTagRef {
|
|
104
111
|
slug: string;
|
|
105
112
|
name: string;
|
|
@@ -212,6 +219,40 @@ export const specAddresses = (spec: ApiSpecData): SpecAddresses => {
|
|
|
212
219
|
return { addresses, label: "Base URL" };
|
|
213
220
|
};
|
|
214
221
|
|
|
222
|
+
const SERVER_VARIABLE = /\{(?<name>[^{}]+)\}/gu;
|
|
223
|
+
|
|
224
|
+
const isDocumentObject = (
|
|
225
|
+
value: AsyncApiSpecValue
|
|
226
|
+
): value is Record<string, AsyncApiSpecValue> =>
|
|
227
|
+
typeof value === "object" && value !== null && !Array.isArray(value);
|
|
228
|
+
|
|
229
|
+
// YAML reads an unquoted `default: 8443` as a number; it is still the port.
|
|
230
|
+
const isVariableDefault = (
|
|
231
|
+
value: AsyncApiSpecValue
|
|
232
|
+
): value is string | number =>
|
|
233
|
+
typeof value === "string" || typeof value === "number";
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* A server URL template — an OpenAPI `servers[].url`, an AsyncAPI server's
|
|
237
|
+
* `host` or `pathname` — with each `{variable}` replaced by the `default` its
|
|
238
|
+
* `variables` map declares. Code samples, the playground's Send, and the
|
|
239
|
+
* proxy allowlist all need a real address, and
|
|
240
|
+
* `https://{region}.api.example.com` is not one. A variable the map doesn't
|
|
241
|
+
* define with a default (invalid per both specs) stays templated.
|
|
242
|
+
*/
|
|
243
|
+
export const withServerDefaults = (
|
|
244
|
+
template: string,
|
|
245
|
+
variables: AsyncApiSpecValue
|
|
246
|
+
): string =>
|
|
247
|
+
template.replaceAll(SERVER_VARIABLE, (match, name: string) => {
|
|
248
|
+
const variable =
|
|
249
|
+
isDocumentObject(variables) && Object.hasOwn(variables, name)
|
|
250
|
+
? variables[name]
|
|
251
|
+
: undefined;
|
|
252
|
+
const value = isDocumentObject(variable) ? variable.default : undefined;
|
|
253
|
+
return isVariableDefault(value) ? String(value) : match;
|
|
254
|
+
});
|
|
255
|
+
|
|
215
256
|
// The runtime object check stands guard because the document was parsed from
|
|
216
257
|
// arbitrary YAML/JSON: a spec can put a scalar where the type promises an
|
|
217
258
|
// operation object.
|
|
@@ -276,9 +317,10 @@ export interface ExtractedOperations extends CollectedOperations {
|
|
|
276
317
|
|
|
277
318
|
/**
|
|
278
319
|
* The collector behind both extractors (OpenAPI here, AsyncAPI in
|
|
279
|
-
* `asyncapi.ts`): first-seen tag ordering, key de-duplication
|
|
280
|
-
* gains its method/action as a suffix), and the shared route
|
|
281
|
-
* URL shape and slug rules can never drift between the two spec
|
|
320
|
+
* `asyncapi.ts`): declared-then-first-seen tag ordering, key de-duplication
|
|
321
|
+
* (a repeated key gains its method/action as a suffix), and the shared route
|
|
322
|
+
* template — so URL shape and slug rules can never drift between the two spec
|
|
323
|
+
* kinds.
|
|
282
324
|
*/
|
|
283
325
|
export const operationCollector = (
|
|
284
326
|
baseRoute: string,
|
|
@@ -310,15 +352,27 @@ export const operationCollector = (
|
|
|
310
352
|
});
|
|
311
353
|
};
|
|
312
354
|
|
|
355
|
+
// Declared tags in the order the spec's `tags` list gives them, then any
|
|
356
|
+
// tag an operation uses without declaring it, in first-use order. The
|
|
357
|
+
// declared order is the author's section order, not an accident of which
|
|
358
|
+
// path happens to come first.
|
|
359
|
+
const declared = [...tagMeta.keys()];
|
|
360
|
+
const rank = (name: string): number => {
|
|
361
|
+
const index = declared.indexOf(name);
|
|
362
|
+
return index === -1 ? declared.length : index;
|
|
363
|
+
};
|
|
364
|
+
|
|
313
365
|
const finish = (): CollectedOperations => ({
|
|
314
366
|
operations,
|
|
315
|
-
tags: tagOrder
|
|
316
|
-
|
|
317
|
-
name
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
367
|
+
tags: tagOrder
|
|
368
|
+
.toSorted((a, b) => rank(a) - rank(b))
|
|
369
|
+
.map((name) => ({
|
|
370
|
+
description: tagMeta.get(name) ?? "",
|
|
371
|
+
name,
|
|
372
|
+
// The same slugger instance, so every tag resolves to the slug its
|
|
373
|
+
// operations were routed under.
|
|
374
|
+
slug: slugForTag(name),
|
|
375
|
+
})),
|
|
322
376
|
});
|
|
323
377
|
|
|
324
378
|
return { add, finish };
|
|
@@ -328,7 +382,8 @@ export const operationCollector = (
|
|
|
328
382
|
* Flatten a 3.1 document into a route-mapped operation list and its ordered
|
|
329
383
|
* tags. Operations inherit the first tag they declare; keys are de-duplicated so
|
|
330
384
|
* a repeated `operationId` still yields distinct routes. `warnings` reports
|
|
331
|
-
* anything skipped (a `$ref` path item), so missing operations
|
|
385
|
+
* anything skipped (a `$ref` path item, webhooks), so missing operations
|
|
386
|
+
* aren't silent.
|
|
332
387
|
*/
|
|
333
388
|
export const extractOperations = (
|
|
334
389
|
document: ApiDocument,
|
|
@@ -371,6 +426,15 @@ export const extractOperations = (
|
|
|
371
426
|
}
|
|
372
427
|
}
|
|
373
428
|
|
|
429
|
+
// OpenAPI 3.1 webhooks (requests the API sends, not ones it serves) have no
|
|
430
|
+
// page renderer; say so rather than dropping them silently.
|
|
431
|
+
const webhooks = Object.keys(document.webhooks ?? {});
|
|
432
|
+
if (webhooks.length > 0) {
|
|
433
|
+
warnings.push(
|
|
434
|
+
`The spec declares ${webhooks.length === 1 ? "a webhook" : `${webhooks.length} webhooks`} under "webhooks" (${webhooks.join(", ")}); webhooks aren't rendered, so they are missing from the reference.`
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
|
|
374
438
|
return { ...collector.finish(), warnings };
|
|
375
439
|
};
|
|
376
440
|
|
|
@@ -90,6 +90,9 @@ const codeSpans = (text: string, tree: Root): [number, number][] => {
|
|
|
90
90
|
return spans;
|
|
91
91
|
};
|
|
92
92
|
|
|
93
|
+
/** Stands in for `<` when locating code spans (see `mdxSafe`). */
|
|
94
|
+
const HTML_MASK = "";
|
|
95
|
+
|
|
93
96
|
/**
|
|
94
97
|
* Escape MDX-special syntax in prose while leaving code verbatim. A spec is
|
|
95
98
|
* someone else's content, so a link whose destination isn't a web, mail, or
|
|
@@ -101,8 +104,14 @@ const mdxSafe = (text: string): string => {
|
|
|
101
104
|
const unsafe = unsafeLinkSpans(tree);
|
|
102
105
|
const insideUnsafe = (offset: number): boolean =>
|
|
103
106
|
unsafe.some((span) => offset >= span.start && offset < span.end);
|
|
107
|
+
// Code spans come from a parse with every `<` masked. The emitted MDX
|
|
108
|
+
// escapes each `<` outside code to `<`, so no line of it is an HTML
|
|
109
|
+
// block — but CommonMark reads `<div>` as one and never looks for the code
|
|
110
|
+
// spans inside it, which would leave their braces to be escaped and shown
|
|
111
|
+
// as a literal `{`. The mask is one code unit, so offsets line up.
|
|
112
|
+
const masked = text.replaceAll("<", HTML_MASK);
|
|
104
113
|
const segments = [
|
|
105
|
-
...codeSpans(
|
|
114
|
+
...codeSpans(masked, fromMarkdown(masked))
|
|
106
115
|
.filter(([start]) => !insideUnsafe(start))
|
|
107
116
|
.map(([start, end]) => ({ end, start, text: text.slice(start, end) })),
|
|
108
117
|
...unsafe,
|