blume 2.1.0 → 2.1.1
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 +94 -0
- package/dist/cli/{chunk-5cg2rzgf.js → chunk-073rdvgb.js} +19 -48
- package/dist/cli/{chunk-5cg2rzgf.js.map → chunk-073rdvgb.js.map} +4 -5
- 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-150jk7km.js +52 -0
- package/dist/cli/chunk-150jk7km.js.map +10 -0
- package/dist/cli/{chunk-rekqz4qg.js → chunk-313vnn40.js} +16 -15
- package/dist/cli/chunk-313vnn40.js.map +10 -0
- package/dist/cli/{chunk-gp84za0h.js → chunk-3v3ffbrj.js} +2 -2
- package/dist/cli/{chunk-9xr4dgee.js → chunk-3x6x3pph.js} +29 -16
- package/dist/cli/chunk-3x6x3pph.js.map +10 -0
- package/dist/cli/{chunk-0gz36akr.js → chunk-42t4jxdx.js} +21 -5
- package/dist/cli/chunk-42t4jxdx.js.map +12 -0
- package/dist/cli/{chunk-ta27feev.js → chunk-54ztw1nj.js} +21 -2
- package/dist/cli/chunk-54ztw1nj.js.map +10 -0
- package/dist/cli/{chunk-jnwj5kjj.js → chunk-5vjtgs6e.js} +20 -7
- package/dist/cli/{chunk-jnwj5kjj.js.map → chunk-5vjtgs6e.js.map} +3 -3
- package/dist/cli/chunk-66w9j7p6.js +322 -0
- package/dist/cli/chunk-66w9j7p6.js.map +14 -0
- package/dist/cli/{chunk-nd1821sk.js → chunk-7ppf1rva.js} +32 -18
- package/dist/cli/chunk-7ppf1rva.js.map +10 -0
- package/dist/cli/{chunk-p804f3pe.js → chunk-8y2kmjv8.js} +6 -6
- package/dist/cli/{chunk-2f7n4qzt.js → chunk-c1y538kd.js} +1 -1
- package/dist/cli/{chunk-1n228xqb.js → chunk-c887fj0s.js} +2 -2
- package/dist/cli/{chunk-y5h4r2y0.js → chunk-cfyg47zg.js} +15 -5
- package/dist/cli/chunk-cfyg47zg.js.map +10 -0
- package/dist/cli/{chunk-cmtcnh9f.js → chunk-dzwehpy7.js} +2 -2
- package/dist/cli/{chunk-h2a48yhd.js → chunk-e6atmn4h.js} +119 -15
- package/dist/cli/{chunk-h2a48yhd.js.map → chunk-e6atmn4h.js.map} +10 -10
- package/dist/cli/{chunk-5ffywsmb.js → chunk-e8xw9qkh.js} +8 -6
- package/dist/cli/{chunk-5ffywsmb.js.map → chunk-e8xw9qkh.js.map} +3 -3
- package/dist/cli/{chunk-ntv4qrt7.js → chunk-e9q7a2ak.js} +14 -14
- package/dist/cli/{chunk-xkz5xbtv.js → chunk-ehweg4fr.js} +2366 -1114
- package/dist/cli/chunk-ehweg4fr.js.map +218 -0
- package/dist/cli/{chunk-zf748avv.js → chunk-ep6nd0rn.js} +176 -281
- package/dist/cli/{chunk-zf748avv.js.map → chunk-ep6nd0rn.js.map} +11 -12
- package/dist/cli/{chunk-yxmk4pec.js → chunk-fdrkhpjy.js} +16 -15
- package/dist/cli/{chunk-yxmk4pec.js.map → chunk-fdrkhpjy.js.map} +3 -3
- package/dist/cli/{chunk-5wj2qwn2.js → chunk-ft9vfys6.js} +4 -4
- package/dist/cli/{chunk-4h8z9vv5.js → chunk-gb420vvz.js} +89 -46
- package/dist/cli/chunk-gb420vvz.js.map +17 -0
- package/dist/cli/{chunk-713mngdq.js → chunk-hebn0tpx.js} +76 -10
- package/dist/cli/chunk-hebn0tpx.js.map +14 -0
- package/dist/cli/{chunk-2n4ps1b3.js → chunk-htrwgabr.js} +40 -22
- package/dist/cli/chunk-htrwgabr.js.map +15 -0
- package/dist/cli/{chunk-zhvd6adm.js → chunk-hx3xqnk9.js} +13 -13
- package/dist/cli/chunk-j134vrp2.js +65 -0
- package/dist/cli/chunk-j134vrp2.js.map +10 -0
- package/dist/cli/{chunk-y4ykbark.js → chunk-kma9f6sf.js} +2 -2
- package/dist/cli/{chunk-py62fewh.js → chunk-mrcccgnh.js} +3 -3
- package/dist/cli/{chunk-2f3ndkpw.js → chunk-n0v7m8ry.js} +3 -3
- package/dist/cli/{chunk-43zcf4gz.js → chunk-ncj7w596.js} +522 -353
- package/dist/cli/chunk-ncj7w596.js.map +35 -0
- package/dist/cli/{chunk-9geymqns.js → chunk-nq1d20w2.js} +14 -14
- package/dist/cli/{chunk-g2z9x01v.js → chunk-nrns10mj.js} +3 -3
- package/dist/cli/{chunk-e7vvk5ya.js → chunk-sds923fa.js} +3 -3
- package/dist/cli/{chunk-cy5bm0j0.js → chunk-t2m5j4pr.js} +2 -2
- package/dist/cli/{chunk-cy5bm0j0.js.map → chunk-t2m5j4pr.js.map} +1 -1
- package/dist/cli/{chunk-yj17vs39.js → chunk-tdqhyxfz.js} +5 -5
- package/dist/cli/{chunk-q3wxxq39.js → chunk-tphac74f.js} +5 -5
- package/dist/cli/{chunk-6bm0xpgd.js → chunk-w1r4vase.js} +2 -2
- 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/component-markdown.d.ts +2 -0
- package/dist/types/ai/openapi-components.d.ts +4 -1
- package/dist/types/astro/integration.d.ts +5 -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 +30 -3
- 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/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 +119 -0
- 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/normalize.d.ts +20 -1
- 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/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 +20 -1
- 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 +42 -21
- 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/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/sources/contentful.d.ts +16 -0
- package/dist/types/sources/github-releases.d.ts +8 -1
- package/dist/types/sources/payload.d.ts +2 -0
- package/dist/types/sources/registry.d.ts +54 -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 +31 -13
- package/docs/advanced/changelog.mdx +11 -0
- package/docs/advanced/skills.mdx +1 -1
- package/docs/cli/audit.mdx +30 -7
- package/docs/cli/doctor.mdx +5 -1
- 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 +1 -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 +3 -2
- 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 +4 -4
- package/docs/content/sources/contentful.mdx +7 -4
- package/docs/content/sources/custom.mdx +7 -3
- package/docs/content/sources/github-releases.mdx +10 -2
- package/docs/content/sources/index.mdx +3 -3
- package/docs/content/sources/notion.mdx +1 -1
- package/docs/content/sources/obsidian.mdx +1 -1
- package/docs/content/sources/payload.mdx +3 -2
- package/docs/content/sources/remote-mdx.mdx +4 -0
- package/docs/content/sources/sanity.mdx +2 -2
- package/docs/content/sources/strapi.mdx +3 -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 +4 -0
- package/docs/discoverability/sitemap-and-robots.mdx +1 -1
- package/docs/references/api-pages.mdx +1 -1
- package/docs/references/asyncapi.mdx +3 -1
- package/docs/references/graphql.mdx +13 -1
- package/docs/references/openapi.mdx +50 -8
- package/package.json +9 -9
- package/skills/blume-migrate/SKILL.md +1 -1
- package/skills/blume-migrate/references/docusaurus.md +4 -4
- package/skills/blume-migrate/references/fumadocs.md +17 -13
- 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/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/astro/generate.ts +78 -48
- package/src/astro/integration.ts +16 -13
- 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/templates.ts +125 -80
- 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 +3 -3
- package/src/cli/prepare.ts +6 -3
- package/src/cli/required-secrets.ts +1 -1
- 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/islands/hooks.ts +27 -1
- package/src/components/layout/PageActions.astro +139 -24
- package/src/components/layout/RootLayout.astro +10 -2
- package/src/components/layout/Search.astro +17 -3
- package/src/components/layout/TableOfContents.astro +24 -17
- 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 +24 -5
- 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/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 +31 -3
- 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/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 +21 -2
- 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/mdx-remote.ts +62 -10
- package/src/core/sources/normalize.ts +227 -41
- package/src/core/sources/notion.ts +7 -1
- 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/platforms/cloudflare.ts +13 -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 +113 -16
- 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 +290 -102
- 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/relative-links.ts +3 -2
- 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/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 +3 -1
- 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/sources/contentful.ts +12 -0
- package/src/sources/github-releases.ts +7 -1
- package/src/sources/shared.ts +3 -0
- package/src/theme/entry.ts +29 -3
- 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-gp84za0h.js.map → chunk-3v3ffbrj.js.map} +0 -0
- /package/dist/cli/{chunk-p804f3pe.js.map → chunk-8y2kmjv8.js.map} +0 -0
- /package/dist/cli/{chunk-2f7n4qzt.js.map → chunk-c1y538kd.js.map} +0 -0
- /package/dist/cli/{chunk-1n228xqb.js.map → chunk-c887fj0s.js.map} +0 -0
- /package/dist/cli/{chunk-cmtcnh9f.js.map → chunk-dzwehpy7.js.map} +0 -0
- /package/dist/cli/{chunk-ntv4qrt7.js.map → chunk-e9q7a2ak.js.map} +0 -0
- /package/dist/cli/{chunk-5wj2qwn2.js.map → chunk-ft9vfys6.js.map} +0 -0
- /package/dist/cli/{chunk-zhvd6adm.js.map → chunk-hx3xqnk9.js.map} +0 -0
- /package/dist/cli/{chunk-y4ykbark.js.map → chunk-kma9f6sf.js.map} +0 -0
- /package/dist/cli/{chunk-py62fewh.js.map → chunk-mrcccgnh.js.map} +0 -0
- /package/dist/cli/{chunk-2f3ndkpw.js.map → chunk-n0v7m8ry.js.map} +0 -0
- /package/dist/cli/{chunk-9geymqns.js.map → chunk-nq1d20w2.js.map} +0 -0
- /package/dist/cli/{chunk-g2z9x01v.js.map → chunk-nrns10mj.js.map} +0 -0
- /package/dist/cli/{chunk-e7vvk5ya.js.map → chunk-sds923fa.js.map} +0 -0
- /package/dist/cli/{chunk-yj17vs39.js.map → chunk-tdqhyxfz.js.map} +0 -0
- /package/dist/cli/{chunk-q3wxxq39.js.map → chunk-tphac74f.js.map} +0 -0
- /package/dist/cli/{chunk-6bm0xpgd.js.map → chunk-w1r4vase.js.map} +0 -0
package/src/core/config-input.ts
CHANGED
|
@@ -155,6 +155,19 @@ export interface FooterConfig {
|
|
|
155
155
|
socials?: Partial<Record<FooterSocial, string>>;
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
+
/**
|
|
159
|
+
* The generated `/changelog` index's heading and description. Each is a
|
|
160
|
+
* string, or a map of locale code to text; the index renders in the default
|
|
161
|
+
* locale. Unset, they're the `changelog` UI strings (`Changelog` in English),
|
|
162
|
+
* which `i18n.ui` translates.
|
|
163
|
+
*/
|
|
164
|
+
export interface ChangelogConfig {
|
|
165
|
+
/** The index's description, below its heading and in its meta description. */
|
|
166
|
+
description?: LocalizableLabel;
|
|
167
|
+
/** The index's heading, page title, and OG card title. */
|
|
168
|
+
title?: LocalizableLabel;
|
|
169
|
+
}
|
|
170
|
+
|
|
158
171
|
/**
|
|
159
172
|
* Where content lives and how it's discovered. `root`/`include`/`exclude` are
|
|
160
173
|
* zero-config shorthand for a single `filesystem()` source; set `sources` to
|
|
@@ -506,6 +519,16 @@ export type SearchProviderConfig = AnySearchAdapter | false;
|
|
|
506
519
|
|
|
507
520
|
/** The object form of `search`: the adapter plus adapter-independent settings. */
|
|
508
521
|
export interface SearchOptions {
|
|
522
|
+
/** What search reports to the configured analytics providers. */
|
|
523
|
+
analytics?: {
|
|
524
|
+
/**
|
|
525
|
+
* Send each query's text with the `search` and `search_select` events.
|
|
526
|
+
* Defaults to `true`. `false` sends the query's length (`queryChars`)
|
|
527
|
+
* instead, and the text rides only the `blume:track` DOM event, for a
|
|
528
|
+
* site to forward on its own terms.
|
|
529
|
+
*/
|
|
530
|
+
queries?: boolean;
|
|
531
|
+
};
|
|
509
532
|
/** Indexing behavior. */
|
|
510
533
|
indexing?: {
|
|
511
534
|
/**
|
|
@@ -529,7 +552,7 @@ export interface SearchOptions {
|
|
|
529
552
|
/**
|
|
530
553
|
* Search configuration. Pass an adapter directly (`search: algolia({…})`, or
|
|
531
554
|
* `false`) as shorthand for the object form, which also carries `popular`
|
|
532
|
-
* links and `indexing` settings.
|
|
555
|
+
* links and `indexing` and `analytics` settings.
|
|
533
556
|
*/
|
|
534
557
|
export type SearchConfig = SearchProviderConfig | SearchOptions;
|
|
535
558
|
|
|
@@ -1308,8 +1331,8 @@ export interface MarkdownConfig {
|
|
|
1308
1331
|
export interface ReactConfig {
|
|
1309
1332
|
/**
|
|
1310
1333
|
* Auto-memoize React components/hooks with the React Compiler
|
|
1311
|
-
* (`
|
|
1312
|
-
* set to `false` to skip the compiler
|
|
1334
|
+
* (`oxc-transform-react`). On by default whenever React is enabled;
|
|
1335
|
+
* set to `false` to skip the compiler pass. Defaults to `true`.
|
|
1313
1336
|
*/
|
|
1314
1337
|
compiler?: boolean;
|
|
1315
1338
|
}
|
|
@@ -1495,6 +1518,11 @@ export interface BlumeConfig {
|
|
|
1495
1518
|
* compose: with both set, a page lands at `{deployment.base}/{basePath}/page`.
|
|
1496
1519
|
*/
|
|
1497
1520
|
basePath?: string;
|
|
1521
|
+
/**
|
|
1522
|
+
* The generated `/changelog` index's title and description, each a string
|
|
1523
|
+
* or a per-locale map. Unset, they're the `changelog` UI strings.
|
|
1524
|
+
*/
|
|
1525
|
+
changelog?: ChangelogConfig;
|
|
1498
1526
|
/**
|
|
1499
1527
|
* Ask readers before analytics runs, with an adapter from `blume/consent`:
|
|
1500
1528
|
* `native()` for Blume's own banner, or a hosted consent manager
|
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
|
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([]),
|
|
@@ -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({}),
|
|
@@ -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",
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Contentful rich text → Markdown. Covers the block, inline, and mark types
|
|
3
3
|
* the Rich Text editor emits; embedded entries fall through to a serializer
|
|
4
|
-
* keyed by content type or are noted in a comment
|
|
4
|
+
* keyed by content type or are noted in a comment, and a link to an entry
|
|
5
|
+
* points at its page when the source says it has one. Links to assets and
|
|
5
6
|
* entries arrive unresolved from the Delivery API (`data.target` is a
|
|
6
7
|
* `sys` link), so the source hands in resolvers over the response's
|
|
7
8
|
* `includes`; a document the SDK already resolved (with `fields` inline)
|
|
@@ -25,14 +26,22 @@ import {
|
|
|
25
26
|
writesMdx,
|
|
26
27
|
} from "./lower.ts";
|
|
27
28
|
|
|
28
|
-
/** An asset's file as a Markdown image needs it. */
|
|
29
|
+
/** An asset's file as a Markdown image or link needs it. */
|
|
29
30
|
export interface ContentfulAsset {
|
|
31
|
+
/** The file's MIME type; an asset that isn't an image renders as a link. */
|
|
32
|
+
contentType?: string;
|
|
30
33
|
description?: string;
|
|
34
|
+
fileName?: string;
|
|
31
35
|
title?: string;
|
|
32
36
|
url: string;
|
|
33
37
|
}
|
|
34
38
|
|
|
35
39
|
export interface ContentfulRichTextOptions {
|
|
40
|
+
/**
|
|
41
|
+
* The route of a linked entry's page; an entry hyperlink to an entry with
|
|
42
|
+
* none keeps only its text.
|
|
43
|
+
*/
|
|
44
|
+
entryHref?: (entry: JsonObject) => string | undefined;
|
|
36
45
|
/** Resolve an asset link (`data.target.sys.id`) to its file. */
|
|
37
46
|
resolveAsset?: (id: string) => ContentfulAsset | null;
|
|
38
47
|
/** Resolve an entry link to the entry (`sys` plus `fields`). */
|
|
@@ -55,7 +64,9 @@ export const assetFromEntry = (asset: JsonObject): ContentfulAsset | null => {
|
|
|
55
64
|
return null;
|
|
56
65
|
}
|
|
57
66
|
return {
|
|
67
|
+
contentType: asString(getPath(asset, "fields.file.contentType")),
|
|
58
68
|
description: asString(getPath(asset, "fields.description")),
|
|
69
|
+
fileName: asString(getPath(asset, "fields.file.fileName")),
|
|
59
70
|
title: asString(getPath(asset, "fields.title")),
|
|
60
71
|
url: absoluteUrl(url),
|
|
61
72
|
};
|
|
@@ -166,11 +177,16 @@ const inlineNodeParts = (
|
|
|
166
177
|
linkedAsset(node, options)?.url
|
|
167
178
|
);
|
|
168
179
|
}
|
|
180
|
+
case "entry-hyperlink": {
|
|
181
|
+
const entry = linkedTarget(node, options.resolveEntry);
|
|
182
|
+
const href = entry ? options.entryHref?.(entry) : undefined;
|
|
183
|
+
return linkParts(inlineParts(children(node), options), href);
|
|
184
|
+
}
|
|
169
185
|
case "embedded-entry-inline": {
|
|
170
186
|
return [{ markdown: embeddedEntry(node, options) }];
|
|
171
187
|
}
|
|
172
188
|
default: {
|
|
173
|
-
//
|
|
189
|
+
// Anything unknown: the label.
|
|
174
190
|
return inlineParts(children(node), options);
|
|
175
191
|
}
|
|
176
192
|
}
|
|
@@ -264,11 +280,18 @@ const renderBlock = (
|
|
|
264
280
|
}
|
|
265
281
|
case "embedded-asset-block": {
|
|
266
282
|
const asset = linkedAsset(node, options);
|
|
283
|
+
if (!asset) {
|
|
284
|
+
return "";
|
|
285
|
+
}
|
|
286
|
+
// Any file can be embedded: one that isn't an image (a PDF, a video)
|
|
287
|
+
// would render as a broken image, so it's a link to the file.
|
|
288
|
+
if (asset.contentType && !asset.contentType.startsWith("image/")) {
|
|
289
|
+
const label = asset.title ?? asset.fileName ?? asset.url;
|
|
290
|
+
return renderInline(linkParts([{ marks: {}, text: label }], asset.url));
|
|
291
|
+
}
|
|
267
292
|
// Contentful's own rich-text renderer uses the description as alt text
|
|
268
293
|
// and the title only as a fallback.
|
|
269
|
-
return asset
|
|
270
|
-
? image(asset.description ?? asset.title ?? "", asset.url)
|
|
271
|
-
: "";
|
|
294
|
+
return image(asset.description ?? asset.title ?? "", asset.url);
|
|
272
295
|
}
|
|
273
296
|
case "embedded-entry-block": {
|
|
274
297
|
return embeddedEntry(node, options);
|