blume 1.7.2 → 2.0.0
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 +19 -0
- package/CHANGELOG.md +227 -0
- package/README.md +34 -21
- package/dist/cli/chunk-11j0384y.js +148 -0
- package/dist/cli/chunk-11j0384y.js.map +10 -0
- package/dist/cli/{chunk-xhtpx3ff.js → chunk-1w8dp3qb.js} +17 -16
- package/dist/cli/{chunk-xhtpx3ff.js.map → chunk-1w8dp3qb.js.map} +3 -3
- package/dist/cli/chunk-2q1dwty4.js +75 -0
- package/dist/cli/chunk-2q1dwty4.js.map +11 -0
- package/dist/cli/chunk-41za066z.js +122 -0
- package/dist/cli/chunk-41za066z.js.map +11 -0
- package/dist/cli/chunk-5a2z0198.js +133 -0
- package/dist/cli/chunk-5a2z0198.js.map +10 -0
- package/dist/cli/{chunk-9he6crym.js → chunk-6k8vp3ta.js} +25 -13
- package/dist/cli/{chunk-9he6crym.js.map → chunk-6k8vp3ta.js.map} +4 -4
- package/dist/cli/{chunk-mt76t7dj.js → chunk-79njf86q.js} +133 -54
- package/dist/cli/chunk-79njf86q.js.map +11 -0
- package/dist/cli/{chunk-3w7b2vcx.js → chunk-7ez8ny0t.js} +2 -2
- package/dist/cli/{chunk-688e0dde.js → chunk-88cpgt6h.js} +1 -1
- package/dist/cli/chunk-a9kptbw5.js +361 -0
- package/dist/cli/chunk-a9kptbw5.js.map +14 -0
- package/dist/cli/chunk-abh8yjkn.js +31 -0
- package/dist/cli/chunk-abh8yjkn.js.map +10 -0
- package/dist/cli/chunk-b5aj94ah.js +91 -0
- package/dist/cli/chunk-b5aj94ah.js.map +10 -0
- package/dist/cli/{chunk-hs3gbh8p.js → chunk-bctazmbk.js} +9 -5
- package/dist/cli/chunk-bctazmbk.js.map +10 -0
- package/dist/cli/chunk-beat36xx.js +279 -0
- package/dist/cli/chunk-beat36xx.js.map +10 -0
- package/dist/cli/chunk-bnbmcwfb.js +145 -0
- package/dist/cli/chunk-bnbmcwfb.js.map +11 -0
- package/dist/cli/{chunk-9bkjd11x.js → chunk-bw22s759.js} +15 -5
- package/dist/cli/{chunk-9bkjd11x.js.map → chunk-bw22s759.js.map} +4 -4
- package/dist/cli/chunk-by2290sx.js +39 -0
- package/dist/cli/chunk-by2290sx.js.map +10 -0
- package/dist/cli/chunk-d1tadaw7.js +79 -0
- package/dist/cli/chunk-d1tadaw7.js.map +10 -0
- package/dist/cli/{chunk-t3tj0dgr.js → chunk-d80hr03s.js} +24 -19
- package/dist/cli/chunk-d80hr03s.js.map +15 -0
- package/dist/cli/chunk-ernrthtr.js +97 -0
- package/dist/cli/chunk-ernrthtr.js.map +10 -0
- package/dist/cli/chunk-f2972sbt.js +374 -0
- package/dist/cli/chunk-f2972sbt.js.map +10 -0
- package/dist/cli/{chunk-exeeb35e.js → chunk-f7t03s3g.js} +2 -2
- package/dist/cli/{chunk-2z47ypj8.js → chunk-fa25z98p.js} +16 -3
- package/dist/cli/chunk-fa25z98p.js.map +11 -0
- package/dist/cli/{chunk-12dxjqk7.js → chunk-fh5hj5jt.js} +44 -21
- package/dist/cli/chunk-fh5hj5jt.js.map +10 -0
- package/dist/cli/{chunk-8cd8tj54.js → chunk-j8mw0za6.js} +86 -57
- package/dist/cli/chunk-j8mw0za6.js.map +35 -0
- package/dist/cli/chunk-jts8mvcz.js +106 -0
- package/dist/cli/{chunk-2mzebbbz.js.map → chunk-jts8mvcz.js.map} +6 -4
- package/dist/cli/{chunk-ejjx8znq.js → chunk-mnqj32sj.js} +505 -536
- package/dist/cli/chunk-mnqj32sj.js.map +12 -0
- package/dist/cli/{chunk-196vjxp9.js → chunk-mwt1k8n7.js} +100 -372
- package/dist/cli/chunk-mwt1k8n7.js.map +10 -0
- package/dist/cli/chunk-nk3ts2xk.js +51 -0
- package/dist/cli/chunk-nk3ts2xk.js.map +10 -0
- package/dist/cli/{chunk-n9sra6sy.js → chunk-pat2zzwc.js} +10 -14
- package/dist/cli/{chunk-n9sra6sy.js.map → chunk-pat2zzwc.js.map} +2 -2
- package/dist/cli/chunk-pnnvybbk.js +176 -0
- package/dist/cli/chunk-pnnvybbk.js.map +11 -0
- package/dist/cli/{chunk-cvky9gb2.js → chunk-sqn5t4q0.js} +81 -87
- package/dist/cli/chunk-sqn5t4q0.js.map +10 -0
- package/dist/cli/{chunk-eevwt1sc.js → chunk-tzne8qfq.js} +15 -15
- package/dist/cli/{chunk-eevwt1sc.js.map → chunk-tzne8qfq.js.map} +1 -1
- package/dist/cli/chunk-xaz13gwg.js +12449 -0
- package/dist/cli/chunk-xaz13gwg.js.map +182 -0
- package/dist/cli/chunk-y3e45rc8.js +102 -0
- package/dist/cli/chunk-y3e45rc8.js.map +10 -0
- package/dist/cli/chunk-z01ze5c1.js +261 -0
- package/dist/cli/chunk-z01ze5c1.js.map +10 -0
- package/dist/cli/{chunk-5n7t497w.js → chunk-z1f5arsg.js} +247 -755
- package/dist/cli/chunk-z1f5arsg.js.map +36 -0
- package/dist/cli/{chunk-hdm2dkd2.js → chunk-zg2gtj10.js} +1086 -2596
- package/dist/cli/chunk-zg2gtj10.js.map +35 -0
- package/dist/cli/{chunk-ppfvdcd4.js → chunk-zxccj738.js} +1 -1
- package/dist/cli/index.js +214 -57
- package/dist/cli/index.js.map +8 -7
- package/dist/types/ai/agent-readability.d.ts +52 -0
- package/dist/types/ai/ai-catalog.d.ts +42 -0
- package/dist/types/ai/api/paths.d.ts +17 -0
- package/dist/types/ai/api-catalog.d.ts +18 -0
- package/dist/types/ai/ask.d.ts +368 -0
- package/dist/types/ai/changelog-markdown.d.ts +2 -0
- package/dist/types/ai/component-markdown.d.ts +2 -2
- package/dist/types/ai/index.d.ts +24 -0
- package/dist/types/ai/link-headers.d.ts +24 -0
- package/dist/types/ai/llms.d.ts +25 -0
- package/dist/types/ai/markdown.d.ts +45 -0
- package/dist/types/ai/mcp/discovery.d.ts +68 -0
- package/dist/types/ai/mcp/tools.d.ts +16 -0
- package/dist/types/ai/openapi-components.d.ts +43 -0
- package/dist/types/ai/relative-links.d.ts +26 -0
- package/dist/types/ai/serializers.d.ts +15 -0
- package/dist/types/ai/skills.d.ts +42 -0
- package/dist/types/ai/tar.d.ts +25 -0
- package/dist/types/ai/visibility.d.ts +17 -0
- package/dist/types/ai/web-bot-auth.d.ts +16 -0
- package/dist/types/analytics/adobe.d.ts +35 -0
- package/dist/types/analytics/amplitude.d.ts +50 -0
- package/dist/types/analytics/clarity.d.ts +31 -0
- package/dist/types/analytics/clearbit.d.ts +29 -0
- package/dist/types/analytics/cloudflare.d.ts +41 -0
- package/dist/types/analytics/fathom.d.ts +41 -0
- package/dist/types/analytics/google-analytics.d.ts +44 -0
- package/dist/types/analytics/google-tag-manager.d.ts +40 -0
- package/dist/types/analytics/head.d.ts +30 -0
- package/dist/types/analytics/heap.d.ts +40 -0
- package/dist/types/analytics/hightouch.d.ts +44 -0
- package/dist/types/analytics/hotjar.d.ts +33 -0
- package/dist/types/analytics/index.d.ts +61 -0
- package/dist/types/analytics/inline.d.ts +17 -0
- package/dist/types/analytics/logrocket.d.ts +44 -0
- package/dist/types/analytics/mixpanel.d.ts +67 -0
- package/dist/types/analytics/pirsch.d.ts +42 -0
- package/dist/types/analytics/plausible.d.ts +56 -0
- package/dist/types/analytics/posthog.d.ts +45 -0
- package/dist/types/analytics/schema.d.ts +320 -0
- package/dist/types/analytics/script.d.ts +46 -0
- package/dist/types/analytics/segment.d.ts +50 -0
- package/dist/types/analytics/vercel.d.ts +48 -0
- package/dist/types/astro/integration.d.ts +76 -0
- package/dist/types/astro/markdown-negotiation.d.ts +23 -0
- package/dist/types/astro/module-types.d.ts +14 -0
- package/dist/types/astro/pages.d.ts +44 -0
- package/dist/types/cli/env.d.ts +12 -0
- package/dist/types/cli/init/scaffold.d.ts +154 -0
- package/dist/types/components/layout/nav-utils.d.ts +11 -0
- package/dist/types/core/adapter.d.ts +47 -0
- package/dist/types/core/api-name.d.ts +7 -0
- package/dist/types/core/changelog-index.d.ts +13 -0
- package/dist/types/core/config-input.d.ts +209 -505
- package/dist/types/core/config.d.ts +60 -34
- package/dist/types/core/content-assets.d.ts +76 -0
- package/dist/types/core/custom-pages.d.ts +33 -0
- package/dist/types/core/data.d.ts +28 -11
- package/dist/types/core/define-components.d.ts +12 -9
- package/dist/types/core/deployment-env.d.ts +6 -11
- package/dist/types/core/frontmatter.d.ts +10 -0
- package/dist/types/core/graph.d.ts +18 -0
- package/dist/types/core/heading-markers.d.ts +54 -0
- package/dist/types/core/i18n-ui.d.ts +6 -2
- package/dist/types/core/i18n.d.ts +87 -0
- package/dist/types/core/includes.d.ts +138 -0
- package/dist/types/core/last-modified.d.ts +47 -0
- package/dist/types/core/links.d.ts +95 -0
- package/dist/types/core/locale-links.d.ts +60 -0
- package/dist/types/core/manifest.d.ts +17 -0
- package/dist/types/core/meta.d.ts +38 -0
- package/dist/types/core/nav-diagnostics.d.ts +26 -0
- package/dist/types/core/navigation.d.ts +6 -0
- package/dist/types/core/node-require.d.ts +19 -0
- package/dist/types/core/package-json.d.ts +13 -0
- package/dist/types/core/probe.d.ts +42 -0
- package/dist/types/core/project-graph.d.ts +51 -0
- package/dist/types/core/project.d.ts +2 -0
- package/dist/types/core/safe-href.d.ts +2 -0
- package/dist/types/core/safe-links.d.ts +26 -0
- package/dist/types/core/schema.d.ts +2291 -441
- package/dist/types/core/site-url.d.ts +16 -0
- package/dist/types/core/sources/assets.d.ts +36 -0
- package/dist/types/core/sources/cache.d.ts +37 -0
- package/dist/types/core/sources/collection.d.ts +28 -0
- package/dist/types/core/sources/contentful-rich-text.d.ts +31 -0
- package/dist/types/core/sources/contentful.d.ts +40 -0
- package/dist/types/core/sources/filesystem.d.ts +24 -0
- package/dist/types/core/sources/github-releases.d.ts +31 -0
- package/dist/types/core/sources/json.d.ts +30 -0
- package/dist/types/core/sources/lexical.d.ts +19 -0
- package/dist/types/core/sources/lower.d.ts +75 -0
- package/dist/types/core/sources/mdx-remote.d.ts +28 -0
- package/dist/types/core/sources/normalize.d.ts +147 -0
- package/dist/types/core/sources/notion.d.ts +131 -0
- package/dist/types/core/sources/obsidian.d.ts +46 -0
- package/dist/types/core/sources/payload.d.ts +39 -0
- package/dist/types/core/sources/portable-text.d.ts +42 -0
- package/dist/types/core/sources/read.d.ts +23 -0
- package/dist/types/core/sources/remote.d.ts +74 -0
- package/dist/types/core/sources/resolve.d.ts +19 -0
- package/dist/types/core/sources/sanity.d.ts +43 -0
- package/dist/types/core/sources/strapi-blocks.d.ts +12 -0
- package/dist/types/core/sources/strapi.d.ts +33 -0
- package/dist/types/core/sources/types.d.ts +7 -0
- package/dist/types/core/sources/watch.d.ts +45 -0
- package/dist/types/core/text-width.d.ts +11 -0
- package/dist/types/core/types.d.ts +20 -1
- package/dist/types/core/unrecognized-keys.d.ts +7 -0
- package/dist/types/core/versions.d.ts +72 -0
- package/dist/types/core/yaml.d.ts +9 -0
- package/dist/types/deploy/adapter-output.d.ts +45 -0
- package/dist/types/deploy/adapters/cloudflare.d.ts +40 -0
- package/dist/types/deploy/adapters/index.d.ts +29 -0
- package/dist/types/deploy/adapters/netlify.d.ts +37 -0
- package/dist/types/deploy/adapters/node.d.ts +37 -0
- package/dist/types/deploy/adapters/registry.d.ts +133 -0
- package/dist/types/deploy/adapters/types.d.ts +71 -0
- package/dist/types/deploy/adapters/vercel.d.ts +38 -0
- package/dist/types/deploy/artifacts.d.ts +65 -0
- package/dist/types/deploy/cloudflare-negotiation.d.ts +196 -0
- package/dist/types/deploy/function-bundle.d.ts +80 -0
- package/dist/types/deploy/headers.d.ts +50 -0
- package/dist/types/deploy/node-headers.d.ts +42 -0
- package/dist/types/deploy/platforms/cloudflare.d.ts +40 -0
- package/dist/types/deploy/platforms/index.d.ts +18 -0
- package/dist/types/deploy/platforms/netlify.d.ts +13 -0
- package/dist/types/deploy/platforms/node.d.ts +11 -0
- package/dist/types/deploy/platforms/paths.d.ts +27 -0
- package/dist/types/deploy/platforms/static.d.ts +10 -0
- package/dist/types/deploy/platforms/types.d.ts +104 -0
- package/dist/types/deploy/platforms/vercel.d.ts +32 -0
- package/dist/types/deploy/redirects.d.ts +53 -0
- package/dist/types/deploy/robots.d.ts +8 -0
- package/dist/types/deploy/rss.d.ts +31 -0
- package/dist/types/deploy/sitemap.d.ts +21 -0
- package/dist/types/deploy/vercel-negotiation.d.ts +109 -0
- package/dist/types/markdown/code-title.d.ts +32 -0
- package/dist/types/markdown/fence-meta.d.ts +23 -0
- package/dist/types/markdown/themes.d.ts +3 -3
- package/dist/types/openapi/asyncapi.d.ts +129 -0
- package/dist/types/openapi/graphql-build.d.ts +8 -0
- package/dist/types/openapi/graphql.d.ts +122 -0
- package/dist/types/openapi/model.d.ts +158 -0
- package/dist/types/openapi/parse.d.ts +57 -0
- package/dist/types/openapi/references.d.ts +37 -25
- package/dist/types/openapi/render-mdx.d.ts +33 -0
- package/dist/types/openapi/sentence.d.ts +7 -0
- package/dist/types/openapi/signature.d.ts +10 -0
- package/dist/types/openapi/source.d.ts +22 -0
- package/dist/types/openapi/spec-dependency-error.d.ts +10 -0
- package/dist/types/reference/asyncapi.d.ts +166 -0
- package/dist/types/reference/graphql.d.ts +181 -0
- package/dist/types/reference/index.d.ts +32 -0
- package/dist/types/reference/openapi.d.ts +165 -0
- package/dist/types/reference/options.d.ts +157 -0
- package/dist/types/reference/scalar.d.ts +136 -0
- package/dist/types/reference/schema.d.ts +630 -0
- package/dist/types/search/adapters/algolia.d.ts +32 -0
- package/dist/types/search/adapters/flexsearch.d.ts +12 -0
- package/dist/types/search/adapters/index.d.ts +31 -0
- package/dist/types/search/adapters/mixedbread.d.ts +22 -0
- package/dist/types/search/adapters/orama-cloud.d.ts +33 -0
- package/dist/types/search/adapters/orama.d.ts +13 -0
- package/dist/types/search/adapters/pagefind.d.ts +12 -0
- package/dist/types/search/adapters/registry.d.ts +228 -0
- package/dist/types/search/adapters/types.d.ts +34 -0
- package/dist/types/search/adapters/typesense.d.ts +42 -0
- package/dist/types/search/build.d.ts +23 -0
- package/dist/types/search/documents.d.ts +90 -0
- package/dist/types/search/facets.d.ts +3 -0
- package/dist/types/search/sync/algolia.d.ts +14 -0
- package/dist/types/search/sync/index.d.ts +14 -0
- package/dist/types/search/sync/orama-cloud.d.ts +10 -0
- package/dist/types/search/sync/typesense.d.ts +14 -0
- package/dist/types/sources/contentful.d.ts +68 -0
- package/dist/types/sources/custom.d.ts +20 -0
- package/dist/types/sources/filesystem.d.ts +42 -0
- package/dist/types/sources/github-releases.d.ts +46 -0
- package/dist/types/sources/index.d.ts +48 -0
- package/dist/types/sources/mdx-remote.d.ts +63 -0
- package/dist/types/sources/notion.d.ts +65 -0
- package/dist/types/sources/obsidian.d.ts +34 -0
- package/dist/types/sources/payload.d.ts +68 -0
- package/dist/types/sources/registry.d.ts +1414 -0
- package/dist/types/sources/sanity.d.ts +69 -0
- package/dist/types/sources/shared.d.ts +46 -0
- package/dist/types/sources/strapi.d.ts +66 -0
- package/dist/types/theme/icon-kind.d.ts +11 -0
- package/dist/types/theme/icons.d.ts +20 -0
- package/docs/01-quickstart.mdx +18 -18
- package/docs/02-deployment.mdx +50 -26
- package/docs/03-upgrading.mdx +351 -0
- package/docs/04-migrating.mdx +58 -0
- package/docs/08-faq.mdx +3 -3
- package/docs/advanced/blog.mdx +13 -6
- package/docs/advanced/changelog.mdx +23 -35
- package/docs/advanced/custom-pages.mdx +20 -8
- package/docs/advanced/meta.ts +1 -8
- package/docs/advanced/skills.mdx +8 -0
- package/docs/cli/audit.mdx +646 -0
- package/docs/cli/doctor.mdx +29 -0
- package/docs/{reference/eval.mdx → cli/evals.mdx} +9 -8
- package/docs/cli/index.mdx +105 -0
- package/docs/cli/meta.ts +7 -0
- package/docs/{reference → cli}/translate.mdx +1 -1
- package/docs/cli/validate.mdx +41 -0
- package/docs/cli/version.mdx +40 -0
- package/docs/configuration/analytics.mdx +350 -59
- package/docs/configuration/ask-ai.mdx +176 -61
- package/docs/configuration/customization.mdx +15 -9
- package/docs/configuration/index.mdx +52 -31
- package/docs/configuration/search.mdx +75 -54
- package/docs/configuration/theming.mdx +24 -15
- package/docs/content/components.mdx +21 -5
- package/docs/{reference → content}/frontmatter.mdx +37 -1
- package/docs/content/i18n.mdx +8 -6
- package/docs/content/includes.mdx +2 -4
- package/docs/content/index.mdx +1 -1
- package/docs/content/islands.mdx +10 -5
- package/docs/content/meta.mdx +1 -1
- package/docs/content/meta.ts +1 -0
- package/docs/content/navigation.mdx +12 -7
- package/docs/content/sources.mdx +145 -50
- package/docs/content/syntax.mdx +26 -12
- package/docs/content/versioning.mdx +3 -3
- package/docs/discoverability/agent-discovery.mdx +91 -10
- package/docs/discoverability/index.mdx +5 -5
- package/docs/discoverability/json-api.mdx +4 -4
- package/docs/discoverability/llms-txt.mdx +6 -6
- package/docs/discoverability/markdown.mdx +4 -4
- package/docs/discoverability/mcp.mdx +11 -11
- package/docs/discoverability/sitemap-and-robots.mdx +3 -3
- package/docs/index.mdx +2 -2
- package/docs/references/asyncapi.mdx +59 -0
- package/docs/{advanced → references}/graphql.mdx +45 -32
- package/docs/{reference → references}/meta.ts +2 -2
- package/docs/references/openapi.mdx +171 -0
- package/docs/references/scalar.mdx +64 -0
- package/package.json +42 -8
- package/skills/blume/SKILL.md +21 -8
- package/skills/blume-migrate/SKILL.md +22 -21
- package/skills/blume-migrate/assets/oxfmt@0.67.0.patch +49 -0
- package/skills/blume-migrate/references/docusaurus.md +5 -4
- package/skills/blume-migrate/references/fumadocs.md +4 -4
- package/skills/blume-migrate/references/mintlify.md +23 -8
- package/skills/blume-migrate/references/monorepo.md +6 -6
- package/skills/blume-migrate/references/starlight.md +4 -4
- package/src/ai/agent-readability.ts +26 -22
- package/src/ai/ai-catalog.ts +259 -0
- package/src/ai/api/handlers.ts +46 -11
- package/src/ai/api-catalog.ts +8 -8
- package/src/ai/ask-data.ts +1 -1
- package/src/ai/ask.ts +624 -100
- package/src/ai/changelog-markdown.ts +91 -0
- package/src/ai/component-markdown.ts +328 -12
- package/src/ai/index.ts +45 -0
- package/src/ai/link-headers.ts +19 -6
- package/src/ai/llms.ts +26 -15
- package/src/ai/markdown.ts +35 -5
- package/src/ai/mcp/data.ts +9 -5
- package/src/ai/mcp/discovery.ts +1 -1
- package/src/ai/openapi-components.ts +4 -1
- package/src/ai/relative-links.ts +170 -0
- package/src/ai/serializers.ts +2 -2
- package/src/ai/skills.ts +1 -1
- package/src/ai/web-bot-auth.ts +2 -2
- package/src/analytics/adobe.ts +46 -0
- package/src/analytics/amplitude.ts +79 -0
- package/src/analytics/clarity.ts +46 -0
- package/src/analytics/clearbit.ts +47 -0
- package/src/analytics/cloudflare.ts +67 -0
- package/src/analytics/fathom.ts +64 -0
- package/src/analytics/google-analytics.ts +84 -0
- package/src/analytics/google-tag-manager.ts +61 -0
- package/src/analytics/head.ts +130 -0
- package/src/analytics/heap.ts +65 -0
- package/src/analytics/hightouch.ts +77 -0
- package/src/analytics/hotjar.ts +47 -0
- package/src/analytics/index.ts +71 -0
- package/src/analytics/inline.ts +21 -0
- package/src/analytics/logrocket.ts +71 -0
- package/src/analytics/mixpanel.ts +92 -0
- package/src/analytics/pirsch.ts +66 -0
- package/src/analytics/plausible.ts +83 -0
- package/src/analytics/posthog.ts +78 -0
- package/src/analytics/schema.ts +60 -0
- package/src/analytics/script.ts +60 -0
- package/src/analytics/segment.ts +85 -0
- package/src/analytics/vercel.ts +50 -0
- package/src/astro/adapter-root.ts +7 -9
- package/src/astro/component-slots.ts +131 -91
- package/src/astro/generate.ts +114 -345
- package/src/astro/integration.ts +2 -6
- package/src/astro/pages.ts +13 -101
- package/src/astro/render-deps.ts +379 -0
- package/src/astro/runtime-deps.ts +201 -0
- package/src/astro/templates.ts +554 -541
- package/src/audit/agent.ts +24 -0
- package/src/audit/catalog.ts +2 -2
- package/src/audit/checks/assets.ts +2 -2
- package/src/audit/checks/dns-aid.ts +1 -1
- package/src/audit/checks/duplicates.ts +3 -1
- package/src/audit/checks/i18n.ts +1 -1
- package/src/audit/checks/indexability.ts +7 -7
- package/src/audit/checks/links.ts +2 -2
- package/src/audit/checks/llms.ts +6 -6
- package/src/audit/checks/network.ts +3 -3
- package/src/audit/checks/og-image.ts +2 -2
- package/src/audit/checks/robots.ts +1 -1
- package/src/audit/checks/sitemap.ts +2 -2
- package/src/audit/checks/social.ts +1 -1
- package/src/audit/run.ts +4 -3
- package/src/audit/terms.ts +31 -0
- package/src/audit/url.ts +13 -13
- package/src/cli/command-meta.ts +10 -0
- package/src/cli/commands/audit.ts +39 -20
- package/src/cli/commands/build.ts +71 -314
- package/src/cli/commands/check.ts +1 -0
- package/src/cli/commands/dev.ts +40 -1
- package/src/cli/commands/doctor.ts +90 -14
- package/src/cli/commands/eject.ts +44 -7
- package/src/cli/commands/eval.ts +2 -2
- package/src/cli/commands/init.ts +161 -41
- package/src/cli/commands/migrate.ts +121 -0
- package/src/cli/commands/preview.ts +7 -1
- package/src/cli/commands/translate.ts +2 -2
- package/src/cli/commands/upgrade.ts +141 -0
- package/src/cli/commands/version.ts +57 -44
- package/src/cli/eject-scripts.ts +121 -6
- package/src/cli/index.ts +11 -1
- package/src/cli/init/install.ts +70 -0
- package/src/cli/init/questions.ts +7 -0
- package/src/cli/init/scaffold.ts +459 -75
- package/src/cli/lazy-command.ts +37 -1
- package/src/cli/prepare.ts +40 -6
- package/src/cli/required-secrets.ts +38 -11
- package/src/cli/unknown-flags.ts +266 -0
- package/src/cli/yarn-pnp.ts +52 -0
- package/src/components/content/AccordionItem.astro +7 -1
- package/src/components/content/Card.astro +4 -3
- package/src/components/content/ColorItem.astro +22 -4
- package/src/components/content/GithubInfo.astro +2 -2
- package/src/components/content/Prompt.astro +25 -25
- package/src/components/content/Tabs.astro +3 -1
- package/src/components/content/Tile.astro +2 -3
- package/src/components/content/Tooltip.astro +57 -9
- package/src/components/content/content-strings.ts +34 -0
- package/src/components/content/diff.ts +1 -1
- package/src/components/content/mermaid-element.ts +13 -2
- package/src/components/content/prompt-markdown.ts +292 -0
- package/src/components/content/tooltip-id.ts +41 -0
- package/src/components/islands/hooks.ts +75 -3
- package/src/components/layout/Analytics.astro +21 -79
- package/src/components/layout/Banner.astro +3 -1
- package/src/components/layout/DiscoveryLinks.astro +69 -0
- package/src/components/layout/Header.astro +83 -20
- package/src/components/layout/LanguageSwitcher.astro +4 -1
- package/src/components/layout/Logo.astro +23 -2
- package/src/components/layout/NavSelector.astro +13 -2
- package/src/components/layout/NavTree.astro +12 -3
- package/src/components/layout/NavTreeScript.astro +17 -3
- package/src/components/layout/PageActions.astro +3 -3
- package/src/components/layout/PageFeedback.astro +11 -1
- package/src/components/layout/PageLayout.astro +52 -5
- package/src/components/layout/ReferenceLayout.astro +21 -12
- package/src/components/layout/RootLayout.astro +69 -43
- package/src/components/layout/Search.astro +30 -6
- package/src/components/layout/WebMcp.astro +1 -1
- package/src/components/layout/analytics-client.ts +101 -15
- package/src/components/layout/drawer-inert.ts +113 -15
- package/src/components/layout/dropdown-clamp.ts +105 -0
- package/src/components/layout/head-scripts.ts +15 -6
- package/src/components/layout/nav-utils.ts +17 -0
- package/src/components/layout/search/algolia.ts +8 -8
- package/src/components/layout/search/orama-cloud.ts +9 -7
- package/src/components/layout/search/typesense.ts +11 -17
- package/src/components/openapi/MessageComposer.astro +2 -2
- package/src/components/openapi/Playground.astro +2 -2
- package/src/components/openapi/playground-client.ts +79 -10
- package/src/core/changelog-index.ts +24 -0
- package/src/core/component-overrides.ts +399 -154
- package/src/core/config-input.ts +212 -543
- package/src/core/config.ts +130 -41
- package/src/core/custom-pages.ts +105 -0
- package/src/core/data.ts +32 -11
- package/src/core/define-components.ts +12 -9
- package/src/core/deployment-env.ts +18 -74
- package/src/core/diagnostics.ts +14 -7
- package/src/core/graph.ts +69 -25
- package/src/core/i18n-ui.ts +6 -2
- package/src/core/i18n.ts +17 -0
- package/src/core/includes.ts +156 -38
- package/src/core/last-modified.ts +6 -11
- package/src/core/links.ts +166 -2
- package/src/core/manifest.ts +10 -3
- package/src/core/navigation.ts +137 -19
- package/src/core/new-tab.ts +35 -0
- package/src/core/node-require.ts +21 -0
- package/src/core/project-graph.ts +42 -26
- package/src/core/project.ts +9 -4
- package/src/core/request-body.ts +61 -0
- package/src/core/safe-href.ts +28 -0
- package/src/core/safe-links.ts +68 -0
- package/src/core/schema.ts +531 -721
- package/src/core/server-features.ts +8 -9
- package/src/core/sources/assets.ts +47 -11
- package/src/core/sources/collection.ts +67 -0
- package/src/core/sources/contentful-rich-text.ts +285 -0
- package/src/core/sources/contentful.ts +173 -0
- package/src/core/sources/github-releases.ts +51 -1
- package/src/core/sources/json.ts +71 -0
- package/src/core/sources/lexical.ts +195 -0
- package/src/core/sources/lower.ts +226 -0
- package/src/core/sources/normalize.ts +52 -2
- package/src/core/sources/notion.ts +39 -28
- package/src/core/sources/payload.ts +135 -0
- package/src/core/sources/portable-text.ts +11 -16
- package/src/core/sources/remote.ts +226 -0
- package/src/core/sources/resolve.ts +104 -166
- package/src/core/sources/sanity.ts +16 -49
- package/src/core/sources/strapi-blocks.ts +124 -0
- package/src/core/sources/strapi.ts +191 -0
- package/src/core/sources/types.ts +12 -1
- package/src/core/types.ts +20 -1
- package/src/core/ui-packs/ar.ts +6 -2
- package/src/core/ui-packs/bg.ts +6 -2
- package/src/core/ui-packs/bn.ts +6 -2
- package/src/core/ui-packs/ca.ts +3 -1
- package/src/core/ui-packs/cs.ts +6 -2
- package/src/core/ui-packs/da.ts +6 -2
- package/src/core/ui-packs/de.ts +6 -2
- package/src/core/ui-packs/el.ts +3 -1
- package/src/core/ui-packs/es.ts +3 -1
- package/src/core/ui-packs/fa.ts +6 -2
- package/src/core/ui-packs/fi.ts +6 -2
- package/src/core/ui-packs/fr.ts +3 -1
- package/src/core/ui-packs/he.ts +6 -2
- package/src/core/ui-packs/hi.ts +6 -2
- package/src/core/ui-packs/hr.ts +6 -2
- package/src/core/ui-packs/hu.ts +6 -2
- package/src/core/ui-packs/id.ts +6 -2
- package/src/core/ui-packs/it.ts +3 -1
- package/src/core/ui-packs/ja.ts +3 -1
- package/src/core/ui-packs/ko.ts +3 -1
- package/src/core/ui-packs/nl.ts +6 -2
- package/src/core/ui-packs/no.ts +6 -2
- package/src/core/ui-packs/pl.ts +6 -2
- package/src/core/ui-packs/pt-br.ts +3 -1
- package/src/core/ui-packs/pt.ts +3 -1
- package/src/core/ui-packs/ro.ts +6 -2
- package/src/core/ui-packs/ru.ts +6 -2
- package/src/core/ui-packs/sk.ts +6 -2
- package/src/core/ui-packs/sr.ts +6 -2
- package/src/core/ui-packs/sv.ts +6 -2
- package/src/core/ui-packs/th.ts +3 -1
- package/src/core/ui-packs/tr.ts +6 -2
- package/src/core/ui-packs/uk.ts +6 -2
- package/src/core/ui-packs/vi.ts +3 -1
- package/src/core/ui-packs/zh-tw.ts +3 -1
- package/src/core/ui-packs/zh.ts +3 -1
- package/src/core/unrecognized-keys.ts +10 -0
- package/src/core/version-cut.ts +116 -8
- package/src/deploy/adapter-output.ts +57 -97
- package/src/deploy/adapters/cloudflare.ts +39 -0
- package/src/deploy/adapters/index.ts +41 -0
- package/src/deploy/adapters/netlify.ts +34 -0
- package/src/deploy/adapters/node.ts +34 -0
- package/src/deploy/adapters/registry.ts +127 -0
- package/src/deploy/adapters/types.ts +92 -0
- package/src/deploy/adapters/vercel.ts +35 -0
- package/src/deploy/artifacts.ts +67 -28
- package/src/deploy/cloudflare-negotiation.ts +179 -100
- package/src/deploy/function-bundle.ts +18 -3
- package/src/deploy/headers.ts +129 -22
- package/src/deploy/node-headers.ts +198 -0
- package/src/deploy/platforms/cloudflare.ts +312 -0
- package/src/deploy/platforms/index.ts +67 -0
- package/src/deploy/platforms/netlify.ts +52 -0
- package/src/deploy/platforms/node.ts +40 -0
- package/src/deploy/platforms/paths.ts +42 -0
- package/src/deploy/platforms/static.ts +29 -0
- package/src/deploy/platforms/types.ts +112 -0
- package/src/deploy/platforms/vercel.ts +193 -0
- package/src/deploy/redirects.ts +30 -18
- package/src/deploy/robots.ts +3 -3
- package/src/deploy/rss.ts +2 -2
- package/src/deploy/sitemap.ts +2 -2
- package/src/deploy/vercel-negotiation.ts +27 -4
- package/src/eval/agents.ts +32 -1
- package/src/eval/findings.ts +19 -11
- package/src/eval/report.ts +10 -2
- package/src/markdown/external-links.ts +65 -0
- package/src/markdown/include.ts +45 -27
- package/src/markdown/index.ts +34 -9
- package/src/markdown/inline-code.ts +12 -6
- package/src/markdown/relative-links.ts +324 -0
- package/src/markdown/themes.ts +3 -3
- package/src/migrate/migrate.ts +149 -0
- package/src/openapi/parse.ts +64 -16
- package/src/openapi/proxy.ts +62 -10
- package/src/openapi/references.ts +147 -147
- package/src/openapi/render-mdx.ts +32 -10
- package/src/openapi/scalar.ts +15 -13
- package/src/openapi/sentence.ts +14 -0
- package/src/openapi/source.ts +37 -21
- package/src/openapi/spec-dependency-error.ts +15 -0
- package/src/reference/asyncapi.ts +83 -0
- package/src/reference/graphql.ts +89 -0
- package/src/reference/index.ts +40 -0
- package/src/reference/openapi.ts +83 -0
- package/src/reference/options.ts +201 -0
- package/src/reference/scalar.ts +136 -0
- package/src/reference/schema.ts +88 -0
- package/src/registry/eject.ts +97 -42
- package/src/search/adapters/algolia.ts +46 -0
- package/src/search/adapters/flexsearch.ts +29 -0
- package/src/search/adapters/index.ts +39 -0
- package/src/search/adapters/mixedbread.ts +39 -0
- package/src/search/adapters/orama-cloud.ts +51 -0
- package/src/search/adapters/orama.ts +24 -0
- package/src/search/adapters/pagefind.ts +27 -0
- package/src/search/adapters/registry.ts +131 -0
- package/src/search/adapters/types.ts +46 -0
- package/src/search/adapters/typesense.ts +57 -0
- package/src/search/build.ts +33 -7
- package/src/search/documents.ts +6 -0
- package/src/search/sync/algolia.ts +12 -11
- package/src/search/sync/index.ts +35 -20
- package/src/search/sync/orama-cloud.ts +12 -9
- package/src/search/sync/typesense.ts +15 -13
- package/src/sources/contentful.ts +63 -0
- package/src/sources/custom.ts +38 -0
- package/src/sources/filesystem.ts +51 -0
- package/src/sources/github-releases.ts +52 -0
- package/src/sources/index.ts +60 -0
- package/src/sources/mdx-remote.ts +76 -0
- package/src/sources/notion.ts +63 -0
- package/src/sources/obsidian.ts +38 -0
- package/src/sources/payload.ts +60 -0
- package/src/sources/registry.ts +182 -0
- package/src/sources/sanity.ts +66 -0
- package/src/sources/shared.ts +52 -0
- package/src/sources/strapi.ts +58 -0
- package/src/theme/entry.ts +47 -2
- package/src/translate/report.ts +40 -7
- package/src/upgrade/upgrade.ts +499 -0
- package/dist/cli/chunk-12dxjqk7.js.map +0 -10
- package/dist/cli/chunk-196vjxp9.js.map +0 -13
- package/dist/cli/chunk-2mzebbbz.js +0 -69
- package/dist/cli/chunk-2z47ypj8.js.map +0 -11
- package/dist/cli/chunk-30e87n55.js +0 -108
- package/dist/cli/chunk-30e87n55.js.map +0 -10
- package/dist/cli/chunk-450a7rcr.js +0 -185
- package/dist/cli/chunk-450a7rcr.js.map +0 -11
- package/dist/cli/chunk-5n7t497w.js.map +0 -40
- package/dist/cli/chunk-61j18dwk.js +0 -5342
- package/dist/cli/chunk-61j18dwk.js.map +0 -58
- package/dist/cli/chunk-88by27n5.js +0 -17
- package/dist/cli/chunk-88by27n5.js.map +0 -10
- package/dist/cli/chunk-8cd8tj54.js.map +0 -34
- package/dist/cli/chunk-aztttvb3.js +0 -381
- package/dist/cli/chunk-aztttvb3.js.map +0 -12
- package/dist/cli/chunk-cvky9gb2.js.map +0 -11
- package/dist/cli/chunk-ejjx8znq.js.map +0 -15
- package/dist/cli/chunk-fmceyezb.js +0 -1007
- package/dist/cli/chunk-fmceyezb.js.map +0 -13
- package/dist/cli/chunk-hdm2dkd2.js.map +0 -48
- package/dist/cli/chunk-hs3gbh8p.js.map +0 -10
- package/dist/cli/chunk-jbj4qhfw.js +0 -30
- package/dist/cli/chunk-jbj4qhfw.js.map +0 -10
- package/dist/cli/chunk-mqb2ka8m.js +0 -68
- package/dist/cli/chunk-mqb2ka8m.js.map +0 -10
- package/dist/cli/chunk-mt76t7dj.js.map +0 -11
- package/dist/cli/chunk-q4rae3bg.js +0 -60
- package/dist/cli/chunk-q4rae3bg.js.map +0 -10
- package/dist/cli/chunk-ra1v2nc2.js +0 -35
- package/dist/cli/chunk-ra1v2nc2.js.map +0 -10
- package/dist/cli/chunk-t3tj0dgr.js.map +0 -15
- package/dist/cli/chunk-tqa1s0k8.js +0 -69
- package/dist/cli/chunk-tqa1s0k8.js.map +0 -11
- package/dist/cli/chunk-vh9w1sgp.js +0 -73
- package/dist/cli/chunk-vh9w1sgp.js.map +0 -10
- package/dist/cli/chunk-vkrsvbr5.js +0 -107
- package/dist/cli/chunk-vkrsvbr5.js.map +0 -11
- package/dist/cli/chunk-vrfp10qk.js +0 -81
- package/dist/cli/chunk-vrfp10qk.js.map +0 -10
- package/dist/cli/chunk-wjt80jps.js +0 -1049
- package/dist/cli/chunk-wjt80jps.js.map +0 -24
- package/docs/advanced/api-reference.mdx +0 -240
- package/docs/reference/cli.mdx +0 -197
- package/skills/blume-migrate/assets/oxfmt@0.55.0.patch +0 -20
- package/src/components/content/changelog-element.ts +0 -69
- package/src/search/providers.ts +0 -91
- /package/dist/cli/{chunk-3w7b2vcx.js.map → chunk-7ez8ny0t.js.map} +0 -0
- /package/dist/cli/{chunk-688e0dde.js.map → chunk-88cpgt6h.js.map} +0 -0
- /package/dist/cli/{chunk-exeeb35e.js.map → chunk-f7t03s3g.js.map} +0 -0
- /package/dist/cli/{chunk-ppfvdcd4.js.map → chunk-zxccj738.js.map} +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Heading, Nodes } from "mdast";
|
|
1
2
|
import { fromMarkdown } from "mdast-util-from-markdown";
|
|
2
3
|
import { gfmFromMarkdown } from "mdast-util-gfm";
|
|
3
4
|
import { toString as mdastToString } from "mdast-util-to-string";
|
|
@@ -6,6 +7,7 @@ import stringWidth from "string-width";
|
|
|
6
7
|
|
|
7
8
|
import matter from "../frontmatter.ts";
|
|
8
9
|
import { PUBLIC_API_URL } from "../github.ts";
|
|
10
|
+
import { neutralizeUnsafeLinks } from "../safe-links.ts";
|
|
9
11
|
import { columnsPrefix } from "../text-width.ts";
|
|
10
12
|
import {
|
|
11
13
|
hashText,
|
|
@@ -121,6 +123,47 @@ const releaseDescription = (body: string): string | undefined => {
|
|
|
121
123
|
return `${head}…`;
|
|
122
124
|
};
|
|
123
125
|
|
|
126
|
+
/** Every heading in a tree, in document order (lists and quotes included). */
|
|
127
|
+
const headingsIn = (node: Nodes): Heading[] => {
|
|
128
|
+
if (node.type === "heading") {
|
|
129
|
+
return [node];
|
|
130
|
+
}
|
|
131
|
+
const found: Heading[] = [];
|
|
132
|
+
if ("children" in node) {
|
|
133
|
+
for (const child of node.children) {
|
|
134
|
+
found.push(...headingsIn(child));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return found;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Lift the notes' headings so the shallowest is an h2. The release page's
|
|
142
|
+
* title is its h1, and changesets opens every section at `### Patch Changes`,
|
|
143
|
+
* which would skip a level on every page. Only ATX headings can sit deeper
|
|
144
|
+
* than h2, so each lift drops leading `#`s; headings are read from the parsed
|
|
145
|
+
* tree, so a `#` inside a code fence is never touched.
|
|
146
|
+
*/
|
|
147
|
+
const liftHeadings = (body: string): string => {
|
|
148
|
+
const headings = headingsIn(
|
|
149
|
+
fromMarkdown(body, {
|
|
150
|
+
extensions: [gfm()],
|
|
151
|
+
mdastExtensions: [gfmFromMarkdown()],
|
|
152
|
+
})
|
|
153
|
+
);
|
|
154
|
+
const lift = Math.min(...headings.map((heading) => heading.depth)) - 2;
|
|
155
|
+
if (headings.length === 0 || lift <= 0) {
|
|
156
|
+
return body;
|
|
157
|
+
}
|
|
158
|
+
let lifted = body;
|
|
159
|
+
// Back to front, so each cut leaves the earlier offsets valid.
|
|
160
|
+
for (const heading of headings.toReversed()) {
|
|
161
|
+
const marker = lifted.indexOf("#", heading.position?.start.offset);
|
|
162
|
+
lifted = lifted.slice(0, marker) + lifted.slice(marker + lift);
|
|
163
|
+
}
|
|
164
|
+
return lifted;
|
|
165
|
+
};
|
|
166
|
+
|
|
124
167
|
/** Slugify a tag into a stable, URL-safe source ref (`v1.2.0` -> `v1-2-0`). */
|
|
125
168
|
const slugifyTag = (tag: string): string =>
|
|
126
169
|
tag.toLowerCase().replaceAll(NON_SLUG, "-").replaceAll(EDGE_DASHES, "");
|
|
@@ -159,7 +202,12 @@ const releaseToEntry = (release: GithubRelease): SourceEntry => {
|
|
|
159
202
|
const title = release.name?.trim() || release.tag_name;
|
|
160
203
|
const date = release.published_at ?? release.created_at;
|
|
161
204
|
const category = release.prerelease ? "Prerelease" : "Release";
|
|
162
|
-
|
|
205
|
+
// Release notes are the repository's content, not the site author's, so a
|
|
206
|
+
// link whose destination isn't a web, mail, or relative address
|
|
207
|
+
// (`javascript:`, `data:`) keeps only its label (see `safe-links.ts`).
|
|
208
|
+
const body = liftHeadings(
|
|
209
|
+
neutralizeUnsafeLinks((release.body ?? "").replaceAll("\r\n", "\n")).trim()
|
|
210
|
+
);
|
|
163
211
|
// A summary in `seo.description` gives each release page a unique meta
|
|
164
212
|
// description (instead of the site-wide fallback) without also rendering the
|
|
165
213
|
// visible lede paragraph a top-level `description` would add.
|
|
@@ -286,6 +334,8 @@ export const githubReleasesSource = (
|
|
|
286
334
|
|
|
287
335
|
return {
|
|
288
336
|
load,
|
|
337
|
+
// Releases are written once, in one language: no locale can translate them.
|
|
338
|
+
monolingual: true,
|
|
289
339
|
name: options.name,
|
|
290
340
|
prefix: options.prefix,
|
|
291
341
|
read,
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The JSON a CMS API returns: arbitrary values a query or REST endpoint
|
|
3
|
+
* hands back, typed as a closed union so a source narrows with predicates
|
|
4
|
+
* instead of casting.
|
|
5
|
+
*/
|
|
6
|
+
export type JsonValue =
|
|
7
|
+
| string
|
|
8
|
+
| number
|
|
9
|
+
| boolean
|
|
10
|
+
| null
|
|
11
|
+
| JsonValue[]
|
|
12
|
+
| { [key: string]: JsonValue };
|
|
13
|
+
|
|
14
|
+
/** A JSON object: a document, a node in a rich-text tree, an API envelope. */
|
|
15
|
+
export interface JsonObject {
|
|
16
|
+
[key: string]: JsonValue;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A node a dot path can descend into. Arrays pass too (matching their runtime
|
|
21
|
+
* string-key indexing, e.g. `items.0`), so the predicate types them as the
|
|
22
|
+
* keyed form both traverse through.
|
|
23
|
+
*/
|
|
24
|
+
export const isJsonObject = (
|
|
25
|
+
value: JsonValue | undefined
|
|
26
|
+
): value is JsonObject => typeof value === "object" && value !== null;
|
|
27
|
+
|
|
28
|
+
/** Resolve a dot path (`slug.current`) against a document. */
|
|
29
|
+
export const getPath = (
|
|
30
|
+
doc: JsonObject,
|
|
31
|
+
path: string
|
|
32
|
+
): JsonValue | undefined => {
|
|
33
|
+
let current: JsonValue | undefined = doc;
|
|
34
|
+
for (const key of path.split(".")) {
|
|
35
|
+
if (isJsonObject(current)) {
|
|
36
|
+
current = current[key];
|
|
37
|
+
} else {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return current;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const isStringValue = (value: JsonValue | undefined): value is string =>
|
|
45
|
+
typeof value === "string";
|
|
46
|
+
|
|
47
|
+
export const asString = (value: JsonValue | undefined): string | undefined =>
|
|
48
|
+
isStringValue(value) ? value : undefined;
|
|
49
|
+
|
|
50
|
+
/** The elements of an array value, or none when the value is not an array. */
|
|
51
|
+
export const asArray = (value: JsonValue | undefined): JsonValue[] =>
|
|
52
|
+
Array.isArray(value) ? value : [];
|
|
53
|
+
|
|
54
|
+
/** The object form of a value, or none when it is a scalar, array, or absent. */
|
|
55
|
+
export const asObject = (
|
|
56
|
+
value: JsonValue | undefined
|
|
57
|
+
): JsonObject | undefined =>
|
|
58
|
+
isJsonObject(value) && !Array.isArray(value) ? value : undefined;
|
|
59
|
+
|
|
60
|
+
export const isNumberValue = (value: JsonValue | undefined): value is number =>
|
|
61
|
+
typeof value === "number";
|
|
62
|
+
|
|
63
|
+
export const asNumber = (value: JsonValue | undefined): number | undefined =>
|
|
64
|
+
isNumberValue(value) ? value : undefined;
|
|
65
|
+
|
|
66
|
+
/** The object elements of an array value; scalars and nested arrays are dropped. */
|
|
67
|
+
export const objectsIn = (value: JsonValue | undefined): JsonObject[] =>
|
|
68
|
+
asArray(value).flatMap((item) => {
|
|
69
|
+
const object = asObject(item);
|
|
70
|
+
return object ? [object] : [];
|
|
71
|
+
});
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lexical (Payload's rich text editor) → Markdown. Covers the nodes Payload's
|
|
3
|
+
* default features emit — paragraphs, headings, lists (bullet, numbered,
|
|
4
|
+
* check), quotes, links, uploads, horizontal rules — and hands `block` /
|
|
5
|
+
* `inlineBlock` nodes to a serializer keyed by their `blockType`, or notes
|
|
6
|
+
* them in a comment.
|
|
7
|
+
*/
|
|
8
|
+
import type { JsonObject } from "./json.ts";
|
|
9
|
+
import { asNumber, asObject, asString, getPath, objectsIn } from "./json.ts";
|
|
10
|
+
import type { InlineMarks } from "./lower.ts";
|
|
11
|
+
import {
|
|
12
|
+
absoluteUrl,
|
|
13
|
+
blockquote,
|
|
14
|
+
headingPrefix,
|
|
15
|
+
image,
|
|
16
|
+
indent,
|
|
17
|
+
listItem,
|
|
18
|
+
markdownDocument,
|
|
19
|
+
renderInline,
|
|
20
|
+
renderLink,
|
|
21
|
+
unsupported,
|
|
22
|
+
writesMdx,
|
|
23
|
+
} from "./lower.ts";
|
|
24
|
+
|
|
25
|
+
export interface LexicalOptions {
|
|
26
|
+
/** The CMS origin a relative upload URL (`/media/x.png`) resolves against. */
|
|
27
|
+
baseUrl?: string;
|
|
28
|
+
/** Serializers for `block`/`inlineBlock` nodes, keyed by `fields.blockType`. */
|
|
29
|
+
serializers?: Record<string, (fields: JsonObject) => string>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Lexical's text `format` bitmask.
|
|
33
|
+
const IS_BOLD = 1;
|
|
34
|
+
const IS_ITALIC = 2;
|
|
35
|
+
const IS_STRIKETHROUGH = 4;
|
|
36
|
+
const IS_CODE = 16;
|
|
37
|
+
|
|
38
|
+
/** Whether a power-of-two flag is set in the format bitmask. */
|
|
39
|
+
const hasFlag = (format: number, flag: number): boolean =>
|
|
40
|
+
Math.floor(format / flag) % 2 === 1;
|
|
41
|
+
|
|
42
|
+
const marksOf = (node: JsonObject): InlineMarks => {
|
|
43
|
+
const format = asNumber(node.format) ?? 0;
|
|
44
|
+
return {
|
|
45
|
+
bold: hasFlag(format, IS_BOLD),
|
|
46
|
+
code: hasFlag(format, IS_CODE),
|
|
47
|
+
italic: hasFlag(format, IS_ITALIC),
|
|
48
|
+
strike: hasFlag(format, IS_STRIKETHROUGH),
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const children = (node: JsonObject): JsonObject[] => objectsIn(node.children);
|
|
53
|
+
|
|
54
|
+
const typeOf = (node: JsonObject): string => asString(node.type) ?? "";
|
|
55
|
+
|
|
56
|
+
const customBlock = (node: JsonObject, options: LexicalOptions): string => {
|
|
57
|
+
const fields = asObject(node.fields);
|
|
58
|
+
const blockType = fields ? asString(fields.blockType) : undefined;
|
|
59
|
+
const serializer = blockType ? options.serializers?.[blockType] : undefined;
|
|
60
|
+
if (fields && serializer) {
|
|
61
|
+
return serializer(fields);
|
|
62
|
+
}
|
|
63
|
+
return unsupported(
|
|
64
|
+
`Lexical block${blockType ? `: ${blockType}` : ""}`,
|
|
65
|
+
writesMdx(options.serializers)
|
|
66
|
+
);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const renderInlines = (nodes: JsonObject[], options: LexicalOptions): string =>
|
|
70
|
+
nodes
|
|
71
|
+
// oxlint-disable-next-line no-use-before-define -- mutual recursion: a link's label is itself inline content
|
|
72
|
+
.map((node) => renderInlineNode(node, options))
|
|
73
|
+
.join("");
|
|
74
|
+
|
|
75
|
+
const renderInlineNode = (
|
|
76
|
+
node: JsonObject,
|
|
77
|
+
options: LexicalOptions
|
|
78
|
+
): string => {
|
|
79
|
+
switch (typeOf(node)) {
|
|
80
|
+
case "text": {
|
|
81
|
+
return renderInline(asString(node.text) ?? "", marksOf(node));
|
|
82
|
+
}
|
|
83
|
+
case "linebreak": {
|
|
84
|
+
return "\n";
|
|
85
|
+
}
|
|
86
|
+
case "tab": {
|
|
87
|
+
return "\t";
|
|
88
|
+
}
|
|
89
|
+
case "link":
|
|
90
|
+
case "autolink": {
|
|
91
|
+
// `fields.url` on Payload 3; `url` on older Lexical payloads. An
|
|
92
|
+
// internal link (`linkType: "internal"`) carries a document, not a
|
|
93
|
+
// URL, so its label stays.
|
|
94
|
+
return renderLink(
|
|
95
|
+
renderInlines(children(node), options),
|
|
96
|
+
asString(getPath(node, "fields.url")) ?? asString(node.url)
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
case "inlineBlock": {
|
|
100
|
+
return customBlock(node, options);
|
|
101
|
+
}
|
|
102
|
+
default: {
|
|
103
|
+
return renderInlines(children(node), options);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const renderUpload = (node: JsonObject, options: LexicalOptions): string => {
|
|
109
|
+
const value = asObject(node.value);
|
|
110
|
+
const url = value ? asString(value.url) : undefined;
|
|
111
|
+
if (!(value && url)) {
|
|
112
|
+
return unsupported(
|
|
113
|
+
"Lexical upload (fetch with depth 1 to populate it)",
|
|
114
|
+
writesMdx(options.serializers)
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
const alt = asString(value.alt) ?? asString(value.filename) ?? "";
|
|
118
|
+
return image(alt, absoluteUrl(url, options.baseUrl));
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const renderList = (node: JsonObject, options: LexicalOptions): string => {
|
|
122
|
+
const listType = asString(node.listType) ?? "bullet";
|
|
123
|
+
const start = asNumber(node.start) ?? 1;
|
|
124
|
+
const items: string[] = [];
|
|
125
|
+
let lastMarker = "-";
|
|
126
|
+
let index = 0;
|
|
127
|
+
for (const item of children(node)) {
|
|
128
|
+
const kids = children(item);
|
|
129
|
+
const nested = kids.filter((kid) => typeOf(kid) === "list");
|
|
130
|
+
const inline = kids.filter((kid) => typeOf(kid) !== "list");
|
|
131
|
+
const sub = nested.map((list) => renderList(list, options)).join("\n");
|
|
132
|
+
if (inline.length === 0 && nested.length > 0) {
|
|
133
|
+
// Lexical nests a sub-list as its own item holding only the list; it
|
|
134
|
+
// hangs under the item before it, or stands alone when there is none.
|
|
135
|
+
items.push(items.length > 0 ? indent(sub, lastMarker.length + 1) : sub);
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
lastMarker = listType === "number" ? `${start + index}.` : "-";
|
|
139
|
+
let check = "";
|
|
140
|
+
if (listType === "check") {
|
|
141
|
+
check = item.checked === true ? "[x] " : "[ ] ";
|
|
142
|
+
}
|
|
143
|
+
const line = `${check}${renderInlines(inline, options)}`;
|
|
144
|
+
items.push(listItem(lastMarker, sub ? `${line}\n${sub}` : line));
|
|
145
|
+
index += 1;
|
|
146
|
+
}
|
|
147
|
+
return items.join("\n");
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const renderBlock = (node: JsonObject, options: LexicalOptions): string => {
|
|
151
|
+
switch (typeOf(node)) {
|
|
152
|
+
case "paragraph": {
|
|
153
|
+
return renderInlines(children(node), options);
|
|
154
|
+
}
|
|
155
|
+
case "heading": {
|
|
156
|
+
const level = Number((asString(node.tag) ?? "h1").slice(1));
|
|
157
|
+
return `${headingPrefix(level)}${renderInlines(children(node), options)}`;
|
|
158
|
+
}
|
|
159
|
+
case "quote": {
|
|
160
|
+
return blockquote(renderInlines(children(node), options));
|
|
161
|
+
}
|
|
162
|
+
case "list": {
|
|
163
|
+
return renderList(node, options);
|
|
164
|
+
}
|
|
165
|
+
case "horizontalrule": {
|
|
166
|
+
return "---";
|
|
167
|
+
}
|
|
168
|
+
case "upload": {
|
|
169
|
+
return renderUpload(node, options);
|
|
170
|
+
}
|
|
171
|
+
case "block": {
|
|
172
|
+
return customBlock(node, options);
|
|
173
|
+
}
|
|
174
|
+
default: {
|
|
175
|
+
return unsupported(
|
|
176
|
+
`Lexical node: ${typeOf(node)}`,
|
|
177
|
+
writesMdx(options.serializers)
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Serialize a Lexical editor state (`{ root: { children } }`, or the root
|
|
185
|
+
* node itself) to Markdown.
|
|
186
|
+
*/
|
|
187
|
+
export const lexicalToMarkdown = (
|
|
188
|
+
state: JsonObject,
|
|
189
|
+
options: LexicalOptions = {}
|
|
190
|
+
): string => {
|
|
191
|
+
const root = asObject(state.root) ?? state;
|
|
192
|
+
return markdownDocument(
|
|
193
|
+
children(root).map((node) => renderBlock(node, options))
|
|
194
|
+
);
|
|
195
|
+
};
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared primitives for lowering a CMS's rich-text tree to Markdown text.
|
|
3
|
+
* Every rich-text lowerer (Portable Text, Contentful, Lexical, Strapi
|
|
4
|
+
* blocks) renders inline runs, wraps them in block syntax, and joins blocks
|
|
5
|
+
* with blank lines; these helpers keep that Markdown identical across them.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { isSafeHref } from "../safe-href.ts";
|
|
9
|
+
|
|
10
|
+
// Markdown/raw-HTML structure characters. Rich-text leaves are *plain text* —
|
|
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 or a code span mid-paragraph, and `<`
|
|
14
|
+
// let CMS prose inject raw HTML into the rendered page. `{` and `}` open an
|
|
15
|
+
// expression once an entry is written as MDX (see {@link writesMdx}). CommonMark
|
|
16
|
+
// backslash-escapes every ASCII punctuation character, so `\*` is always the
|
|
17
|
+
// literal asterisk, in `.md` and `.mdx` alike.
|
|
18
|
+
const MARKDOWN_SPECIALS = /[\\`*_{}[\]~<]/gu;
|
|
19
|
+
|
|
20
|
+
// An entity or numeric character reference (`©`, `&`) is decoded
|
|
21
|
+
// even in plain text, so one typed in the CMS would render as the character
|
|
22
|
+
// it names rather than as written. A bare `&` is left alone.
|
|
23
|
+
const CHARACTER_REFERENCE = /&(?=#?[a-z0-9]+;)/giu;
|
|
24
|
+
|
|
25
|
+
// Block syntax is only syntax at the start of a line: `# `, `- `, `+ ` and
|
|
26
|
+
// `1. `/`1) ` need the space (or line end) to become a heading or list item,
|
|
27
|
+
// while `>` opens a quote on its own, and a line of only `=` or `-` turns the
|
|
28
|
+
// line above it into a heading (or, alone, into a rule). A CMS paragraph that
|
|
29
|
+
// begins with one of these — a soft break inside it counts as a line start
|
|
30
|
+
// too — must stay prose.
|
|
31
|
+
const BLOCK_START =
|
|
32
|
+
/^(?<marker>[#+-]|\d+[.)])(?=[ \t]|$)|^(?<quote>>)|^(?<rule>=+|-{2,})(?=[ \t]*$)/gmu;
|
|
33
|
+
|
|
34
|
+
// MDX reads a top-level paragraph that opens with `import ` or `export ` as an
|
|
35
|
+
// ESM statement, and prose like "import the CSV first" then fails the whole
|
|
36
|
+
// page. The keyword's first letter as a character reference renders the same
|
|
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;
|
|
40
|
+
|
|
41
|
+
const escapeBlockStart = (text: string): string =>
|
|
42
|
+
text.replaceAll(BLOCK_START, (marker: string) => {
|
|
43
|
+
// A numbered marker escapes its punctuation (`1\.`), the rest themselves.
|
|
44
|
+
const index = marker.search(/[.)]/u);
|
|
45
|
+
return index === -1
|
|
46
|
+
? `\\${marker}`
|
|
47
|
+
: `${marker.slice(0, index)}\\${marker.slice(index)}`;
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const escapeEsmStart = (text: string): string =>
|
|
51
|
+
text.replaceAll(
|
|
52
|
+
ESM_START,
|
|
53
|
+
(keyword: string) => `&#${keyword.codePointAt(0)};${keyword.slice(1)}`
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
export const escapeMarkdownText = (text: string): string =>
|
|
57
|
+
escapeEsmStart(
|
|
58
|
+
escapeBlockStart(
|
|
59
|
+
text
|
|
60
|
+
.replaceAll(MARKDOWN_SPECIALS, String.raw`\$&`)
|
|
61
|
+
.replaceAll(CHARACTER_REFERENCE, String.raw`\&`)
|
|
62
|
+
)
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
/** The marks a lowerer can put on an inline run. */
|
|
66
|
+
export interface InlineMarks {
|
|
67
|
+
bold?: boolean;
|
|
68
|
+
code?: boolean;
|
|
69
|
+
italic?: boolean;
|
|
70
|
+
strike?: boolean;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const BACKTICK_RUN = /`+/gu;
|
|
74
|
+
|
|
75
|
+
/** The longest run of backticks in `text`, 0 when there is none. */
|
|
76
|
+
const longestBacktickRun = (text: string): number => {
|
|
77
|
+
let longest = 0;
|
|
78
|
+
for (const run of text.match(BACKTICK_RUN) ?? []) {
|
|
79
|
+
longest = Math.max(longest, run.length);
|
|
80
|
+
}
|
|
81
|
+
return longest;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* A code span whose delimiter outruns any backtick run inside it, padded with
|
|
86
|
+
* a space on each side when the code starts or ends with a backtick (the
|
|
87
|
+
* CommonMark rule that keeps the padding out of the rendered code).
|
|
88
|
+
*/
|
|
89
|
+
export const codeSpan = (code: string): string => {
|
|
90
|
+
const fence = "`".repeat(longestBacktickRun(code) + 1);
|
|
91
|
+
const pad = code.startsWith("`") || code.endsWith("`") ? " " : "";
|
|
92
|
+
return `${fence}${pad}${code}${pad}${fence}`;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const EDGE_SPACE = /^(?<lead>\s*)(?<body>[\s\S]*?)(?<trail>\s*)$/u;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Wrap a text run in the Markdown for its marks. Code stays verbatim inside
|
|
99
|
+
* the backticks (an escape would render as a backslash); everything else is
|
|
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.
|
|
103
|
+
*/
|
|
104
|
+
export const renderInline = (text: string, marks: InlineMarks): string => {
|
|
105
|
+
const match = EDGE_SPACE.exec(text);
|
|
106
|
+
const lead = match?.groups?.lead ?? "";
|
|
107
|
+
const body = match?.groups?.body ?? "";
|
|
108
|
+
const trail = match?.groups?.trail ?? "";
|
|
109
|
+
if (body === "") {
|
|
110
|
+
return text;
|
|
111
|
+
}
|
|
112
|
+
let out = marks.code ? codeSpan(body) : escapeMarkdownText(body);
|
|
113
|
+
if (marks.bold) {
|
|
114
|
+
out = `**${out}**`;
|
|
115
|
+
}
|
|
116
|
+
if (marks.italic) {
|
|
117
|
+
out = `*${out}*`;
|
|
118
|
+
}
|
|
119
|
+
if (marks.strike) {
|
|
120
|
+
out = `~~${out}~~`;
|
|
121
|
+
}
|
|
122
|
+
return `${lead}${out}${trail}`;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// A destination with whitespace or parentheses ends early in `[label](…)`;
|
|
126
|
+
// CommonMark's pointy-bracket form carries it intact.
|
|
127
|
+
const UNSAFE_DESTINATION = /[\s()]/u;
|
|
128
|
+
|
|
129
|
+
/** A link or image destination as Markdown can carry it verbatim. */
|
|
130
|
+
export const destination = (url: string): string =>
|
|
131
|
+
UNSAFE_DESTINATION.test(url) ? `<${url}>` : url;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* A Markdown link, or the label alone when the target is missing — or unsafe:
|
|
135
|
+
* CMS content is the editor's, not the site author's, so a destination that
|
|
136
|
+
* isn't a web, mail, or relative address (`javascript:`, `data:`) never
|
|
137
|
+
* becomes a clickable link on the docs site (see `safe-links.ts`).
|
|
138
|
+
*/
|
|
139
|
+
export const renderLink = (label: string, href?: string): string =>
|
|
140
|
+
href && isSafeHref(href) ? `[${label}](${destination(href)})` : label;
|
|
141
|
+
|
|
142
|
+
/** The ATX prefix for a heading level, clamped to Markdown's six. */
|
|
143
|
+
export const headingPrefix = (level: number): string =>
|
|
144
|
+
`${"#".repeat(Math.min(Math.max(Math.trunc(level), 1), 6))} `;
|
|
145
|
+
|
|
146
|
+
/** Quote every line of a block. */
|
|
147
|
+
export const blockquote = (text: string): string =>
|
|
148
|
+
text
|
|
149
|
+
.split("\n")
|
|
150
|
+
.map((line) => (line === "" ? ">" : `> ${line}`))
|
|
151
|
+
.join("\n");
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* A list item: the marker on the first line, and every continuation line
|
|
155
|
+
* indented to the marker's width so nested blocks stay inside the item.
|
|
156
|
+
*/
|
|
157
|
+
export const listItem = (marker: string, body: string): string => {
|
|
158
|
+
const [first = "", ...rest] = body.split("\n");
|
|
159
|
+
const pad = " ".repeat(marker.length + 1);
|
|
160
|
+
return [
|
|
161
|
+
`${marker} ${first}`,
|
|
162
|
+
...rest.map((line) => (line === "" ? "" : `${pad}${line}`)),
|
|
163
|
+
].join("\n");
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
/** Indent every non-empty line, for a nested list hanging off an item. */
|
|
167
|
+
export const indent = (text: string, width: number): string => {
|
|
168
|
+
const pad = " ".repeat(width);
|
|
169
|
+
return text
|
|
170
|
+
.split("\n")
|
|
171
|
+
.map((line) => (line === "" ? "" : `${pad}${line}`))
|
|
172
|
+
.join("\n");
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
/** A fenced code block whose fence outruns any backtick run in the code. */
|
|
176
|
+
export const codeFence = (code: string, language = ""): string => {
|
|
177
|
+
const fence = "`".repeat(Math.max(3, longestBacktickRun(code) + 1));
|
|
178
|
+
return `${fence}${language}\n${code}\n${fence}`;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
/** A Markdown image; the alt is escaped so a `]` in a caption can't close it. */
|
|
182
|
+
export const image = (alt: string, url: string): string =>
|
|
183
|
+
`})`;
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Whether a lowerer's output is written as MDX. Serializers are how a CMS
|
|
187
|
+
* block becomes a Blume component, and components (like directives) only
|
|
188
|
+
* render in MDX, so configuring any serializer switches the source's
|
|
189
|
+
* rich-text bodies to `.mdx`. The escaping above keeps the rest of the
|
|
190
|
+
* lowered text valid there.
|
|
191
|
+
*/
|
|
192
|
+
export const writesMdx = <Node>(
|
|
193
|
+
serializers?: Record<string, (node: Node) => string>
|
|
194
|
+
): boolean => serializers !== undefined && Object.keys(serializers).length > 0;
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* A comment marking a node the lowerer has no Markdown for — an MDX comment
|
|
198
|
+
* when the output is MDX, which rejects `<!-- -->`.
|
|
199
|
+
*/
|
|
200
|
+
export const unsupported = (what: string, mdx = false): string =>
|
|
201
|
+
mdx ? `{/* unsupported ${what} */}` : `<!-- unsupported ${what} -->`;
|
|
202
|
+
|
|
203
|
+
/** Blocks separated by blank lines; empty blocks are dropped. */
|
|
204
|
+
export const joinBlocks = (blocks: string[]): string =>
|
|
205
|
+
blocks.filter((block) => block !== "").join("\n\n");
|
|
206
|
+
|
|
207
|
+
/** A whole document: its blocks plus the trailing newline a file ends with. */
|
|
208
|
+
export const markdownDocument = (blocks: string[]): string =>
|
|
209
|
+
`${joinBlocks(blocks)}\n`;
|
|
210
|
+
|
|
211
|
+
const ABSOLUTE = /^[a-z][a-z0-9+.-]*:/iu;
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* A media URL as a static site can reference it. A protocol-relative
|
|
215
|
+
* Contentful asset (`//images.ctfassets.net/…`) gets `https:`; a path a
|
|
216
|
+
* self-hosted CMS serves (`/uploads/x.png`) resolves against the CMS origin.
|
|
217
|
+
*/
|
|
218
|
+
export const absoluteUrl = (url: string, base?: string): string => {
|
|
219
|
+
if (url.startsWith("//")) {
|
|
220
|
+
return `https:${url}`;
|
|
221
|
+
}
|
|
222
|
+
if (ABSOLUTE.test(url) || base === undefined) {
|
|
223
|
+
return url;
|
|
224
|
+
}
|
|
225
|
+
return new URL(url, base).href;
|
|
226
|
+
};
|
|
@@ -675,17 +675,66 @@ const scanLinkLine = (
|
|
|
675
675
|
return next;
|
|
676
676
|
};
|
|
677
677
|
|
|
678
|
+
/**
|
|
679
|
+
* A component's string `href` (`<Card href="./install" />`): the attribute
|
|
680
|
+
* may sit on a later line than the tag name, where a formatter wraps a long
|
|
681
|
+
* element, so the gap between them spans lines. An expression-valued
|
|
682
|
+
* `href={…}` isn't a literal target, and a lowercase tag is raw HTML in a
|
|
683
|
+
* `.md` page, so neither is matched.
|
|
684
|
+
*/
|
|
685
|
+
const COMPONENT_HREF =
|
|
686
|
+
/<[A-Z][\w.]*(?=[\s/>])[^<>]*?\shref=(?:"(?<double>[^"]*)"|'(?<single>[^']*)')/gu;
|
|
687
|
+
|
|
688
|
+
/** The body with fenced blocks and inline code blanked, shape preserved. */
|
|
689
|
+
const maskCode = (lines: readonly string[]): string => {
|
|
690
|
+
let fence: FenceState = null;
|
|
691
|
+
const masked = lines.map((line) => {
|
|
692
|
+
const next = nextFenceState(line, fence);
|
|
693
|
+
const inFence = fence !== null || next !== null;
|
|
694
|
+
fence = next;
|
|
695
|
+
return inFence
|
|
696
|
+
? " ".repeat(line.length)
|
|
697
|
+
: line.replaceAll(INLINE_CODE, (span) => " ".repeat(span.length));
|
|
698
|
+
});
|
|
699
|
+
return masked.join("\n");
|
|
700
|
+
};
|
|
701
|
+
|
|
702
|
+
/** Every component `href` target in `body`, with its 1-based position. */
|
|
703
|
+
const componentHrefs = (
|
|
704
|
+
lines: readonly string[],
|
|
705
|
+
lineOffset: number
|
|
706
|
+
): PageLink[] => {
|
|
707
|
+
const links: PageLink[] = [];
|
|
708
|
+
const text = maskCode(lines);
|
|
709
|
+
for (const match of text.matchAll(COMPONENT_HREF)) {
|
|
710
|
+
const target = match.groups?.double ?? match.groups?.single ?? "";
|
|
711
|
+
// The value ends one character (its closing quote) before the match does.
|
|
712
|
+
const at = match.index + match[0].length - target.length - 1;
|
|
713
|
+
const before = text.slice(0, at);
|
|
714
|
+
const lineStart = before.lastIndexOf("\n") + 1;
|
|
715
|
+
links.push({
|
|
716
|
+
column: at - lineStart + 1,
|
|
717
|
+
line: lineOffset + before.split("\n").length,
|
|
718
|
+
target,
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
return links;
|
|
722
|
+
};
|
|
723
|
+
|
|
678
724
|
export const extractLinks = (body: string, lineOffset = 0): PageLink[] => {
|
|
679
725
|
const links: PageLink[] = [];
|
|
680
726
|
let fence: FenceState = null;
|
|
681
727
|
let lineNumber = lineOffset;
|
|
682
728
|
|
|
683
|
-
|
|
729
|
+
const lines = body.split("\n");
|
|
730
|
+
for (const line of lines) {
|
|
684
731
|
lineNumber += 1;
|
|
685
732
|
fence = scanLinkLine(line, lineNumber, fence, links);
|
|
686
733
|
}
|
|
687
734
|
|
|
688
|
-
return
|
|
735
|
+
return body.includes("href=")
|
|
736
|
+
? [...links, ...componentHrefs(lines, lineOffset)]
|
|
737
|
+
: links;
|
|
689
738
|
};
|
|
690
739
|
|
|
691
740
|
// Double-quoted strings hold JSX attribute values and JSON in `{...}` props; a
|
|
@@ -1172,6 +1221,7 @@ export const normalizeEntry = (
|
|
|
1172
1221
|
lastModified: meta.lastModified ?? entry.lastModified,
|
|
1173
1222
|
links: entryLinks(entry),
|
|
1174
1223
|
meta,
|
|
1224
|
+
monolingual: ctx.source.monolingual,
|
|
1175
1225
|
navPath,
|
|
1176
1226
|
segments,
|
|
1177
1227
|
source: { name: ctx.source.name, ref: entry.ref },
|