blume 2.1.0 → 2.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +110 -0
- package/dist/cli/{chunk-ga0pf4aj.js → chunk-0n9xkp3d.js} +12 -3
- package/dist/cli/{chunk-ga0pf4aj.js.map → chunk-0n9xkp3d.js.map} +3 -3
- package/dist/cli/{chunk-43zcf4gz.js → chunk-0pmb4tr4.js} +541 -368
- package/dist/cli/chunk-0pmb4tr4.js.map +35 -0
- package/dist/cli/{chunk-9geymqns.js → chunk-0w6hs479.js} +15 -15
- package/dist/cli/{chunk-xkz5xbtv.js → chunk-124fvmzq.js} +2802 -1422
- package/dist/cli/chunk-124fvmzq.js.map +220 -0
- package/dist/cli/chunk-150jk7km.js +52 -0
- package/dist/cli/chunk-150jk7km.js.map +10 -0
- package/dist/cli/{chunk-y4ykbark.js → chunk-1f882900.js} +2 -2
- package/dist/cli/{chunk-5ffywsmb.js → chunk-297ajdcv.js} +8 -6
- package/dist/cli/{chunk-5ffywsmb.js.map → chunk-297ajdcv.js.map} +3 -3
- package/dist/cli/{chunk-h2a48yhd.js → chunk-32khwcb6.js} +120 -16
- package/dist/cli/{chunk-h2a48yhd.js.map → chunk-32khwcb6.js.map} +10 -10
- package/dist/cli/{chunk-5cg2rzgf.js → chunk-36bxs9c7.js} +20 -49
- package/dist/cli/{chunk-5cg2rzgf.js.map → chunk-36bxs9c7.js.map} +4 -5
- package/dist/cli/{chunk-py62fewh.js → chunk-53jqw8z0.js} +3 -3
- package/dist/cli/{chunk-5wj2qwn2.js → chunk-58bt3hag.js} +4 -4
- package/dist/cli/{chunk-e7vvk5ya.js → chunk-5cmts6dv.js} +3 -3
- package/dist/cli/{chunk-y5h4r2y0.js → chunk-60hz265r.js} +15 -5
- package/dist/cli/chunk-60hz265r.js.map +10 -0
- package/dist/cli/chunk-74wfhv79.js +65 -0
- package/dist/cli/chunk-74wfhv79.js.map +10 -0
- package/dist/cli/{chunk-g2z9x01v.js → chunk-9qzgg6mj.js} +3 -3
- package/dist/cli/{chunk-q3wxxq39.js → chunk-a57yh0h9.js} +5 -5
- package/dist/cli/{chunk-713mngdq.js → chunk-ave1wcbr.js} +76 -10
- package/dist/cli/chunk-ave1wcbr.js.map +14 -0
- package/dist/cli/{chunk-2n4ps1b3.js → chunk-bqxaqn5p.js} +40 -22
- package/dist/cli/chunk-bqxaqn5p.js.map +15 -0
- package/dist/cli/{chunk-jnwj5kjj.js → chunk-bxwhtvw8.js} +20 -7
- package/dist/cli/{chunk-jnwj5kjj.js.map → chunk-bxwhtvw8.js.map} +3 -3
- package/dist/cli/chunk-ckrcm08h.js +324 -0
- package/dist/cli/chunk-ckrcm08h.js.map +14 -0
- package/dist/cli/{chunk-2f3ndkpw.js → chunk-desvm72z.js} +3 -3
- package/dist/cli/{chunk-gp84za0h.js → chunk-dhwtqnnd.js} +2 -2
- package/dist/cli/{chunk-4h8z9vv5.js → chunk-dpy8qdjf.js} +93 -47
- package/dist/cli/chunk-dpy8qdjf.js.map +17 -0
- package/dist/cli/{chunk-2f7n4qzt.js → chunk-dqhg54mr.js} +1 -1
- package/dist/cli/{chunk-0gz36akr.js → chunk-dv2z0rzf.js} +21 -5
- package/dist/cli/chunk-dv2z0rzf.js.map +12 -0
- package/dist/cli/{chunk-cm89scdv.js → chunk-es8h0v3j.js} +9 -11
- package/dist/cli/{chunk-cm89scdv.js.map → chunk-es8h0v3j.js.map} +3 -3
- package/dist/cli/{chunk-zhvd6adm.js → chunk-gfc5fqvf.js} +14 -14
- package/dist/cli/{chunk-nd1821sk.js → chunk-gxtg6ed2.js} +33 -19
- package/dist/cli/chunk-gxtg6ed2.js.map +10 -0
- package/dist/cli/{chunk-p804f3pe.js → chunk-hvcxng5f.js} +6 -6
- package/dist/cli/{chunk-ta27feev.js → chunk-j1x92mx9.js} +21 -2
- package/dist/cli/chunk-j1x92mx9.js.map +10 -0
- package/dist/cli/{chunk-rekqz4qg.js → chunk-k3gr1nm9.js} +19 -16
- package/dist/cli/chunk-k3gr1nm9.js.map +10 -0
- package/dist/cli/{chunk-1n228xqb.js → chunk-kdpzw38a.js} +2 -2
- package/dist/cli/{chunk-cy5bm0j0.js → chunk-ms9z66nz.js} +2 -2
- package/dist/cli/{chunk-cy5bm0j0.js.map → chunk-ms9z66nz.js.map} +2 -2
- package/dist/cli/{chunk-zf748avv.js → chunk-nbyvgnnm.js} +193 -286
- package/dist/cli/chunk-nbyvgnnm.js.map +36 -0
- package/dist/cli/{chunk-6bm0xpgd.js → chunk-s0bdnc7v.js} +2 -2
- package/dist/cli/{chunk-yxmk4pec.js → chunk-sc93apfs.js} +17 -16
- package/dist/cli/{chunk-yxmk4pec.js.map → chunk-sc93apfs.js.map} +3 -3
- package/dist/cli/{chunk-cmtcnh9f.js → chunk-tfycddbt.js} +2 -2
- package/dist/cli/{chunk-9xr4dgee.js → chunk-twy7psdz.js} +29 -16
- package/dist/cli/chunk-twy7psdz.js.map +10 -0
- package/dist/cli/{chunk-ntv4qrt7.js → chunk-y4msrwm9.js} +15 -15
- package/dist/cli/{chunk-yj17vs39.js → chunk-ydp3065p.js} +5 -5
- package/dist/cli/index.js +24 -19
- package/dist/cli/index.js.map +3 -3
- package/dist/types/ai/ask-context.d.ts +6 -2
- package/dist/types/ai/ask.d.ts +24 -5
- package/dist/types/ai/component-markdown.d.ts +2 -0
- package/dist/types/ai/openapi-components.d.ts +4 -1
- package/dist/types/analytics/clics.d.ts +37 -0
- package/dist/types/analytics/index.d.ts +2 -0
- package/dist/types/analytics/schema.d.ts +18 -0
- package/dist/types/astro/integration.d.ts +11 -4
- package/dist/types/astro/markdown-negotiation.d.ts +3 -0
- package/dist/types/astro/pages.d.ts +10 -0
- package/dist/types/cli/init/scaffold.d.ts +1 -1
- package/dist/types/components/content/api-field.d.ts +4 -1
- package/dist/types/components/content/api-page.d.ts +6 -1
- package/dist/types/components/openapi/code-samples.d.ts +29 -0
- package/dist/types/components/openapi/helpers.d.ts +22 -1
- package/dist/types/components/openapi/security.d.ts +10 -0
- package/dist/types/components/openapi/snippets.d.ts +60 -0
- package/dist/types/core/changelog-index.d.ts +8 -0
- package/dist/types/core/config-input.d.ts +37 -6
- package/dist/types/core/data.d.ts +4 -0
- package/dist/types/core/i18n-ui.d.ts +2 -0
- package/dist/types/core/links.d.ts +8 -0
- package/dist/types/core/powered-by.d.ts +12 -0
- package/dist/types/core/probe.d.ts +2 -0
- package/dist/types/core/project-graph.d.ts +6 -0
- package/dist/types/core/redirect-patterns.d.ts +11 -3
- package/dist/types/core/schema.d.ts +228 -3
- package/dist/types/core/sources/cache.d.ts +3 -0
- package/dist/types/core/sources/contentful-rich-text.d.ts +11 -2
- package/dist/types/core/sources/contentful.d.ts +17 -1
- package/dist/types/core/sources/lexical.d.ts +5 -0
- package/dist/types/core/sources/lower.d.ts +10 -2
- package/dist/types/core/sources/normalize.d.ts +20 -1
- package/dist/types/core/sources/notion.d.ts +8 -0
- package/dist/types/core/sources/remote.d.ts +37 -10
- package/dist/types/core/sources/sanity.d.ts +1 -1
- package/dist/types/core/types.d.ts +17 -0
- package/dist/types/deploy/adapter-output.d.ts +2 -1
- package/dist/types/deploy/adapters/cloudflare.d.ts +7 -5
- package/dist/types/deploy/adapters/node.d.ts +58 -23
- package/dist/types/deploy/adapters/registry.d.ts +10 -0
- package/dist/types/deploy/adapters/types.d.ts +3 -2
- package/dist/types/deploy/cloudflare-negotiation.d.ts +5 -2
- package/dist/types/deploy/node-headers.d.ts +12 -4
- package/dist/types/deploy/platforms/cloudflare.d.ts +10 -7
- package/dist/types/deploy/platforms/node.d.ts +3 -1
- package/dist/types/deploy/platforms/types.d.ts +18 -7
- package/dist/types/deploy/platforms/vercel.d.ts +21 -2
- package/dist/types/deploy/redirects.d.ts +17 -1
- package/dist/types/deploy/sitemap.d.ts +2 -1
- package/dist/types/deploy/vercel-negotiation.d.ts +44 -21
- package/dist/types/narration/build.d.ts +34 -12
- package/dist/types/narration/provider.d.ts +96 -8
- package/dist/types/openapi/checks.d.ts +27 -0
- package/dist/types/openapi/graphql.d.ts +6 -0
- package/dist/types/openapi/model.d.ts +15 -4
- package/dist/types/openapi/parse.d.ts +3 -0
- package/dist/types/openapi/references.d.ts +16 -0
- package/dist/types/openapi/sentence.d.ts +11 -1
- package/dist/types/ratelimit/index.d.ts +1 -1
- package/dist/types/ratelimit/schema.d.ts +8 -0
- package/dist/types/ratelimit/upstash.d.ts +39 -14
- package/dist/types/reference/graphql.d.ts +76 -1
- package/dist/types/reference/index.d.ts +1 -1
- package/dist/types/reference/openapi.d.ts +2 -2
- package/dist/types/reference/options.d.ts +37 -1
- package/dist/types/reference/schema.d.ts +84 -0
- package/dist/types/search/adapters/registry.d.ts +3 -0
- package/dist/types/search/adapters/typesense.d.ts +14 -4
- package/dist/types/search/build.d.ts +3 -1
- package/dist/types/search/orama-index.d.ts +9 -9
- package/dist/types/search/sync/algolia.d.ts +5 -2
- package/dist/types/search/sync/index.d.ts +3 -1
- package/dist/types/search/sync/typesense.d.ts +11 -7
- package/dist/types/seo/jsonld.d.ts +11 -6
- package/dist/types/sources/contentful.d.ts +16 -0
- package/dist/types/sources/github-releases.d.ts +8 -1
- package/dist/types/sources/notion.d.ts +8 -0
- package/dist/types/sources/payload.d.ts +2 -0
- package/dist/types/sources/registry.d.ts +63 -0
- package/dist/types/sources/shared.d.ts +3 -0
- package/dist/types/sources/strapi.d.ts +2 -0
- package/docs/01-quickstart.mdx +4 -2
- package/docs/02-deployment.mdx +59 -13
- package/docs/03-upgrading.mdx +2 -1
- package/docs/04-migrating.mdx +9 -9
- package/docs/08-faq.mdx +12 -12
- package/docs/advanced/changelog.mdx +30 -2
- package/docs/advanced/custom-pages.mdx +1 -1
- package/docs/advanced/skills.mdx +1 -1
- package/docs/cli/audit.mdx +30 -7
- package/docs/cli/doctor.mdx +6 -2
- package/docs/cli/evals.mdx +4 -4
- package/docs/cli/translate.mdx +1 -0
- package/docs/cli/validate.mdx +4 -3
- package/docs/configuration/analytics.mdx +19 -1
- package/docs/configuration/assistant.mdx +6 -2
- package/docs/configuration/customization.mdx +4 -2
- package/docs/configuration/export.mdx +5 -3
- package/docs/configuration/index.mdx +4 -2
- package/docs/configuration/narration.mdx +43 -6
- package/docs/configuration/rate-limiting.mdx +23 -2
- package/docs/configuration/search.mdx +37 -14
- package/docs/content/components.mdx +2 -2
- package/docs/content/frontmatter.mdx +5 -3
- package/docs/content/i18n.mdx +2 -2
- package/docs/content/includes.mdx +1 -1
- package/docs/content/navigation.mdx +8 -6
- package/docs/content/sources/contentful.mdx +9 -4
- package/docs/content/sources/custom.mdx +7 -3
- package/docs/content/sources/github-releases.mdx +12 -2
- package/docs/content/sources/index.mdx +3 -3
- package/docs/content/sources/notion.mdx +17 -2
- package/docs/content/sources/obsidian.mdx +3 -1
- package/docs/content/sources/payload.mdx +5 -2
- package/docs/content/sources/remote-mdx.mdx +6 -0
- package/docs/content/sources/sanity.mdx +4 -2
- package/docs/content/sources/strapi.mdx +5 -2
- package/docs/content/syntax.mdx +2 -2
- package/docs/discoverability/agent-discovery.mdx +1 -1
- package/docs/discoverability/index.mdx +1 -1
- package/docs/discoverability/json-api.mdx +2 -0
- package/docs/discoverability/llms-txt.mdx +1 -1
- package/docs/discoverability/markdown.mdx +3 -1
- package/docs/discoverability/mcp.mdx +6 -0
- package/docs/discoverability/sitemap-and-robots.mdx +1 -1
- package/docs/discoverability/structured-data.mdx +8 -6
- package/docs/references/api-pages.mdx +1 -1
- package/docs/references/asyncapi.mdx +5 -1
- package/docs/references/graphql.mdx +15 -1
- package/docs/references/openapi.mdx +64 -8
- package/package.json +9 -9
- package/skills/blume/SKILL.md +1 -1
- package/skills/blume-migrate/SKILL.md +2 -2
- package/skills/blume-migrate/assets/{oxfmt@0.67.0.patch → oxfmt@0.71.0.patch} +7 -7
- package/skills/blume-migrate/references/docusaurus.md +4 -4
- package/skills/blume-migrate/references/fumadocs.md +17 -13
- package/skills/blume-migrate/references/monorepo.md +5 -5
- package/skills/blume-migrate/references/nextra.md +8 -5
- package/skills/blume-migrate/references/starlight.md +9 -7
- package/src/ai/ask-context.ts +33 -3
- package/src/ai/ask-limits.ts +13 -0
- package/src/ai/ask.ts +24 -6
- package/src/ai/changelog-markdown.ts +11 -6
- package/src/ai/component-markdown.ts +1 -1
- package/src/ai/cors.ts +12 -3
- package/src/ai/mcp/query.ts +14 -6
- package/src/ai/openapi-components.ts +28 -4
- package/src/ai/relative-links.ts +4 -38
- package/src/analytics/clics.ts +58 -0
- package/src/analytics/head.ts +5 -0
- package/src/analytics/index.ts +2 -0
- package/src/analytics/schema.ts +2 -0
- package/src/astro/generate.ts +91 -56
- package/src/astro/integration.ts +72 -22
- package/src/astro/markdown-negotiation.ts +3 -0
- package/src/astro/module-types.ts +3 -1
- package/src/astro/pages.ts +23 -0
- package/src/astro/render-deps.ts +10 -5
- package/src/astro/runtime-deps.ts +23 -2
- package/src/astro/templates.ts +129 -81
- package/src/audit/catalog.ts +19 -3
- package/src/audit/checks/links.ts +14 -1
- package/src/audit/checks/network.ts +110 -6
- package/src/audit/checks/redirects.ts +6 -4
- package/src/audit/checks/robots.ts +58 -2
- package/src/audit/run.ts +3 -0
- package/src/audit/types.ts +2 -0
- package/src/audit/url.ts +14 -5
- package/src/blume-modules.d.ts +5 -1
- package/src/cli/args.ts +51 -0
- package/src/cli/build-failure.ts +23 -0
- package/src/cli/commands/audit.ts +10 -1
- package/src/cli/commands/dev.ts +18 -2
- package/src/cli/commands/doctor.ts +45 -12
- package/src/cli/commands/eval.ts +36 -4
- package/src/cli/commands/init.ts +6 -4
- package/src/cli/commands/validate.ts +10 -1
- package/src/cli/init/scaffold.ts +9 -6
- package/src/cli/prepare.ts +6 -3
- package/src/cli/required-secrets.ts +3 -2
- package/src/components/content/ApiPlayground.astro +6 -2
- package/src/components/content/api-field.ts +4 -1
- package/src/components/content/api-page.ts +31 -7
- package/src/components/content/mermaid-element.ts +15 -1
- package/src/components/content/view-switcher.ts +7 -4
- package/src/components/islands/hooks.ts +27 -1
- package/src/components/layout/NavTree.astro +49 -5
- package/src/components/layout/NavTreeScript.astro +18 -0
- package/src/components/layout/PageActions.astro +139 -24
- package/src/components/layout/PageLayout.astro +13 -3
- package/src/components/layout/RootLayout.astro +27 -14
- package/src/components/layout/Search.astro +17 -3
- package/src/components/layout/TableOfContents.astro +24 -17
- package/src/components/layout/analytics-client.ts +8 -8
- package/src/components/layout/head-scripts.ts +6 -1
- package/src/components/layout/search/algolia.ts +10 -1
- package/src/components/layout/search/analytics.ts +42 -14
- package/src/components/layout/search/endpoint.ts +48 -5
- package/src/components/layout/search/pagefind.ts +114 -5
- package/src/components/layout/search/types.ts +12 -0
- package/src/components/layout/search/typesense.ts +18 -6
- package/src/components/openapi/AsyncApiOperation.astro +14 -7
- package/src/components/openapi/Authorization.astro +10 -0
- package/src/components/openapi/GraphqlOperation.astro +5 -0
- package/src/components/openapi/Operation.astro +24 -1
- package/src/components/openapi/ParametersTable.astro +8 -4
- package/src/components/openapi/Playground.astro +7 -1
- package/src/components/openapi/RequestBody.astro +19 -2
- package/src/components/openapi/Responses.astro +1 -0
- package/src/components/openapi/SchemaTable.astro +15 -3
- package/src/components/openapi/async-snippets.ts +61 -5
- package/src/components/openapi/async.ts +123 -29
- package/src/components/openapi/graphql-helpers.ts +35 -12
- package/src/components/openapi/helpers.ts +36 -2
- package/src/components/openapi/message-model.ts +54 -11
- package/src/components/openapi/message.ts +14 -4
- package/src/components/openapi/operation-model.ts +16 -7
- package/src/components/openapi/playground-client.ts +77 -58
- package/src/components/openapi/playground-schema.ts +1 -1
- package/src/components/openapi/schema-tree.ts +44 -14
- package/src/components/openapi/security.ts +10 -0
- package/src/components/openapi/snippets.ts +5 -3
- package/src/components/openapi/validate-json.ts +4 -3
- package/src/components/powered-by-middleware.ts +33 -0
- package/src/core/changelog-index.ts +31 -1
- package/src/core/component-diagnostics.ts +53 -0
- package/src/core/component-overrides.ts +32 -0
- package/src/core/config-input.ts +42 -6
- package/src/core/data.ts +2 -0
- package/src/core/diagnostics.ts +5 -0
- package/src/core/i18n-ui.ts +2 -0
- package/src/core/includes.ts +1 -1
- package/src/core/links.ts +47 -17
- package/src/core/powered-by.ts +12 -0
- package/src/core/probe.ts +26 -6
- package/src/core/project-graph.ts +59 -18
- package/src/core/redirect-patterns.ts +36 -7
- package/src/core/schema.ts +26 -6
- package/src/core/sources/cache.ts +6 -0
- package/src/core/sources/contentful-rich-text.ts +29 -6
- package/src/core/sources/contentful.ts +135 -25
- package/src/core/sources/filesystem.ts +26 -4
- package/src/core/sources/github-releases.ts +12 -1
- package/src/core/sources/lexical.ts +15 -10
- package/src/core/sources/lower.ts +19 -3
- package/src/core/sources/mdx-remote.ts +62 -10
- package/src/core/sources/normalize.ts +227 -41
- package/src/core/sources/notion.ts +34 -10
- package/src/core/sources/obsidian.ts +16 -3
- package/src/core/sources/payload.ts +38 -15
- package/src/core/sources/remote.ts +72 -15
- package/src/core/sources/resolve.ts +11 -1
- package/src/core/sources/sanity.ts +43 -25
- package/src/core/sources/strapi.ts +2 -2
- package/src/core/types.ts +18 -0
- package/src/core/ui-packs/ar.ts +1 -0
- package/src/core/ui-packs/bg.ts +1 -0
- package/src/core/ui-packs/bn.ts +1 -0
- package/src/core/ui-packs/ca.ts +1 -0
- package/src/core/ui-packs/cs.ts +1 -0
- package/src/core/ui-packs/da.ts +1 -0
- package/src/core/ui-packs/de.ts +1 -0
- package/src/core/ui-packs/el.ts +1 -0
- package/src/core/ui-packs/es.ts +1 -0
- package/src/core/ui-packs/fa.ts +1 -0
- package/src/core/ui-packs/fi.ts +1 -0
- package/src/core/ui-packs/fr.ts +1 -0
- package/src/core/ui-packs/he.ts +1 -0
- package/src/core/ui-packs/hi.ts +1 -0
- package/src/core/ui-packs/hr.ts +1 -0
- package/src/core/ui-packs/hu.ts +1 -0
- package/src/core/ui-packs/id.ts +1 -0
- package/src/core/ui-packs/it.ts +1 -0
- package/src/core/ui-packs/ja.ts +1 -0
- package/src/core/ui-packs/ko.ts +1 -0
- package/src/core/ui-packs/nl.ts +1 -0
- package/src/core/ui-packs/no.ts +1 -0
- package/src/core/ui-packs/pl.ts +1 -0
- package/src/core/ui-packs/pt-br.ts +1 -0
- package/src/core/ui-packs/pt.ts +1 -0
- package/src/core/ui-packs/ro.ts +1 -0
- package/src/core/ui-packs/ru.ts +1 -0
- package/src/core/ui-packs/sk.ts +1 -0
- package/src/core/ui-packs/sr.ts +1 -0
- package/src/core/ui-packs/sv.ts +1 -0
- package/src/core/ui-packs/th.ts +1 -0
- package/src/core/ui-packs/tr.ts +1 -0
- package/src/core/ui-packs/uk.ts +1 -0
- package/src/core/ui-packs/vi.ts +1 -0
- package/src/core/ui-packs/zh-tw.ts +1 -0
- package/src/core/ui-packs/zh.ts +1 -0
- package/src/deploy/adapter-output.ts +7 -2
- package/src/deploy/adapters/cloudflare.ts +7 -5
- package/src/deploy/adapters/node.ts +47 -9
- package/src/deploy/adapters/types.ts +7 -3
- package/src/deploy/artifacts.ts +2 -1
- package/src/deploy/cloudflare-negotiation.ts +36 -7
- package/src/deploy/headers.ts +15 -1
- package/src/deploy/node-headers.ts +32 -20
- package/src/deploy/platforms/cloudflare.ts +14 -9
- package/src/deploy/platforms/netlify.ts +2 -1
- package/src/deploy/platforms/node.ts +12 -2
- package/src/deploy/platforms/types.ts +21 -7
- package/src/deploy/platforms/vercel.ts +115 -17
- package/src/deploy/redirects.ts +66 -6
- package/src/deploy/rss.ts +3 -1
- package/src/deploy/sitemap.ts +39 -5
- package/src/deploy/vercel-negotiation.ts +325 -103
- package/src/eval/agents.ts +18 -1
- package/src/eval/prompts.ts +6 -3
- package/src/eval/report.ts +34 -13
- package/src/eval/run.ts +5 -0
- package/src/markdown/base-links.ts +15 -14
- package/src/markdown/package-commands.ts +9 -8
- package/src/markdown/relative-links.ts +3 -2
- package/src/narration/build.ts +218 -40
- package/src/narration/provider.ts +89 -9
- package/src/openapi/checks.ts +273 -0
- package/src/openapi/graphql-build.ts +21 -0
- package/src/openapi/graphql.ts +11 -0
- package/src/openapi/model.ts +15 -3
- package/src/openapi/parse.ts +127 -2
- package/src/openapi/references.ts +43 -3
- package/src/openapi/render-mdx.ts +68 -8
- package/src/openapi/sentence.ts +29 -2
- package/src/openapi/source.ts +39 -14
- package/src/ratelimit/index.ts +1 -1
- package/src/ratelimit/runtime.ts +37 -19
- package/src/ratelimit/upstash.ts +40 -10
- package/src/reference/graphql.ts +15 -1
- package/src/reference/index.ts +1 -0
- package/src/reference/openapi.ts +2 -2
- package/src/reference/options.ts +29 -1
- package/src/registry/eject.ts +8 -2
- package/src/search/adapters/locale-scope.ts +30 -0
- package/src/search/adapters/typesense.ts +14 -4
- package/src/search/build.ts +21 -3
- package/src/search/orama-index.ts +163 -31
- package/src/search/source-pages.ts +29 -0
- package/src/search/sync/algolia.ts +99 -9
- package/src/search/sync/index.ts +52 -13
- package/src/search/sync/typesense.ts +72 -27
- package/src/seo/jsonld.ts +38 -20
- package/src/sources/contentful.ts +12 -0
- package/src/sources/github-releases.ts +7 -1
- package/src/sources/notion.ts +7 -0
- package/src/sources/shared.ts +3 -0
- package/src/theme/color.ts +9 -10
- package/src/theme/entry.ts +43 -6
- package/src/translate/validate.ts +23 -1
- package/dist/cli/chunk-0gz36akr.js.map +0 -12
- package/dist/cli/chunk-2n4ps1b3.js.map +0 -15
- package/dist/cli/chunk-43zcf4gz.js.map +0 -36
- package/dist/cli/chunk-4ejxbzee.js +0 -39
- package/dist/cli/chunk-4ejxbzee.js.map +0 -10
- package/dist/cli/chunk-4h8z9vv5.js.map +0 -16
- package/dist/cli/chunk-713mngdq.js.map +0 -14
- package/dist/cli/chunk-9xr4dgee.js.map +0 -10
- package/dist/cli/chunk-amrb6hgh.js +0 -145
- package/dist/cli/chunk-amrb6hgh.js.map +0 -11
- package/dist/cli/chunk-nd1821sk.js.map +0 -10
- package/dist/cli/chunk-rekqz4qg.js.map +0 -10
- package/dist/cli/chunk-ta27feev.js.map +0 -10
- package/dist/cli/chunk-xkz5xbtv.js.map +0 -213
- package/dist/cli/chunk-y5h4r2y0.js.map +0 -10
- package/dist/cli/chunk-zf748avv.js.map +0 -37
- /package/dist/cli/{chunk-9geymqns.js.map → chunk-0w6hs479.js.map} +0 -0
- /package/dist/cli/{chunk-y4ykbark.js.map → chunk-1f882900.js.map} +0 -0
- /package/dist/cli/{chunk-py62fewh.js.map → chunk-53jqw8z0.js.map} +0 -0
- /package/dist/cli/{chunk-5wj2qwn2.js.map → chunk-58bt3hag.js.map} +0 -0
- /package/dist/cli/{chunk-e7vvk5ya.js.map → chunk-5cmts6dv.js.map} +0 -0
- /package/dist/cli/{chunk-g2z9x01v.js.map → chunk-9qzgg6mj.js.map} +0 -0
- /package/dist/cli/{chunk-q3wxxq39.js.map → chunk-a57yh0h9.js.map} +0 -0
- /package/dist/cli/{chunk-2f3ndkpw.js.map → chunk-desvm72z.js.map} +0 -0
- /package/dist/cli/{chunk-gp84za0h.js.map → chunk-dhwtqnnd.js.map} +0 -0
- /package/dist/cli/{chunk-2f7n4qzt.js.map → chunk-dqhg54mr.js.map} +0 -0
- /package/dist/cli/{chunk-zhvd6adm.js.map → chunk-gfc5fqvf.js.map} +0 -0
- /package/dist/cli/{chunk-p804f3pe.js.map → chunk-hvcxng5f.js.map} +0 -0
- /package/dist/cli/{chunk-1n228xqb.js.map → chunk-kdpzw38a.js.map} +0 -0
- /package/dist/cli/{chunk-6bm0xpgd.js.map → chunk-s0bdnc7v.js.map} +0 -0
- /package/dist/cli/{chunk-cmtcnh9f.js.map → chunk-tfycddbt.js.map} +0 -0
- /package/dist/cli/{chunk-ntv4qrt7.js.map → chunk-y4msrwm9.js.map} +0 -0
- /package/dist/cli/{chunk-yj17vs39.js.map → chunk-ydp3065p.js.map} +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
|
|
3
3
|
import GithubSlugger from "github-slugger";
|
|
4
|
+
import type { YAMLException } from "js-yaml";
|
|
4
5
|
import type { Nodes } from "mdast";
|
|
5
6
|
import { extname } from "pathe";
|
|
6
7
|
import { markdownToMdast } from "satteri";
|
|
@@ -22,7 +23,13 @@ import type {
|
|
|
22
23
|
ResolvedI18nConfig,
|
|
23
24
|
} from "../schema.ts";
|
|
24
25
|
import { trimChar } from "../trim.ts";
|
|
25
|
-
import type {
|
|
26
|
+
import type {
|
|
27
|
+
Diagnostic,
|
|
28
|
+
ExampleUse,
|
|
29
|
+
Heading,
|
|
30
|
+
PageLink,
|
|
31
|
+
PageRecord,
|
|
32
|
+
} from "../types.ts";
|
|
26
33
|
import { detectVersionRef, versionizeRoute } from "../versions.ts";
|
|
27
34
|
import type { NormalizeContext, SourceEntry } from "./types.ts";
|
|
28
35
|
|
|
@@ -127,6 +134,29 @@ export const unloadablePathDiagnostic = (
|
|
|
127
134
|
}
|
|
128
135
|
: undefined;
|
|
129
136
|
|
|
137
|
+
/**
|
|
138
|
+
* The error for a content file whose front matter isn't valid YAML — most
|
|
139
|
+
* often an unquoted value holding `: `, which YAML reads as a nested mapping.
|
|
140
|
+
* Thrown out of a source's scan, it would fail every command with
|
|
141
|
+
* BLUME_INTERNAL; a source leaves the file out and reports this instead, like
|
|
142
|
+
* a page whose front matter fails the schema. js-yaml counts lines from the
|
|
143
|
+
* text after the opening `---`, which starts with the rest of that fence
|
|
144
|
+
* line, so its 0-based line is already the file's line minus one.
|
|
145
|
+
*/
|
|
146
|
+
export const frontmatterYamlDiagnostic = (
|
|
147
|
+
error: YAMLException,
|
|
148
|
+
file: string
|
|
149
|
+
): Diagnostic => ({
|
|
150
|
+
code: "BLUME_FRONTMATTER_INVALID",
|
|
151
|
+
column: error.mark ? error.mark.column + 1 : undefined,
|
|
152
|
+
file,
|
|
153
|
+
line: error.mark ? error.mark.line + 1 : undefined,
|
|
154
|
+
message: `Front matter isn't valid YAML (${error.reason}), so the page was left out of the site.`,
|
|
155
|
+
severity: "error",
|
|
156
|
+
suggestion:
|
|
157
|
+
'Wrap the value at that line in double quotes, e.g. description: "Setup: the easy way".',
|
|
158
|
+
});
|
|
159
|
+
|
|
130
160
|
/**
|
|
131
161
|
* Fold one raw path part into the accumulating route segments/groups.
|
|
132
162
|
* `ordered` parts are file or folder names, whose ordering prefix is dropped.
|
|
@@ -235,9 +265,11 @@ const PROMPT_CLOSE = /<\/Prompt>/u;
|
|
|
235
265
|
// the same marker — and the only spelling that survives the MDX parser, where
|
|
236
266
|
// a bare `{…}` is a JSX expression and `#id` is not a valid one (`Could not
|
|
237
267
|
// parse expression with acorn`). Further bracket markers may follow the brace
|
|
238
|
-
// (`{#id} [toc]`), nothing else.
|
|
268
|
+
// (`{#id} [toc]`), nothing else. The spaced `{ #id }` and kramdown's
|
|
269
|
+
// `{: #id }` (MkDocs `attr_list` writes both) fail the compile the same way,
|
|
270
|
+
// so they match too, though neither is an anchor in `.md`.
|
|
239
271
|
const BARE_CURLY_MARKER =
|
|
240
|
-
/(?<!\\)
|
|
272
|
+
/(?<!\\)(?<marker>\{:?\s*#(?<id>[^\s}]+)\s*\})(?:\s*\[(?:#[^\s\]]+|!?toc)\])*\s*$/u;
|
|
241
273
|
|
|
242
274
|
// A raw HTML element carrying an `id` — `<a id="…">`, `<section id='…'>`,
|
|
243
275
|
// the unquoted `<a id=plain>`, or the JSX spelling `<div id={"…"}>` — whose
|
|
@@ -259,6 +291,8 @@ export interface CurlyMarker {
|
|
|
259
291
|
id: string;
|
|
260
292
|
/** 1-based line of the heading (a setext heading's first text line) in the body. */
|
|
261
293
|
line: number;
|
|
294
|
+
/** The braces as written (`{#id}`, `{ #id }`, `{: #id }`). */
|
|
295
|
+
marker: string;
|
|
262
296
|
}
|
|
263
297
|
|
|
264
298
|
/** Scanner state: the open fence plus the paragraph lines accumulated so far. */
|
|
@@ -628,9 +662,9 @@ const noteCurlyMarker = (
|
|
|
628
662
|
line: number,
|
|
629
663
|
state: HeadingScanState
|
|
630
664
|
): void => {
|
|
631
|
-
const
|
|
632
|
-
if (id !== undefined) {
|
|
633
|
-
state.curlyMarkers.push({ id, line });
|
|
665
|
+
const groups = text.match(BARE_CURLY_MARKER)?.groups;
|
|
666
|
+
if (groups?.id !== undefined && groups.marker !== undefined) {
|
|
667
|
+
state.curlyMarkers.push({ id: groups.id, line, marker: groups.marker });
|
|
634
668
|
}
|
|
635
669
|
};
|
|
636
670
|
|
|
@@ -890,6 +924,42 @@ export const targetOffsetIn = (
|
|
|
890
924
|
title: string | undefined
|
|
891
925
|
): number => matched.length - 1 - (title?.length ?? 0) - target.length;
|
|
892
926
|
|
|
927
|
+
// A link-reference definition's destination, at the start of what follows its
|
|
928
|
+
// `[label]:` (see `REF_DEFINITION`): `<…>`, which may hold spaces, or else the
|
|
929
|
+
// run up to the first space, before any title.
|
|
930
|
+
const DEFINITION_DESTINATION = /^(?:<(?<angle>[^<>]*)>|(?<bare>\S+))/u;
|
|
931
|
+
|
|
932
|
+
// A CommonMark autolink (`<https://example.com>`). Only http(s) targets are
|
|
933
|
+
// ever checked, so no other scheme is read. MDX rejects the syntax, so in
|
|
934
|
+
// practice it's a `.md` page's.
|
|
935
|
+
const AUTOLINK = /<(?<target>https?:\/\/[^\s<>]*)>/giu;
|
|
936
|
+
|
|
937
|
+
/**
|
|
938
|
+
* Record the destination of a link-reference definition on `line`: a
|
|
939
|
+
* reference link (`[text][label]`, `[label]`) renders with it, so it's the
|
|
940
|
+
* target to check, once for every link that cites it. A footnote definition
|
|
941
|
+
* (`[^1]: …`) is no link; the links inside it are read like any others.
|
|
942
|
+
*/
|
|
943
|
+
const scanDefinition = (
|
|
944
|
+
line: string,
|
|
945
|
+
lineNumber: number,
|
|
946
|
+
links: PageLink[]
|
|
947
|
+
): void => {
|
|
948
|
+
const groups = line.match(REF_DEFINITION)?.groups;
|
|
949
|
+
if (groups?.label === undefined || groups.label.startsWith("^")) {
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
const rest = groups.rest ?? "";
|
|
953
|
+
const destination = rest.match(DEFINITION_DESTINATION)?.groups;
|
|
954
|
+
const target = destination?.angle ?? destination?.bare;
|
|
955
|
+
if (target) {
|
|
956
|
+
// `rest` runs to the end of the line; an angle-bracketed target starts
|
|
957
|
+
// one past its `<`.
|
|
958
|
+
const at = line.length - rest.length + (destination?.angle ? 1 : 0);
|
|
959
|
+
links.push({ column: at + 1, line: lineNumber, target });
|
|
960
|
+
}
|
|
961
|
+
};
|
|
962
|
+
|
|
893
963
|
/**
|
|
894
964
|
* Extract link targets from a markdown body for later validation, recording the
|
|
895
965
|
* 1-based line/column of each target. Skips fenced code blocks and inline code.
|
|
@@ -954,18 +1024,32 @@ const scanLinkLine = (
|
|
|
954
1024
|
});
|
|
955
1025
|
}
|
|
956
1026
|
}
|
|
1027
|
+
for (const match of masked.matchAll(AUTOLINK)) {
|
|
1028
|
+
const target = match.groups?.target ?? "";
|
|
1029
|
+
links.push({ column: match.index + 2, line: lineNumber, target });
|
|
1030
|
+
}
|
|
1031
|
+
scanDefinition(masked, lineNumber, links);
|
|
957
1032
|
return next;
|
|
958
1033
|
};
|
|
959
1034
|
|
|
960
1035
|
/**
|
|
961
|
-
* A component's string `href` (`<Card href="./install" />`)
|
|
962
|
-
* may sit on a later line than the tag name, where
|
|
963
|
-
* element, so the gap between them spans lines. An
|
|
964
|
-
* `href={…}` isn't a literal target,
|
|
965
|
-
*
|
|
1036
|
+
* A component's string `href` (`<Card href="./install" />`), or a lowercase
|
|
1037
|
+
* `<a href>`: the attribute may sit on a later line than the tag name, where
|
|
1038
|
+
* a formatter wraps a long element, so the gap between them spans lines. An
|
|
1039
|
+
* expression-valued `href={…}` isn't a literal target, so it isn't matched,
|
|
1040
|
+
* and no other lowercase tag is a link.
|
|
966
1041
|
*/
|
|
967
|
-
const
|
|
968
|
-
/<[A-Z][\w.]
|
|
1042
|
+
const ELEMENT_HREF =
|
|
1043
|
+
/<(?:[A-Z][\w.]*|(?<anchor>a))(?=[\s/>])[^<>]*?\shref=(?:"(?<double>[^"]*)"|'(?<single>[^']*)')/gu;
|
|
1044
|
+
|
|
1045
|
+
/**
|
|
1046
|
+
* An inline link whose label wraps onto later lines of its paragraph
|
|
1047
|
+
* (`[a long⏎label](/x)`), as a formatter leaves one: the line scan can't see
|
|
1048
|
+
* it. The label holds a line break but no blank line (which would end the
|
|
1049
|
+
* paragraph) and no brackets, so a match never repeats a one-line link.
|
|
1050
|
+
*/
|
|
1051
|
+
const WRAPPED_LINK =
|
|
1052
|
+
/\[[^[\]\n]*(?:\n(?![ \t]*(?:\n|$))[^[\]\n]*)+\]\((?<target>(?:[^()\s]|\([^()\s]*\))+)(?<title>\s+"[^"]*")?\)/gu;
|
|
969
1053
|
|
|
970
1054
|
/** The body with fenced blocks and inline code blanked, shape preserved. */
|
|
971
1055
|
const maskCode = (lines: readonly string[]): string => {
|
|
@@ -981,24 +1065,83 @@ const maskCode = (lines: readonly string[]): string => {
|
|
|
981
1065
|
return masked.join("\n");
|
|
982
1066
|
};
|
|
983
1067
|
|
|
984
|
-
/**
|
|
985
|
-
const
|
|
1068
|
+
/** The offset each of `lines` starts at once they're joined with `\n`. */
|
|
1069
|
+
const lineStartsOf = (lines: readonly string[]): number[] => {
|
|
1070
|
+
const starts: number[] = [];
|
|
1071
|
+
let at = 0;
|
|
1072
|
+
for (const line of lines) {
|
|
1073
|
+
starts.push(at);
|
|
1074
|
+
at += line.length + 1;
|
|
1075
|
+
}
|
|
1076
|
+
return starts;
|
|
1077
|
+
};
|
|
1078
|
+
|
|
1079
|
+
/**
|
|
1080
|
+
* The 1-based position of offset `at` in the joined text whose `lineStarts`
|
|
1081
|
+
* are given, lines shifted by `lineOffset`. A binary search, so a page with
|
|
1082
|
+
* many links isn't rescanned from the top for each one.
|
|
1083
|
+
*/
|
|
1084
|
+
const positionIn = (
|
|
1085
|
+
lineStarts: readonly number[],
|
|
1086
|
+
at: number,
|
|
1087
|
+
lineOffset: number
|
|
1088
|
+
): Pick<PageLink, "column" | "line"> => {
|
|
1089
|
+
let low = 0;
|
|
1090
|
+
let high = lineStarts.length - 1;
|
|
1091
|
+
while (low < high) {
|
|
1092
|
+
const mid = Math.ceil((low + high) / 2);
|
|
1093
|
+
if ((lineStarts[mid] ?? 0) <= at) {
|
|
1094
|
+
low = mid;
|
|
1095
|
+
} else {
|
|
1096
|
+
high = mid - 1;
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
return {
|
|
1100
|
+
column: at - (lineStarts[low] ?? 0) + 1,
|
|
1101
|
+
line: lineOffset + low + 1,
|
|
1102
|
+
};
|
|
1103
|
+
};
|
|
1104
|
+
|
|
1105
|
+
/**
|
|
1106
|
+
* Every link in `body` that can span lines, with its 1-based position: an
|
|
1107
|
+
* element's `href` (see `ELEMENT_HREF`) and a link whose label wraps (see
|
|
1108
|
+
* `WRAPPED_LINK`). A lowercase `<a>` is raw HTML in `.md` and a plain element
|
|
1109
|
+
* in `.mdx`; the Markdown pipeline passes either through as written, so its
|
|
1110
|
+
* target is marked `raw`.
|
|
1111
|
+
*/
|
|
1112
|
+
const multilineLinks = (
|
|
986
1113
|
lines: readonly string[],
|
|
987
1114
|
lineOffset: number
|
|
988
1115
|
): PageLink[] => {
|
|
989
1116
|
const links: PageLink[] = [];
|
|
990
1117
|
const text = maskCode(lines);
|
|
991
|
-
|
|
1118
|
+
const lineStarts = lineStartsOf(lines);
|
|
1119
|
+
for (const match of text.matchAll(ELEMENT_HREF)) {
|
|
992
1120
|
const target = match.groups?.double ?? match.groups?.single ?? "";
|
|
993
1121
|
// The value ends one character (its closing quote) before the match does.
|
|
994
1122
|
const at = match.index + match[0].length - target.length - 1;
|
|
995
|
-
const
|
|
996
|
-
|
|
997
|
-
links.push({
|
|
998
|
-
column: at - lineStart + 1,
|
|
999
|
-
line: lineOffset + before.split("\n").length,
|
|
1123
|
+
const link: PageLink = {
|
|
1124
|
+
...positionIn(lineStarts, at, lineOffset),
|
|
1000
1125
|
target,
|
|
1001
|
-
}
|
|
1126
|
+
};
|
|
1127
|
+
if (match.groups?.anchor) {
|
|
1128
|
+
link.raw = true;
|
|
1129
|
+
}
|
|
1130
|
+
links.push(link);
|
|
1131
|
+
}
|
|
1132
|
+
for (const match of text.matchAll(WRAPPED_LINK)) {
|
|
1133
|
+
const target = match.groups?.target ?? "";
|
|
1134
|
+
const at =
|
|
1135
|
+
match.index + targetOffsetIn(match[0], target, match.groups?.title);
|
|
1136
|
+
const link: PageLink = {
|
|
1137
|
+
...positionIn(lineStarts, at, lineOffset),
|
|
1138
|
+
target,
|
|
1139
|
+
};
|
|
1140
|
+
// As on one line, a preceding `!` makes it an image embed.
|
|
1141
|
+
if (text[match.index - 1] === "!") {
|
|
1142
|
+
link.image = true;
|
|
1143
|
+
}
|
|
1144
|
+
links.push(link);
|
|
1002
1145
|
}
|
|
1003
1146
|
return links;
|
|
1004
1147
|
};
|
|
@@ -1014,9 +1157,37 @@ export const extractLinks = (body: string, lineOffset = 0): PageLink[] => {
|
|
|
1014
1157
|
fence = scanLinkLine(line, lineNumber, fence, links);
|
|
1015
1158
|
}
|
|
1016
1159
|
|
|
1017
|
-
return
|
|
1018
|
-
|
|
1019
|
-
|
|
1160
|
+
return [...links, ...multilineLinks(lines, lineOffset)];
|
|
1161
|
+
};
|
|
1162
|
+
|
|
1163
|
+
/**
|
|
1164
|
+
* A `<Component>` example's string `path` (`<Component path="forms/login" />`),
|
|
1165
|
+
* wrapped onto a later line or not, read the way `ELEMENT_HREF` reads an
|
|
1166
|
+
* `href`. An expression-valued `path={…}` isn't a literal, so it isn't matched.
|
|
1167
|
+
*/
|
|
1168
|
+
const COMPONENT_PATH =
|
|
1169
|
+
/<Component(?=[\s/>])[^<>]*?\spath=(?:"(?<double>[^"]*)"|'(?<single>[^']*)')/gu;
|
|
1170
|
+
|
|
1171
|
+
/**
|
|
1172
|
+
* Every `<Component path>` in an `.mdx` body, with the 1-based position of its
|
|
1173
|
+
* value; lines are shifted by `lineOffset`, as {@link extractLinks} does. Code
|
|
1174
|
+
* blocks and inline code are skipped.
|
|
1175
|
+
*/
|
|
1176
|
+
export const extractExampleUses = (
|
|
1177
|
+
body: string,
|
|
1178
|
+
lineOffset = 0
|
|
1179
|
+
): ExampleUse[] => {
|
|
1180
|
+
if (!body.includes("<Component")) {
|
|
1181
|
+
return [];
|
|
1182
|
+
}
|
|
1183
|
+
const lines = body.split("\n");
|
|
1184
|
+
const lineStarts = lineStartsOf(lines);
|
|
1185
|
+
return [...maskCode(lines).matchAll(COMPONENT_PATH)].map((match) => {
|
|
1186
|
+
const path = match.groups?.double ?? match.groups?.single ?? "";
|
|
1187
|
+
// The value ends one character (its closing quote) before the match does.
|
|
1188
|
+
const at = match.index + match[0].length - path.length - 1;
|
|
1189
|
+
return { ...positionIn(lineStarts, at, lineOffset), path };
|
|
1190
|
+
});
|
|
1020
1191
|
};
|
|
1021
1192
|
|
|
1022
1193
|
// Double-quoted strings hold JSX attribute values and JSON in `{...}` props; a
|
|
@@ -1082,23 +1253,29 @@ export const strippedLineOffset = (
|
|
|
1082
1253
|
const entryLineOffset = (entry: SourceEntry): number =>
|
|
1083
1254
|
entry.bodyLineOffset ?? strippedLineOffset(entry.raw, entry.body.text);
|
|
1084
1255
|
|
|
1256
|
+
/** A link (or `<Component>` example) found in a body: its line, and file. */
|
|
1257
|
+
interface Located {
|
|
1258
|
+
file?: string;
|
|
1259
|
+
line: number;
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1085
1262
|
/**
|
|
1086
1263
|
* Map links extracted from include-expanded text back to the file and raw
|
|
1087
1264
|
* line each expanded line came from, so a broken link inside a partial is
|
|
1088
1265
|
* reported against the partial. Links whose origin is the page's own source
|
|
1089
1266
|
* carry no `file` override (origins already hold raw-file lines).
|
|
1090
1267
|
*/
|
|
1091
|
-
const remapExpandedLinks = (
|
|
1092
|
-
links:
|
|
1268
|
+
const remapExpandedLinks = <T extends Located>(
|
|
1269
|
+
links: T[],
|
|
1093
1270
|
origins: { file: string; line: number }[],
|
|
1094
1271
|
sourcePath: string | undefined
|
|
1095
|
-
):
|
|
1272
|
+
): T[] =>
|
|
1096
1273
|
links.map((link) => {
|
|
1097
1274
|
const origin = origins[link.line - 1];
|
|
1098
1275
|
if (!origin) {
|
|
1099
1276
|
return link;
|
|
1100
1277
|
}
|
|
1101
|
-
const remapped:
|
|
1278
|
+
const remapped: T = { ...link, line: origin.line };
|
|
1102
1279
|
if (origin.file !== sourcePath) {
|
|
1103
1280
|
remapped.file = origin.file;
|
|
1104
1281
|
}
|
|
@@ -1112,22 +1289,26 @@ const entryIncludes = (entry: SourceEntry): string[] | undefined =>
|
|
|
1112
1289
|
: undefined;
|
|
1113
1290
|
|
|
1114
1291
|
/**
|
|
1115
|
-
* Extract an entry's links for validation. When
|
|
1116
|
-
* extraction runs over the expanded text (origins
|
|
1117
|
-
* lines); otherwise over the stripped body, shifted by
|
|
1118
|
-
* matter block's height.
|
|
1292
|
+
* Extract an entry's links (or `<Component>` examples) for validation. When
|
|
1293
|
+
* the scan expanded includes, extraction runs over the expanded text (origins
|
|
1294
|
+
* already hold raw-file lines); otherwise over the stripped body, shifted by
|
|
1295
|
+
* the stripped front matter block's height.
|
|
1119
1296
|
*/
|
|
1120
|
-
const entryLinks =
|
|
1297
|
+
const entryLinks = <T extends Located>(
|
|
1298
|
+
entry: SourceEntry,
|
|
1299
|
+
extract: (body: string, lineOffset?: number) => T[]
|
|
1300
|
+
): T[] =>
|
|
1121
1301
|
entry.expanded
|
|
1122
1302
|
? remapExpandedLinks(
|
|
1123
|
-
|
|
1303
|
+
extract(entry.expanded.text),
|
|
1124
1304
|
entry.expanded.origins,
|
|
1125
1305
|
entry.sourcePath
|
|
1126
1306
|
)
|
|
1127
|
-
:
|
|
1307
|
+
: extract(entry.body.text, entryLineOffset(entry));
|
|
1128
1308
|
|
|
1129
1309
|
/**
|
|
1130
|
-
* Diagnostics for `{#id}` heading markers
|
|
1310
|
+
* Diagnostics for `{#id}` heading markers (and the spaced `{ #id }` and
|
|
1311
|
+
* kramdown `{: #id }` spellings) in an `.mdx` page. The MDX parser
|
|
1131
1312
|
* reads a bare `{…}` as a JSX expression, so the page fails to compile —
|
|
1132
1313
|
* reported here, at the marker's source line, instead of as a raw acorn error
|
|
1133
1314
|
* at render time. An included `.md` partial is spliced into the including
|
|
@@ -1140,7 +1321,7 @@ const curlyMarkerDiagnostics = (
|
|
|
1140
1321
|
markers: CurlyMarker[],
|
|
1141
1322
|
sourceName: string
|
|
1142
1323
|
): Diagnostic[] =>
|
|
1143
|
-
markers.map(({ id, line }) => {
|
|
1324
|
+
markers.map(({ id, line, marker }) => {
|
|
1144
1325
|
const origin = entry.expanded?.origins[line - 1];
|
|
1145
1326
|
const page = entry.sourcePath ?? `${sourceName}:${entry.ref}`;
|
|
1146
1327
|
const inPartial = origin !== undefined && origin.file !== entry.sourcePath;
|
|
@@ -1149,8 +1330,8 @@ const curlyMarkerDiagnostics = (
|
|
|
1149
1330
|
file: origin?.file ?? page,
|
|
1150
1331
|
line: origin?.line ?? line + entryLineOffset(entry),
|
|
1151
1332
|
message: inPartial
|
|
1152
|
-
?
|
|
1153
|
-
:
|
|
1333
|
+
? `\`${marker}\` is a JSX expression once this partial is included in ${page} (.mdx), so that page fails to compile.`
|
|
1334
|
+
: `\`${marker}\` is a JSX expression in .mdx, so this page fails to compile.`,
|
|
1154
1335
|
severity: "error",
|
|
1155
1336
|
suggestion: `Write \`[#${id}]\` or escape it as \`\\{#${id}\\}\` — both pin the same anchor in .md and .mdx.`,
|
|
1156
1337
|
};
|
|
@@ -1549,13 +1730,18 @@ export const normalizeEntry = (
|
|
|
1549
1730
|
description: meta.description,
|
|
1550
1731
|
editUrl: entry.editUrl,
|
|
1551
1732
|
entryId: staged ? `${ctx.source.name}/${entry.ref}` : undefined,
|
|
1733
|
+
examplesUsed:
|
|
1734
|
+
format === "mdx" ? entryLinks(entry, extractExampleUses) : undefined,
|
|
1552
1735
|
format,
|
|
1553
1736
|
groups,
|
|
1554
1737
|
headings,
|
|
1555
1738
|
id: `${ctx.source.name}:${entry.ref}`,
|
|
1556
1739
|
includes: entryIncludes(entry),
|
|
1557
1740
|
lastModified: meta.lastModified ?? entry.lastModified,
|
|
1558
|
-
links: [
|
|
1741
|
+
links: [
|
|
1742
|
+
...entryLinks(entry, extractLinks),
|
|
1743
|
+
...relatedPageLinks(meta.related),
|
|
1744
|
+
],
|
|
1559
1745
|
meta,
|
|
1560
1746
|
monolingual: ctx.source.monolingual,
|
|
1561
1747
|
navPath,
|
|
@@ -22,8 +22,10 @@ import {
|
|
|
22
22
|
image,
|
|
23
23
|
linkParts,
|
|
24
24
|
renderInline,
|
|
25
|
+
renderInlineMdx,
|
|
25
26
|
} from "./lower.ts";
|
|
26
27
|
import { slugify, slugifyPath } from "./normalize.ts";
|
|
28
|
+
import { missingSecretError } from "./remote.ts";
|
|
27
29
|
import type {
|
|
28
30
|
ContentSource,
|
|
29
31
|
SourceContext,
|
|
@@ -142,6 +144,14 @@ export interface NotionSourceOptions {
|
|
|
142
144
|
* Status/select property are always imported as published.
|
|
143
145
|
*/
|
|
144
146
|
publishedValue?: string;
|
|
147
|
+
/**
|
|
148
|
+
* Read the text of text blocks (paragraphs, headings, list items, quotes,
|
|
149
|
+
* callouts) as MDX source, so a component, expression, or Markdown typed in
|
|
150
|
+
* Notion renders instead of showing as written. MDX runs code at build
|
|
151
|
+
* time, so this hands everyone who can edit the database the site's build:
|
|
152
|
+
* set it only when you trust them with that. Default false.
|
|
153
|
+
*/
|
|
154
|
+
mdx?: boolean;
|
|
145
155
|
/** Opt-in dev polling interval (seconds); omit to freeze for the session. */
|
|
146
156
|
pollInterval?: number;
|
|
147
157
|
/** Injected for tests; otherwise built from `@notionhq/client`. */
|
|
@@ -173,9 +183,10 @@ interface LinkedRuns {
|
|
|
173
183
|
* itself instead of opening a JSX expression, a tag, or emphasis, a code run
|
|
174
184
|
* keeps its text verbatim inside a long-enough code span, neighbors share
|
|
175
185
|
* their marks' delimiters, and neighbors that link to one `href` share one
|
|
176
|
-
* link.
|
|
186
|
+
* link. With `mdx`, the text goes in as typed instead (see
|
|
187
|
+
* `NotionSourceOptions.mdx`).
|
|
177
188
|
*/
|
|
178
|
-
const richToMarkdown = (rich: NotionRichText[] = []): string => {
|
|
189
|
+
const richToMarkdown = (rich: NotionRichText[] = [], mdx = false): string => {
|
|
179
190
|
const linked: LinkedRuns[] = [];
|
|
180
191
|
for (const { annotations = {}, href, plain_text: text } of rich) {
|
|
181
192
|
const run: InlineRun = {
|
|
@@ -194,11 +205,18 @@ const richToMarkdown = (rich: NotionRichText[] = []): string => {
|
|
|
194
205
|
linked.push({ href: href ?? undefined, runs: [run] });
|
|
195
206
|
}
|
|
196
207
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
);
|
|
208
|
+
const parts = linked.flatMap(({ href, runs }) => linkParts(runs, href, mdx));
|
|
209
|
+
return mdx ? renderInlineMdx(parts) : renderInline(parts);
|
|
200
210
|
};
|
|
201
211
|
|
|
212
|
+
/**
|
|
213
|
+
* A text block's rich text: Markdown that renders as written, guarded at its
|
|
214
|
+
* block start — or, with `mdx`, MDX source as typed, which an `.mdx` file's
|
|
215
|
+
* own block starts (`import`, `#`, indentation) read the same way.
|
|
216
|
+
*/
|
|
217
|
+
const blockText = (rich: NotionRichText[] | undefined, mdx: boolean): string =>
|
|
218
|
+
mdx ? richToMarkdown(rich, true) : guardBlockStart(richToMarkdown(rich));
|
|
219
|
+
|
|
202
220
|
const isBlockPayload = (
|
|
203
221
|
value: NotionBlockPayload | boolean | string | undefined
|
|
204
222
|
): value is NotionBlockPayload => typeof value === "object";
|
|
@@ -339,9 +357,9 @@ const renderVideo = (data: NotionBlockPayload): string => {
|
|
|
339
357
|
};
|
|
340
358
|
|
|
341
359
|
/** Render a leaf (non-container) block to Markdown, or null for containers. */
|
|
342
|
-
const renderLeaf = (block: NotionBlock): string | null => {
|
|
360
|
+
const renderLeaf = (block: NotionBlock, mdx: boolean): string | null => {
|
|
343
361
|
const data = payloadOf(block) ?? {};
|
|
344
|
-
const text =
|
|
362
|
+
const text = blockText(blockField(block), mdx);
|
|
345
363
|
switch (block.type) {
|
|
346
364
|
case "paragraph": {
|
|
347
365
|
return text;
|
|
@@ -397,6 +415,7 @@ export const notionSource = (
|
|
|
397
415
|
ctx?: SourceContext
|
|
398
416
|
): ContentSource => {
|
|
399
417
|
const props = options.properties ?? {};
|
|
418
|
+
const textAsMdx = options.mdx ?? false;
|
|
400
419
|
// A FIFO semaphore: at most N calls run at once, the rest queue. Notion's
|
|
401
420
|
// rate limit is per-integration (an average of 3 req/s), and a large
|
|
402
421
|
// database fans out one block-children request per page plus one per nested
|
|
@@ -446,7 +465,12 @@ export const notionSource = (
|
|
|
446
465
|
severity: "error",
|
|
447
466
|
});
|
|
448
467
|
}
|
|
449
|
-
|
|
468
|
+
// Every Notion API call needs the integration token.
|
|
469
|
+
const auth = options.token ?? process.env.NOTION_TOKEN;
|
|
470
|
+
if (!auth) {
|
|
471
|
+
throw missingSecretError(options.name, "NOTION_TOKEN");
|
|
472
|
+
}
|
|
473
|
+
return new Client({ auth });
|
|
450
474
|
};
|
|
451
475
|
|
|
452
476
|
const childrenOf = (
|
|
@@ -475,7 +499,7 @@ export const notionSource = (
|
|
|
475
499
|
|
|
476
500
|
if (block.type === "callout") {
|
|
477
501
|
const body = [
|
|
478
|
-
|
|
502
|
+
blockText(blockField(block), textAsMdx),
|
|
479
503
|
await children(block),
|
|
480
504
|
]
|
|
481
505
|
.filter(Boolean)
|
|
@@ -503,7 +527,7 @@ export const notionSource = (
|
|
|
503
527
|
): Promise<string> => {
|
|
504
528
|
const parts = await Promise.all(
|
|
505
529
|
blocks.map(async (block) => {
|
|
506
|
-
const leaf = renderLeaf(block);
|
|
530
|
+
const leaf = renderLeaf(block, textAsMdx);
|
|
507
531
|
if (leaf === null) {
|
|
508
532
|
return renderContainer(client, block, renderBlocks);
|
|
509
533
|
}
|
|
@@ -2,6 +2,7 @@ import { existsSync, watch as fsWatch, statSync } from "node:fs";
|
|
|
2
2
|
import type { Dirent } from "node:fs";
|
|
3
3
|
import { readdir, readFile, stat } from "node:fs/promises";
|
|
4
4
|
|
|
5
|
+
import { YAMLException } from "js-yaml";
|
|
5
6
|
import { basename, isAbsolute, join, relative, resolve } from "pathe";
|
|
6
7
|
|
|
7
8
|
import { nextFenceState } from "../code-fences.ts";
|
|
@@ -19,6 +20,7 @@ import {
|
|
|
19
20
|
localizedRoute,
|
|
20
21
|
placeEntryRef,
|
|
21
22
|
resolveEntryRoute,
|
|
23
|
+
frontmatterYamlDiagnostic,
|
|
22
24
|
slugifyPath,
|
|
23
25
|
strippedLineOffset,
|
|
24
26
|
unloadablePathDiagnostic,
|
|
@@ -785,13 +787,14 @@ const noteToEntry = (
|
|
|
785
787
|
* Read and split one note, or null when it vanished between the walk and the
|
|
786
788
|
* read. Obsidian renames and deletes notes while a dev server watches the
|
|
787
789
|
* vault, and one file disappearing mid-load must not fail the whole reload.
|
|
788
|
-
*
|
|
790
|
+
* A note whose front matter isn't valid YAML comes back as its diagnostic,
|
|
791
|
+
* so it's left out the same way. Any other read failure still throws.
|
|
789
792
|
*/
|
|
790
793
|
const readNote = async (
|
|
791
794
|
vaultDir: string,
|
|
792
795
|
rel: string,
|
|
793
796
|
options: Pick<ObsidianSourceOptions, "i18n" | "versions">
|
|
794
|
-
): Promise<ParsedNote | null> => {
|
|
797
|
+
): Promise<ParsedNote | Diagnostic | null> => {
|
|
795
798
|
const absPath = join(vaultDir, rel);
|
|
796
799
|
try {
|
|
797
800
|
const file = await readFile(absPath, "utf-8");
|
|
@@ -808,6 +811,9 @@ const readNote = async (
|
|
|
808
811
|
rel,
|
|
809
812
|
};
|
|
810
813
|
} catch (error) {
|
|
814
|
+
if (error instanceof YAMLException) {
|
|
815
|
+
return frontmatterYamlDiagnostic(error, absPath);
|
|
816
|
+
}
|
|
811
817
|
// SAFETY: a rejected `readFile` always yields a Node system error, whose
|
|
812
818
|
// `code` is the only field read here.
|
|
813
819
|
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
|
|
@@ -976,7 +982,14 @@ export const obsidianSource = (
|
|
|
976
982
|
const read = await Promise.all(
|
|
977
983
|
files.map((rel) => readNote(vaultDir, rel, options))
|
|
978
984
|
);
|
|
979
|
-
const notes = read.filter(
|
|
985
|
+
const notes = read.filter(
|
|
986
|
+
(note): note is ParsedNote => note !== null && "rel" in note
|
|
987
|
+
);
|
|
988
|
+
skipped.push(
|
|
989
|
+
...read.filter(
|
|
990
|
+
(note): note is Diagnostic => note !== null && "code" in note
|
|
991
|
+
)
|
|
992
|
+
);
|
|
980
993
|
// Resolving `[[Note#H]]` needs the target's route and headings, so every
|
|
981
994
|
// note is parsed and indexed before any body is rewritten.
|
|
982
995
|
const { index, pairs } = buildLinkIndex(notes, options);
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import type { Diagnostic } from "../types.ts";
|
|
1
2
|
import type { JsonObject } from "./json.ts";
|
|
2
3
|
import { asObject, asString, isJsonObject, objectsIn } from "./json.ts";
|
|
3
4
|
import { lexicalToMarkdown } from "./lexical.ts";
|
|
4
5
|
import { writesMdx } from "./lower.ts";
|
|
5
|
-
import type { RemoteFieldMap, RestClient } from "./remote.ts";
|
|
6
|
+
import type { RemoteFieldMap, RemoteFields, RestClient } from "./remote.ts";
|
|
6
7
|
import {
|
|
7
8
|
documentEntry,
|
|
8
9
|
fetchJson,
|
|
@@ -44,7 +45,7 @@ export interface PayloadSourceOptions {
|
|
|
44
45
|
|
|
45
46
|
const PAGE_SIZE = 100;
|
|
46
47
|
|
|
47
|
-
const DEFAULT_FIELDS:
|
|
48
|
+
const DEFAULT_FIELDS: RemoteFields = {
|
|
48
49
|
body: "content",
|
|
49
50
|
description: "description",
|
|
50
51
|
lastModified: "updatedAt",
|
|
@@ -64,13 +65,45 @@ export const payloadSource = (
|
|
|
64
65
|
const fields = { ...DEFAULT_FIELDS, ...options.fields };
|
|
65
66
|
const origin = options.url.replace(/\/+$/u, "");
|
|
66
67
|
|
|
67
|
-
const lower = (body: JsonObject): string =>
|
|
68
|
+
const lower = (body: JsonObject, leftOut: Set<string>): string =>
|
|
68
69
|
lexicalToMarkdown(body, {
|
|
69
70
|
baseUrl: origin,
|
|
71
|
+
onUnsupported: (what) => leftOut.add(what),
|
|
70
72
|
serializers: options.serializers,
|
|
71
73
|
});
|
|
72
74
|
|
|
73
|
-
|
|
75
|
+
// A document mapped to its entry, with a warning for each kind of node its
|
|
76
|
+
// body left out as a comment.
|
|
77
|
+
const toEntry = (
|
|
78
|
+
doc: JsonObject,
|
|
79
|
+
draft: boolean,
|
|
80
|
+
warn: (diagnostic: Diagnostic) => void
|
|
81
|
+
): SourceEntry => {
|
|
82
|
+
const leftOut = new Set<string>();
|
|
83
|
+
const id = asString(doc.id) ?? String(doc.id ?? "");
|
|
84
|
+
const entry = documentEntry(
|
|
85
|
+
doc,
|
|
86
|
+
fields,
|
|
87
|
+
id,
|
|
88
|
+
(body) => (isJsonObject(body) ? lower(body, leftOut) : ""),
|
|
89
|
+
draft,
|
|
90
|
+
writesMdx(options.serializers)
|
|
91
|
+
);
|
|
92
|
+
for (const what of leftOut) {
|
|
93
|
+
warn({
|
|
94
|
+
code: "BLUME_SOURCE_UNSUPPORTED_NODE",
|
|
95
|
+
message: `Source "${options.name}": "${entry.ref}" has content Blume has no Markdown for (${what}), left out as a comment.`,
|
|
96
|
+
severity: "warning",
|
|
97
|
+
suggestion:
|
|
98
|
+
"Map a block type to a component with a serializer on payloadSource, passed to custom(); other nodes have no Markdown equivalent.",
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
return entry;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const fetchEntries = async (
|
|
105
|
+
warn: (diagnostic: Diagnostic) => void
|
|
106
|
+
): Promise<SourceEntry[]> => {
|
|
74
107
|
const preview = ctx?.preview ?? false;
|
|
75
108
|
const token = options.token ?? process.env.PAYLOAD_API_KEY;
|
|
76
109
|
const client: RestClient = {
|
|
@@ -105,17 +138,7 @@ export const payloadSource = (
|
|
|
105
138
|
if (draft && !preview) {
|
|
106
139
|
continue;
|
|
107
140
|
}
|
|
108
|
-
|
|
109
|
-
entries.push(
|
|
110
|
-
documentEntry(
|
|
111
|
-
doc,
|
|
112
|
-
fields,
|
|
113
|
-
id,
|
|
114
|
-
(body) => (isJsonObject(body) ? lower(body) : ""),
|
|
115
|
-
draft,
|
|
116
|
-
writesMdx(options.serializers)
|
|
117
|
-
)
|
|
118
|
-
);
|
|
141
|
+
entries.push(toEntry(doc, draft, warn));
|
|
119
142
|
}
|
|
120
143
|
more = result.hasNextPage === true;
|
|
121
144
|
page += 1;
|