blume 2.0.2 → 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 +65 -0
- package/dist/cli/{chunk-6k4ftwze.js → chunk-1d7ve1dm.js} +1 -1
- package/dist/cli/{chunk-zp79m0ts.js → chunk-2eytanqx.js} +2 -2
- package/dist/cli/{chunk-8g8ytmgx.js → chunk-2hsdwb9n.js} +19 -19
- package/dist/cli/{chunk-8g8ytmgx.js.map → chunk-2hsdwb9n.js.map} +1 -1
- package/dist/cli/{chunk-g698a744.js → chunk-2z928egk.js} +5 -5
- package/dist/cli/{chunk-ppzjqwx2.js → chunk-35d4wj9f.js} +17 -18
- package/dist/cli/{chunk-ppzjqwx2.js.map → chunk-35d4wj9f.js.map} +3 -3
- package/dist/cli/{chunk-mqc662a6.js → chunk-364znk6q.js} +2 -2
- package/dist/cli/{chunk-gs7r695n.js → chunk-3em5wd2y.js} +21 -8
- package/dist/cli/{chunk-gs7r695n.js.map → chunk-3em5wd2y.js.map} +3 -3
- package/dist/cli/{chunk-k7pj68a8.js → chunk-5f86nr5m.js} +15 -15
- package/dist/cli/{chunk-hr8ne106.js → chunk-5m5nmvyq.js} +68 -43
- package/dist/cli/chunk-5m5nmvyq.js.map +13 -0
- package/dist/cli/{chunk-91ws1n6j.js → chunk-5xvm6tfj.js} +14 -14
- package/dist/cli/{chunk-w4bxdvsa.js → chunk-6dsbexzp.js} +14 -14
- package/dist/cli/{chunk-00gs3wqs.js → chunk-8ktnccpt.js} +1 -1
- package/dist/cli/{chunk-j85scx15.js → chunk-9t7a85s3.js} +130 -44
- package/dist/cli/chunk-9t7a85s3.js.map +10 -0
- package/dist/cli/{chunk-bbnwccaz.js → chunk-a58773jm.js} +2 -2
- package/dist/cli/{chunk-nfcyttvj.js → chunk-acanzt5p.js} +9 -9
- package/dist/cli/{chunk-nfcyttvj.js.map → chunk-acanzt5p.js.map} +1 -1
- package/dist/cli/{chunk-wdrt2k2v.js → chunk-akbpwfxc.js} +90 -26
- package/dist/cli/chunk-akbpwfxc.js.map +10 -0
- package/dist/cli/{chunk-sqw4ekg1.js → chunk-b07cmahc.js} +2 -2
- package/dist/cli/{chunk-xh43dwgw.js → chunk-c8chx29p.js} +42 -29
- package/dist/cli/{chunk-xh43dwgw.js.map → chunk-c8chx29p.js.map} +9 -9
- package/dist/cli/{chunk-7mbqtmgb.js → chunk-crgn1q09.js} +21 -12
- 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-4e9b9ra6.js → chunk-ey84smr6.js} +3 -3
- package/dist/cli/{chunk-d1v5rhy0.js → chunk-g4hq16wv.js} +13 -13
- package/dist/cli/{chunk-273ygyr4.js → chunk-ga0pf4aj.js} +7 -7
- package/dist/cli/{chunk-273ygyr4.js.map → chunk-ga0pf4aj.js.map} +3 -3
- package/dist/cli/{chunk-pbg5a4s3.js → chunk-hm3vjy5s.js} +64 -45
- package/dist/cli/chunk-hm3vjy5s.js.map +19 -0
- package/dist/cli/{chunk-5r8g91qn.js → chunk-j85vccga.js} +306 -107
- package/dist/cli/chunk-j85vccga.js.map +36 -0
- package/dist/cli/{chunk-n1yg3tj3.js → chunk-p3v96n38.js} +6 -6
- package/dist/cli/{chunk-n1yg3tj3.js.map → chunk-p3v96n38.js.map} +3 -3
- package/dist/cli/{chunk-7vtckvaw.js → chunk-p73c0m7w.js} +14 -14
- package/dist/cli/{chunk-7vtckvaw.js.map → chunk-p73c0m7w.js.map} +1 -1
- package/dist/cli/{chunk-qkb5a8sa.js → chunk-pehfxfta.js} +3 -3
- package/dist/cli/{chunk-v6ya5kcb.js → chunk-pv29h0wf.js} +1223 -978
- package/dist/cli/{chunk-v6ya5kcb.js.map → chunk-pv29h0wf.js.map} +47 -46
- package/dist/cli/{chunk-6dtt0zfn.js → chunk-q58y5e6a.js} +10 -10
- package/dist/cli/{chunk-6dtt0zfn.js.map → chunk-q58y5e6a.js.map} +3 -3
- package/dist/cli/{chunk-fsmrqk8a.js → chunk-r20tn01b.js} +1 -1
- package/dist/cli/{chunk-bfwp9vp6.js → chunk-r9rcc4w7.js} +7 -7
- package/dist/cli/{chunk-h7k3nq3v.js → chunk-tkacnehg.js} +2 -2
- package/dist/cli/{chunk-h2ez8dzb.js → chunk-tzmab476.js} +4 -4
- package/dist/cli/{chunk-3yce002v.js → chunk-vg9r4eb9.js} +6 -6
- package/dist/cli/{chunk-3yce002v.js.map → chunk-vg9r4eb9.js.map} +4 -4
- package/dist/cli/{chunk-hqp2ajnh.js → chunk-wrr3j9w9.js} +16 -17
- package/dist/cli/{chunk-hqp2ajnh.js.map → chunk-wrr3j9w9.js.map} +7 -7
- package/dist/cli/{chunk-ddndchfr.js → chunk-yfyb25rh.js} +38 -26
- package/dist/cli/chunk-yfyb25rh.js.map +14 -0
- package/dist/cli/index.js +20 -18
- package/dist/cli/index.js.map +3 -3
- 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 +9 -7
- package/dist/types/ai/skills.d.ts +4 -1
- package/dist/types/ai/tar.d.ts +1 -3
- package/dist/types/analytics/index.d.ts +2 -0
- package/dist/types/analytics/one-dollar-stats.d.ts +48 -0
- package/dist/types/analytics/schema.d.ts +14 -0
- package/dist/types/astro/integration.d.ts +3 -2
- package/dist/types/core/base-path.d.ts +21 -7
- package/dist/types/core/config-input.d.ts +9 -7
- package/dist/types/core/directive-diagnostics.d.ts +12 -0
- package/dist/types/core/heading-markers.d.ts +5 -7
- package/dist/types/core/i18n.d.ts +2 -0
- package/dist/types/core/last-modified.d.ts +10 -0
- package/dist/types/core/meta.d.ts +8 -0
- package/dist/types/core/schema.d.ts +7 -0
- package/dist/types/core/sources/lower.d.ts +29 -16
- package/dist/types/core/sources/normalize.d.ts +13 -1
- package/dist/types/core/sources/watch.d.ts +12 -5
- package/dist/types/core/standard-schema.d.ts +5 -0
- package/dist/types/deploy/artifacts.d.ts +6 -4
- package/dist/types/deploy/headers.d.ts +5 -0
- package/dist/types/deploy/platforms/types.d.ts +8 -0
- package/dist/types/deploy/redirects.d.ts +24 -13
- 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/docs/02-deployment.mdx +4 -2
- package/docs/08-faq.mdx +1 -1
- package/docs/advanced/custom-pages.mdx +3 -3
- package/docs/cli/audit.mdx +17 -1
- package/docs/cli/evals.mdx +3 -3
- package/docs/cli/translate.mdx +3 -3
- package/docs/cli/version.mdx +1 -1
- package/docs/configuration/analytics.mdx +20 -1
- package/docs/configuration/customization.mdx +2 -0
- package/docs/configuration/search.mdx +1 -1
- package/docs/content/components.mdx +1 -1
- package/docs/content/frontmatter.mdx +1 -1
- package/docs/content/i18n.mdx +2 -0
- package/docs/content/index.mdx +4 -2
- package/docs/content/islands.mdx +1 -1
- package/docs/content/meta.mdx +3 -1
- package/docs/content/sources.mdx +1 -5
- package/docs/content/syntax.mdx +14 -0
- package/docs/content/versioning.mdx +1 -1
- package/docs/discoverability/agent-discovery.mdx +1 -1
- package/docs/discoverability/index.mdx +2 -1
- package/docs/discoverability/markdown.mdx +2 -2
- package/docs/discoverability/open-graph.mdx +5 -3
- package/docs/discoverability/rss.mdx +1 -1
- package/docs/references/asyncapi.mdx +1 -1
- package/docs/references/graphql.mdx +1 -1
- package/docs/references/openapi.mdx +5 -3
- package/package.json +1 -1
- package/skills/blume-migrate/references/fumadocs.md +1 -1
- package/skills/blume-migrate/references/nextra.md +1 -1
- package/skills/blume-migrate/scripts/mintlify-codemod.mjs +7 -2
- package/src/ai/agent-readability.ts +2 -2
- package/src/ai/ai-catalog.ts +6 -2
- package/src/ai/api/handlers.ts +2 -2
- package/src/ai/api/spec.ts +2 -2
- package/src/ai/api-catalog.ts +6 -2
- package/src/ai/changelog-markdown.ts +2 -2
- package/src/ai/link-headers.ts +14 -5
- package/src/ai/llms.ts +18 -7
- package/src/ai/mcp/discovery.ts +2 -2
- package/src/ai/mcp/query.ts +2 -2
- package/src/ai/mcp/server.ts +2 -2
- package/src/ai/openapi-components.ts +22 -6
- package/src/ai/relative-links.ts +42 -26
- package/src/ai/serializers.ts +2 -1
- package/src/ai/skills.ts +14 -1
- package/src/ai/tar.ts +139 -12
- package/src/analytics/head.ts +4 -0
- package/src/analytics/index.ts +5 -0
- package/src/analytics/one-dollar-stats.ts +86 -0
- package/src/analytics/schema.ts +2 -0
- package/src/astro/generate.ts +1 -1
- 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 +81 -37
- package/src/audit/checks/links.ts +8 -1
- package/src/audit/graph.ts +3 -1
- package/src/audit/run.ts +12 -9
- package/src/audit/snapshot.ts +5 -0
- package/src/cli/args.ts +32 -0
- package/src/cli/commands/audit.ts +4 -1
- package/src/cli/commands/doctor.ts +3 -1
- package/src/cli/commands/eval.ts +19 -11
- package/src/cli/commands/translate.ts +9 -12
- package/src/cli/commands/validate.ts +3 -1
- package/src/cli/dev-lock.ts +157 -37
- package/src/cli/report-format.ts +11 -6
- package/src/components/colors.ts +19 -0
- package/src/components/content/Badge.astro +5 -3
- package/src/components/content/Component.astro +2 -2
- package/src/components/content/Tab.astro +0 -1
- package/src/components/content/Tabs.astro +4 -0
- package/src/components/content/badge-color.ts +5 -2
- package/src/components/content/base-href.ts +13 -36
- package/src/components/islands/assistant.tsx +21 -4
- package/src/components/islands/base-path.ts +47 -10
- package/src/components/islands/hooks.ts +35 -19
- package/src/components/islands/webmcp.ts +4 -2
- package/src/components/layout/Breadcrumbs.astro +5 -2
- package/src/components/layout/DiscoveryLinks.astro +9 -5
- package/src/components/layout/Header.astro +2 -2
- package/src/components/layout/LanguageSwitcher.astro +2 -2
- package/src/components/layout/NavSelector.astro +2 -2
- package/src/components/layout/NavTabMenu.astro +3 -3
- package/src/components/layout/NavTree.astro +16 -6
- package/src/components/layout/PageActions.astro +4 -3
- package/src/components/layout/PageLayout.astro +7 -6
- package/src/components/layout/Pagination.astro +3 -3
- package/src/components/layout/RootLayout.astro +5 -5
- package/src/components/layout/Search.astro +33 -13
- package/src/components/layout/VersionBanner.astro +2 -2
- package/src/components/layout/analytics-client.ts +12 -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/Operation.astro +2 -0
- package/src/components/openapi/Playground.astro +4 -4
- package/src/components/openapi/RequestPanel.astro +5 -2
- package/src/components/openapi/SchemaTable.astro +7 -0
- package/src/components/openapi/async.ts +38 -6
- package/src/components/openapi/helpers.ts +19 -9
- package/src/components/openapi/message-composer.ts +6 -1
- package/src/components/openapi/message.ts +17 -2
- package/src/components/openapi/operation-model.ts +81 -11
- package/src/components/openapi/panel.ts +4 -2
- package/src/components/openapi/param-style.ts +181 -0
- package/src/components/openapi/playground-client.ts +73 -16
- package/src/components/openapi/request.ts +190 -26
- package/src/components/openapi/schema-tree.ts +30 -24
- package/src/components/openapi/snippets.ts +85 -6
- package/src/components/openapi/ws-client.ts +18 -2
- package/src/core/base-path.ts +62 -16
- package/src/core/config-input.ts +9 -7
- package/src/core/diagnostics.ts +2 -0
- package/src/core/directive-diagnostics.ts +99 -0
- package/src/core/frontmatter.ts +21 -18
- package/src/core/heading-markers.ts +5 -18
- package/src/core/i18n.ts +31 -3
- package/src/core/last-modified.ts +25 -3
- package/src/core/locale-links.ts +5 -1
- package/src/core/meta.ts +33 -19
- package/src/core/navigation.ts +28 -8
- package/src/core/project-graph.ts +27 -5
- package/src/core/schema.ts +24 -4
- package/src/core/sources/contentful-rich-text.ts +27 -20
- package/src/core/sources/filesystem.ts +20 -2
- package/src/core/sources/github-releases.ts +25 -6
- package/src/core/sources/lexical.ts +23 -18
- package/src/core/sources/lower.ts +201 -34
- package/src/core/sources/mdx-remote.ts +51 -16
- package/src/core/sources/normalize.ts +325 -90
- package/src/core/sources/notion.ts +52 -25
- package/src/core/sources/obsidian.ts +23 -6
- package/src/core/sources/portable-text.ts +38 -21
- package/src/core/sources/strapi-blocks.ts +20 -16
- package/src/core/sources/watch.ts +20 -7
- package/src/core/standard-schema.ts +10 -6
- package/src/core/version-cut.ts +52 -15
- package/src/deploy/artifacts.ts +27 -6
- package/src/deploy/cloudflare-negotiation.ts +4 -12
- package/src/deploy/headers.ts +8 -4
- package/src/deploy/node-headers.ts +1 -1
- package/src/deploy/platforms/cloudflare.ts +12 -3
- package/src/deploy/platforms/netlify.ts +1 -0
- package/src/deploy/platforms/node.ts +1 -0
- package/src/deploy/platforms/static.ts +1 -0
- package/src/deploy/platforms/types.ts +8 -0
- package/src/deploy/platforms/vercel.ts +1 -0
- package/src/deploy/redirects.ts +54 -18
- package/src/deploy/robots.ts +2 -2
- package/src/deploy/rss.ts +4 -3
- package/src/deploy/sitemap.ts +7 -5
- package/src/markdown/base-links.ts +34 -36
- package/src/markdown/directives.ts +242 -36
- package/src/markdown/features.ts +17 -0
- package/src/markdown/index.ts +7 -6
- package/src/markdown/mdast.ts +5 -2
- package/src/markdown/relative-links.ts +12 -4
- package/src/og/card.ts +129 -5
- package/src/og/derive.ts +41 -31
- package/src/og/index.ts +1 -0
- package/src/openapi/asyncapi.ts +4 -2
- package/src/openapi/model.ts +22 -14
- package/src/openapi/proxy.ts +63 -10
- package/src/openapi/render-mdx.ts +41 -2
- package/src/registry/eject.ts +181 -24
- package/src/search/adapters/version-scope.ts +30 -0
- package/src/search/documents.ts +67 -26
- package/src/search/popular.ts +2 -1
- package/src/seo/jsonld.ts +7 -3
- package/src/translate/meta.ts +68 -24
- package/src/translate/run.ts +3 -3
- package/src/translate/validate.ts +4 -1
- package/src/translate/work-list.ts +56 -17
- package/dist/cli/chunk-5r8g91qn.js.map +0 -34
- package/dist/cli/chunk-7mbqtmgb.js.map +0 -10
- package/dist/cli/chunk-ddndchfr.js.map +0 -14
- package/dist/cli/chunk-esh98wmb.js +0 -23
- package/dist/cli/chunk-esh98wmb.js.map +0 -10
- package/dist/cli/chunk-hr8ne106.js.map +0 -13
- package/dist/cli/chunk-j85scx15.js.map +0 -10
- package/dist/cli/chunk-pbg5a4s3.js.map +0 -19
- package/dist/cli/chunk-wdrt2k2v.js.map +0 -10
- /package/dist/cli/{chunk-6k4ftwze.js.map → chunk-1d7ve1dm.js.map} +0 -0
- /package/dist/cli/{chunk-zp79m0ts.js.map → chunk-2eytanqx.js.map} +0 -0
- /package/dist/cli/{chunk-g698a744.js.map → chunk-2z928egk.js.map} +0 -0
- /package/dist/cli/{chunk-mqc662a6.js.map → chunk-364znk6q.js.map} +0 -0
- /package/dist/cli/{chunk-k7pj68a8.js.map → chunk-5f86nr5m.js.map} +0 -0
- /package/dist/cli/{chunk-91ws1n6j.js.map → chunk-5xvm6tfj.js.map} +0 -0
- /package/dist/cli/{chunk-w4bxdvsa.js.map → chunk-6dsbexzp.js.map} +0 -0
- /package/dist/cli/{chunk-00gs3wqs.js.map → chunk-8ktnccpt.js.map} +0 -0
- /package/dist/cli/{chunk-bbnwccaz.js.map → chunk-a58773jm.js.map} +0 -0
- /package/dist/cli/{chunk-sqw4ekg1.js.map → chunk-b07cmahc.js.map} +0 -0
- /package/dist/cli/{chunk-4e9b9ra6.js.map → chunk-ey84smr6.js.map} +0 -0
- /package/dist/cli/{chunk-d1v5rhy0.js.map → chunk-g4hq16wv.js.map} +0 -0
- /package/dist/cli/{chunk-qkb5a8sa.js.map → chunk-pehfxfta.js.map} +0 -0
- /package/dist/cli/{chunk-fsmrqk8a.js.map → chunk-r20tn01b.js.map} +0 -0
- /package/dist/cli/{chunk-bfwp9vp6.js.map → chunk-r9rcc4w7.js.map} +0 -0
- /package/dist/cli/{chunk-h7k3nq3v.js.map → chunk-tkacnehg.js.map} +0 -0
- /package/dist/cli/{chunk-h2ez8dzb.js.map → chunk-tzmab476.js.map} +0 -0
package/src/og/derive.ts
CHANGED
|
@@ -2,9 +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
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* sites keep Takumi's built-in font. Either way every card gets a Noto
|
|
6
|
+
* fallback stack for the scripts that font can't draw (Japanese, Hindi,
|
|
7
|
+
* Russian, …), which it fetches only when its text needs one.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { existsSync } from "node:fs";
|
|
@@ -19,9 +19,10 @@ import type {
|
|
|
19
19
|
import { GOOGLE_FONTS, isFontSlug, localeFontSubsets } from "../theme/fonts.ts";
|
|
20
20
|
import type { OgFont, OgFontFamilies, OgLocalFont } from "./card.ts";
|
|
21
21
|
|
|
22
|
-
/** Fonts
|
|
22
|
+
/** Fonts, script fallbacks, and per-role families for the OG endpoint. */
|
|
23
23
|
export interface DerivedOgFonts {
|
|
24
24
|
families?: OgFontFamilies;
|
|
25
|
+
fallbacks?: OgFallbackFont[];
|
|
25
26
|
fonts: OgFont[];
|
|
26
27
|
}
|
|
27
28
|
|
|
@@ -165,17 +166,17 @@ export const deriveOgFonts = (
|
|
|
165
166
|
/**
|
|
166
167
|
* The family Takumi renders a card in when no font is loaded: its embedded
|
|
167
168
|
* Geist, which covers Latin only. Naming it keeps a card's Latin text in that
|
|
168
|
-
* face once
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
169
|
+
* face once fallbacks are loaded, since Takumi otherwise tries loaded fonts
|
|
170
|
+
* first; a glyph Geist lacks still falls back to them. Were Takumi to rename
|
|
171
|
+
* it, the name would resolve to nothing and cards would fall back to the
|
|
172
|
+
* loaded fonts, never to tofu.
|
|
172
173
|
*/
|
|
173
174
|
const BUILT_IN_FAMILY = "Geist";
|
|
174
175
|
|
|
175
176
|
/**
|
|
176
177
|
* The Google Noto family that draws each language's script, keyed by BCP 47
|
|
177
178
|
* language subtag, for scripts beyond the Latin, Cyrillic, Greek, and
|
|
178
|
-
* Vietnamese that `Noto Sans` covers (see {@link
|
|
179
|
+
* Vietnamese that `Noto Sans` covers (see {@link localeFamily}). Keep keys
|
|
179
180
|
* alphabetical.
|
|
180
181
|
*/
|
|
181
182
|
const SCRIPT_FAMILIES = {
|
|
@@ -219,8 +220,8 @@ const isScriptLanguage = (
|
|
|
219
220
|
): language is keyof typeof SCRIPT_FAMILIES =>
|
|
220
221
|
Object.hasOwn(SCRIPT_FAMILIES, language);
|
|
221
222
|
|
|
222
|
-
/** A
|
|
223
|
-
export interface
|
|
223
|
+
/** A script fallback: a Google family at the weights the card renders. */
|
|
224
|
+
export interface OgFallbackFont {
|
|
224
225
|
name: string;
|
|
225
226
|
weight: number[];
|
|
226
227
|
}
|
|
@@ -246,16 +247,28 @@ const localeFamily = (locale: string): string | null => {
|
|
|
246
247
|
};
|
|
247
248
|
|
|
248
249
|
/**
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
*
|
|
252
|
-
*
|
|
253
|
-
* the same site pulls nothing extra.
|
|
250
|
+
* Every script's fallback, in the order a card tries them: `Noto Sans` for
|
|
251
|
+
* extended Latin, Cyrillic, Greek, and Vietnamese, then a family per script.
|
|
252
|
+
* The CJK families all draw Han ideographs, so the first one wins them —
|
|
253
|
+
* Japanese forms, unless a configured locale moves its own family ahead.
|
|
254
254
|
*/
|
|
255
|
-
|
|
255
|
+
const DEFAULT_FALLBACKS = [
|
|
256
|
+
"Noto Sans",
|
|
257
|
+
...new Set(Object.values(SCRIPT_FAMILIES)),
|
|
258
|
+
"Noto Sans TC",
|
|
259
|
+
];
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* The card's script fallbacks, at the card's weights: each configured
|
|
263
|
+
* locale's family first, so a Chinese site draws Han in Chinese forms, then
|
|
264
|
+
* every other script's, so a page in a language the site never configured
|
|
265
|
+
* still renders instead of tofu. Takumi's built-in font covers only basic
|
|
266
|
+
* Latin; the card fetches these only when its text needs a glyph beyond it,
|
|
267
|
+
* and then only the families and subsets that glyph needs.
|
|
268
|
+
*/
|
|
269
|
+
export const ogFallbackFonts = (locales: string[]): OgFallbackFont[] => {
|
|
256
270
|
const names = new Set<string>();
|
|
257
|
-
for (const
|
|
258
|
-
const family = localeFamily(locale);
|
|
271
|
+
for (const family of [...locales.map(localeFamily), ...DEFAULT_FALLBACKS]) {
|
|
259
272
|
if (family) {
|
|
260
273
|
names.add(family);
|
|
261
274
|
}
|
|
@@ -272,27 +285,24 @@ const ogFontName = (font: OgFont): string =>
|
|
|
272
285
|
isGoogleFamilyName(font) ? font : font.name;
|
|
273
286
|
|
|
274
287
|
/**
|
|
275
|
-
* `derived` plus the
|
|
288
|
+
* `derived` plus the script fallbacks it doesn't already load. A card that
|
|
276
289
|
* names no family is pinned to {@link BUILT_IN_FAMILY}, so its Latin text
|
|
277
290
|
* keeps the face it had before any fallback was loaded.
|
|
278
291
|
*/
|
|
279
|
-
const
|
|
292
|
+
const withFallbacks = (
|
|
280
293
|
derived: DerivedOgFonts,
|
|
281
294
|
locales: string[]
|
|
282
295
|
): DerivedOgFonts => {
|
|
283
296
|
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
297
|
return {
|
|
298
|
+
fallbacks: ogFallbackFonts(locales).filter(
|
|
299
|
+
(font) => !loaded.has(font.name)
|
|
300
|
+
),
|
|
291
301
|
families: derived.families ?? {
|
|
292
302
|
body: BUILT_IN_FAMILY,
|
|
293
303
|
title: BUILT_IN_FAMILY,
|
|
294
304
|
},
|
|
295
|
-
fonts:
|
|
305
|
+
fonts: derived.fonts,
|
|
296
306
|
};
|
|
297
307
|
};
|
|
298
308
|
|
|
@@ -307,8 +317,8 @@ export const resolveOgFontSources = (fonts: OgFont[], root: string): OgFont[] =>
|
|
|
307
317
|
* always wins (including `[]` to opt out, keeping the card's role styling
|
|
308
318
|
* untouched); otherwise a site that explicitly set `theme.fonts` gets its
|
|
309
319
|
* display/body fonts derived so cards match the site without extra config,
|
|
310
|
-
* and either way
|
|
311
|
-
*
|
|
320
|
+
* and either way every card gets the script fallbacks, the configured
|
|
321
|
+
* locales' first.
|
|
312
322
|
*/
|
|
313
323
|
export const resolveOgFonts = (
|
|
314
324
|
options: {
|
|
@@ -328,7 +338,7 @@ export const resolveOgFonts = (
|
|
|
328
338
|
const derived = options.themeFontsConfigured
|
|
329
339
|
? deriveOgFonts(options.themeFonts, root)
|
|
330
340
|
: { fonts: [] };
|
|
331
|
-
return
|
|
341
|
+
return withFallbacks(derived, options.locales ?? []);
|
|
332
342
|
};
|
|
333
343
|
|
|
334
344
|
/**
|
package/src/og/index.ts
CHANGED
package/src/openapi/asyncapi.ts
CHANGED
|
@@ -46,7 +46,8 @@ export interface AsyncApiChannelObject {
|
|
|
46
46
|
messages?: Record<string, AsyncApiRefLike>;
|
|
47
47
|
parameters?: Record<string, AsyncApiRefLike>;
|
|
48
48
|
servers?: AsyncApiRefLike[];
|
|
49
|
-
|
|
49
|
+
/** Protocol-keyed binding objects, or a `$ref` to a components entry. */
|
|
50
|
+
bindings?: Record<string, AsyncApiSpecValue>;
|
|
50
51
|
[key: string]: AsyncApiSpecValue;
|
|
51
52
|
}
|
|
52
53
|
|
|
@@ -61,7 +62,8 @@ export interface AsyncApiOperationObject {
|
|
|
61
62
|
tags?: { name?: string; description?: string }[];
|
|
62
63
|
security?: AsyncApiRefLike[];
|
|
63
64
|
messages?: AsyncApiRefLike[];
|
|
64
|
-
|
|
65
|
+
/** Protocol-keyed binding objects, or a `$ref` to a components entry. */
|
|
66
|
+
bindings?: Record<string, AsyncApiSpecValue>;
|
|
65
67
|
[key: string]: AsyncApiSpecValue;
|
|
66
68
|
}
|
|
67
69
|
|
package/src/openapi/model.ts
CHANGED
|
@@ -260,9 +260,18 @@ const isOperation = (
|
|
|
260
260
|
value: OperationObject | undefined
|
|
261
261
|
): value is OperationObject => typeof value === "object" && value !== null;
|
|
262
262
|
|
|
263
|
-
/**
|
|
264
|
-
const
|
|
265
|
-
typeof
|
|
263
|
+
/** Whether a spec value is text YAML may have read as a number. */
|
|
264
|
+
const isText = <Value>(value: Value): value is Value & (string | number) =>
|
|
265
|
+
typeof value === "string" || typeof value === "number";
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* A spec field that names something, as a string. YAML reads an unquoted
|
|
269
|
+
* `operationId: 404` or `tags: [2024]` as a number, which still names the
|
|
270
|
+
* operation or tag; anything else a hand-written spec puts there counts as
|
|
271
|
+
* absent.
|
|
272
|
+
*/
|
|
273
|
+
const specText = <Value>(value: Value): string | undefined =>
|
|
274
|
+
isText(value) ? String(value) : undefined;
|
|
266
275
|
|
|
267
276
|
/**
|
|
268
277
|
* Assign each distinct tag name a unique slug. `slugify` can collapse
|
|
@@ -295,9 +304,6 @@ export const tagSlugger = (): ((name: string) => string) => {
|
|
|
295
304
|
/** An operation before the collector assigns its unique key and route. */
|
|
296
305
|
type CollectedOperation = Omit<ApiOperationRef, "route" | "tagSlug">;
|
|
297
306
|
|
|
298
|
-
/** A document's declared tag entry (`tags[n]`). */
|
|
299
|
-
type SpecTag = NonNullable<ApiDocument["tags"]>[number];
|
|
300
|
-
|
|
301
307
|
/** The flattened output both extractors produce. */
|
|
302
308
|
export interface CollectedOperations {
|
|
303
309
|
operations: ApiOperationRef[];
|
|
@@ -391,9 +397,10 @@ export const extractOperations = (
|
|
|
391
397
|
): ExtractedOperations => {
|
|
392
398
|
const warnings: string[] = [];
|
|
393
399
|
const tagMeta = new Map(
|
|
394
|
-
(document.tags ?? [])
|
|
395
|
-
.
|
|
396
|
-
|
|
400
|
+
(document.tags ?? []).flatMap((tag): [string, string][] => {
|
|
401
|
+
const name = specText(tag.name);
|
|
402
|
+
return name === undefined ? [] : [[name, tag.description ?? ""]];
|
|
403
|
+
})
|
|
397
404
|
);
|
|
398
405
|
const collector = operationCollector(baseRoute, tagMeta);
|
|
399
406
|
|
|
@@ -413,15 +420,16 @@ export const extractOperations = (
|
|
|
413
420
|
if (!isOperation(operation)) {
|
|
414
421
|
continue;
|
|
415
422
|
}
|
|
423
|
+
const operationId = specText(operation.operationId);
|
|
416
424
|
collector.add({
|
|
417
425
|
deprecated: operation.deprecated ?? false,
|
|
418
|
-
description: operation.description ?? "",
|
|
419
|
-
key: operationKey(method, path,
|
|
426
|
+
description: specText(operation.description) ?? "",
|
|
427
|
+
key: operationKey(method, path, operationId),
|
|
420
428
|
method,
|
|
421
|
-
operationId
|
|
429
|
+
operationId,
|
|
422
430
|
path,
|
|
423
|
-
summary: operation.summary ?? "",
|
|
424
|
-
tag: operation.tags?.[0] ?? UNTAGGED,
|
|
431
|
+
summary: specText(operation.summary) ?? "",
|
|
432
|
+
tag: specText(operation.tags?.[0]) ?? UNTAGGED,
|
|
425
433
|
});
|
|
426
434
|
}
|
|
427
435
|
}
|
package/src/openapi/proxy.ts
CHANGED
|
@@ -12,25 +12,52 @@
|
|
|
12
12
|
* network and stays safe to bundle into the generated endpoint file.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
+
import { PROXY_HEADERS_HEADER } from "../components/openapi/request.ts";
|
|
15
16
|
import { readCappedBody } from "../core/request-body.ts";
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
|
-
* Request headers never forwarded upstream
|
|
19
|
-
* connection (not the upstream one),
|
|
20
|
-
* misattribute the docs site,
|
|
21
|
-
* to an arbitrary target
|
|
22
|
-
* by the
|
|
19
|
+
* Request headers never forwarded upstream, even when the playground names
|
|
20
|
+
* them: hop-by-hop headers describe this connection (not the upstream one),
|
|
21
|
+
* `host`/`origin`/`referer` would leak or misattribute the docs site, `cookie`
|
|
22
|
+
* would forward reader credentials to an arbitrary target, and the rest are
|
|
23
|
+
* set or rewritten by the platform in front of the docs server — the
|
|
24
|
+
* reader's address, the edge's own identity — whatever the browser sent.
|
|
25
|
+
* `accept-encoding`/`content-length` are recomputed by the runtime's own
|
|
26
|
+
* fetch.
|
|
23
27
|
*/
|
|
24
28
|
const REQUEST_DROP = {
|
|
25
29
|
"accept-encoding": true,
|
|
30
|
+
"cdn-loop": true,
|
|
26
31
|
connection: true,
|
|
27
32
|
"content-length": true,
|
|
28
33
|
cookie: true,
|
|
34
|
+
forwarded: true,
|
|
29
35
|
host: true,
|
|
36
|
+
"keep-alive": true,
|
|
30
37
|
origin: true,
|
|
38
|
+
"proxy-authorization": true,
|
|
31
39
|
referer: true,
|
|
40
|
+
te: true,
|
|
41
|
+
trailer: true,
|
|
42
|
+
"transfer-encoding": true,
|
|
43
|
+
"true-client-ip": true,
|
|
44
|
+
upgrade: true,
|
|
45
|
+
via: true,
|
|
46
|
+
"x-client-ip": true,
|
|
47
|
+
"x-real-ip": true,
|
|
32
48
|
} satisfies Record<string, true>;
|
|
33
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Platform header families, dropped like {@link REQUEST_DROP}: Cloudflare's
|
|
52
|
+
* (`cf-connecting-ip`, the Access `cf-access-jwt-assertion`), the
|
|
53
|
+
* `x-forwarded-*` set, Vercel's (`x-vercel-oidc-token`), Netlify's, Fly's,
|
|
54
|
+
* and AWS load balancers' (`x-amzn-oidc-data`).
|
|
55
|
+
*/
|
|
56
|
+
const PLATFORM_HEADER = /^(?:cf-|x-forwarded-|x-vercel-|x-nf-|fly-|x-amzn-)/u;
|
|
57
|
+
|
|
58
|
+
/** The {@link PROXY_HEADERS_HEADER} name as `Headers` iterates it. */
|
|
59
|
+
const DECLARED = PROXY_HEADERS_HEADER.toLowerCase();
|
|
60
|
+
|
|
34
61
|
/**
|
|
35
62
|
* Upstream response headers never returned to the browser: the runtime's
|
|
36
63
|
* fetch already decoded the body (so `content-encoding`/`content-length` no
|
|
@@ -99,20 +126,45 @@ const DOCUMENT_TYPE =
|
|
|
99
126
|
const badRequest = (error: string): Response =>
|
|
100
127
|
Response.json({ error }, { status: 400 });
|
|
101
128
|
|
|
102
|
-
/**
|
|
129
|
+
/**
|
|
130
|
+
* Copy headers, skipping the given denylist and any name `keep` rejects
|
|
131
|
+
* (names are already lowercase).
|
|
132
|
+
*/
|
|
103
133
|
const filterHeaders = (
|
|
104
134
|
source: Headers,
|
|
105
|
-
drop: Record<string, true
|
|
135
|
+
drop: Record<string, true>,
|
|
136
|
+
keep: (name: string) => boolean = () => true
|
|
106
137
|
): Headers => {
|
|
107
138
|
const headers = new Headers();
|
|
108
139
|
for (const [name, value] of source) {
|
|
109
|
-
if (!drop[name]) {
|
|
140
|
+
if (!drop[name] && keep(name)) {
|
|
110
141
|
headers.set(name, value);
|
|
111
142
|
}
|
|
112
143
|
}
|
|
113
144
|
return headers;
|
|
114
145
|
};
|
|
115
146
|
|
|
147
|
+
/**
|
|
148
|
+
* The headers to send upstream: only those the playground set itself, which
|
|
149
|
+
* it names in {@link PROXY_HEADERS_HEADER}. Forwarding everything else minus
|
|
150
|
+
* a denylist would hand the documented API whatever the browser and the
|
|
151
|
+
* platform attach on their own — the HTTP Basic credentials of a docs site
|
|
152
|
+
* behind a password (a same-origin fetch with no `Authorization` of its own
|
|
153
|
+
* carries them), a Cloudflare Access assertion, a Vercel OIDC token.
|
|
154
|
+
*/
|
|
155
|
+
const forwardedHeaders = (source: Headers): Headers => {
|
|
156
|
+
const named = new Set(
|
|
157
|
+
(source.get(DECLARED) ?? "")
|
|
158
|
+
.split(",")
|
|
159
|
+
.map((name) => name.trim().toLowerCase())
|
|
160
|
+
);
|
|
161
|
+
return filterHeaders(
|
|
162
|
+
source,
|
|
163
|
+
REQUEST_DROP,
|
|
164
|
+
(name) => named.has(name) && !PLATFORM_HEADER.test(name)
|
|
165
|
+
);
|
|
166
|
+
};
|
|
167
|
+
|
|
116
168
|
/** A 403 for a target no configured spec declares as one of its servers. */
|
|
117
169
|
const forbidden = (origin: string): Response =>
|
|
118
170
|
Response.json(
|
|
@@ -186,7 +238,8 @@ const followUpstream = async (args: {
|
|
|
186
238
|
/**
|
|
187
239
|
* Build the `/_api-proxy` fetch handler. The client sends its REAL method,
|
|
188
240
|
* headers, and body to `?url=<encodeURIComponent(target)>`; the handler
|
|
189
|
-
* forwards
|
|
241
|
+
* forwards the method, the body, and the headers the client names (see
|
|
242
|
+
* {@link forwardedHeaders}), and mirrors the upstream response
|
|
190
243
|
* (minus {@link RESPONSE_DROP}) with an `x-blume-proxy` marker. An unreachable
|
|
191
244
|
* upstream is a 502 with a JSON `error`.
|
|
192
245
|
*
|
|
@@ -252,7 +305,7 @@ export const createPlaygroundProxyHandler = (
|
|
|
252
305
|
allowed,
|
|
253
306
|
body,
|
|
254
307
|
fetchImpl,
|
|
255
|
-
headers:
|
|
308
|
+
headers: forwardedHeaders(request.headers),
|
|
256
309
|
hop: 0,
|
|
257
310
|
method: request.method,
|
|
258
311
|
signal: AbortSignal.timeout(timeoutMs),
|
|
@@ -126,6 +126,45 @@ const mdxSafe = (text: string): string => {
|
|
|
126
126
|
return out + escapeProse(text.slice(cursor));
|
|
127
127
|
};
|
|
128
128
|
|
|
129
|
+
// A fence opener: up to three columns of indentation, then a run of three or
|
|
130
|
+
// more backticks or tildes.
|
|
131
|
+
const FENCE_OPEN = /^ {0,3}(?<fence>`{3,}|~{3,})/u;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Close a fenced code block the text leaves open. An unclosed fence runs to
|
|
135
|
+
* the end of the document, so the component appended after a description
|
|
136
|
+
* would render as code, and the page would lose its parameters, responses,
|
|
137
|
+
* and playground. Only the last top-level block can run that far — a fence in
|
|
138
|
+
* a quote or list closes with its container — and it's read from the same
|
|
139
|
+
* `<`-masked parse as `mdxSafe`, since the emitted MDX has no HTML blocks to
|
|
140
|
+
* hide a fence in.
|
|
141
|
+
*/
|
|
142
|
+
const closeOpenFence = (text: string): string => {
|
|
143
|
+
const last = fromMarkdown(text.replaceAll("<", HTML_MASK)).children.at(-1);
|
|
144
|
+
if (last?.type !== "code") {
|
|
145
|
+
return text;
|
|
146
|
+
}
|
|
147
|
+
// fromMarkdown always stamps positions; 0 is an unreachable guard.
|
|
148
|
+
const block = text.slice(last.position?.start.offset ?? 0);
|
|
149
|
+
const fence = FENCE_OPEN.exec(block)?.groups?.fence;
|
|
150
|
+
// No fence: indented code, which MDX reads as a paragraph.
|
|
151
|
+
if (!fence) {
|
|
152
|
+
return text;
|
|
153
|
+
}
|
|
154
|
+
const lines = block.split("\n");
|
|
155
|
+
const closer = new RegExp(
|
|
156
|
+
`^ {0,3}${fence[0]}{${fence.length},}[ \\t\\r]*$`,
|
|
157
|
+
"u"
|
|
158
|
+
);
|
|
159
|
+
return lines.length > 1 && closer.test(lines.at(-1) ?? "")
|
|
160
|
+
? text
|
|
161
|
+
: `${text}\n${fence}`;
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
/** Spec prose as MDX that is safe to follow with a component. */
|
|
165
|
+
const descriptionMdx = (text: string): string =>
|
|
166
|
+
mdxSafe(closeOpenFence(text.trim()));
|
|
167
|
+
|
|
129
168
|
/**
|
|
130
169
|
* Frontmatter emitted for one operation or overview page. Boolean flags are
|
|
131
170
|
* assigned only when set, so absent keys stay absent in the staged MDX.
|
|
@@ -249,7 +288,7 @@ const operationDescription = (
|
|
|
249
288
|
/** Prepend a markdown description (if any) above a component invocation. */
|
|
250
289
|
const withDescription = (description: string, component: string): string =>
|
|
251
290
|
description.trim()
|
|
252
|
-
? `${
|
|
291
|
+
? `${descriptionMdx(description)}\n\n${component}`
|
|
253
292
|
: component;
|
|
254
293
|
|
|
255
294
|
export const operationMdx = (
|
|
@@ -365,7 +404,7 @@ export const overviewMdx = (
|
|
|
365
404
|
continue;
|
|
366
405
|
}
|
|
367
406
|
const description = tag.description.trim()
|
|
368
|
-
? [
|
|
407
|
+
? [descriptionMdx(tag.description)]
|
|
369
408
|
: [];
|
|
370
409
|
tagSections.push(
|
|
371
410
|
[
|