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
|
@@ -9,42 +9,66 @@ import { isSafeHref } from "../safe-href.ts";
|
|
|
9
9
|
|
|
10
10
|
// Markdown/raw-HTML structure characters. Rich-text leaves are *plain text* —
|
|
11
11
|
// formatting arrives as marks, never as syntax in the text — so a literal
|
|
12
|
-
// `*`, `_`, `[`, backtick, `~`, or `<` typed in the CMS must render as
|
|
13
|
-
// itself. Unescaped, it opened emphasis
|
|
14
|
-
// let CMS prose inject raw HTML into
|
|
15
|
-
// expression once an entry is written
|
|
12
|
+
// `*`, `_`, `[`, backtick, `~`, `^`, or `<` typed in the CMS must render as
|
|
13
|
+
// itself. Unescaped, it opened emphasis, a code span, or a superscript
|
|
14
|
+
// (`2^10 and 2^20`) mid-paragraph, and `<` let CMS prose inject raw HTML into
|
|
15
|
+
// the rendered page. `{` and `}` open an expression once an entry is written
|
|
16
|
+
// as MDX (see {@link writesMdx}), and `$$` opens math there. CommonMark
|
|
16
17
|
// backslash-escapes every ASCII punctuation character, so `\*` is always the
|
|
17
18
|
// literal asterisk, in `.md` and `.mdx` alike.
|
|
18
|
-
const MARKDOWN_SPECIALS = /[\\`*_{}[\]
|
|
19
|
+
const MARKDOWN_SPECIALS = /[\\`*_{}[\]~<$^]/gu;
|
|
20
|
+
|
|
21
|
+
// MDX reads a colon ahead of a letter or digit as a text directive (`10:30`,
|
|
22
|
+
// `og:image`, `pets:read`), so that colon is escaped too. One inside a bare
|
|
23
|
+
// URL (`http://localhost:3000`) stays as written: an escape there would end
|
|
24
|
+
// the autolink early.
|
|
25
|
+
const DIRECTIVE_COLON = /(?<!(?:[a-z][\w+.-]*:\/\/|www\.)\S*):(?=[a-z\d])/giu;
|
|
26
|
+
|
|
27
|
+
// GFM autolinks a bare `http://`, `https://`, or `www.` URL in prose, taking
|
|
28
|
+
// its characters verbatim up to the next space or `<`: an escape inside one
|
|
29
|
+
// reaches the href as a backslash (`a\_b`), so prose leaves a bare URL's
|
|
30
|
+
// characters as written. That includes a trailing `*`, `_`, or `~`, which
|
|
31
|
+
// GFM leaves out of the link: escaped, its backslash would still land inside.
|
|
32
|
+
// A link label is the exception — GFM links nothing inside one, so there
|
|
33
|
+
// every special is escaped (see `linkParts`).
|
|
34
|
+
const BARE_URL = /\b(?:https?:\/\/|www\.)[\w-][^\s<]*/giu;
|
|
19
35
|
|
|
20
36
|
// An entity or numeric character reference (`©`, `&`) is decoded
|
|
21
37
|
// even in plain text, so one typed in the CMS would render as the character
|
|
22
38
|
// it names rather than as written. A bare `&` is left alone.
|
|
23
39
|
const CHARACTER_REFERENCE = /&(?=#?[a-z0-9]+;)/giu;
|
|
24
40
|
|
|
25
|
-
// Block syntax is only syntax at the start of a line: `#
|
|
26
|
-
// `1. `/`1) ` need the space (or line end) to
|
|
27
|
-
// while `>` opens a quote on its own, and a
|
|
28
|
-
// line above it into a heading (or, alone,
|
|
29
|
-
//
|
|
30
|
-
//
|
|
41
|
+
// Block syntax is only syntax at the start of a line: `# ` (through
|
|
42
|
+
// `###### `), `- `, `+ ` and `1. `/`1) ` need the space (or line end) to
|
|
43
|
+
// become a heading or list item, while `>` opens a quote on its own, and a
|
|
44
|
+
// line of only `=` or `-` turns the line above it into a heading (or, alone,
|
|
45
|
+
// into a rule). Indentation of up to three spaces still counts. A CMS
|
|
46
|
+
// paragraph that begins with one of these — a soft break inside it counts as
|
|
47
|
+
// a line start too — must stay prose.
|
|
31
48
|
const BLOCK_START =
|
|
32
|
-
/^(?<
|
|
49
|
+
/^(?<indent>[ \t]*)(?:(?<marker>#+|[+-]|\d+[.)])(?=[ \t]|$)|(?<quote>>)|(?<rule>=+|-{2,})(?=[ \t]*$))/gmu;
|
|
50
|
+
|
|
51
|
+
// MDX reads a top-level line that opens with `import` or `export` followed by
|
|
52
|
+
// a space, a tab, `{`, or `*` as an ESM statement, and prose like "import the
|
|
53
|
+
// CSV first" then fails the whole page. The keyword's first letter as a
|
|
54
|
+
// character reference renders the same in `.md` and `.mdx`, and MDX no longer
|
|
55
|
+
// sees the keyword. The keyword is escaped wherever a word ends on it, which
|
|
56
|
+
// covers every character that opens a statement. Only a block's first line
|
|
57
|
+
// counts; a blank line inside the text starts a new block.
|
|
58
|
+
const ESM_START = /(?<=^|\n[ \t]*\n)(?<keyword>import|export)(?![\w$])/gu;
|
|
33
59
|
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
// in `.md` and `.mdx`, and MDX no longer sees the keyword. Only a paragraph's
|
|
38
|
-
// first line counts; a blank line inside a run starts a new paragraph.
|
|
39
|
-
const ESM_START = /(?<=^|\n[ \t]*\n)(?<keyword>import|export)(?= )/gu;
|
|
60
|
+
// Whitespace opening a block: four columns of it (or a tab) make an indented
|
|
61
|
+
// code block, and a paragraph drops it when it renders anyway.
|
|
62
|
+
const BLOCK_INDENT = /(?<=^|\n[ \t]*\n)[ \t]+/gu;
|
|
40
63
|
|
|
41
64
|
const escapeBlockStart = (text: string): string =>
|
|
42
|
-
text.replaceAll(BLOCK_START, (
|
|
65
|
+
text.replaceAll(BLOCK_START, (match: string, indent: string) => {
|
|
43
66
|
// A numbered marker escapes its punctuation (`1\.`), the rest themselves.
|
|
67
|
+
const marker = match.slice(indent.length);
|
|
44
68
|
const index = marker.search(/[.)]/u);
|
|
45
69
|
return index === -1
|
|
46
|
-
?
|
|
47
|
-
: `${marker.slice(0, index)}\\${marker.slice(index)}`;
|
|
70
|
+
? `${indent}\\${marker}`
|
|
71
|
+
: `${indent}${marker.slice(0, index)}\\${marker.slice(index)}`;
|
|
48
72
|
});
|
|
49
73
|
|
|
50
74
|
const escapeEsmStart = (text: string): string =>
|
|
@@ -53,15 +77,37 @@ const escapeEsmStart = (text: string): string =>
|
|
|
53
77
|
(keyword: string) => `&#${keyword.codePointAt(0)};${keyword.slice(1)}`
|
|
54
78
|
);
|
|
55
79
|
|
|
56
|
-
|
|
80
|
+
const escapeSpecials = (text: string): string =>
|
|
81
|
+
text.replaceAll(MARKDOWN_SPECIALS, String.raw`\$&`);
|
|
82
|
+
|
|
83
|
+
/** The escapes that apply everywhere, once the specials are escaped. */
|
|
84
|
+
const escapeRest = (text: string): string =>
|
|
57
85
|
escapeEsmStart(
|
|
58
86
|
escapeBlockStart(
|
|
59
87
|
text
|
|
60
|
-
.replaceAll(
|
|
88
|
+
.replaceAll(DIRECTIVE_COLON, String.raw`\:`)
|
|
61
89
|
.replaceAll(CHARACTER_REFERENCE, String.raw`\&`)
|
|
62
90
|
)
|
|
63
91
|
);
|
|
64
92
|
|
|
93
|
+
/**
|
|
94
|
+
* CMS text as Markdown that renders as written, wherever it lands — a link
|
|
95
|
+
* label included, where GFM autolinks nothing, so every special is escaped.
|
|
96
|
+
*/
|
|
97
|
+
export const escapeMarkdownText = (text: string): string =>
|
|
98
|
+
escapeRest(escapeSpecials(text));
|
|
99
|
+
|
|
100
|
+
/** CMS prose as {@link escapeMarkdownText}, except inside a bare URL. */
|
|
101
|
+
const escapeProse = (text: string): string => {
|
|
102
|
+
let out = "";
|
|
103
|
+
let last = 0;
|
|
104
|
+
for (const match of text.matchAll(BARE_URL)) {
|
|
105
|
+
out += escapeSpecials(text.slice(last, match.index)) + match[0];
|
|
106
|
+
last = match.index + match[0].length;
|
|
107
|
+
}
|
|
108
|
+
return escapeRest(out + escapeSpecials(text.slice(last)));
|
|
109
|
+
};
|
|
110
|
+
|
|
65
111
|
/** The marks a lowerer can put on an inline run. */
|
|
66
112
|
export interface InlineMarks {
|
|
67
113
|
bold?: boolean;
|
|
@@ -70,6 +116,20 @@ export interface InlineMarks {
|
|
|
70
116
|
strike?: boolean;
|
|
71
117
|
}
|
|
72
118
|
|
|
119
|
+
/** A run of CMS text and the marks on it. */
|
|
120
|
+
export interface InlineRun {
|
|
121
|
+
marks: InlineMarks;
|
|
122
|
+
text: string;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Inline Markdown a lowerer rendered itself: a link, an embed, a break. */
|
|
126
|
+
export interface InlineMarkdown {
|
|
127
|
+
markdown: string;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** One piece of a block's inline content, in order. */
|
|
131
|
+
export type InlinePart = InlineMarkdown | InlineRun;
|
|
132
|
+
|
|
73
133
|
const BACKTICK_RUN = /`+/gu;
|
|
74
134
|
|
|
75
135
|
/** The longest run of backticks in `text`, 0 when there is none. */
|
|
@@ -92,52 +152,195 @@ export const codeSpan = (code: string): string => {
|
|
|
92
152
|
return `${fence}${pad}${code}${pad}${fence}`;
|
|
93
153
|
};
|
|
94
154
|
|
|
155
|
+
// Two struck runs side by side meet as `~~~~`: a closer and an opener that
|
|
156
|
+
// GFM reads as one four-tilde run, so both print as literal tildes. A literal
|
|
157
|
+
// `~` is escaped outside a bare URL, so an unescaped `~~~~` outside a code
|
|
158
|
+
// span, a link destination, or a bare URL is exactly that seam, and dropping
|
|
159
|
+
// it merges the runs into one strikethrough. The other alternatives only step
|
|
160
|
+
// over escapes, code spans, destinations, and bare URLs so the seam is never
|
|
161
|
+
// matched inside them.
|
|
162
|
+
const SEAM_STEPS =
|
|
163
|
+
/\\[\s\S]|(?<ticks>`+)[\s\S]*?(?<!`)\k<ticks>(?!`)|\]\((?:<(?:\\.|[^\\<>\n])*>|(?:\\.|[^\\\s)])+)\)/u;
|
|
164
|
+
const STRIKE_SEAM = new RegExp(
|
|
165
|
+
`${SEAM_STEPS.source}|${BARE_URL.source}|~~~~`,
|
|
166
|
+
"giu"
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
// A heading that ends in a space and a run of `#` reads that run as the
|
|
170
|
+
// optional closing sequence and drops it (`## Issue #` renders "Issue").
|
|
171
|
+
const CLOSING_HASHES = /(?<=[ \t])#+(?=[ \t]*$)/u;
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* A block's inline text, guarded where only the whole block can tell. Runs
|
|
175
|
+
* are escaped one at a time, but "import" ending one run and a space or code
|
|
176
|
+
* span opening the next form an MDX `import` statement only once they are
|
|
177
|
+
* joined, whitespace the first runs put in front of a block turns it into
|
|
178
|
+
* an indented code block, two struck runs fuse into literal tildes, and a
|
|
179
|
+
* trailing ` #` is a heading's closing sequence. Apply it to the joined text
|
|
180
|
+
* of a paragraph, heading, quote, or list item — never to code.
|
|
181
|
+
*/
|
|
182
|
+
export const guardBlockStart = (text: string): string =>
|
|
183
|
+
escapeEsmStart(
|
|
184
|
+
text
|
|
185
|
+
.replaceAll(BLOCK_INDENT, "")
|
|
186
|
+
.replaceAll(STRIKE_SEAM, (match: string) =>
|
|
187
|
+
match === "~~~~" ? "" : match
|
|
188
|
+
)
|
|
189
|
+
.replace(CLOSING_HASHES, String.raw`\$&`)
|
|
190
|
+
);
|
|
191
|
+
|
|
95
192
|
const EDGE_SPACE = /^(?<lead>\s*)(?<body>[\s\S]*?)(?<trail>\s*)$/u;
|
|
96
193
|
|
|
97
194
|
/**
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
* escaped first. Whitespace at either edge moves outside the delimiters,
|
|
101
|
-
* since `** bold **` is not strong emphasis but `**bold**` is — editors
|
|
102
|
-
* routinely bold a word together with the space after it.
|
|
195
|
+
* `text` with `render` applied between its edge whitespace, or as it is when
|
|
196
|
+
* it is only whitespace.
|
|
103
197
|
*/
|
|
104
|
-
|
|
198
|
+
const betweenEdges = (
|
|
199
|
+
text: string,
|
|
200
|
+
render: (body: string) => string
|
|
201
|
+
): string => {
|
|
105
202
|
const match = EDGE_SPACE.exec(text);
|
|
106
|
-
const lead = match?.groups?.lead ?? "";
|
|
107
203
|
const body = match?.groups?.body ?? "";
|
|
108
|
-
const trail = match?.groups?.trail ?? "";
|
|
109
204
|
if (body === "") {
|
|
110
205
|
return text;
|
|
111
206
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
207
|
+
return `${match?.groups?.lead ?? ""}${render(body)}${match?.groups?.trail ?? ""}`;
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const MARK_NAMES = ["bold", "code", "italic", "strike"] as const;
|
|
211
|
+
|
|
212
|
+
const sameMarks = (
|
|
213
|
+
a: InlineMarks,
|
|
214
|
+
b: InlineMarks,
|
|
215
|
+
names: readonly (keyof InlineMarks)[] = MARK_NAMES
|
|
216
|
+
): boolean => names.every((mark) => Boolean(a[mark]) === Boolean(b[mark]));
|
|
217
|
+
|
|
218
|
+
/** A stretch of neighboring runs that share marks, and those marks. */
|
|
219
|
+
interface RunGroup {
|
|
220
|
+
marks: InlineMarks;
|
|
221
|
+
runs: InlineRun[];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** Runs split into stretches of neighbors that share the `names` marks. */
|
|
225
|
+
const groupRuns = (
|
|
226
|
+
runs: InlineRun[],
|
|
227
|
+
names: readonly (keyof InlineMarks)[] = MARK_NAMES
|
|
228
|
+
): RunGroup[] => {
|
|
229
|
+
const groups: RunGroup[] = [];
|
|
230
|
+
for (const run of runs) {
|
|
231
|
+
const group = groups.at(-1);
|
|
232
|
+
if (group && sameMarks(group.marks, run.marks, names)) {
|
|
233
|
+
group.runs.push(run);
|
|
234
|
+
} else {
|
|
235
|
+
groups.push({ marks: run.marks, runs: [run] });
|
|
236
|
+
}
|
|
118
237
|
}
|
|
119
|
-
|
|
120
|
-
|
|
238
|
+
return groups;
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Consecutive text runs as Markdown. Neighbors with the same bold, italic,
|
|
243
|
+
* and strikethrough share one set of delimiters — rendered one run at a
|
|
244
|
+
* time, two bold runs met as `****` and two italic ones as `**`, which print
|
|
245
|
+
* as literal asterisks. Code stays verbatim inside the backticks (an escape
|
|
246
|
+
* would render as a backslash), and neighboring code joins into one span;
|
|
247
|
+
* everything else is escaped. Whitespace at either edge moves outside the
|
|
248
|
+
* delimiters, since `** bold **` is not strong emphasis but `**bold**` is —
|
|
249
|
+
* editors routinely bold a word together with the space after it.
|
|
250
|
+
*/
|
|
251
|
+
const renderRuns = (
|
|
252
|
+
runs: InlineRun[],
|
|
253
|
+
escape: (text: string) => string
|
|
254
|
+
): string =>
|
|
255
|
+
groupRuns(runs, ["bold", "italic", "strike"])
|
|
256
|
+
.map(({ marks, runs: shared }) => {
|
|
257
|
+
const inner = groupRuns(shared)
|
|
258
|
+
.map((same) =>
|
|
259
|
+
betweenEdges(same.runs.map((run) => run.text).join(""), (body) =>
|
|
260
|
+
same.marks.code ? codeSpan(body) : escape(body)
|
|
261
|
+
)
|
|
262
|
+
)
|
|
263
|
+
.join("");
|
|
264
|
+
return betweenEdges(inner, (body) => {
|
|
265
|
+
let out = body;
|
|
266
|
+
if (marks.bold) {
|
|
267
|
+
out = `**${out}**`;
|
|
268
|
+
}
|
|
269
|
+
if (marks.italic) {
|
|
270
|
+
out = `*${out}*`;
|
|
271
|
+
}
|
|
272
|
+
if (marks.strike) {
|
|
273
|
+
out = `~~${out}~~`;
|
|
274
|
+
}
|
|
275
|
+
return out;
|
|
276
|
+
});
|
|
277
|
+
})
|
|
278
|
+
.join("");
|
|
279
|
+
|
|
280
|
+
/** Inline parts as Markdown, each stretch of runs through {@link renderRuns}. */
|
|
281
|
+
const renderParts = (
|
|
282
|
+
parts: InlinePart[],
|
|
283
|
+
escape: (text: string) => string
|
|
284
|
+
): string => {
|
|
285
|
+
let out = "";
|
|
286
|
+
let runs: InlineRun[] = [];
|
|
287
|
+
for (const part of parts) {
|
|
288
|
+
if ("markdown" in part) {
|
|
289
|
+
out += renderRuns(runs, escape) + part.markdown;
|
|
290
|
+
runs = [];
|
|
291
|
+
} else {
|
|
292
|
+
runs.push(part);
|
|
293
|
+
}
|
|
121
294
|
}
|
|
122
|
-
return
|
|
295
|
+
return out + renderRuns(runs, escape);
|
|
123
296
|
};
|
|
124
297
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
298
|
+
/** A block's inline content as Markdown. */
|
|
299
|
+
export const renderInline = (parts: InlinePart[]): string =>
|
|
300
|
+
renderParts(parts, escapeProse);
|
|
301
|
+
|
|
302
|
+
// A destination with whitespace, a control character, or parentheses ends
|
|
303
|
+
// early in `[label](…)`; CommonMark's pointy-bracket form carries it intact.
|
|
304
|
+
// oxlint-disable-next-line no-control-regex -- control characters are exactly what the bare form refuses.
|
|
305
|
+
const UNSAFE_DESTINATION = /[\s()\u0000-\u001F\u007F]/u;
|
|
128
306
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
307
|
+
// Markdown decodes a destination before the browser sees it: a backslash
|
|
308
|
+
// escape or a character reference becomes its character, so
|
|
309
|
+
// `javascript:` would reach the `href` as `javascript:` after the safety
|
|
310
|
+
// check passed it. Escaping both — and `<`/`>`, which would close the
|
|
311
|
+
// pointy form early and let the rest of the URL open a second link — keeps
|
|
312
|
+
// the `href` exactly the URL the CMS holds.
|
|
313
|
+
const DESTINATION_SPECIALS = /[\\<>]|&(?=#?[a-z0-9]+;)/giu;
|
|
314
|
+
|
|
315
|
+
// A destination can't hold a line break, and the URL parser drops tabs and
|
|
316
|
+
// line breaks anyway, so removing them leaves the same URL.
|
|
317
|
+
const DESTINATION_IGNORED = /[\t\n\r]/gu;
|
|
318
|
+
|
|
319
|
+
/** A link or image destination as Markdown carries it verbatim. */
|
|
320
|
+
export const destination = (url: string): string => {
|
|
321
|
+
const escaped = url
|
|
322
|
+
.replaceAll(DESTINATION_IGNORED, "")
|
|
323
|
+
.replaceAll(DESTINATION_SPECIALS, String.raw`\$&`);
|
|
324
|
+
return UNSAFE_DESTINATION.test(escaped) ? `<${escaped}>` : escaped;
|
|
325
|
+
};
|
|
132
326
|
|
|
133
327
|
/**
|
|
134
|
-
* A Markdown link, or the
|
|
135
|
-
* CMS content is the editor's, not the site author's, so
|
|
136
|
-
* isn't a web, mail, or relative address (`javascript:`,
|
|
137
|
-
* becomes a clickable link on the docs site (see
|
|
328
|
+
* A Markdown link over inline parts, or the parts alone when the target is
|
|
329
|
+
* missing — or unsafe: CMS content is the editor's, not the site author's, so
|
|
330
|
+
* a destination that isn't a web, mail, or relative address (`javascript:`,
|
|
331
|
+
* `data:`) never becomes a clickable link on the docs site (see
|
|
332
|
+
* `safe-links.ts`). The parts stay parts then, so their runs merge with their
|
|
333
|
+
* neighbors'. A label escapes a bare URL too: GFM autolinks nothing inside a
|
|
334
|
+
* link, so its specials would read as syntax.
|
|
138
335
|
*/
|
|
139
|
-
export const
|
|
140
|
-
href && isSafeHref(href)
|
|
336
|
+
export const linkParts = (parts: InlinePart[], href?: string): InlinePart[] =>
|
|
337
|
+
href && isSafeHref(href)
|
|
338
|
+
? [
|
|
339
|
+
{
|
|
340
|
+
markdown: `[${renderParts(parts, escapeMarkdownText)}](${destination(href)})`,
|
|
341
|
+
},
|
|
342
|
+
]
|
|
343
|
+
: parts;
|
|
141
344
|
|
|
142
345
|
/** The ATX prefix for a heading level, clamped to Markdown's six. */
|
|
143
346
|
export const headingPrefix = (level: number): string =>
|
|
@@ -172,10 +375,20 @@ export const indent = (text: string, width: number): string => {
|
|
|
172
375
|
.join("\n");
|
|
173
376
|
};
|
|
174
377
|
|
|
175
|
-
|
|
378
|
+
// A fence's language is one word of its info string. A backtick in it keeps
|
|
379
|
+
// the fence from opening at all, so the code renders as Markdown and HTML,
|
|
380
|
+
// and whitespace would start the meta Blume reads code titles from; a
|
|
381
|
+
// language that isn't a single such word is dropped.
|
|
382
|
+
const FENCE_LANGUAGE = /^[^\s`]+$/u;
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* A fenced code block whose fence outruns any backtick run in the code,
|
|
386
|
+
* labeled with its language when that is one word a fence can carry.
|
|
387
|
+
*/
|
|
176
388
|
export const codeFence = (code: string, language = ""): string => {
|
|
177
389
|
const fence = "`".repeat(Math.max(3, longestBacktickRun(code) + 1));
|
|
178
|
-
|
|
390
|
+
const label = FENCE_LANGUAGE.test(language) ? language : "";
|
|
391
|
+
return `${fence}${label}\n${code}\n${fence}`;
|
|
179
392
|
};
|
|
180
393
|
|
|
181
394
|
/** A Markdown image; the alt is escaped so a `]` in a caption can't close it. */
|
|
@@ -193,12 +406,19 @@ export const writesMdx = <Node>(
|
|
|
193
406
|
serializers?: Record<string, (node: Node) => string>
|
|
194
407
|
): boolean => serializers !== undefined && Object.keys(serializers).length > 0;
|
|
195
408
|
|
|
409
|
+
// A node's type is the CMS's data too: a `*/` (or `--`) in it would close the
|
|
410
|
+
// comment early and let the rest run as MDX (or render as HTML). A space
|
|
411
|
+
// between the two characters keeps it inside.
|
|
412
|
+
const COMMENT_END = /\*\/|--/gu;
|
|
413
|
+
|
|
196
414
|
/**
|
|
197
415
|
* A comment marking a node the lowerer has no Markdown for — an MDX comment
|
|
198
416
|
* when the output is MDX, which rejects `<!-- -->`.
|
|
199
417
|
*/
|
|
200
|
-
export const unsupported = (what: string, mdx = false): string =>
|
|
201
|
-
|
|
418
|
+
export const unsupported = (what: string, mdx = false): string => {
|
|
419
|
+
const label = what.replaceAll(COMMENT_END, (end) => [...end].join(" "));
|
|
420
|
+
return mdx ? `{/* unsupported ${label} */}` : `<!-- unsupported ${label} -->`;
|
|
421
|
+
};
|
|
202
422
|
|
|
203
423
|
/** Blocks separated by blank lines; empty blocks are dropped. */
|
|
204
424
|
export const joinBlocks = (blocks: string[]): string =>
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
pollingWatch,
|
|
10
10
|
snapshotCache,
|
|
11
11
|
} from "./cache.ts";
|
|
12
|
+
import { REMOTE_TIMEOUT_MS } from "./remote.ts";
|
|
12
13
|
import type {
|
|
13
14
|
ContentSource,
|
|
14
15
|
SourceContext,
|
|
@@ -64,6 +65,38 @@ const githubHeaders = (url: string): Record<string, string> => {
|
|
|
64
65
|
return GITHUB_HOSTS.has(host) ? { authorization: `Bearer ${token}` } : {};
|
|
65
66
|
};
|
|
66
67
|
|
|
68
|
+
/**
|
|
69
|
+
* GET `url`'s body, giving up after {@link REMOTE_TIMEOUT_MS}: a server that
|
|
70
|
+
* accepts the connection and never finishes answering would otherwise hold the
|
|
71
|
+
* scan, and so the build or every dev rescan, indefinitely. A timeout rejects
|
|
72
|
+
* with an error naming the URL and the limit, so the skip or offline
|
|
73
|
+
* diagnostic says what stalled instead of only that an operation was aborted.
|
|
74
|
+
*/
|
|
75
|
+
const fetchText = async (
|
|
76
|
+
url: string,
|
|
77
|
+
doFetch: typeof fetch
|
|
78
|
+
): Promise<string> => {
|
|
79
|
+
try {
|
|
80
|
+
// The signal bounds the body read too, not only the response headers.
|
|
81
|
+
const res = await doFetch(url, {
|
|
82
|
+
headers: githubHeaders(url),
|
|
83
|
+
signal: AbortSignal.timeout(REMOTE_TIMEOUT_MS),
|
|
84
|
+
});
|
|
85
|
+
if (!res.ok) {
|
|
86
|
+
throw new Error(`${url} -> ${res.status}`);
|
|
87
|
+
}
|
|
88
|
+
return await res.text();
|
|
89
|
+
} catch (error) {
|
|
90
|
+
if (error instanceof DOMException && error.name === "TimeoutError") {
|
|
91
|
+
throw new Error(
|
|
92
|
+
`${url} did not respond within ${REMOTE_TIMEOUT_MS / 1000}s`,
|
|
93
|
+
{ cause: error }
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
67
100
|
interface GithubTreeEntry {
|
|
68
101
|
path: string;
|
|
69
102
|
type: string;
|
|
@@ -78,13 +111,10 @@ const enumerateGithub = async (
|
|
|
78
111
|
const { owner, repo, ref } = github;
|
|
79
112
|
const base = github.path.replaceAll(/^\/|\/$/gu, "");
|
|
80
113
|
const treeUrl = `https://api.github.com/repos/${owner}/${repo}/git/trees/${ref}?recursive=1`;
|
|
81
|
-
const
|
|
82
|
-
if (!res.ok) {
|
|
83
|
-
throw new Error(`${treeUrl} -> ${res.status}`);
|
|
84
|
-
}
|
|
114
|
+
const text = await fetchText(treeUrl, doFetch);
|
|
85
115
|
// SAFETY: GitHub's git/trees endpoint returns this envelope; a missing or
|
|
86
116
|
// differently-typed field falls through the `?? []` and blob filters below.
|
|
87
|
-
const body =
|
|
117
|
+
const body = JSON.parse(text) as {
|
|
88
118
|
tree?: GithubTreeEntry[];
|
|
89
119
|
truncated?: boolean;
|
|
90
120
|
};
|
|
@@ -157,13 +187,7 @@ export const mdxRemoteSource = (
|
|
|
157
187
|
};
|
|
158
188
|
|
|
159
189
|
const fetchEntry = async (item: RemoteRef): Promise<SourceEntry> => {
|
|
160
|
-
const
|
|
161
|
-
headers: githubHeaders(item.fetchUrl),
|
|
162
|
-
});
|
|
163
|
-
if (!res.ok) {
|
|
164
|
-
throw new Error(`${item.fetchUrl} -> ${res.status}`);
|
|
165
|
-
}
|
|
166
|
-
const text = await res.text();
|
|
190
|
+
const text = await fetchText(item.fetchUrl, doFetch);
|
|
167
191
|
const parsed = matter(text);
|
|
168
192
|
const format = item.ref.toLowerCase().endsWith(".mdx") ? "mdx" : "md";
|
|
169
193
|
return {
|
|
@@ -193,16 +217,19 @@ export const mdxRemoteSource = (
|
|
|
193
217
|
severity: "warning",
|
|
194
218
|
});
|
|
195
219
|
}
|
|
220
|
+
const reasons: string[] = [];
|
|
196
221
|
const settled = await Promise.all(
|
|
197
222
|
refs.map(async (ref) => {
|
|
198
223
|
try {
|
|
199
224
|
return await fetchEntry(ref);
|
|
200
225
|
} catch (error) {
|
|
226
|
+
// SAFETY: fetch and decode failures throw Error instances;
|
|
227
|
+
// only the message is read for the skip diagnostic.
|
|
228
|
+
const reason = (error as Error).message;
|
|
229
|
+
reasons.push(reason);
|
|
201
230
|
skipped.push({
|
|
202
231
|
code: "BLUME_SOURCE_FETCH_FAILED",
|
|
203
|
-
|
|
204
|
-
// only the message is read for the skip diagnostic.
|
|
205
|
-
message: `Source "${options.name}" skipped "${ref.ref}" (${(error as Error).message}); the rest were imported.`,
|
|
232
|
+
message: `Source "${options.name}" skipped "${ref.ref}" (${reason}); the rest were imported.`,
|
|
206
233
|
severity: "warning",
|
|
207
234
|
});
|
|
208
235
|
return null;
|
|
@@ -215,9 +242,13 @@ export const mdxRemoteSource = (
|
|
|
215
242
|
// Only a total wipeout is a hard failure — let loadWithCache fall back
|
|
216
243
|
// to cache or fail loudly rather than silently importing nothing. A
|
|
217
244
|
// partial failure keeps the healthy pages and warns about the rest.
|
|
245
|
+
// The first failure's reason rides along, so a server that timed out
|
|
246
|
+
// on every file says so.
|
|
218
247
|
if (refs.length > 0 && entries.length === 0) {
|
|
219
248
|
skipped.length = 0;
|
|
220
|
-
throw new Error(
|
|
249
|
+
throw new Error(
|
|
250
|
+
`all ${refs.length} remote file(s) failed to fetch (${reasons[0]})`
|
|
251
|
+
);
|
|
221
252
|
}
|
|
222
253
|
return entries;
|
|
223
254
|
},
|
|
@@ -243,6 +274,10 @@ export const mdxRemoteSource = (
|
|
|
243
274
|
return {
|
|
244
275
|
load,
|
|
245
276
|
name: options.name,
|
|
277
|
+
// Refs are the remote tree's file and folder names, so an ordering prefix
|
|
278
|
+
// (`01-getting-started/02-install.mdx`) sorts the sidebar and drops from
|
|
279
|
+
// the route, the way it does in a local content folder.
|
|
280
|
+
orderedNames: true,
|
|
246
281
|
prefix: options.prefix,
|
|
247
282
|
read,
|
|
248
283
|
staged: true,
|
|
@@ -253,5 +288,6 @@ export const mdxRemoteSource = (
|
|
|
253
288
|
() => load()
|
|
254
289
|
)
|
|
255
290
|
: undefined,
|
|
291
|
+
withContext: (next) => mdxRemoteSource(options, next),
|
|
256
292
|
};
|
|
257
293
|
};
|