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
package/src/core/config-input.ts
CHANGED
|
@@ -2,7 +2,11 @@ import type { AstroIntegration } from "astro";
|
|
|
2
2
|
import type { z } from "zod";
|
|
3
3
|
|
|
4
4
|
import type { AskRetrievalOptions } from "../ai/ask-context.ts";
|
|
5
|
-
import type {
|
|
5
|
+
import type {
|
|
6
|
+
AssistantAdapter,
|
|
7
|
+
AssistantGatewayAdapter,
|
|
8
|
+
AssistantOpenAIAdapter,
|
|
9
|
+
} from "../ai/ask.ts";
|
|
6
10
|
import type { ComponentMarkdown } from "../ai/component-markdown.ts";
|
|
7
11
|
import type { AnalyticsAdapter } from "../analytics/schema.ts";
|
|
8
12
|
import type { CaptchaAdapter } from "../captcha/schema.ts";
|
|
@@ -155,6 +159,19 @@ export interface FooterConfig {
|
|
|
155
159
|
socials?: Partial<Record<FooterSocial, string>>;
|
|
156
160
|
}
|
|
157
161
|
|
|
162
|
+
/**
|
|
163
|
+
* The generated `/changelog` index's heading and description. Each is a
|
|
164
|
+
* string, or a map of locale code to text; the index renders in the default
|
|
165
|
+
* locale. Unset, they're the `changelog` UI strings (`Changelog` in English),
|
|
166
|
+
* which `i18n.ui` translates.
|
|
167
|
+
*/
|
|
168
|
+
export interface ChangelogConfig {
|
|
169
|
+
/** The index's description, below its heading and in its meta description. */
|
|
170
|
+
description?: LocalizableLabel;
|
|
171
|
+
/** The index's heading, page title, and OG card title. */
|
|
172
|
+
title?: LocalizableLabel;
|
|
173
|
+
}
|
|
174
|
+
|
|
158
175
|
/**
|
|
159
176
|
* Where content lives and how it's discovered. `root`/`include`/`exclude` are
|
|
160
177
|
* zero-config shorthand for a single `filesystem()` source; set `sources` to
|
|
@@ -506,6 +523,16 @@ export type SearchProviderConfig = AnySearchAdapter | false;
|
|
|
506
523
|
|
|
507
524
|
/** The object form of `search`: the adapter plus adapter-independent settings. */
|
|
508
525
|
export interface SearchOptions {
|
|
526
|
+
/** What search reports to the configured analytics providers. */
|
|
527
|
+
analytics?: {
|
|
528
|
+
/**
|
|
529
|
+
* Send each query's text with the `search` and `search_select` events.
|
|
530
|
+
* Defaults to `true`. `false` sends the query's length (`queryChars`)
|
|
531
|
+
* instead, and the text rides only the `blume:track` DOM event, for a
|
|
532
|
+
* site to forward on its own terms.
|
|
533
|
+
*/
|
|
534
|
+
queries?: boolean;
|
|
535
|
+
};
|
|
509
536
|
/** Indexing behavior. */
|
|
510
537
|
indexing?: {
|
|
511
538
|
/**
|
|
@@ -529,7 +556,7 @@ export interface SearchOptions {
|
|
|
529
556
|
/**
|
|
530
557
|
* Search configuration. Pass an adapter directly (`search: algolia({…})`, or
|
|
531
558
|
* `false`) as shorthand for the object form, which also carries `popular`
|
|
532
|
-
* links and `indexing` settings.
|
|
559
|
+
* links and `indexing` and `analytics` settings.
|
|
533
560
|
*/
|
|
534
561
|
export type SearchConfig = SearchProviderConfig | SearchOptions;
|
|
535
562
|
|
|
@@ -1308,8 +1335,8 @@ export interface MarkdownConfig {
|
|
|
1308
1335
|
export interface ReactConfig {
|
|
1309
1336
|
/**
|
|
1310
1337
|
* Auto-memoize React components/hooks with the React Compiler
|
|
1311
|
-
* (`
|
|
1312
|
-
* set to `false` to skip the compiler
|
|
1338
|
+
* (`oxc-transform-react`). On by default whenever React is enabled;
|
|
1339
|
+
* set to `false` to skip the compiler pass. Defaults to `true`.
|
|
1313
1340
|
*/
|
|
1314
1341
|
compiler?: boolean;
|
|
1315
1342
|
}
|
|
@@ -1376,9 +1403,11 @@ export type NarrationConfig =
|
|
|
1376
1403
|
/**
|
|
1377
1404
|
* Generate audio at build with a speech model:
|
|
1378
1405
|
* `gateway({ model: "openai/tts-1-hd", voice: "alloy" })` from
|
|
1379
|
-
* `blume/ai
|
|
1406
|
+
* `blume/ai`, or `openai({ model, voice })` for OpenAI or, with a
|
|
1407
|
+
* `baseUrl`, any OpenAI-compatible speech endpoint. Without it, pages
|
|
1408
|
+
* are read with browser voices.
|
|
1380
1409
|
*/
|
|
1381
|
-
provider?: AssistantGatewayAdapter;
|
|
1410
|
+
provider?: AssistantGatewayAdapter | AssistantOpenAIAdapter;
|
|
1382
1411
|
};
|
|
1383
1412
|
|
|
1384
1413
|
/**
|
|
@@ -1495,6 +1524,11 @@ export interface BlumeConfig {
|
|
|
1495
1524
|
* compose: with both set, a page lands at `{deployment.base}/{basePath}/page`.
|
|
1496
1525
|
*/
|
|
1497
1526
|
basePath?: string;
|
|
1527
|
+
/**
|
|
1528
|
+
* The generated `/changelog` index's title and description, each a string
|
|
1529
|
+
* or a per-locale map. Unset, they're the `changelog` UI strings.
|
|
1530
|
+
*/
|
|
1531
|
+
changelog?: ChangelogConfig;
|
|
1498
1532
|
/**
|
|
1499
1533
|
* Ask readers before analytics runs, with an adapter from `blume/consent`:
|
|
1500
1534
|
* `native()` for Blume's own banner, or a hosted consent manager
|
|
@@ -1557,6 +1591,8 @@ export interface BlumeConfig {
|
|
|
1557
1591
|
narration?: NarrationConfig;
|
|
1558
1592
|
/** Header, sidebar, tabs, and switchers. */
|
|
1559
1593
|
navigation?: NavigationConfig;
|
|
1594
|
+
/** Send `X-Powered-By: Blume` with responses. Defaults to `true`. */
|
|
1595
|
+
poweredBy?: boolean;
|
|
1560
1596
|
/**
|
|
1561
1597
|
* Limit how often one reader (by IP address) can call the server routes —
|
|
1562
1598
|
* the assistant, the API playground proxy, server-side search — with an
|
package/src/core/data.ts
CHANGED
|
@@ -248,6 +248,8 @@ export interface BlumeDataConfig {
|
|
|
248
248
|
/** Repository URL for header/edit links, or `null`. */
|
|
249
249
|
repoUrl: string | null;
|
|
250
250
|
search: {
|
|
251
|
+
/** `search.analytics`: whether the dialog's events carry each query's text. */
|
|
252
|
+
analytics: { queries: boolean };
|
|
251
253
|
enabled: boolean;
|
|
252
254
|
/** Resolved empty-state links; empty when unset (Search falls back to sidebar). */
|
|
253
255
|
popular: { icon?: string; label: string; route: string }[];
|
package/src/core/diagnostics.ts
CHANGED
|
@@ -45,6 +45,7 @@ const DOCS_PATHS = new Map(
|
|
|
45
45
|
BLUME_DEPENDENCY_MISSING: "/docs/cli/doctor",
|
|
46
46
|
BLUME_DUPLICATE_ROUTE: DOCS_CONTENT_NAVIGATION,
|
|
47
47
|
BLUME_DUPLICATE_SIDEBAR_ORDER: DOCS_CONTENT_NAVIGATION,
|
|
48
|
+
BLUME_EXAMPLE_NOT_FOUND: "/docs/content/components#component",
|
|
48
49
|
BLUME_FRONTMATTER_INVALID: "/docs/content/frontmatter",
|
|
49
50
|
BLUME_INCLUDE_CYCLE: DOCS_CONTENT_INCLUDES,
|
|
50
51
|
BLUME_INCLUDE_MALFORMED: DOCS_CONTENT_INCLUDES,
|
|
@@ -60,13 +61,17 @@ const DOCS_PATHS = new Map(
|
|
|
60
61
|
BLUME_NAV_INDEX_TITLE_MISMATCH: DOCS_CONTENT_NAVIGATION,
|
|
61
62
|
BLUME_NAV_MISSING_PAGE: DOCS_CONTENT_NAVIGATION,
|
|
62
63
|
BLUME_NODE_VERSION: "/docs/quickstart",
|
|
64
|
+
BLUME_SEARCH_SYNC_FAILED: "/docs/configuration/search",
|
|
63
65
|
BLUME_SERVER_FEATURE_REQUIRED: DOCS_DEPLOYMENT,
|
|
64
66
|
BLUME_SIDEBAR_DISPLAY_IGNORED: DOCS_CONTENT_NAVIGATION,
|
|
65
67
|
BLUME_SOURCE_FETCH_FAILED: DOCS_CONTENT_SOURCES,
|
|
66
68
|
BLUME_SOURCE_MISCONFIGURED: DOCS_CONTENT_SOURCES,
|
|
67
69
|
BLUME_SOURCE_OFFLINE: DOCS_CONTENT_SOURCES,
|
|
70
|
+
BLUME_SOURCE_PATH_MISSING: "/docs/content/sources/remote-mdx",
|
|
68
71
|
BLUME_SOURCE_SDK_MISSING: DOCS_CONTENT_SOURCES,
|
|
69
72
|
BLUME_SOURCE_UNAVAILABLE: DOCS_CONTENT_SOURCES,
|
|
73
|
+
BLUME_SOURCE_UNRESOLVED_LINK: "/docs/content/sources/contentful",
|
|
74
|
+
BLUME_SOURCE_UNSUPPORTED_NODE: "/docs/content/sources/payload",
|
|
70
75
|
BLUME_TRANSLATE_MISSING: "/docs/cli/translate",
|
|
71
76
|
BLUME_TRANSLATE_STALE: "/docs/cli/translate",
|
|
72
77
|
BLUME_UNKNOWN_COMPONENT: "/docs/configuration/customization",
|
package/src/core/i18n-ui.ts
CHANGED
|
@@ -90,6 +90,8 @@ const uiStringsObject = z.object({
|
|
|
90
90
|
.default(
|
|
91
91
|
"Product updates, new features, and fixes from every release."
|
|
92
92
|
),
|
|
93
|
+
// What the index says when it has no entries to list.
|
|
94
|
+
empty: z.string().default("No changelog entries yet."),
|
|
93
95
|
title: z.string().default("Changelog"),
|
|
94
96
|
})
|
|
95
97
|
.prefault({}),
|
package/src/core/includes.ts
CHANGED
|
@@ -242,7 +242,7 @@ export const malformedIncludeDiagnostic = (
|
|
|
242
242
|
"This <include> isn't a statement Blume can read, so the page shows it as written.",
|
|
243
243
|
severity: "warning",
|
|
244
244
|
suggestion:
|
|
245
|
-
|
|
245
|
+
'Write <include>./path.mdx</include> on a line of its own, or wrap it with the path on its own line and no blank lines. Attributes take lowercase names and quoted values (plan="Pro"): lang and meta shape a code include, and any other is a prop the included file reads as {{name}}.',
|
|
246
246
|
});
|
|
247
247
|
|
|
248
248
|
/**
|
package/src/core/links.ts
CHANGED
|
@@ -312,10 +312,13 @@ const checkPathLink = (
|
|
|
312
312
|
): LinkResult => {
|
|
313
313
|
// Page routes carry the site-wide base; an absolute author path is written
|
|
314
314
|
// as if mounted at root, so base it for the route lookup (idempotent — a
|
|
315
|
-
// relative link already resolved against the based `page.route`). A
|
|
316
|
-
//
|
|
317
|
-
//
|
|
318
|
-
|
|
315
|
+
// relative link already resolved against the based `page.route`). A raw
|
|
316
|
+
// `<a href>` ships unbased, so it's looked up as written. A real route
|
|
317
|
+
// always wins over the asset-extension heuristic, so a dotted route (e.g.
|
|
318
|
+
// `/releases/v1.0`) isn't misread as a missing asset.
|
|
319
|
+
const authoredRoute = toRoute(
|
|
320
|
+
link.raw ? resolved : withBasePath(ctx.basePath, resolved)
|
|
321
|
+
);
|
|
319
322
|
const route = servedRoute(authoredRoute, page, ctx);
|
|
320
323
|
if (ctx.routes.has(route)) {
|
|
321
324
|
return fragment ? checkAnchor(route, fragment, site, ctx, via) : null;
|
|
@@ -436,6 +439,12 @@ export interface FileRouteIndex {
|
|
|
436
439
|
* into the reader's locale onto the fallback copy.
|
|
437
440
|
*/
|
|
438
441
|
byDefaultNavPath: Map<string, string>;
|
|
442
|
+
/**
|
|
443
|
+
* A staged page's entry id (`<source>/<ref>`) → its route. A remote
|
|
444
|
+
* source's files have no path on disk, so a file link between two of them
|
|
445
|
+
* (`./02-errors.mdx`) resolves by entry id, as the rendered link does.
|
|
446
|
+
*/
|
|
447
|
+
byEntryId: Map<string, string>;
|
|
439
448
|
}
|
|
440
449
|
|
|
441
450
|
/** The linking side of a file link: where the page's source lives. */
|
|
@@ -450,12 +459,19 @@ export const buildFileRouteIndex = (
|
|
|
450
459
|
): FileRouteIndex => {
|
|
451
460
|
const bySource = new Map<string, string>();
|
|
452
461
|
const byDefaultNavPath = new Map<string, string>();
|
|
462
|
+
const byEntryId = new Map<string, string>();
|
|
453
463
|
for (const page of pages) {
|
|
454
|
-
const { sourcePath } = page;
|
|
455
|
-
if (
|
|
464
|
+
const { entryId, sourcePath } = page;
|
|
465
|
+
if (page.fallback) {
|
|
456
466
|
continue;
|
|
457
467
|
}
|
|
458
468
|
const isDefault = page.locale === i18n?.defaultLocale;
|
|
469
|
+
if (entryId && (!byEntryId.has(entryId) || isDefault)) {
|
|
470
|
+
byEntryId.set(entryId, page.route);
|
|
471
|
+
}
|
|
472
|
+
if (!sourcePath) {
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
459
475
|
if (!bySource.has(sourcePath) || isDefault) {
|
|
460
476
|
bySource.set(sourcePath, page.route);
|
|
461
477
|
}
|
|
@@ -463,7 +479,7 @@ export const buildFileRouteIndex = (
|
|
|
463
479
|
byDefaultNavPath.set(normalize(page.navPath), page.route);
|
|
464
480
|
}
|
|
465
481
|
}
|
|
466
|
-
return { byDefaultNavPath, bySource };
|
|
482
|
+
return { byDefaultNavPath, byEntryId, bySource };
|
|
467
483
|
};
|
|
468
484
|
|
|
469
485
|
/**
|
|
@@ -492,11 +508,15 @@ const relativeTarget = (
|
|
|
492
508
|
ctx: LinkContext
|
|
493
509
|
): string => {
|
|
494
510
|
const base = { isIndex: isIndexPage(page), route: page.route };
|
|
495
|
-
const { navPath, sourcePath } = page;
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
511
|
+
const { entryId, navPath, sourcePath } = page;
|
|
512
|
+
let resolveFile: ((path: string) => string | undefined) | undefined;
|
|
513
|
+
if (sourcePath) {
|
|
514
|
+
resolveFile = (path) =>
|
|
515
|
+
routeOfLinkedFile(ctx.fileRoutes, { navPath, sourcePath }, path);
|
|
516
|
+
} else if (entryId) {
|
|
517
|
+
resolveFile = (path) =>
|
|
518
|
+
ctx.fileRoutes.byEntryId.get(normalize(join(dirname(entryId), path)));
|
|
519
|
+
}
|
|
500
520
|
return (
|
|
501
521
|
resolveRelativeHref(rawPath, base, resolveFile, (route) =>
|
|
502
522
|
ctx.routes.has(route)
|
|
@@ -555,9 +575,14 @@ const classifyLink = (
|
|
|
555
575
|
return fragment ? checkAnchor(page.route, fragment, site, ctx, via) : null;
|
|
556
576
|
}
|
|
557
577
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
578
|
+
let resolved = rawPath;
|
|
579
|
+
if (!rawPath.startsWith("/")) {
|
|
580
|
+
// A raw `<a href>` isn't rewritten, so the browser resolves it against
|
|
581
|
+
// the page's slashless URL: its parent directory, even on an index page.
|
|
582
|
+
resolved = link.raw
|
|
583
|
+
? resolveRelative(page.route, rawPath, false)
|
|
584
|
+
: relativeTarget(page, rawPath, ctx);
|
|
585
|
+
}
|
|
561
586
|
return checkPathLink(resolved, fragment, page, link, site, ctx, via);
|
|
562
587
|
};
|
|
563
588
|
|
|
@@ -582,6 +607,8 @@ export const validateLinks = async (
|
|
|
582
607
|
i18n?: LocaleRouting | null;
|
|
583
608
|
publicDir: string | null;
|
|
584
609
|
checkExternal?: boolean;
|
|
610
|
+
/** External URLs not to request (`--ignore`). Internal links are always checked. */
|
|
611
|
+
ignore?: (url: string) => boolean;
|
|
585
612
|
/** Configured redirects; their `from` paths count as valid link targets. */
|
|
586
613
|
redirects?: { from: string }[];
|
|
587
614
|
}
|
|
@@ -629,8 +656,11 @@ export const validateLinks = async (
|
|
|
629
656
|
});
|
|
630
657
|
}
|
|
631
658
|
|
|
632
|
-
|
|
633
|
-
|
|
659
|
+
// `--ignore` names URLs that can't be checked from where this runs: a
|
|
660
|
+
// placeholder host, a local server, a site that turns bots away.
|
|
661
|
+
const probed = external.filter((ref) => !options.ignore?.(ref.url));
|
|
662
|
+
if (options.checkExternal && probed.length > 0) {
|
|
663
|
+
diagnostics.push(...(await checkExternalLinks(probed)));
|
|
634
664
|
}
|
|
635
665
|
|
|
636
666
|
// A partial spliced into several pages (or every locale of one) yields the
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The header that names Blume as the framework behind a response, sent unless
|
|
3
|
+
* `poweredBy` is `false`. The dev server and Blume's runtime middleware
|
|
4
|
+
* (`components/powered-by-middleware.ts`) send it, and so does every host
|
|
5
|
+
* for the built site: static hosts through `deploy/headers.ts`'s rules,
|
|
6
|
+
* server builds through each platform's own mechanism (`deploy/platforms`).
|
|
7
|
+
* It lives apart from those modules so the runtime middleware can import it
|
|
8
|
+
* without pulling in the deploy code.
|
|
9
|
+
*/
|
|
10
|
+
export const POWERED_BY_HEADERS = {
|
|
11
|
+
"X-Powered-By": "Blume",
|
|
12
|
+
} satisfies Record<string, string>;
|
package/src/core/probe.ts
CHANGED
|
@@ -16,6 +16,8 @@ export interface ProbeResult {
|
|
|
16
16
|
status?: number;
|
|
17
17
|
timedOut?: boolean;
|
|
18
18
|
error?: string;
|
|
19
|
+
/** The system error code behind a request that got no response (`ENOTFOUND`). */
|
|
20
|
+
code?: string;
|
|
19
21
|
/** Whether the request was redirected before landing on `status`. */
|
|
20
22
|
redirected?: boolean;
|
|
21
23
|
/** The URL finally landed on, after following redirects. */
|
|
@@ -28,6 +30,18 @@ export interface ProbeResult {
|
|
|
28
30
|
robotsTag?: string | null;
|
|
29
31
|
}
|
|
30
32
|
|
|
33
|
+
/**
|
|
34
|
+
* The reason a request got no response. Node's fetch rejects with a bare
|
|
35
|
+
* "fetch failed" and puts the system error (`getaddrinfo ENOTFOUND …`, with
|
|
36
|
+
* its `code`) on `cause`; Bun's rejects with the system error itself.
|
|
37
|
+
*/
|
|
38
|
+
const failure = (error: Error): Pick<ProbeResult, "code" | "error"> => {
|
|
39
|
+
const reason = error.cause instanceof Error ? error.cause : error;
|
|
40
|
+
return "code" in reason
|
|
41
|
+
? { code: String(reason.code), error: reason.message }
|
|
42
|
+
: { error: reason.message };
|
|
43
|
+
};
|
|
44
|
+
|
|
31
45
|
/** Probe a URL with the given method, normalizing failures to a result. */
|
|
32
46
|
const request = async (
|
|
33
47
|
url: string,
|
|
@@ -56,10 +70,9 @@ const request = async (
|
|
|
56
70
|
if (error instanceof Error && error.name === "AbortError") {
|
|
57
71
|
return { ok: false, timedOut: true };
|
|
58
72
|
}
|
|
59
|
-
|
|
60
|
-
error
|
|
61
|
-
|
|
62
|
-
};
|
|
73
|
+
const reason =
|
|
74
|
+
error instanceof Error ? failure(error) : { error: String(error) };
|
|
75
|
+
return { ...reason, ok: false };
|
|
63
76
|
} finally {
|
|
64
77
|
clearTimeout(timer);
|
|
65
78
|
}
|
|
@@ -95,10 +108,17 @@ export const gradeExternal = (
|
|
|
95
108
|
if (result.timedOut) {
|
|
96
109
|
return { detail: "request timed out", severity: "warning" };
|
|
97
110
|
}
|
|
111
|
+
// No response at all. A host name that doesn't resolve is as dead as a 404;
|
|
112
|
+
// a refused, reset, or failed connection is more often someone else's outage
|
|
113
|
+
// or the runner's own network, so it's graded like a timeout.
|
|
98
114
|
if (result.status === undefined) {
|
|
99
|
-
return {
|
|
115
|
+
return {
|
|
116
|
+
detail: result.error ?? "unreachable",
|
|
117
|
+
severity: result.code === "ENOTFOUND" ? "error" : "warning",
|
|
118
|
+
};
|
|
100
119
|
}
|
|
101
|
-
// A 404/410 is definitively dead.
|
|
120
|
+
// A 404/410 is definitively dead. Any other status — a 401/403 from a site
|
|
121
|
+
// that turns bots away, a 429, a 5xx — may well be rate limiting or a
|
|
102
122
|
// transient blip, which is not the author's bug to fix.
|
|
103
123
|
if (result.status === STATUS_NOT_FOUND || result.status === STATUS_GONE) {
|
|
104
124
|
return { detail: `HTTP ${result.status}`, severity: "error" };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isAbsolute, relative } from "pathe";
|
|
2
2
|
|
|
3
|
-
import { withBasePath } from "./base-path.ts";
|
|
3
|
+
import { normalizePath, withBasePath } from "./base-path.ts";
|
|
4
4
|
import { CHANGELOG_INDEX_ROUTE, hasChangelogIndex } from "./changelog-index.ts";
|
|
5
5
|
import { loadConfig } from "./config.ts";
|
|
6
6
|
import { customStaticRoutes, discoverPages } from "./custom-pages.ts";
|
|
@@ -19,10 +19,11 @@ import { buildManifest } from "./manifest.ts";
|
|
|
19
19
|
import { discoverFolderMeta } from "./meta.ts";
|
|
20
20
|
import type { FolderMetaSource } from "./meta.ts";
|
|
21
21
|
import { resolveProjectContext } from "./project.ts";
|
|
22
|
-
import { pathsUnderPattern } from "./redirect-patterns.ts";
|
|
22
|
+
import { isPatternPath, pathsUnderPattern } from "./redirect-patterns.ts";
|
|
23
23
|
import type { ResolvedConfig } from "./schema.ts";
|
|
24
24
|
import { normalizeEntry, strippedLineOffset } from "./sources/normalize.ts";
|
|
25
25
|
import { resolveDocsCollection, resolveSources } from "./sources/resolve.ts";
|
|
26
|
+
import type { SourceRuntime } from "./sources/resolve.ts";
|
|
26
27
|
import type {
|
|
27
28
|
ContentSource,
|
|
28
29
|
SourceEntry,
|
|
@@ -309,17 +310,36 @@ const substituteLoadedVariables = (
|
|
|
309
310
|
* and the Node server serve the page), so the pattern is rejected rather than
|
|
310
311
|
* let the answer depend on the host. `pages` are the served page paths, which
|
|
311
312
|
* carry `basePath`, as the based `from` does.
|
|
313
|
+
*
|
|
314
|
+
* An exact redirect from a page's own URL answers the same way everywhere:
|
|
315
|
+
* Astro ranks its route above the page's catch-all, in dev and build alike,
|
|
316
|
+
* and writes the redirect page where the page would go, so the page never
|
|
317
|
+
* publishes. That's a warning rather than an error, since the outcome doesn't
|
|
318
|
+
* depend on the host.
|
|
312
319
|
*/
|
|
313
320
|
const redirectPageDiagnostics = (
|
|
314
321
|
config: ResolvedConfig,
|
|
315
322
|
pages: readonly string[],
|
|
316
323
|
configFile: string | null
|
|
317
324
|
): Diagnostic[] =>
|
|
318
|
-
config.redirects.flatMap((redirect) => {
|
|
319
|
-
const
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
325
|
+
config.redirects.flatMap((redirect): Diagnostic[] => {
|
|
326
|
+
const from = withBasePath(config.basePath, redirect.from);
|
|
327
|
+
if (!isPatternPath(redirect.from)) {
|
|
328
|
+
const page = normalizePath(from);
|
|
329
|
+
return pages.includes(page)
|
|
330
|
+
? [
|
|
331
|
+
{
|
|
332
|
+
code: "BLUME_REDIRECT_MATCHES_PAGE",
|
|
333
|
+
file: configFile ?? undefined,
|
|
334
|
+
message: `The redirect from ${redirect.from} is also the page ${page}, which never publishes: its URL redirects to ${redirect.to} instead.`,
|
|
335
|
+
severity: "warning",
|
|
336
|
+
suggestion:
|
|
337
|
+
"If the page moved, delete it or give it a new slug; otherwise remove the redirect.",
|
|
338
|
+
} satisfies Diagnostic,
|
|
339
|
+
]
|
|
340
|
+
: [];
|
|
341
|
+
}
|
|
342
|
+
const matched = pathsUnderPattern(from, pages);
|
|
323
343
|
const [first] = matched;
|
|
324
344
|
if (first === undefined) {
|
|
325
345
|
return [];
|
|
@@ -345,6 +365,26 @@ const bannerLinkHref = (
|
|
|
345
365
|
? undefined
|
|
346
366
|
: banner.link?.href;
|
|
347
367
|
|
|
368
|
+
/**
|
|
369
|
+
* The project's sources, before any of them loads: `beforeSources` sees the
|
|
370
|
+
* config first, then each source validates itself (e.g. the filesystem
|
|
371
|
+
* source checks its root exists), replacing the single hard `contentRoot`
|
|
372
|
+
* check.
|
|
373
|
+
*/
|
|
374
|
+
const validatedSources = (
|
|
375
|
+
config: ResolvedConfig,
|
|
376
|
+
context: ProjectContext,
|
|
377
|
+
runtime: SourceRuntime,
|
|
378
|
+
beforeSources?: (config: ResolvedConfig) => void
|
|
379
|
+
): ContentSource[] => {
|
|
380
|
+
beforeSources?.(config);
|
|
381
|
+
const sources = resolveSources(config, context, runtime);
|
|
382
|
+
for (const source of sources) {
|
|
383
|
+
source.validate?.();
|
|
384
|
+
}
|
|
385
|
+
return sources;
|
|
386
|
+
};
|
|
387
|
+
|
|
348
388
|
export const scanProject = async (
|
|
349
389
|
root: string,
|
|
350
390
|
options: {
|
|
@@ -356,6 +396,12 @@ export const scanProject = async (
|
|
|
356
396
|
overrides?: ConfigOverrides;
|
|
357
397
|
/** Relocate the generated runtime (e.g. `.blume-verify` for isolation). */
|
|
358
398
|
runtimeDir?: string;
|
|
399
|
+
/**
|
|
400
|
+
* Called with the resolved config before any source loads, so a command
|
|
401
|
+
* can report an unset secret ahead of a fetch that fails without it: a
|
|
402
|
+
* source that can't load fails the scan, and nothing after it runs.
|
|
403
|
+
*/
|
|
404
|
+
beforeSources?: (config: ResolvedConfig) => void;
|
|
359
405
|
} = {}
|
|
360
406
|
): Promise<BlumeProject> => {
|
|
361
407
|
const mode = options.mode ?? "dev";
|
|
@@ -367,17 +413,12 @@ export const scanProject = async (
|
|
|
367
413
|
const context = resolveProjectContext(root, config, {
|
|
368
414
|
runtimeDir: options.runtimeDir,
|
|
369
415
|
});
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
refresh: options.refresh,
|
|
377
|
-
});
|
|
378
|
-
for (const source of sources) {
|
|
379
|
-
source.validate?.();
|
|
380
|
-
}
|
|
416
|
+
const sources = validatedSources(
|
|
417
|
+
config,
|
|
418
|
+
context,
|
|
419
|
+
{ mode, preview, refresh: options.refresh },
|
|
420
|
+
options.beforeSources
|
|
421
|
+
);
|
|
381
422
|
|
|
382
423
|
// Folder meta is discovered per filesystem source, under each source's own
|
|
383
424
|
// root and keyed by its route prefix, so a prefixed/root-differing source's
|
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
* `/v2` rather than `/v2/` on every host.
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
+
import { normalizePath } from "./base-path.ts";
|
|
24
|
+
|
|
23
25
|
/** The name `*` captures under, which `_redirects` reads as `:splat`. */
|
|
24
26
|
export const SPLAT = "splat";
|
|
25
27
|
|
|
@@ -401,19 +403,46 @@ export type CompiledRedirect = [
|
|
|
401
403
|
status: number,
|
|
402
404
|
];
|
|
403
405
|
|
|
406
|
+
/** One rule, compiled (see {@link compileRedirects}). */
|
|
407
|
+
const compileRule = (rule: RedirectRule): CompiledRedirect => {
|
|
408
|
+
const { location, source } = regexRedirect(rule);
|
|
409
|
+
return [source, encodeURI(location), rule.status];
|
|
410
|
+
};
|
|
411
|
+
|
|
404
412
|
/**
|
|
405
413
|
* The pattern redirects among `redirects`, compiled for the server wrappers
|
|
406
|
-
* and
|
|
407
|
-
* percent-encoded as the header needs; a capture is encoded when
|
|
408
|
-
* (see {@link fillLocation}).
|
|
414
|
+
* and Vercel's routing config, in the order to try them. The `Location`
|
|
415
|
+
* template is percent-encoded as the header needs; a capture is encoded when
|
|
416
|
+
* it's filled (see {@link fillLocation}).
|
|
409
417
|
*/
|
|
410
418
|
export const compileRedirects = (
|
|
411
419
|
redirects: readonly RedirectEnds[]
|
|
420
|
+
): CompiledRedirect[] => redirects.flatMap(expandRedirect).map(compileRule);
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Every redirect among `redirects` compiled, exact paths first: a pattern as
|
|
424
|
+
* {@link compileRedirects} compiles it, an exact path as a rule matching
|
|
425
|
+
* just that path, a trailing slash optional. What the dev server answers
|
|
426
|
+
* with, where Astro's own handler would send an exact redirect to a page
|
|
427
|
+
* served by `[...slug]` as a `301` whatever its status.
|
|
428
|
+
*/
|
|
429
|
+
export const compileEveryRedirect = (
|
|
430
|
+
redirects: readonly RedirectEnds[]
|
|
412
431
|
): CompiledRedirect[] =>
|
|
413
|
-
redirects
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
432
|
+
exactFirst(redirects)
|
|
433
|
+
.flatMap((redirect) => {
|
|
434
|
+
const rules = expandRedirect(redirect);
|
|
435
|
+
return rules.length > 0
|
|
436
|
+
? rules
|
|
437
|
+
: [
|
|
438
|
+
{
|
|
439
|
+
parts: [{ kind: "text", text: normalizePath(redirect.from) }],
|
|
440
|
+
status: redirect.status,
|
|
441
|
+
to: redirect.to,
|
|
442
|
+
} satisfies RedirectRule,
|
|
443
|
+
];
|
|
444
|
+
})
|
|
445
|
+
.map(compileRule);
|
|
417
446
|
|
|
418
447
|
/** A captured value, encoded for a `Location` header that must not grow a query or fragment. */
|
|
419
448
|
export const encodeCapture = (value: string): string =>
|
package/src/core/schema.ts
CHANGED
|
@@ -485,6 +485,16 @@ const footerConfigSchema = z.strictObject({
|
|
|
485
485
|
socials: z.partialRecord(z.enum(FOOTER_SOCIALS), z.string()).default({}),
|
|
486
486
|
});
|
|
487
487
|
|
|
488
|
+
/**
|
|
489
|
+
* The generated `/changelog` index. Its title and description may be
|
|
490
|
+
* per-locale maps; unset, they're the `changelog` UI strings `i18n.ui`
|
|
491
|
+
* translates.
|
|
492
|
+
*/
|
|
493
|
+
const changelogConfigSchema = z.strictObject({
|
|
494
|
+
description: localizableLabelSchema.optional(),
|
|
495
|
+
title: localizableLabelSchema.optional(),
|
|
496
|
+
});
|
|
497
|
+
|
|
488
498
|
/** A validated `content.sources` entry: an adapter descriptor from `blume/sources`. */
|
|
489
499
|
export type { ContentSourceAdapter } from "../sources/registry.ts";
|
|
490
500
|
|
|
@@ -778,9 +788,17 @@ const searchIndexingSchema = z
|
|
|
778
788
|
})
|
|
779
789
|
.prefault({});
|
|
780
790
|
|
|
791
|
+
/** What the search dialog's analytics events carry. */
|
|
792
|
+
const searchAnalyticsSchema = z
|
|
793
|
+
.strictObject({
|
|
794
|
+
queries: z.boolean().default(true),
|
|
795
|
+
})
|
|
796
|
+
.prefault({});
|
|
797
|
+
|
|
781
798
|
/** The object form of `search`: the adapter plus its adapter-independent settings. */
|
|
782
799
|
const searchOptionsSchema = z.strictObject(
|
|
783
800
|
{
|
|
801
|
+
analytics: searchAnalyticsSchema,
|
|
784
802
|
indexing: searchIndexingSchema,
|
|
785
803
|
/** Curated links for the Cmd+K empty state; defaults to the first sidebar pages. */
|
|
786
804
|
popular: z.array(searchPopularLinkSchema).default([]),
|
|
@@ -1188,10 +1206,10 @@ const feedbackConfigSchema = z
|
|
|
1188
1206
|
|
|
1189
1207
|
// "Listen to this page". Off by default. `true` reads pages aloud with the
|
|
1190
1208
|
// reader's browser voices, which needs no key and works on any host; an object
|
|
1191
|
-
// with a `provider` (`gateway()` from `blume/ai`) generates
|
|
1192
|
-
// build instead, one cached clip per sentence, and falls back
|
|
1193
|
-
// voices where no clips exist (`blume dev`, a build without
|
|
1194
|
-
// normalize to `{ enabled, provider }`.
|
|
1209
|
+
// with a `provider` (`gateway()` or `openai()` from `blume/ai`) generates
|
|
1210
|
+
// neural audio at build instead, one cached clip per sentence, and falls back
|
|
1211
|
+
// to the browser's voices where no clips exist (`blume dev`, a build without
|
|
1212
|
+
// the key). Both normalize to `{ enabled, provider }`.
|
|
1195
1213
|
const narrationConfigSchema = z
|
|
1196
1214
|
.union([
|
|
1197
1215
|
z.boolean(),
|
|
@@ -2022,9 +2040,9 @@ const markdownConfigSchema = z.strictObject(
|
|
|
2022
2040
|
const reactConfigSchema = z.strictObject({
|
|
2023
2041
|
/**
|
|
2024
2042
|
* Auto-memoize React components/hooks with the React Compiler
|
|
2025
|
-
* (`
|
|
2043
|
+
* (`oxc-transform-react`). On by default whenever React is enabled
|
|
2026
2044
|
* (a project `.tsx`/`.jsx`, a React island/example/override, or the assistant); set
|
|
2027
|
-
* to `false` to skip the compiler
|
|
2045
|
+
* to `false` to skip the compiler pass.
|
|
2028
2046
|
*/
|
|
2029
2047
|
compiler: z.boolean().default(true),
|
|
2030
2048
|
});
|
|
@@ -2102,6 +2120,7 @@ export const blumeConfigSchema = z
|
|
|
2102
2120
|
})
|
|
2103
2121
|
.optional()
|
|
2104
2122
|
.transform((value) => normalizeBasePath(value)),
|
|
2123
|
+
changelog: changelogConfigSchema.optional(),
|
|
2105
2124
|
// An adapter from `blume/consent`; analytics waits for the reader.
|
|
2106
2125
|
consent: consentConfigSchema,
|
|
2107
2126
|
content: contentConfigSchema.prefault({}),
|
|
@@ -2140,6 +2159,7 @@ export const blumeConfigSchema = z
|
|
|
2140
2159
|
markdown: markdownConfigSchema.prefault({}),
|
|
2141
2160
|
narration: narrationConfigSchema.prefault(false),
|
|
2142
2161
|
navigation: navigationConfigSchema.prefault({}),
|
|
2162
|
+
poweredBy: z.boolean().default(true),
|
|
2143
2163
|
// An adapter from `blume/ratelimit`, `memory()` by default; `false` is off.
|
|
2144
2164
|
rateLimit: rateLimitConfigSchema,
|
|
2145
2165
|
react: reactConfigSchema.prefault({}),
|
|
@@ -109,6 +109,9 @@ export const snapshotCache = (cacheDir: string): SnapshotCache => {
|
|
|
109
109
|
* snapshot exists, serve it without fetching (cache-first dev). On fetch failure,
|
|
110
110
|
* serve the last-known-good snapshot with a warning so a CMS/network outage
|
|
111
111
|
* doesn't fail the build; if there is no snapshot either, surface a hard error.
|
|
112
|
+
* A `BlumeError` from the source (a missing SDK or token, checked before any
|
|
113
|
+
* request, or a misconfiguration) already names its cause, so without a
|
|
114
|
+
* snapshot it surfaces as itself rather than as a fetch failure.
|
|
112
115
|
*/
|
|
113
116
|
export const loadWithCache = async (
|
|
114
117
|
name: string,
|
|
@@ -138,6 +141,9 @@ export const loadWithCache = async (
|
|
|
138
141
|
};
|
|
139
142
|
return { diagnostics: [diagnostic], entries: fallback };
|
|
140
143
|
}
|
|
144
|
+
if (error instanceof BlumeError) {
|
|
145
|
+
throw error;
|
|
146
|
+
}
|
|
141
147
|
// SAFETY: same invariant as above — `fetchEntries` failures are Errors.
|
|
142
148
|
throw new BlumeError({
|
|
143
149
|
code: "BLUME_SOURCE_FETCH_FAILED",
|