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/deploy/sitemap.ts
CHANGED
|
@@ -6,7 +6,11 @@ import {
|
|
|
6
6
|
discoverPagesSync,
|
|
7
7
|
hasGeneratedChangelog,
|
|
8
8
|
} from "../astro/pages.ts";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
mountBasePath,
|
|
11
|
+
normalizeBasePath,
|
|
12
|
+
normalizePath,
|
|
13
|
+
} from "../core/base-path.ts";
|
|
10
14
|
import type { BlumeProject } from "../core/project-graph.ts";
|
|
11
15
|
import { siteRoot } from "../core/site-url.ts";
|
|
12
16
|
|
|
@@ -30,6 +34,30 @@ const lastmodTag = (value: string | undefined): string => {
|
|
|
30
34
|
: `<lastmod>${date.toISOString().slice(0, 10)}</lastmod>`;
|
|
31
35
|
};
|
|
32
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Whether a page's own `seo.canonical` names another URL. The sitemap lists
|
|
39
|
+
* canonical URLs only, so such a page stays out — `blume audit` reports a
|
|
40
|
+
* listed one as NON_CANONICAL_IN_SITEMAP and expects it unlisted. Compared by
|
|
41
|
+
* decoded path, as the audit does: `served` is the page's path with the
|
|
42
|
+
* deployment base mounted, which the canonical carries too.
|
|
43
|
+
*/
|
|
44
|
+
const canonicalElsewhere = (
|
|
45
|
+
canonical: string | undefined,
|
|
46
|
+
served: string
|
|
47
|
+
): boolean => {
|
|
48
|
+
if (!canonical) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
const { pathname } = new URL(canonical);
|
|
52
|
+
let path = pathname;
|
|
53
|
+
try {
|
|
54
|
+
path = decodeURI(pathname);
|
|
55
|
+
} catch {
|
|
56
|
+
// A malformed escape stays as written; it can't match the served path.
|
|
57
|
+
}
|
|
58
|
+
return normalizePath(path) !== normalizePath(served);
|
|
59
|
+
};
|
|
60
|
+
|
|
33
61
|
/** One emitted sitemap artifact: its dist-root filename and XML body. */
|
|
34
62
|
export interface SitemapFile {
|
|
35
63
|
name: string;
|
|
@@ -62,7 +90,8 @@ ${urls.join("\n")}
|
|
|
62
90
|
* can't see: custom `.astro` pages (most importantly a custom landing `/`) and
|
|
63
91
|
* the generated `/changelog` index. Returns null when the sitemap is disabled
|
|
64
92
|
* or no `site` is configured (absolute URLs are required for a valid sitemap).
|
|
65
|
-
* Drafts, hidden, and `noindex` pages are excluded
|
|
93
|
+
* Drafts, hidden, and `noindex` pages are excluded, and so is a page whose
|
|
94
|
+
* `seo.canonical` points elsewhere.
|
|
66
95
|
*
|
|
67
96
|
* Sites within the per-file URL cap get the single classic `sitemap.xml`;
|
|
68
97
|
* larger sites get `sitemap.xml` as a sitemap index over numbered
|
|
@@ -87,7 +116,9 @@ export const buildSitemapFiles = (
|
|
|
87
116
|
// or when their canonical points at a still-existing latest equivalent —
|
|
88
117
|
// listing a URL whose canonical says "index the other page" invites the
|
|
89
118
|
// noindexed-page-in-sitemap incoherence Docusaurus is known for. A page
|
|
90
|
-
// that exists only in an archived version stays listed (self-canonical)
|
|
119
|
+
// that exists only in an archived version stays listed (self-canonical),
|
|
120
|
+
// and a page's own `seo.canonical` wins over the version's default, as it
|
|
121
|
+
// does in the page head (see `canonicalElsewhere`).
|
|
91
122
|
const { versions } = project.config;
|
|
92
123
|
const archivedById = new Map(
|
|
93
124
|
(versions?.archived ?? []).map((version) => [version.id, version])
|
|
@@ -109,6 +140,7 @@ export const buildSitemapFiles = (
|
|
|
109
140
|
return Boolean(
|
|
110
141
|
archived?.noindex ||
|
|
111
142
|
(archived?.canonical === "latest" &&
|
|
143
|
+
!page.meta.seo.canonical &&
|
|
112
144
|
currentKeys?.has(`${page.versionKey}\u0000${page.locale}`))
|
|
113
145
|
);
|
|
114
146
|
};
|
|
@@ -128,16 +160,18 @@ export const buildSitemapFiles = (
|
|
|
128
160
|
urls.push(` <url><loc>${loc}</loc>${lastmodTag(lastModified)}</url>`);
|
|
129
161
|
};
|
|
130
162
|
for (const page of project.graph.pages) {
|
|
163
|
+
const served = mountBasePath(deployBase, page.route);
|
|
131
164
|
if (
|
|
132
165
|
page.meta.draft ||
|
|
133
166
|
page.meta.sidebar.hidden ||
|
|
134
167
|
page.meta.seo.noindex ||
|
|
135
168
|
ERROR_ROUTES.has(page.route) ||
|
|
136
|
-
archivedExcluded(page)
|
|
169
|
+
archivedExcluded(page) ||
|
|
170
|
+
canonicalElsewhere(page.meta.seo.canonical, served)
|
|
137
171
|
) {
|
|
138
172
|
continue;
|
|
139
173
|
}
|
|
140
|
-
pushUrl(
|
|
174
|
+
pushUrl(served, page.lastModified);
|
|
141
175
|
}
|
|
142
176
|
// Custom `.astro` pages and the generated changelog index mount outside
|
|
143
177
|
// `basePath` (they're injected at their pattern — see `blumeIntegration`), so
|
|
@@ -14,22 +14,103 @@
|
|
|
14
14
|
* prerendered Markdown 404 body with the 404 status, instead of the HTML
|
|
15
15
|
* shell, and one that prefers JSON (or asks for a `.json` URL) gets the
|
|
16
16
|
* prerendered problem-details 404.
|
|
17
|
+
*
|
|
18
|
+
* Under `deployment.base` every route here matches the served URL, base
|
|
19
|
+
* included, and names the files where Blume moves them (`static/<base>/`, see
|
|
20
|
+
* {@link rebaseAdapterRoutes}); `base` is the normalized base, `""` for none.
|
|
17
21
|
*/
|
|
18
22
|
|
|
23
|
+
import { mountBasePath } from "../core/base-path.ts";
|
|
19
24
|
import { SVG_ASSET_HEADERS } from "./headers.ts";
|
|
20
25
|
|
|
21
|
-
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
26
|
+
/*
|
|
27
|
+
* The `accept` header conditions. Dev and the Cloudflare Worker negotiate the
|
|
28
|
+
* way `prefersMarkdown` (`astro/markdown-negotiation.ts`) does: media types in
|
|
29
|
+
* any case, and Markdown only when its q-value is above zero and at least
|
|
30
|
+
* HTML's. A routing condition is a regex over the whole header, with no
|
|
31
|
+
* arithmetic, so the comparison is spelled as two routes per rewrite:
|
|
32
|
+
*
|
|
33
|
+
* - a Markdown entry at full weight (no q-value, or `q=1`) always wins, since
|
|
34
|
+
* nothing can outrank it;
|
|
35
|
+
* - a Markdown entry below full weight wins only when the header has no
|
|
36
|
+
* `text/html` entry above zero (a `missing` condition), since two q-values
|
|
37
|
+
* below 1 can't be compared. That case, like `text/markdown;q=0.9,
|
|
38
|
+
* text/html;q=0.8`, gets HTML on Vercel where dev and Cloudflare send
|
|
39
|
+
* Markdown.
|
|
40
|
+
*
|
|
41
|
+
* Every pattern is anchored at both ends and lets `(?:.*,)?` absorb the
|
|
42
|
+
* earlier list entries, so it holds under full-string and substring matching
|
|
43
|
+
* alike. Case-insensitivity is spelled out letter by letter and the patterns
|
|
44
|
+
* are lookaround-free, so they read the same in RE2, PCRE, and JavaScript.
|
|
45
|
+
* Like dev, only the first `q=` parameter of an entry counts, and a `q=`
|
|
46
|
+
* value is read as a decimal. Browsers never send `text/markdown`, so
|
|
47
|
+
* ordinary page requests are unaffected.
|
|
30
48
|
*/
|
|
31
|
-
|
|
32
|
-
|
|
49
|
+
|
|
50
|
+
/** `word` in any case, one character class per letter: `[Tt][Ee][Xx][Tt]`. */
|
|
51
|
+
const anyCase = (word: string): string =>
|
|
52
|
+
word.replaceAll(/[a-z]/gu, (letter) => `[${letter.toUpperCase()}${letter}]`);
|
|
53
|
+
|
|
54
|
+
/** An entry's parameters before its first `q=`: any that isn't one. */
|
|
55
|
+
const OTHER_PARAMS = String.raw`(?:;\s*(?:[^\sq;,][^;,]*|q(?:[^=;,][^;,]*)?)?)*`;
|
|
56
|
+
|
|
57
|
+
/** Everything after an entry's first `q=` value, up to the next entry. */
|
|
58
|
+
const LATER_PARAMS = "(?:;[^,]*)?";
|
|
59
|
+
|
|
60
|
+
/** A `q=` value of 1 or more: full weight. */
|
|
61
|
+
const FULL_WEIGHT = String.raw`0*[1-9][0-9]*(?:\.[0-9]*)?`;
|
|
62
|
+
|
|
63
|
+
/** A `q=` value above zero and below 1. */
|
|
64
|
+
const PARTIAL_WEIGHT = String.raw`0*\.[0-9]*[1-9][0-9]*`;
|
|
65
|
+
|
|
66
|
+
/** A `q=` value that isn't zero: it has a character besides `0` and `.`. */
|
|
67
|
+
const NONZERO_WEIGHT = String.raw`[\s0.]*[^\s0.;,][^;,]*`;
|
|
68
|
+
|
|
69
|
+
/** A header with an entry of `type` weighed by `weight`, or by no `q=` when it's optional. */
|
|
70
|
+
const acceptEntry = (type: string, weight: string, optional: boolean) =>
|
|
71
|
+
String.raw`^(?:.*,)?\s*${type}\s*${OTHER_PARAMS}(?:;\s*q=\s*${weight}\s*${LATER_PARAMS})${optional ? "?" : ""}(?:,.*)?$`;
|
|
72
|
+
|
|
73
|
+
/** A header with a `type` entry at full weight. */
|
|
74
|
+
const fullWeight = (type: string) => acceptEntry(type, FULL_WEIGHT, true);
|
|
75
|
+
|
|
76
|
+
/** A header with a `type` entry weighed above zero and below 1. */
|
|
77
|
+
const partialWeight = (type: string) =>
|
|
78
|
+
acceptEntry(type, PARTIAL_WEIGHT, false);
|
|
79
|
+
|
|
80
|
+
/** A header with a `text/html` entry weighed above zero. */
|
|
81
|
+
const ACCEPT_HTML = acceptEntry(anyCase("text/html"), NONZERO_WEIGHT, true);
|
|
82
|
+
|
|
83
|
+
const MARKDOWN_TYPE = `${anyCase("text/")}(?:${anyCase("x-")})?${anyCase("markdown")}`;
|
|
84
|
+
|
|
85
|
+
const JSON_TYPE = `${anyCase("application/")}(?:${anyCase("problem")}\\+)?${anyCase("json")}`;
|
|
86
|
+
|
|
87
|
+
/** One header condition on a Build Output route. */
|
|
88
|
+
export interface VercelCondition {
|
|
89
|
+
key?: string;
|
|
90
|
+
type: string;
|
|
91
|
+
value?: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** The conditions of one route: all of `has`, none of `missing`. */
|
|
95
|
+
export interface AcceptConditions {
|
|
96
|
+
has: VercelCondition[];
|
|
97
|
+
missing?: VercelCondition[];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const accept = (value: string): VercelCondition => ({
|
|
101
|
+
key: "accept",
|
|
102
|
+
type: "header",
|
|
103
|
+
value,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
/** The pair of conditions a route answering `type` is written once for each. */
|
|
107
|
+
const preferring = (type: string): AcceptConditions[] => [
|
|
108
|
+
{ has: [accept(fullWeight(type))] },
|
|
109
|
+
{ has: [accept(partialWeight(type))], missing: [accept(ACCEPT_HTML)] },
|
|
110
|
+
];
|
|
111
|
+
|
|
112
|
+
/** When a client prefers `text/markdown` (or `text/x-markdown`) over HTML. */
|
|
113
|
+
export const ACCEPT_MARKDOWN_CONDITIONS = preferring(MARKDOWN_TYPE);
|
|
33
114
|
|
|
34
115
|
/**
|
|
35
116
|
* The JSON counterpart, for the problem-details 404: `application/json` or
|
|
@@ -37,8 +118,7 @@ export const ACCEPT_MARKDOWN_HEADER_VALUE =
|
|
|
37
118
|
* (the catch-all wildcard does not match), so ordinary page requests are
|
|
38
119
|
* unaffected.
|
|
39
120
|
*/
|
|
40
|
-
export const
|
|
41
|
-
"(.*,)?\\s*application/(problem\\+)?json(\\s*[;,].*)?$";
|
|
121
|
+
export const ACCEPT_JSON_CONDITIONS = preferring(JSON_TYPE);
|
|
42
122
|
|
|
43
123
|
/**
|
|
44
124
|
* A Build Output API route — the subset these helpers read and write. Parsed
|
|
@@ -49,23 +129,29 @@ export interface VercelRoute {
|
|
|
49
129
|
continue?: boolean;
|
|
50
130
|
dest?: string;
|
|
51
131
|
handle?: string;
|
|
52
|
-
has?:
|
|
132
|
+
has?: VercelCondition[];
|
|
53
133
|
headers?: Record<string, string>;
|
|
134
|
+
missing?: VercelCondition[];
|
|
54
135
|
src?: string;
|
|
55
136
|
status?: number;
|
|
56
137
|
}
|
|
57
138
|
|
|
139
|
+
/** `route` once per condition pair, so it fires when either holds. */
|
|
140
|
+
const conditioned = (
|
|
141
|
+
route: VercelRoute,
|
|
142
|
+
conditions: readonly AcceptConditions[]
|
|
143
|
+
): VercelRoute[] => conditions.map((condition) => ({ ...route, ...condition }));
|
|
144
|
+
|
|
58
145
|
/** Whether a parsed route field is a real string (the config is raw JSON). */
|
|
59
146
|
const isString = (value: string | undefined): value is string =>
|
|
60
147
|
typeof value === "string";
|
|
61
148
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
];
|
|
149
|
+
/** The Markdown conditions' `has` values, to recognize a route this module wrote. */
|
|
150
|
+
const MARKDOWN_VALUES = new Set(
|
|
151
|
+
ACCEPT_MARKDOWN_CONDITIONS.flatMap(({ has }) =>
|
|
152
|
+
has.map((condition) => condition.value)
|
|
153
|
+
)
|
|
154
|
+
);
|
|
69
155
|
|
|
70
156
|
const VARY_ACCEPT = { vary: "Accept" };
|
|
71
157
|
|
|
@@ -78,44 +164,6 @@ const NOT_FOUND_JSON_DEST = "/404.json";
|
|
|
78
164
|
/** The adapter's own not-found fallback — the anchor the Markdown 404 precedes. */
|
|
79
165
|
const NOT_FOUND_HTML_DEST = "/404.html";
|
|
80
166
|
|
|
81
|
-
/**
|
|
82
|
-
* Miss-phase routes that answer a missing page with the Markdown 404 body: any
|
|
83
|
-
* path when the client prefers Markdown, and any `.md`/`.mdx` URL (a request
|
|
84
|
-
* for a raw-Markdown mirror that has no page wants Markdown back, not the HTML
|
|
85
|
-
* shell). Both keep the 404 status. Spliced immediately before the adapter's
|
|
86
|
-
* `/404.html` fallback, so they run after every server route (the MCP
|
|
87
|
-
* endpoint, server islands, images) has had its turn and never hijack a
|
|
88
|
-
* request one of those would have answered.
|
|
89
|
-
*/
|
|
90
|
-
const NOT_FOUND_MARKDOWN_ROUTES: readonly VercelRoute[] = [
|
|
91
|
-
{
|
|
92
|
-
dest: NOT_FOUND_MARKDOWN_DEST,
|
|
93
|
-
has: ACCEPT_MARKDOWN_CONDITION,
|
|
94
|
-
headers: VARY_ACCEPT,
|
|
95
|
-
src: "^/.*$",
|
|
96
|
-
status: 404,
|
|
97
|
-
},
|
|
98
|
-
{ dest: NOT_FOUND_MARKDOWN_DEST, src: "^/.*\\.mdx?$", status: 404 },
|
|
99
|
-
];
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* The JSON 404's miss-phase routes, the problem-details twin of the Markdown
|
|
103
|
-
* ones: any path when the client prefers JSON, and any `.json` URL no file
|
|
104
|
-
* backs. Spliced at the same anchor, after every server route — so the
|
|
105
|
-
* `/api/` catch-all (which answers its own namespace with a problem document)
|
|
106
|
-
* has already had its turn.
|
|
107
|
-
*/
|
|
108
|
-
const NOT_FOUND_JSON_ROUTES: readonly VercelRoute[] = [
|
|
109
|
-
{
|
|
110
|
-
dest: NOT_FOUND_JSON_DEST,
|
|
111
|
-
has: ACCEPT_JSON_CONDITION,
|
|
112
|
-
headers: VARY_ACCEPT,
|
|
113
|
-
src: "^/.*$",
|
|
114
|
-
status: 404,
|
|
115
|
-
},
|
|
116
|
-
{ dest: NOT_FOUND_JSON_DEST, src: "^/.*\\.json$", status: 404 },
|
|
117
|
-
];
|
|
118
|
-
|
|
119
167
|
/** Which prerendered 404 twins the build emitted, so their routes get wired. */
|
|
120
168
|
export interface NotFoundVariants {
|
|
121
169
|
json?: boolean;
|
|
@@ -140,6 +188,53 @@ const REGEX_SPECIALS = /[$()*+.?[\]^{|}\\]/gu;
|
|
|
140
188
|
const routePattern = (route: string): string =>
|
|
141
189
|
encodeURI(route).replace(REGEX_SPECIALS, "\\$&");
|
|
142
190
|
|
|
191
|
+
/**
|
|
192
|
+
* The `src` matching the homepage: `^/$`, or the base itself under one, served
|
|
193
|
+
* without a trailing slash (the adapter's trailing-slash route strips it).
|
|
194
|
+
*/
|
|
195
|
+
const homeSource = (base: string): string =>
|
|
196
|
+
`^${routePattern(mountBasePath(base, "/"))}$`;
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Miss-phase routes that answer a missing page with the Markdown 404 body: any
|
|
200
|
+
* path when the client prefers Markdown, and any `.md`/`.mdx` URL (a request
|
|
201
|
+
* for a raw-Markdown mirror that has no page wants Markdown back, not the HTML
|
|
202
|
+
* shell). Both keep the 404 status. Spliced immediately before the adapter's
|
|
203
|
+
* `/404.html` fallback, so they run after every server route (the MCP
|
|
204
|
+
* endpoint, server islands, images) has had its turn and never hijack a
|
|
205
|
+
* request one of those would have answered.
|
|
206
|
+
*/
|
|
207
|
+
const notFoundMarkdownRoutes = (base: string): VercelRoute[] => {
|
|
208
|
+
const dest = mountBasePath(base, NOT_FOUND_MARKDOWN_DEST);
|
|
209
|
+
const prefix = routePattern(base);
|
|
210
|
+
return [
|
|
211
|
+
...conditioned(
|
|
212
|
+
{ dest, headers: VARY_ACCEPT, src: `^${prefix}/.*$`, status: 404 },
|
|
213
|
+
ACCEPT_MARKDOWN_CONDITIONS
|
|
214
|
+
),
|
|
215
|
+
{ dest, src: `^${prefix}/.*\\.mdx?$`, status: 404 },
|
|
216
|
+
];
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* The JSON 404's miss-phase routes, the problem-details twin of the Markdown
|
|
221
|
+
* ones: any path when the client prefers JSON, and any `.json` URL no file
|
|
222
|
+
* backs. Spliced at the same anchor, after every server route — so the
|
|
223
|
+
* `/api/` catch-all (which answers its own namespace with a problem document)
|
|
224
|
+
* has already had its turn.
|
|
225
|
+
*/
|
|
226
|
+
const notFoundJsonRoutes = (base: string): VercelRoute[] => {
|
|
227
|
+
const dest = mountBasePath(base, NOT_FOUND_JSON_DEST);
|
|
228
|
+
const prefix = routePattern(base);
|
|
229
|
+
return [
|
|
230
|
+
...conditioned(
|
|
231
|
+
{ dest, headers: VARY_ACCEPT, src: `^${prefix}/.*$`, status: 404 },
|
|
232
|
+
ACCEPT_JSON_CONDITIONS
|
|
233
|
+
),
|
|
234
|
+
{ dest, src: `^${prefix}/.*\\.json$`, status: 404 },
|
|
235
|
+
];
|
|
236
|
+
};
|
|
237
|
+
|
|
143
238
|
/** Group patterns so each group's alternation stays under the `src` limit. */
|
|
144
239
|
const chunkPatterns = (patterns: readonly string[]): string[][] => {
|
|
145
240
|
const chunks: string[][] = [];
|
|
@@ -190,41 +285,49 @@ export interface NegotiationRoutes {
|
|
|
190
285
|
* rewrite also stamps `x-markdown-tokens` — the estimated token count of the
|
|
191
286
|
* homepage mirror (Cloudflare's Markdown for Agents convention). Only the home
|
|
192
287
|
* route can carry it: the other rewrites are chunked alternations spanning
|
|
193
|
-
* many pages, and a count is per-page.
|
|
288
|
+
* many pages, and a count is per-page. Under a `base`, each path is matched
|
|
289
|
+
* and rewritten where it's served (`/docs` → `/docs/index.md`).
|
|
194
290
|
*/
|
|
195
291
|
export const buildNegotiationRoutes = (
|
|
196
292
|
routePaths: readonly string[],
|
|
197
|
-
homeTokens?: number
|
|
293
|
+
homeTokens?: number,
|
|
294
|
+
base = ""
|
|
198
295
|
): NegotiationRoutes => {
|
|
199
296
|
const home = routePaths.includes("/");
|
|
200
297
|
const rest = routePaths
|
|
201
298
|
.filter((path) => path !== "/")
|
|
202
|
-
.map((path) => routePattern(path));
|
|
299
|
+
.map((path) => routePattern(mountBasePath(base, path)));
|
|
203
300
|
const chunks = chunkPatterns(rest);
|
|
204
301
|
|
|
205
302
|
const rewriteRoutes: VercelRoute[] = home
|
|
206
|
-
?
|
|
303
|
+
? conditioned(
|
|
207
304
|
{
|
|
208
|
-
dest: "/index.md",
|
|
209
|
-
has: ACCEPT_MARKDOWN_CONDITION,
|
|
305
|
+
dest: mountBasePath(base, "/index.md"),
|
|
210
306
|
headers:
|
|
211
307
|
homeTokens === undefined
|
|
212
308
|
? VARY_ACCEPT
|
|
213
309
|
: { ...VARY_ACCEPT, "x-markdown-tokens": String(homeTokens) },
|
|
214
|
-
src:
|
|
310
|
+
src: homeSource(base),
|
|
215
311
|
},
|
|
216
|
-
|
|
312
|
+
ACCEPT_MARKDOWN_CONDITIONS
|
|
313
|
+
)
|
|
217
314
|
: [];
|
|
218
315
|
for (const chunk of chunks) {
|
|
219
|
-
rewriteRoutes.push(
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
316
|
+
rewriteRoutes.push(
|
|
317
|
+
...conditioned(
|
|
318
|
+
{
|
|
319
|
+
dest: "$1.md",
|
|
320
|
+
headers: VARY_ACCEPT,
|
|
321
|
+
src: `^(${chunk.join("|")})/?$`,
|
|
322
|
+
},
|
|
323
|
+
ACCEPT_MARKDOWN_CONDITIONS
|
|
324
|
+
)
|
|
325
|
+
);
|
|
225
326
|
}
|
|
226
327
|
|
|
227
|
-
const headerChunks = chunkPatterns(
|
|
328
|
+
const headerChunks = chunkPatterns(
|
|
329
|
+
home ? [routePattern(mountBasePath(base, "/")), ...rest] : rest
|
|
330
|
+
);
|
|
228
331
|
const headerRoutes: VercelRoute[] = headerChunks.map((chunk) => ({
|
|
229
332
|
continue: true,
|
|
230
333
|
headers: VARY_ACCEPT,
|
|
@@ -234,9 +337,6 @@ export const buildNegotiationRoutes = (
|
|
|
234
337
|
return { headerRoutes, rewriteRoutes };
|
|
235
338
|
};
|
|
236
339
|
|
|
237
|
-
/** The `src` of the injected homepage `Link` header route. */
|
|
238
|
-
const HOME_SRC = "^/$";
|
|
239
|
-
|
|
240
340
|
const ALLOW_ANY_ORIGIN = "*";
|
|
241
341
|
|
|
242
342
|
/**
|
|
@@ -244,18 +344,18 @@ const ALLOW_ANY_ORIGIN = "*";
|
|
|
244
344
|
* one static discovery document (see `crossOriginDiscoveryPaths`), so a
|
|
245
345
|
* registry reading it from another origin isn't refused by the browser.
|
|
246
346
|
*/
|
|
247
|
-
const corsRoute = (path: string): VercelRoute => ({
|
|
347
|
+
const corsRoute = (path: string, base: string): VercelRoute => ({
|
|
248
348
|
continue: true,
|
|
249
349
|
headers: { "access-control-allow-origin": ALLOW_ANY_ORIGIN },
|
|
250
|
-
src: `^${routePattern(path)}$`,
|
|
350
|
+
src: `^${routePattern(mountBasePath(base, path))}$`,
|
|
251
351
|
});
|
|
252
352
|
|
|
253
353
|
/**
|
|
254
354
|
* The `src` of the route stamping the content-source SVG headers: every `.svg`
|
|
255
|
-
* under `/blume-assets/`.
|
|
256
|
-
* Output tree without the deployment base, like every path matched here.
|
|
355
|
+
* under `/blume-assets/`.
|
|
257
356
|
*/
|
|
258
|
-
const
|
|
357
|
+
const svgAssetSource = (base: string): string =>
|
|
358
|
+
String.raw`^${routePattern(base)}/blume-assets/.+\.svg$`;
|
|
259
359
|
|
|
260
360
|
/**
|
|
261
361
|
* A main-phase `continue` route stamping the sandbox headers
|
|
@@ -263,7 +363,7 @@ const SVG_ASSET_SRC = String.raw`^/blume-assets/.+\.svg$`;
|
|
|
263
363
|
* static layer — where the prerendered asset endpoint's own headers never
|
|
264
364
|
* reach.
|
|
265
365
|
*/
|
|
266
|
-
const svgAssetRoute = (): VercelRoute => ({
|
|
366
|
+
const svgAssetRoute = (base: string): VercelRoute => ({
|
|
267
367
|
continue: true,
|
|
268
368
|
headers: Object.fromEntries(
|
|
269
369
|
Object.entries(SVG_ASSET_HEADERS).map(([name, value]) => [
|
|
@@ -271,7 +371,7 @@ const svgAssetRoute = (): VercelRoute => ({
|
|
|
271
371
|
value,
|
|
272
372
|
])
|
|
273
373
|
),
|
|
274
|
-
src:
|
|
374
|
+
src: svgAssetSource(base),
|
|
275
375
|
});
|
|
276
376
|
|
|
277
377
|
/** Whether a route is a `corsRoute` — the same three-field shape test as the others. */
|
|
@@ -291,22 +391,23 @@ const isCorsRoute = (route: VercelRoute): boolean =>
|
|
|
291
391
|
* no user-authored route competes in this file); the Markdown 404 routes by
|
|
292
392
|
* their `/404.md` destination.
|
|
293
393
|
*/
|
|
294
|
-
const isNegotiationRoute = (route: VercelRoute): boolean =>
|
|
295
|
-
route.has?.some(
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
(route.dest === NOT_FOUND_JSON_DEST &&
|
|
394
|
+
const isNegotiationRoute = (route: VercelRoute, base: string): boolean =>
|
|
395
|
+
route.has?.some((condition) => MARKDOWN_VALUES.has(condition.value)) ===
|
|
396
|
+
true ||
|
|
397
|
+
(route.dest === mountBasePath(base, NOT_FOUND_MARKDOWN_DEST) &&
|
|
398
|
+
route.status === 404) ||
|
|
399
|
+
(route.dest === mountBasePath(base, NOT_FOUND_JSON_DEST) &&
|
|
400
|
+
route.status === 404) ||
|
|
300
401
|
(route.continue === true &&
|
|
301
402
|
route.headers?.vary === "Accept" &&
|
|
302
403
|
isString(route.src) &&
|
|
303
404
|
Object.keys(route).length === 3) ||
|
|
304
405
|
(route.continue === true &&
|
|
305
406
|
isString(route.headers?.link) &&
|
|
306
|
-
route.src ===
|
|
407
|
+
route.src === homeSource(base) &&
|
|
307
408
|
Object.keys(route).length === 3) ||
|
|
308
409
|
isCorsRoute(route) ||
|
|
309
|
-
(route.continue === true && route.src ===
|
|
410
|
+
(route.continue === true && route.src === svgAssetSource(base));
|
|
310
411
|
|
|
311
412
|
/**
|
|
312
413
|
* Splice the negotiation routes into a Build Output `config.json`, plus — when
|
|
@@ -325,7 +426,9 @@ const isNegotiationRoute = (route: VercelRoute): boolean =>
|
|
|
325
426
|
* phase right before the adapter's `/404.html` fallback — and nowhere when
|
|
326
427
|
* that fallback is absent, since a `dest` with no file behind it would serve
|
|
327
428
|
* nothing. With `svgAssets`, a main-phase route also sandboxes the SVGs a
|
|
328
|
-
* content source downloaded (see {@link svgAssetRoute}).
|
|
429
|
+
* content source downloaded (see {@link svgAssetRoute}). Under a `base`, every
|
|
430
|
+
* route matches and names paths beneath it, and the fallback anchor is the
|
|
431
|
+
* one {@link rebaseAdapterRoutes} moved there. Returns the updated JSON
|
|
329
432
|
* text (tab-indented, like the adapter's own output), or `null` when there is
|
|
330
433
|
* nowhere safe to splice: an unparsable config, no `routes` array, or no
|
|
331
434
|
* `handle: "filesystem"` marker to anchor the splice.
|
|
@@ -338,7 +441,8 @@ export const injectNegotiationRoutes = (
|
|
|
338
441
|
homeTokens?: number,
|
|
339
442
|
notFound: NotFoundVariants = {},
|
|
340
443
|
corsPaths: readonly string[] = [],
|
|
341
|
-
svgAssets = false
|
|
444
|
+
svgAssets = false,
|
|
445
|
+
base = ""
|
|
342
446
|
): string | null => {
|
|
343
447
|
const overrideEntries = Object.entries(contentTypeOverrides ?? {});
|
|
344
448
|
let config: {
|
|
@@ -358,7 +462,9 @@ export const injectNegotiationRoutes = (
|
|
|
358
462
|
// user's own override of the same path is simply refreshed.
|
|
359
463
|
config.overrides = { ...config.overrides, [path]: { contentType } };
|
|
360
464
|
}
|
|
361
|
-
const routes = config.routes.filter(
|
|
465
|
+
const routes = config.routes.filter(
|
|
466
|
+
(route) => !isNegotiationRoute(route, base)
|
|
467
|
+
);
|
|
362
468
|
const filesystemIndex = routes.findIndex(
|
|
363
469
|
(route) => route.handle === "filesystem"
|
|
364
470
|
);
|
|
@@ -367,30 +473,32 @@ export const injectNegotiationRoutes = (
|
|
|
367
473
|
}
|
|
368
474
|
const { headerRoutes, rewriteRoutes } = buildNegotiationRoutes(
|
|
369
475
|
routePaths,
|
|
370
|
-
homeTokens
|
|
476
|
+
homeTokens,
|
|
477
|
+
base
|
|
371
478
|
);
|
|
372
479
|
if (homeLinkHeader) {
|
|
373
480
|
headerRoutes.push({
|
|
374
481
|
continue: true,
|
|
375
482
|
headers: { link: homeLinkHeader },
|
|
376
|
-
src:
|
|
483
|
+
src: homeSource(base),
|
|
377
484
|
});
|
|
378
485
|
}
|
|
379
|
-
headerRoutes.push(...corsPaths.map(corsRoute));
|
|
486
|
+
headerRoutes.push(...corsPaths.map((path) => corsRoute(path, base)));
|
|
380
487
|
if (svgAssets) {
|
|
381
|
-
headerRoutes.push(svgAssetRoute());
|
|
488
|
+
headerRoutes.push(svgAssetRoute(base));
|
|
382
489
|
}
|
|
383
490
|
// Headers first: `continue` routes accumulate, so a request the rewrite
|
|
384
491
|
// route then terminates (Markdown negotiation on the homepage) still carries
|
|
385
492
|
// the Link header.
|
|
386
493
|
routes.splice(filesystemIndex, 0, ...headerRoutes, ...rewriteRoutes);
|
|
387
494
|
const notFoundRoutes = [
|
|
388
|
-
...(notFound.markdown ?
|
|
389
|
-
...(notFound.json ?
|
|
495
|
+
...(notFound.markdown ? notFoundMarkdownRoutes(base) : []),
|
|
496
|
+
...(notFound.json ? notFoundJsonRoutes(base) : []),
|
|
390
497
|
];
|
|
391
498
|
if (notFoundRoutes.length > 0) {
|
|
499
|
+
const fallbackDest = mountBasePath(base, NOT_FOUND_HTML_DEST);
|
|
392
500
|
const fallbackIndex = routes.findIndex(
|
|
393
|
-
(route) => route.status === 404 && route.dest ===
|
|
501
|
+
(route) => route.status === 404 && route.dest === fallbackDest
|
|
394
502
|
);
|
|
395
503
|
if (fallbackIndex !== -1) {
|
|
396
504
|
routes.splice(fallbackIndex, 0, ...notFoundRoutes);
|
|
@@ -435,3 +543,83 @@ export const injectRedirectRoutes = (
|
|
|
435
543
|
config.routes = routes;
|
|
436
544
|
return `${JSON.stringify(config, null, "\t")}\n`;
|
|
437
545
|
};
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* The trailing-slash route `trailingSlash: "never"` gives the adapter's config
|
|
549
|
+
* (`^/(.*)/$` → `/$1`): it keeps whatever path it strips the slash from, base
|
|
550
|
+
* and all, so it already serves a based site.
|
|
551
|
+
*/
|
|
552
|
+
const isTrailingSlashRoute = (route: VercelRoute): boolean =>
|
|
553
|
+
route.headers?.Location === "/$1";
|
|
554
|
+
|
|
555
|
+
/** The pattern body of the root route, anchored (`^/$`) or not (`/`). */
|
|
556
|
+
const ROOT_BODY = /^\/\$?$/u;
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Re-anchor an adapter route pattern under the base, whose escaped form is
|
|
560
|
+
* `prefix`: each matches a base-less path from its start, and the root route
|
|
561
|
+
* becomes the base itself, served without a trailing slash.
|
|
562
|
+
*/
|
|
563
|
+
const rebaseSource = (src: string, prefix: string): string => {
|
|
564
|
+
const body = src.startsWith("^") ? src.slice(1) : src;
|
|
565
|
+
return `^${prefix}${ROOT_BODY.test(body) ? "$" : body}`;
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* Move the adapter's routes under `deployment.base`, to match the static files
|
|
570
|
+
* Blume moves to `static/<base>/`. `@astrojs/vercel` routes a server build as
|
|
571
|
+
* if it were served from the root: its route patterns (the `_astro/` cache
|
|
572
|
+
* header, every server route) match base-less paths, its 404 fallback names
|
|
573
|
+
* `/404.html`, and it joins the base onto each redirect with no slash on the
|
|
574
|
+
* `from` side and a second time on the `to` side, which Astro hands it
|
|
575
|
+
* already based (`^/docsold$` → `/docs/docs/new`). Each pattern is
|
|
576
|
+
* re-anchored under the base and the fallback names the base's `404.html`;
|
|
577
|
+
* the adapter's redirects make way for `redirectRoutes`, the exact redirects
|
|
578
|
+
* as the host matches them. Returns the updated JSON text, or `null` when
|
|
579
|
+
* there is nowhere safe to splice: an unparsable config, no `routes` array,
|
|
580
|
+
* or no `handle: "filesystem"` marker.
|
|
581
|
+
*/
|
|
582
|
+
export const rebaseAdapterRoutes = (
|
|
583
|
+
configText: string,
|
|
584
|
+
base: string,
|
|
585
|
+
redirectRoutes: readonly VercelRoute[]
|
|
586
|
+
): string | null => {
|
|
587
|
+
let config: { routes?: VercelRoute[] };
|
|
588
|
+
try {
|
|
589
|
+
config = JSON.parse(configText);
|
|
590
|
+
} catch {
|
|
591
|
+
return null;
|
|
592
|
+
}
|
|
593
|
+
if (!Array.isArray(config.routes)) {
|
|
594
|
+
return null;
|
|
595
|
+
}
|
|
596
|
+
const filesystemIndex = config.routes.findIndex(
|
|
597
|
+
(route) => route.handle === "filesystem"
|
|
598
|
+
);
|
|
599
|
+
if (filesystemIndex === -1) {
|
|
600
|
+
return null;
|
|
601
|
+
}
|
|
602
|
+
const prefix = routePattern(base);
|
|
603
|
+
const routes = config.routes.flatMap((route, index): VercelRoute[] => {
|
|
604
|
+
// Ahead of the filesystem handler, the adapter writes its redirects and
|
|
605
|
+
// nothing else with a `Location`.
|
|
606
|
+
if (index < filesystemIndex && isString(route.headers?.Location)) {
|
|
607
|
+
return isTrailingSlashRoute(route) ? [route] : [];
|
|
608
|
+
}
|
|
609
|
+
const rebased = { ...route };
|
|
610
|
+
if (isString(route.src)) {
|
|
611
|
+
rebased.src = rebaseSource(route.src, prefix);
|
|
612
|
+
}
|
|
613
|
+
if (route.status === 404 && route.dest === NOT_FOUND_HTML_DEST) {
|
|
614
|
+
rebased.dest = mountBasePath(base, NOT_FOUND_HTML_DEST);
|
|
615
|
+
}
|
|
616
|
+
return [rebased];
|
|
617
|
+
});
|
|
618
|
+
routes.splice(
|
|
619
|
+
routes.findIndex((route) => route.handle === "filesystem"),
|
|
620
|
+
0,
|
|
621
|
+
...redirectRoutes
|
|
622
|
+
);
|
|
623
|
+
config.routes = routes;
|
|
624
|
+
return `${JSON.stringify(config, null, "\t")}\n`;
|
|
625
|
+
};
|
package/src/eval/agents.ts
CHANGED
|
@@ -188,7 +188,24 @@ const CLAUDE_READER_MAX_TURNS = "25";
|
|
|
188
188
|
const CLAUDE_JUDGE_MAX_TURNS = "1";
|
|
189
189
|
|
|
190
190
|
const claudeArgs = (context: InvocationContext): string[] => {
|
|
191
|
-
|
|
191
|
+
// `--tools ""` turns off every Claude Code built-in, including ones the
|
|
192
|
+
// deny list below doesn't name (`Monitor` runs shell commands, `Skill`
|
|
193
|
+
// loads the user's skills), so a reader has only the docs MCP tools and the
|
|
194
|
+
// judge has no tools at all. The deny list stays as a second layer.
|
|
195
|
+
// `--setting-sources ""` is codex's `--ignore-user-config`: no settings
|
|
196
|
+
// files, so the user's hooks, plugins, and CLAUDE.md can't hand the reader
|
|
197
|
+
// context the docs never gave it. Not `--bare`, which also stops reading a
|
|
198
|
+
// subscription login and fails with "Not logged in".
|
|
199
|
+
const base = [
|
|
200
|
+
"-p",
|
|
201
|
+
"--output-format",
|
|
202
|
+
"json",
|
|
203
|
+
"--strict-mcp-config",
|
|
204
|
+
"--tools",
|
|
205
|
+
"",
|
|
206
|
+
"--setting-sources",
|
|
207
|
+
"",
|
|
208
|
+
];
|
|
192
209
|
if (context.mcp) {
|
|
193
210
|
const allowed = MCP_TOOL_NAMES.map(
|
|
194
211
|
(tool) => `mcp__${MCP_SERVER_NAME}__${tool}`
|