blume 2.0.3 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +1 -0
- package/CHANGELOG.md +48 -0
- package/README.md +1 -1
- package/dist/cli/{chunk-1d7ve1dm.js → chunk-0gz36akr.js} +18 -2
- package/dist/cli/{chunk-1d7ve1dm.js.map → chunk-0gz36akr.js.map} +3 -3
- package/dist/cli/{chunk-2eytanqx.js → chunk-1n228xqb.js} +2 -2
- package/dist/cli/{chunk-yfyb25rh.js → chunk-2f3ndkpw.js} +24 -8
- package/dist/cli/chunk-2f3ndkpw.js.map +14 -0
- package/dist/cli/{chunk-r20tn01b.js → chunk-2f7n4qzt.js} +8 -2
- package/dist/cli/{chunk-r20tn01b.js.map → chunk-2f7n4qzt.js.map} +3 -3
- package/dist/cli/{chunk-35d4wj9f.js → chunk-2n4ps1b3.js} +7 -7
- package/dist/cli/{chunk-j85vccga.js → chunk-43zcf4gz.js} +97 -57
- package/dist/cli/{chunk-j85vccga.js.map → chunk-43zcf4gz.js.map} +6 -6
- package/dist/cli/{chunk-e04dxsz1.js → chunk-4ejxbzee.js} +1 -1
- package/dist/cli/{chunk-5m5nmvyq.js → chunk-4h8z9vv5.js} +233 -39
- package/dist/cli/chunk-4h8z9vv5.js.map +16 -0
- package/dist/cli/{chunk-5f86nr5m.js → chunk-5cg2rzgf.js} +18 -16
- package/dist/cli/{chunk-5f86nr5m.js.map → chunk-5cg2rzgf.js.map} +2 -2
- package/dist/cli/{chunk-tzmab476.js → chunk-5ffywsmb.js} +4 -4
- package/dist/cli/{chunk-2z928egk.js → chunk-5wj2qwn2.js} +5 -5
- package/dist/cli/chunk-6bm0xpgd.js +18 -0
- package/dist/cli/chunk-6bm0xpgd.js.map +10 -0
- package/dist/cli/{chunk-vg9r4eb9.js → chunk-713mngdq.js} +12 -5
- package/dist/cli/{chunk-vg9r4eb9.js.map → chunk-713mngdq.js.map} +3 -3
- package/dist/cli/{chunk-5xvm6tfj.js → chunk-9geymqns.js} +17 -15
- package/dist/cli/{chunk-5xvm6tfj.js.map → chunk-9geymqns.js.map} +2 -2
- package/dist/cli/{chunk-q58y5e6a.js → chunk-9xr4dgee.js} +26 -17
- package/dist/cli/chunk-9xr4dgee.js.map +10 -0
- package/dist/cli/{chunk-b07cmahc.js → chunk-amrb6hgh.js} +2 -2
- package/dist/cli/chunk-cm89scdv.js +443 -0
- package/dist/cli/chunk-cm89scdv.js.map +11 -0
- package/dist/cli/{chunk-akbpwfxc.js → chunk-cmtcnh9f.js} +2 -2
- package/dist/cli/chunk-cy5bm0j0.js +104 -0
- package/dist/cli/chunk-cy5bm0j0.js.map +11 -0
- package/dist/cli/{chunk-crgn1q09.js → chunk-e7vvk5ya.js} +4 -4
- package/dist/cli/{chunk-pehfxfta.js → chunk-g2z9x01v.js} +6 -98
- package/dist/cli/chunk-g2z9x01v.js.map +10 -0
- package/dist/cli/{chunk-tkacnehg.js → chunk-gp84za0h.js} +2 -2
- package/dist/cli/{chunk-wrr3j9w9.js → chunk-h2a48yhd.js} +186 -63
- package/dist/cli/chunk-h2a48yhd.js.map +36 -0
- package/dist/cli/{chunk-hm3vjy5s.js → chunk-jnwj5kjj.js} +14 -9
- package/dist/cli/{chunk-hm3vjy5s.js.map → chunk-jnwj5kjj.js.map} +4 -4
- package/dist/cli/chunk-kwrg63tm.js +31 -0
- package/dist/cli/chunk-kwrg63tm.js.map +10 -0
- package/dist/cli/{chunk-2hsdwb9n.js → chunk-nd1821sk.js} +18 -16
- package/dist/cli/{chunk-2hsdwb9n.js.map → chunk-nd1821sk.js.map} +2 -2
- package/dist/cli/{chunk-6dsbexzp.js → chunk-ntv4qrt7.js} +17 -15
- package/dist/cli/{chunk-6dsbexzp.js.map → chunk-ntv4qrt7.js.map} +2 -2
- package/dist/cli/{chunk-r9rcc4w7.js → chunk-p804f3pe.js} +7 -7
- package/dist/cli/{chunk-ey84smr6.js → chunk-py62fewh.js} +6 -6
- package/dist/cli/{chunk-ey84smr6.js.map → chunk-py62fewh.js.map} +1 -1
- package/dist/cli/chunk-q3wxxq39.js +126 -0
- package/dist/cli/chunk-q3wxxq39.js.map +11 -0
- package/dist/cli/{chunk-9t7a85s3.js → chunk-rekqz4qg.js} +23 -13
- package/dist/cli/chunk-rekqz4qg.js.map +10 -0
- package/dist/cli/{chunk-8ktnccpt.js → chunk-ta27feev.js} +1 -1
- package/dist/cli/{chunk-pv29h0wf.js → chunk-xkz5xbtv.js} +3476 -822
- package/dist/cli/chunk-xkz5xbtv.js.map +213 -0
- package/dist/cli/{chunk-a58773jm.js → chunk-y4ykbark.js} +2 -2
- package/dist/cli/{chunk-p3v96n38.js → chunk-y5h4r2y0.js} +4 -4
- package/dist/cli/{chunk-acanzt5p.js → chunk-yj17vs39.js} +6 -6
- package/dist/cli/{chunk-p73c0m7w.js → chunk-yxmk4pec.js} +16 -14
- package/dist/cli/{chunk-p73c0m7w.js.map → chunk-yxmk4pec.js.map} +2 -2
- package/dist/cli/{chunk-3em5wd2y.js → chunk-zccbjcj0.js} +4 -3
- package/dist/cli/{chunk-3em5wd2y.js.map → chunk-zccbjcj0.js.map} +2 -2
- package/dist/cli/{chunk-c8chx29p.js → chunk-zf748avv.js} +384 -207
- package/dist/cli/chunk-zf748avv.js.map +37 -0
- package/dist/cli/{chunk-g4hq16wv.js → chunk-zhvd6adm.js} +16 -14
- package/dist/cli/{chunk-g4hq16wv.js.map → chunk-zhvd6adm.js.map} +2 -2
- package/dist/cli/index.js +26 -20
- package/dist/cli/index.js.map +5 -5
- package/dist/types/ai/agent-surface.d.ts +8 -7
- package/dist/types/ai/ai-catalog.d.ts +7 -0
- package/dist/types/ai/ask-context.d.ts +22 -0
- package/dist/types/ai/ask.d.ts +271 -58
- package/dist/types/ai/index.d.ts +2 -2
- package/dist/types/ai/site-skill.d.ts +26 -0
- package/dist/types/ai/skills.d.ts +16 -0
- package/dist/types/astro/integration.d.ts +8 -0
- package/dist/types/captcha/hcaptcha.d.ts +27 -0
- package/dist/types/captcha/index.d.ts +28 -0
- package/dist/types/captcha/schema.d.ts +29 -0
- package/dist/types/captcha/turnstile.d.ts +28 -0
- package/dist/types/components/content/api-field.d.ts +59 -0
- package/dist/types/components/content/api-page.d.ts +95 -0
- package/dist/types/components/layout/nav-utils.d.ts +26 -0
- package/dist/types/components/openapi/helpers.d.ts +137 -0
- package/dist/types/components/openapi/security.d.ts +85 -0
- package/dist/types/consent/ethyca.d.ts +48 -0
- package/dist/types/consent/index.d.ts +37 -0
- package/dist/types/consent/native.d.ts +28 -0
- package/dist/types/consent/osano.d.ts +42 -0
- package/dist/types/consent/schema.d.ts +110 -0
- package/dist/types/core/config-input.d.ts +187 -20
- package/dist/types/core/data.d.ts +49 -4
- package/dist/types/core/footer.d.ts +8 -0
- package/dist/types/core/i18n-ui.d.ts +86 -0
- package/dist/types/core/includes.d.ts +8 -2
- package/dist/types/core/localizable.d.ts +7 -0
- package/dist/types/core/page-modes.d.ts +29 -0
- package/dist/types/core/redirect-patterns.d.ts +144 -0
- package/dist/types/core/related.d.ts +40 -0
- package/dist/types/core/schema.d.ts +551 -56
- package/dist/types/core/sources/read.d.ts +5 -0
- package/dist/types/core/types.d.ts +1 -1
- package/dist/types/core/variables.d.ts +42 -0
- package/dist/types/deploy/node-headers.d.ts +13 -2
- package/dist/types/deploy/platforms/cloudflare.d.ts +5 -0
- package/dist/types/deploy/platforms/netlify.d.ts +7 -0
- package/dist/types/deploy/platforms/vercel.d.ts +6 -0
- package/dist/types/deploy/redirects.d.ts +39 -11
- package/dist/types/deploy/vercel-negotiation.d.ts +9 -0
- package/dist/types/markdown/code-title.d.ts +17 -0
- package/dist/types/markdown/features.d.ts +3 -2
- package/dist/types/narration/build.d.ts +70 -0
- package/dist/types/narration/provider.d.ts +26 -0
- package/dist/types/narration/script.d.ts +154 -0
- package/dist/types/openapi/model.d.ts +12 -6
- package/dist/types/openapi/overlay.d.ts +54 -0
- package/dist/types/openapi/parse.d.ts +13 -0
- package/dist/types/openapi/references.d.ts +14 -4
- package/dist/types/ratelimit/cloudflare.d.ts +39 -0
- package/dist/types/ratelimit/index.d.ts +27 -0
- package/dist/types/ratelimit/memory.d.ts +36 -0
- package/dist/types/ratelimit/schema.d.ts +111 -0
- package/dist/types/ratelimit/upstash.d.ts +21 -0
- package/dist/types/ratelimit/wrangler.d.ts +23 -0
- package/dist/types/reference/asyncapi.d.ts +10 -10
- package/dist/types/reference/graphql.d.ts +13 -10
- package/dist/types/reference/openapi.d.ts +33 -12
- package/dist/types/reference/options.d.ts +45 -5
- package/dist/types/reference/scalar.d.ts +13 -0
- package/dist/types/reference/schema.d.ts +60 -36
- package/dist/types/search/documents.d.ts +12 -1
- package/dist/types/search/orama-index.d.ts +4 -0
- package/dist/types/seo/metatags.d.ts +20 -0
- package/docs/02-deployment.mdx +41 -5
- package/docs/03-upgrading.mdx +4 -4
- package/docs/04-migrating.mdx +1 -1
- package/docs/advanced/changelog.mdx +2 -2
- package/docs/advanced/skills.mdx +14 -0
- package/docs/cli/audit.mdx +21 -1
- package/docs/cli/index.mdx +1 -0
- package/docs/configuration/analytics.mdx +3 -1
- package/docs/configuration/assistant.mdx +188 -36
- package/docs/configuration/consent.mdx +100 -0
- package/docs/configuration/customization.mdx +4 -4
- package/docs/configuration/index.mdx +63 -5
- package/docs/configuration/meta.ts +3 -0
- package/docs/configuration/narration.mdx +119 -0
- package/docs/configuration/rate-limiting.mdx +69 -0
- package/docs/configuration/search.mdx +29 -1
- package/docs/configuration/theming.mdx +3 -1
- package/docs/content/components.mdx +106 -0
- package/docs/content/frontmatter.mdx +76 -0
- package/docs/content/i18n.mdx +38 -3
- package/docs/content/includes.mdx +16 -0
- package/docs/content/meta.mdx +1 -0
- package/docs/content/meta.ts +1 -0
- package/docs/content/navigation.mdx +28 -6
- package/docs/content/sources/contentful.mdx +32 -0
- package/docs/content/sources/custom.mdx +39 -0
- package/docs/content/sources/github-releases.mdx +31 -0
- package/docs/content/sources/index.mdx +91 -0
- package/docs/content/sources/meta.ts +16 -0
- package/docs/content/sources/notion.mdx +32 -0
- package/docs/content/sources/obsidian.mdx +37 -0
- package/docs/content/sources/payload.mdx +30 -0
- package/docs/content/sources/remote-mdx.mdx +40 -0
- package/docs/content/sources/sanity.mdx +29 -0
- package/docs/content/sources/strapi.mdx +31 -0
- package/docs/content/syntax.mdx +67 -2
- package/docs/content/variables.mdx +41 -0
- package/docs/discoverability/agent-discovery.mdx +29 -2
- package/docs/discoverability/index.mdx +1 -1
- package/docs/discoverability/llms-txt.mdx +1 -1
- package/docs/discoverability/metadata.mdx +17 -0
- package/docs/references/api-pages.mdx +74 -0
- package/docs/references/asyncapi.mdx +1 -1
- package/docs/references/graphql.mdx +2 -2
- package/docs/references/meta.ts +1 -1
- package/docs/references/openapi.mdx +102 -3
- package/docs/references/scalar.mdx +3 -1
- package/package.json +36 -1
- package/skills/blume/SKILL.md +3 -0
- package/skills/blume-migrate/SKILL.md +5 -5
- package/skills/blume-migrate/references/docusaurus.md +3 -3
- package/skills/blume-migrate/references/mintlify.md +37 -19
- package/skills/blume-migrate/references/nextra.md +2 -2
- package/skills/blume-migrate/references/starlight.md +2 -1
- package/skills/blume-migrate/scripts/mintlify-codemod.mjs +60 -14
- package/skills/blume-write-skill/SKILL.md +82 -0
- package/src/ai/agent-readability.ts +2 -1
- package/src/ai/agent-surface.ts +9 -7
- package/src/ai/ai-catalog.ts +6 -3
- package/src/ai/ask-context.ts +73 -20
- package/src/ai/ask-data.ts +6 -0
- package/src/ai/ask-tools.ts +142 -0
- package/src/ai/ask.ts +354 -76
- package/src/ai/component-markdown.ts +75 -0
- package/src/ai/cors.ts +2 -0
- package/src/ai/index.ts +12 -0
- package/src/ai/llms.ts +6 -2
- package/src/ai/markdown.ts +2 -1
- package/src/ai/mcp/data.ts +6 -0
- package/src/ai/openapi-components.ts +9 -3
- package/src/ai/site-skill.ts +274 -0
- package/src/ai/skills.ts +35 -2
- package/src/ai/write-skill.ts +97 -0
- package/src/astro/generate.ts +132 -18
- package/src/astro/index.ts +1 -0
- package/src/astro/integration.ts +35 -0
- package/src/astro/module-types.ts +6 -0
- package/src/astro/templates.ts +283 -30
- package/src/astro/variables.ts +31 -0
- package/src/audit/catalog.ts +19 -0
- package/src/audit/checks/contrast.ts +183 -0
- package/src/audit/checks/i18n.ts +2 -3
- package/src/audit/checks/indexability.ts +2 -3
- package/src/audit/checks/links.ts +5 -12
- package/src/audit/checks/sitemap.ts +2 -3
- package/src/audit/redirects.ts +46 -15
- package/src/audit/run.ts +2 -0
- package/src/audit/types.ts +2 -1
- package/src/blume-modules.d.ts +7 -0
- package/src/captcha/client.ts +152 -0
- package/src/captcha/hcaptcha.ts +36 -0
- package/src/captcha/index.ts +28 -0
- package/src/captcha/schema.ts +32 -0
- package/src/captcha/turnstile.ts +38 -0
- package/src/captcha/verify.ts +71 -0
- package/src/cli/command-meta.ts +5 -0
- package/src/cli/commands/doctor.ts +22 -16
- package/src/cli/commands/skill.ts +96 -0
- package/src/cli/index.ts +5 -0
- package/src/cli/required-secrets.ts +71 -29
- package/src/components/BlumePage.astro +2 -0
- package/src/components/content/ApiEndpoint.astro +18 -0
- package/src/components/content/ApiField.astro +79 -0
- package/src/components/content/ApiPlayground.astro +62 -0
- package/src/components/content/ApiRail.astro +19 -0
- package/src/components/content/Color.astro +1 -0
- package/src/components/content/FileTree.astro +1 -0
- package/src/components/content/GithubInfo.astro +1 -0
- package/src/components/content/Panel.astro +1 -0
- package/src/components/content/ParamField.astro +25 -0
- package/src/components/content/RequestExample.astro +17 -0
- package/src/components/content/ResponseExample.astro +17 -0
- package/src/components/content/ResponseField.astro +32 -0
- package/src/components/content/Step.astro +1 -0
- package/src/components/content/Tile.astro +1 -0
- package/src/components/content/Tooltip.astro +1 -0
- package/src/components/content/Tree.astro +1 -0
- package/src/components/content/TypeTable.astro +4 -1
- package/src/components/content/View.astro +21 -0
- package/src/components/content/ViewSwitcher.astro +168 -0
- package/src/components/content/api-field.ts +100 -0
- package/src/components/content/api-page.ts +224 -0
- package/src/components/content/view-switcher.ts +71 -0
- package/src/components/index.ts +2 -0
- package/src/components/islands/Assistant.astro +10 -1
- package/src/components/islands/assistant.tsx +104 -14
- package/src/components/islands/code-question.ts +62 -0
- package/src/components/islands/hooks.ts +110 -21
- package/src/components/islands/support-link.ts +68 -0
- package/src/components/layout/Analytics.astro +11 -2
- package/src/components/layout/Banner.astro +14 -10
- package/src/components/layout/Breadcrumbs.astro +6 -2
- package/src/components/layout/ConsentBanner.astro +62 -0
- package/src/components/layout/ConsentHead.astro +38 -0
- package/src/components/layout/Directory.astro +94 -0
- package/src/components/layout/DrawerSwitchers.astro +51 -0
- package/src/components/layout/Empty.astro +2 -2
- package/src/components/layout/Header.astro +122 -80
- package/src/components/layout/LanguageSwitcher.astro +48 -15
- package/src/components/layout/NarrationPlayer.astro +1085 -0
- package/src/components/layout/NavSelector.astro +33 -7
- package/src/components/layout/PageFeedback.astro +54 -49
- package/src/components/layout/PageLayout.astro +71 -3
- package/src/components/layout/ReferenceLayout.astro +17 -6
- package/src/components/layout/RelatedPages.astro +42 -0
- package/src/components/layout/RootLayout.astro +308 -45
- package/src/components/layout/Search.astro +31 -17
- package/src/components/layout/SiteFooter.astro +124 -0
- package/src/components/layout/SiteMetatags.astro +16 -0
- package/src/components/layout/consent/native.ts +79 -0
- package/src/components/layout/consent/types.ts +37 -0
- package/src/components/layout/footer-links.ts +53 -0
- package/src/components/layout/footer-socials.ts +87 -0
- package/src/components/layout/locale-redirect.ts +107 -0
- package/src/components/layout/nav-utils.ts +83 -0
- package/src/components/layout/page-feedback.ts +96 -0
- package/src/components/layout/search/analytics.ts +98 -0
- package/src/components/layout/search/flexsearch.ts +17 -5
- package/src/components/layout/search/orama-cloud.ts +26 -14
- package/src/components/layout/search/types.ts +20 -0
- package/src/components/layout/search/typesense.ts +5 -1
- package/src/components/openapi/Operation.astro +161 -13
- package/src/components/openapi/RequestBody.astro +6 -4
- package/src/components/openapi/RequestPanel.astro +59 -7
- package/src/components/openapi/Responses.astro +6 -4
- package/src/components/openapi/async-snippets.ts +4 -4
- package/src/components/openapi/code-samples.ts +97 -0
- package/src/components/openapi/security.ts +6 -3
- package/src/components/openapi/snippets.ts +869 -8
- package/src/components/props.ts +6 -0
- package/src/consent/client.ts +95 -0
- package/src/consent/clients.ts +19 -0
- package/src/consent/ethyca.ts +71 -0
- package/src/consent/head.ts +24 -0
- package/src/consent/index.ts +37 -0
- package/src/consent/init.ts +29 -0
- package/src/consent/native.ts +36 -0
- package/src/consent/osano.ts +59 -0
- package/src/consent/schema.ts +30 -0
- package/src/core/builtin-tags.ts +5 -0
- package/src/core/client-address.ts +19 -0
- package/src/core/config-input.ts +194 -20
- package/src/core/data.ts +44 -4
- package/src/core/diagnostics.ts +3 -2
- package/src/core/footer.ts +26 -0
- package/src/core/graph.ts +39 -36
- package/src/core/i18n-ui.ts +82 -2
- package/src/core/includes.ts +20 -3
- package/src/core/links.ts +16 -2
- package/src/core/localizable.ts +26 -0
- package/src/core/navigation.ts +43 -12
- package/src/core/page-modes.ts +56 -0
- package/src/core/project-graph.ts +111 -5
- package/src/core/redirect-patterns.ts +491 -0
- package/src/core/related.ts +131 -0
- package/src/core/schema.ts +318 -64
- package/src/core/sources/normalize.ts +2 -1
- package/src/core/sources/read.ts +8 -3
- package/src/core/types.ts +1 -1
- package/src/core/ui-packs/ar.ts +43 -0
- package/src/core/ui-packs/bg.ts +45 -0
- package/src/core/ui-packs/bn.ts +43 -0
- package/src/core/ui-packs/ca.ts +46 -0
- package/src/core/ui-packs/cs.ts +44 -0
- package/src/core/ui-packs/da.ts +45 -0
- package/src/core/ui-packs/de.ts +46 -0
- package/src/core/ui-packs/el.ts +45 -0
- package/src/core/ui-packs/es.ts +46 -0
- package/src/core/ui-packs/fa.ts +45 -0
- package/src/core/ui-packs/fi.ts +45 -0
- package/src/core/ui-packs/fr.ts +46 -0
- package/src/core/ui-packs/he.ts +43 -0
- package/src/core/ui-packs/hi.ts +44 -0
- package/src/core/ui-packs/hr.ts +45 -0
- package/src/core/ui-packs/hu.ts +44 -0
- package/src/core/ui-packs/id.ts +45 -0
- package/src/core/ui-packs/it.ts +45 -0
- package/src/core/ui-packs/ja.ts +45 -0
- package/src/core/ui-packs/ko.ts +44 -0
- package/src/core/ui-packs/nl.ts +46 -0
- package/src/core/ui-packs/no.ts +44 -0
- package/src/core/ui-packs/pl.ts +45 -0
- package/src/core/ui-packs/pt-br.ts +46 -0
- package/src/core/ui-packs/pt.ts +45 -0
- package/src/core/ui-packs/ro.ts +45 -0
- package/src/core/ui-packs/ru.ts +45 -0
- package/src/core/ui-packs/sk.ts +44 -0
- package/src/core/ui-packs/sr.ts +45 -0
- package/src/core/ui-packs/sv.ts +44 -0
- package/src/core/ui-packs/th.ts +43 -0
- package/src/core/ui-packs/tr.ts +45 -0
- package/src/core/ui-packs/uk.ts +45 -0
- package/src/core/ui-packs/vi.ts +45 -0
- package/src/core/ui-packs/zh-tw.ts +43 -0
- package/src/core/ui-packs/zh.ts +43 -0
- package/src/core/variables.ts +103 -0
- package/src/deploy/artifacts.ts +77 -8
- package/src/deploy/cloudflare-negotiation.ts +35 -5
- package/src/deploy/headers.ts +2 -1
- package/src/deploy/node-headers.ts +63 -9
- package/src/deploy/platforms/cloudflare.ts +32 -0
- package/src/deploy/platforms/netlify.ts +33 -1
- package/src/deploy/platforms/vercel.ts +37 -2
- package/src/deploy/redirects.ts +91 -24
- package/src/deploy/vercel-negotiation.ts +36 -0
- package/src/markdown/api-rail.ts +259 -0
- package/src/markdown/code-title.ts +35 -9
- package/src/markdown/features.ts +3 -2
- package/src/markdown/fence-meta.ts +2 -0
- package/src/markdown/include.ts +11 -1
- package/src/markdown/index.ts +28 -5
- package/src/markdown/math.ts +4 -4
- package/src/markdown/variables.ts +56 -0
- package/src/markdown/views.ts +82 -0
- package/src/narration/build.ts +412 -0
- package/src/narration/player.ts +249 -0
- package/src/narration/provider.ts +29 -0
- package/src/narration/script.ts +592 -0
- package/src/og/card.ts +7 -6
- package/src/openapi/model.ts +57 -24
- package/src/openapi/overlay.ts +256 -0
- package/src/openapi/parse.ts +55 -2
- package/src/openapi/references.ts +21 -5
- package/src/openapi/render-mdx.ts +5 -1
- package/src/openapi/scalar.ts +40 -6
- package/src/openapi/source.ts +1 -1
- package/src/ratelimit/cloudflare.ts +54 -0
- package/src/ratelimit/index.ts +30 -0
- package/src/ratelimit/memory.ts +45 -0
- package/src/ratelimit/runtime.ts +229 -0
- package/src/ratelimit/schema.ts +28 -0
- package/src/ratelimit/upstash.ts +30 -0
- package/src/ratelimit/wrangler.ts +70 -0
- package/src/reference/asyncapi.ts +2 -2
- package/src/reference/graphql.ts +5 -2
- package/src/reference/openapi.ts +21 -7
- package/src/reference/options.ts +51 -7
- package/src/reference/scalar.ts +33 -8
- package/src/registry/eject.ts +20 -4
- package/src/registry/registry.ts +7 -0
- package/src/search/documents.ts +26 -2
- package/src/search/orama-index.ts +28 -3
- package/src/search/pagefind-ranking.ts +39 -0
- package/src/search/sync/algolia.ts +15 -4
- package/src/search/sync/orama-cloud.ts +3 -0
- package/src/search/sync/typesense.ts +5 -0
- package/src/seo/metatags.ts +67 -0
- package/src/theme/chrome-icons.ts +1 -0
- package/src/theme/color.ts +500 -0
- package/src/theme/entry.ts +56 -3
- package/src/theme/palette.ts +121 -45
- package/src/translate/prompts.ts +1 -0
- package/src/translate/validate.ts +8 -2
- package/src/upgrade/upgrade.ts +4 -171
- package/dist/cli/chunk-364znk6q.js +0 -75
- package/dist/cli/chunk-364znk6q.js.map +0 -11
- package/dist/cli/chunk-5m5nmvyq.js.map +0 -13
- package/dist/cli/chunk-9t7a85s3.js.map +0 -10
- package/dist/cli/chunk-c8chx29p.js.map +0 -36
- package/dist/cli/chunk-pehfxfta.js.map +0 -10
- package/dist/cli/chunk-pv29h0wf.js.map +0 -190
- package/dist/cli/chunk-q58y5e6a.js.map +0 -10
- package/dist/cli/chunk-wrr3j9w9.js.map +0 -35
- package/dist/cli/chunk-yfyb25rh.js.map +0 -14
- package/docs/content/sources.mdx +0 -334
- /package/dist/cli/{chunk-2eytanqx.js.map → chunk-1n228xqb.js.map} +0 -0
- /package/dist/cli/{chunk-35d4wj9f.js.map → chunk-2n4ps1b3.js.map} +0 -0
- /package/dist/cli/{chunk-e04dxsz1.js.map → chunk-4ejxbzee.js.map} +0 -0
- /package/dist/cli/{chunk-tzmab476.js.map → chunk-5ffywsmb.js.map} +0 -0
- /package/dist/cli/{chunk-2z928egk.js.map → chunk-5wj2qwn2.js.map} +0 -0
- /package/dist/cli/{chunk-b07cmahc.js.map → chunk-amrb6hgh.js.map} +0 -0
- /package/dist/cli/{chunk-akbpwfxc.js.map → chunk-cmtcnh9f.js.map} +0 -0
- /package/dist/cli/{chunk-crgn1q09.js.map → chunk-e7vvk5ya.js.map} +0 -0
- /package/dist/cli/{chunk-tkacnehg.js.map → chunk-gp84za0h.js.map} +0 -0
- /package/dist/cli/{chunk-r9rcc4w7.js.map → chunk-p804f3pe.js.map} +0 -0
- /package/dist/cli/{chunk-8ktnccpt.js.map → chunk-ta27feev.js.map} +0 -0
- /package/dist/cli/{chunk-a58773jm.js.map → chunk-y4ykbark.js.map} +0 -0
- /package/dist/cli/{chunk-p3v96n38.js.map → chunk-y5h4r2y0.js.map} +0 -0
- /package/dist/cli/{chunk-acanzt5p.js.map → chunk-yj17vs39.js.map} +0 -0
package/src/core/ui-packs/zh.ts
CHANGED
|
@@ -5,6 +5,7 @@ export const zh: UIStringsOverride = {
|
|
|
5
5
|
actions: {
|
|
6
6
|
addToCursor: "添加到 Cursor",
|
|
7
7
|
addToVscode: "添加到 VS Code",
|
|
8
|
+
askAboutCode: "询问这段代码",
|
|
8
9
|
connectMcp: "连接到 MCP",
|
|
9
10
|
copied: "已复制!",
|
|
10
11
|
copyClaudeCode: "复制 Claude Code 命令",
|
|
@@ -30,11 +31,18 @@ export const zh: UIStringsOverride = {
|
|
|
30
31
|
copy: "复制对话",
|
|
31
32
|
empty: "就文档提出问题。",
|
|
32
33
|
error: "抱歉,出了点问题。",
|
|
34
|
+
explainCode: "解释这段代码。",
|
|
33
35
|
label: "提出问题",
|
|
36
|
+
open: "打开助手",
|
|
34
37
|
placeholder: "提出问题…",
|
|
38
|
+
rateLimited: "你提的问题太多了,请几分钟后再试。",
|
|
39
|
+
removeCode: "移除代码",
|
|
35
40
|
send: "发送",
|
|
41
|
+
support: "联系支持团队",
|
|
42
|
+
supportSubject: "来自文档的问题",
|
|
36
43
|
tip: "提示:可通过以下快捷键打开和关闭聊天",
|
|
37
44
|
title: "助手",
|
|
45
|
+
verifyFailed: "无法确认你是真人,请重试。",
|
|
38
46
|
you: "你",
|
|
39
47
|
},
|
|
40
48
|
banner: { dismiss: "关闭公告" },
|
|
@@ -42,6 +50,14 @@ export const zh: UIStringsOverride = {
|
|
|
42
50
|
description: "产品更新与版本说明。",
|
|
43
51
|
title: "更新日志",
|
|
44
52
|
},
|
|
53
|
+
consent: {
|
|
54
|
+
accept: "接受",
|
|
55
|
+
decline: "拒绝",
|
|
56
|
+
label: "Cookie 同意",
|
|
57
|
+
message: "我们希望使用 Cookie 来了解本文档的使用情况。",
|
|
58
|
+
policy: "隐私政策",
|
|
59
|
+
settings: "Cookie 设置",
|
|
60
|
+
},
|
|
45
61
|
content: {
|
|
46
62
|
code: "代码",
|
|
47
63
|
copyColor: "复制颜色 {name} {value}",
|
|
@@ -52,6 +68,8 @@ export const zh: UIStringsOverride = {
|
|
|
52
68
|
preview: "预览",
|
|
53
69
|
prop: "属性",
|
|
54
70
|
selectTab: "选择标签页",
|
|
71
|
+
selectView: "选择视图",
|
|
72
|
+
showLess: "显示更少",
|
|
55
73
|
showMore: "显示更多",
|
|
56
74
|
stars: "星标",
|
|
57
75
|
tab: "标签页 {n}",
|
|
@@ -59,12 +77,36 @@ export const zh: UIStringsOverride = {
|
|
|
59
77
|
update: "更新",
|
|
60
78
|
},
|
|
61
79
|
feedback: {
|
|
80
|
+
comment: "告诉我们更多(选填)",
|
|
62
81
|
no: "没有帮助",
|
|
63
82
|
question: "这个页面有帮助吗?",
|
|
83
|
+
send: "发送",
|
|
64
84
|
thanks: "感谢你的反馈!",
|
|
65
85
|
yes: "有帮助",
|
|
66
86
|
},
|
|
67
87
|
languageSwitcher: { label: "语言", untranslated: "未翻译" },
|
|
88
|
+
narration: {
|
|
89
|
+
cueDanger: "危险。",
|
|
90
|
+
cueInfo: "信息。",
|
|
91
|
+
cueNote: "备注。",
|
|
92
|
+
cueSection: "可展开部分。",
|
|
93
|
+
cueStep: "第 {n} 步。",
|
|
94
|
+
cueSuccess: "成功。",
|
|
95
|
+
cueTab: "{title} 标签页。",
|
|
96
|
+
cueTip: "提示。",
|
|
97
|
+
cueWarning: "警告。",
|
|
98
|
+
error: "无法加载朗读音频。",
|
|
99
|
+
follow: "跟随朗读",
|
|
100
|
+
label: "收听此页面",
|
|
101
|
+
minutes: "{n} 分钟",
|
|
102
|
+
next: "下一句",
|
|
103
|
+
pause: "暂停",
|
|
104
|
+
play: "播放",
|
|
105
|
+
previous: "上一句",
|
|
106
|
+
progress: "朗读进度",
|
|
107
|
+
speed: "播放速度",
|
|
108
|
+
stop: "停止收听",
|
|
109
|
+
},
|
|
68
110
|
nav: {
|
|
69
111
|
back: "返回",
|
|
70
112
|
breadcrumb: "面包屑导航",
|
|
@@ -89,6 +131,7 @@ export const zh: UIStringsOverride = {
|
|
|
89
131
|
next: "下一页",
|
|
90
132
|
pagination: "分页",
|
|
91
133
|
previous: "上一页",
|
|
134
|
+
related: "相关页面",
|
|
92
135
|
skipToContent: "跳到内容",
|
|
93
136
|
},
|
|
94
137
|
search: {
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { nextFenceState } from "./code-fences.ts";
|
|
2
|
+
import type { FenceState } from "./code-fences.ts";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Content variables: `variables: { version: "2.1.0" }` in `blume.config.ts`
|
|
6
|
+
* makes `{{version}}` in a page read `2.1.0` wherever it appears in the body,
|
|
7
|
+
* code and links included. The syntax is Mintlify's, so migrated pages keep
|
|
8
|
+
* working.
|
|
9
|
+
*
|
|
10
|
+
* Substitution is textual, before any parsing, so a page's HTML, its search
|
|
11
|
+
* entry, its `.md` mirror, and `llms-full.txt` all agree: the MDX pipeline
|
|
12
|
+
* substitutes in a Vite transform ahead of the compiler (an MDX parser would
|
|
13
|
+
* read `{{version}}` as a JavaScript expression), the Markdown pipeline in a
|
|
14
|
+
* plugin over the parsed text, includes as their files are read, and the raw
|
|
15
|
+
* readers through {@link substituteVariables}. Only defined names are
|
|
16
|
+
* replaced; an undefined one in prose is a build error
|
|
17
|
+
* (`BLUME_UNDEFINED_VARIABLE`), and inside code it is left alone, since
|
|
18
|
+
* templating examples (`{{name}}` in a Handlebars snippet) are common there.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/** The variables a site defines, name to value. */
|
|
22
|
+
export type ContentVariables = Readonly<Record<string, string>>;
|
|
23
|
+
|
|
24
|
+
/** A variable name: letters, digits, `_`, and `-` (Mintlify's `api-url`). */
|
|
25
|
+
export const VARIABLE_NAME = /^[\w-]+$/u;
|
|
26
|
+
|
|
27
|
+
/** A `{{name}}` reference, spaces inside the braces allowed. */
|
|
28
|
+
const REFERENCE = /\{\{\s*(?<name>[\w-]+)\s*\}\}/gu;
|
|
29
|
+
|
|
30
|
+
/** Whether a site defines any variables, so the rest can be skipped. */
|
|
31
|
+
export const hasVariables = (
|
|
32
|
+
variables?: ContentVariables
|
|
33
|
+
): variables is ContentVariables =>
|
|
34
|
+
variables !== undefined && Object.keys(variables).length > 0;
|
|
35
|
+
|
|
36
|
+
/** `text` with every reference to a defined variable replaced by its value. */
|
|
37
|
+
export const substituteVariables = (
|
|
38
|
+
text: string,
|
|
39
|
+
variables?: ContentVariables
|
|
40
|
+
): string => {
|
|
41
|
+
if (!(hasVariables(variables) && text.includes("{{"))) {
|
|
42
|
+
return text;
|
|
43
|
+
}
|
|
44
|
+
return text.replaceAll(REFERENCE, (match, name: string) =>
|
|
45
|
+
Object.hasOwn(variables, name) ? (variables[name] ?? match) : match
|
|
46
|
+
);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// A leading front matter block: variables apply to the body, not to it.
|
|
50
|
+
const FRONT_MATTER = /^---\r?\n[\s\S]*?\r?\n---(?:\r?\n|$)/u;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* {@link substituteVariables} over a page's raw source, leaving its front
|
|
54
|
+
* matter as written: the rendered page reads its title and description from
|
|
55
|
+
* the unsubstituted front matter, so the other surfaces must too.
|
|
56
|
+
*/
|
|
57
|
+
export const substituteVariablesInBody = (
|
|
58
|
+
source: string,
|
|
59
|
+
variables?: ContentVariables
|
|
60
|
+
): string => {
|
|
61
|
+
const head = FRONT_MATTER.exec(source)?.[0] ?? "";
|
|
62
|
+
return head + substituteVariables(source.slice(head.length), variables);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/** An undefined variable referenced in prose, with its 1-based line. */
|
|
66
|
+
export interface UndefinedVariable {
|
|
67
|
+
line: number;
|
|
68
|
+
name: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// A backtick code span: the same run of backticks opens and closes it.
|
|
72
|
+
const CODE_SPAN = /(?<ticks>`+)[^`][\s\S]*?\k<ticks>/gu;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The undefined variables `text` references outside code: fenced blocks and
|
|
76
|
+
* inline code spans are skipped, so a templating example never fails a
|
|
77
|
+
* build. Empty when the site defines no variables at all, so a page that
|
|
78
|
+
* shows `{{…}}` literally keeps working until the site opts in.
|
|
79
|
+
*/
|
|
80
|
+
export const undefinedVariables = (
|
|
81
|
+
text: string,
|
|
82
|
+
variables?: ContentVariables
|
|
83
|
+
): UndefinedVariable[] => {
|
|
84
|
+
if (!(hasVariables(variables) && text.includes("{{"))) {
|
|
85
|
+
return [];
|
|
86
|
+
}
|
|
87
|
+
const found: UndefinedVariable[] = [];
|
|
88
|
+
let fence: FenceState = null;
|
|
89
|
+
for (const [index, line] of text.split("\n").entries()) {
|
|
90
|
+
const wasInFence = fence !== null;
|
|
91
|
+
fence = nextFenceState(line, fence);
|
|
92
|
+
if (wasInFence || fence !== null) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
for (const match of line.replaceAll(CODE_SPAN, "").matchAll(REFERENCE)) {
|
|
96
|
+
const name = match.groups?.name ?? "";
|
|
97
|
+
if (!Object.hasOwn(variables, name)) {
|
|
98
|
+
found.push({ line: index + 1, name });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return found;
|
|
103
|
+
};
|
package/src/deploy/artifacts.ts
CHANGED
|
@@ -14,6 +14,11 @@ import { API_CATALOG_PATH, buildApiCatalog } from "../ai/api-catalog.ts";
|
|
|
14
14
|
import { buildHomeLinkHeader } from "../ai/link-headers.ts";
|
|
15
15
|
import { buildLlmsFiles } from "../ai/llms.ts";
|
|
16
16
|
import { markdownRoutePaths } from "../ai/markdown.ts";
|
|
17
|
+
import {
|
|
18
|
+
buildSiteSkill,
|
|
19
|
+
SKILL_MD_PATH,
|
|
20
|
+
siteSkillName,
|
|
21
|
+
} from "../ai/site-skill.ts";
|
|
17
22
|
import {
|
|
18
23
|
AGENT_SKILLS_DIR,
|
|
19
24
|
AGENT_SKILLS_INDEX_PATH,
|
|
@@ -29,6 +34,7 @@ import { normalizeBasePath } from "../core/base-path.ts";
|
|
|
29
34
|
import { discoverPagesSync } from "../core/custom-pages.ts";
|
|
30
35
|
import type { BlumeProject } from "../core/project-graph.ts";
|
|
31
36
|
import type { ResolvedConfig } from "../core/schema.ts";
|
|
37
|
+
import { buildNarration } from "../narration/build.ts";
|
|
32
38
|
import { buildSearchIndex } from "../search/build.ts";
|
|
33
39
|
import { syncSearchProvider } from "../search/sync/index.ts";
|
|
34
40
|
import { readsHeaderFiles } from "./adapter-output.ts";
|
|
@@ -212,7 +218,8 @@ export const emitHeaderFiles = async (
|
|
|
212
218
|
|
|
213
219
|
/**
|
|
214
220
|
* Collect the Agent Skills `agents.skills` publishes, once per build, so both the
|
|
215
|
-
* skills surface and llms.txt (which lists them) read the same set
|
|
221
|
+
* skills surface and llms.txt (which lists them) read the same set (the
|
|
222
|
+
* generated site skill joins them in {@link publishedSkills}). Empty
|
|
216
223
|
* when the feature is off, the directory is missing, nothing in it is
|
|
217
224
|
* publishable (each with a warning), or a user-shipped
|
|
218
225
|
* `public/.well-known/agent-skills/index.json` already owns the surface.
|
|
@@ -248,6 +255,57 @@ const collectConfiguredSkills = async (
|
|
|
248
255
|
return skills;
|
|
249
256
|
};
|
|
250
257
|
|
|
258
|
+
/**
|
|
259
|
+
* Every skill the build publishes: the `agents.skills` ones, plus the
|
|
260
|
+
* generated site skill unless one of those shares its name (the hand-written
|
|
261
|
+
* one wins) or a user-shipped index owns the surface.
|
|
262
|
+
*/
|
|
263
|
+
const publishedSkills = async (
|
|
264
|
+
project: BlumeProject,
|
|
265
|
+
distDir: string,
|
|
266
|
+
surface: { mcp: boolean; userIndex: boolean },
|
|
267
|
+
logger: ArtifactLogger
|
|
268
|
+
): Promise<SkillArtifact[]> => {
|
|
269
|
+
const configured = await collectConfiguredSkills(project, distDir, logger);
|
|
270
|
+
// The skill lists the MCP server only when the build serves it.
|
|
271
|
+
const site = surface.userIndex
|
|
272
|
+
? null
|
|
273
|
+
: buildSiteSkill({
|
|
274
|
+
...project,
|
|
275
|
+
config: advertisedConfig(project.config, {
|
|
276
|
+
mcp: surface.mcp,
|
|
277
|
+
skills: true,
|
|
278
|
+
}),
|
|
279
|
+
});
|
|
280
|
+
return site && !configured.some((skill) => skill.name === site.name)
|
|
281
|
+
? [site, ...configured]
|
|
282
|
+
: configured;
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Serve the site skill at `/skill.md` too, the path agents try first: the
|
|
287
|
+
* generated one, or a same-named hand-written skill that is a lone
|
|
288
|
+
* `SKILL.md` (an archive's relative references need its other files). A
|
|
289
|
+
* `public/skill.md` wins.
|
|
290
|
+
*/
|
|
291
|
+
const emitSkillMd = async (
|
|
292
|
+
project: BlumeProject,
|
|
293
|
+
distDir: string,
|
|
294
|
+
skills: readonly SkillArtifact[],
|
|
295
|
+
logger: ArtifactLogger
|
|
296
|
+
): Promise<void> => {
|
|
297
|
+
const target = join(distDir, SKILL_MD_PATH.slice(1));
|
|
298
|
+
const name = siteSkillName(project);
|
|
299
|
+
const skill = skills.find(
|
|
300
|
+
(candidate) => candidate.name === name && candidate.type === "skill-md"
|
|
301
|
+
);
|
|
302
|
+
if (!(project.config.agents.skillMd && skill) || existsSync(target)) {
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
await writeFile(target, skill.content);
|
|
306
|
+
logger.info(`Generated ${SKILL_MD_PATH.slice(1)} (the "${name}" skill)`);
|
|
307
|
+
};
|
|
308
|
+
|
|
251
309
|
/**
|
|
252
310
|
* Publish the collected Agent Skills: copy each skill artifact under
|
|
253
311
|
* `.well-known/agent-skills/` and emit the discovery index. A user-shipped
|
|
@@ -382,6 +440,10 @@ export const publishBuildArtifacts = async (
|
|
|
382
440
|
logger.info(`Indexed ${indexed} page(s) for search`);
|
|
383
441
|
}
|
|
384
442
|
|
|
443
|
+
// Generated narration reads the built pages, so it runs once they exist.
|
|
444
|
+
// Nothing happens without `narration.provider`.
|
|
445
|
+
await buildNarration(project, distDir, logger);
|
|
446
|
+
|
|
385
447
|
// Upload the index to a hosted provider (Algolia, Orama Cloud, Typesense).
|
|
386
448
|
// Skipped with a warning when its admin key isn't configured.
|
|
387
449
|
await syncSearchProvider(project, {
|
|
@@ -394,18 +456,24 @@ export const publishBuildArtifacts = async (
|
|
|
394
456
|
const userSkillsIndex = existsSync(
|
|
395
457
|
join(distDir, AGENT_SKILLS_INDEX_PATH.slice(1))
|
|
396
458
|
);
|
|
397
|
-
const
|
|
459
|
+
const mcp = servesMcp(
|
|
460
|
+
project,
|
|
461
|
+
project.context.pagesRoot
|
|
462
|
+
? discoverPagesSync(project.context.pagesRoot)
|
|
463
|
+
: []
|
|
464
|
+
);
|
|
465
|
+
const skills = await publishedSkills(
|
|
466
|
+
project,
|
|
467
|
+
distDir,
|
|
468
|
+
{ mcp, userIndex: userSkillsIndex },
|
|
469
|
+
logger
|
|
470
|
+
);
|
|
398
471
|
// The discovery documents advertise what this build serves: no MCP server
|
|
399
472
|
// when a page took its route, no skills index when nothing was published.
|
|
400
473
|
const advertised: BlumeProject = {
|
|
401
474
|
...project,
|
|
402
475
|
config: advertisedConfig(project.config, {
|
|
403
|
-
mcp
|
|
404
|
-
project,
|
|
405
|
-
project.context.pagesRoot
|
|
406
|
-
? discoverPagesSync(project.context.pagesRoot)
|
|
407
|
-
: []
|
|
408
|
-
),
|
|
476
|
+
mcp,
|
|
409
477
|
skills: skills.length > 0 || userSkillsIndex,
|
|
410
478
|
}),
|
|
411
479
|
};
|
|
@@ -444,6 +512,7 @@ export const publishBuildArtifacts = async (
|
|
|
444
512
|
|
|
445
513
|
await emitWellKnownFiles(advertised.config, distDir, skills, logger);
|
|
446
514
|
await emitAgentSkills(project, distDir, skills, logger);
|
|
515
|
+
await emitSkillMd(project, distDir, skills, logger);
|
|
447
516
|
|
|
448
517
|
await emitRedirectFiles(advertised, distDir, logger);
|
|
449
518
|
await emitHeaderFiles(advertised, distDir, logger);
|
|
@@ -85,6 +85,7 @@ import {
|
|
|
85
85
|
OPENAPI_PATH,
|
|
86
86
|
} from "../ai/api/paths.ts";
|
|
87
87
|
import { normalizeBasePath, normalizePath } from "../core/base-path.ts";
|
|
88
|
+
import { compileRedirects, isPatternPath } from "../core/redirect-patterns.ts";
|
|
88
89
|
import { CONTENT_ASSETS_ROOT, SVG_ASSET_HEADERS } from "./headers.ts";
|
|
89
90
|
import type { NotFoundVariants } from "./vercel-negotiation.ts";
|
|
90
91
|
|
|
@@ -289,12 +290,18 @@ export const buildNegotiationWorker = (
|
|
|
289
290
|
// header, which cannot carry non-ASCII.
|
|
290
291
|
const redirects = JSON.stringify(
|
|
291
292
|
Object.fromEntries(
|
|
292
|
-
(options.redirects ?? [])
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
293
|
+
(options.redirects ?? [])
|
|
294
|
+
.filter((redirect) => !isPatternPath(redirect.from))
|
|
295
|
+
.map((redirect) => [
|
|
296
|
+
normalizePath(redirect.from),
|
|
297
|
+
[encodeURI(redirect.to), redirect.status],
|
|
298
|
+
])
|
|
296
299
|
)
|
|
297
300
|
);
|
|
301
|
+
// Patterns (`/beta/:slug*`) are tried in order after the exact paths.
|
|
302
|
+
const patternRedirects = JSON.stringify(
|
|
303
|
+
compileRedirects(options.redirects ?? [])
|
|
304
|
+
);
|
|
298
305
|
return `// Generated by Blume. Do not edit; this file is recreated on each build.
|
|
299
306
|
//
|
|
300
307
|
// Request-time \`Accept: text/markdown\` negotiation for a Cloudflare server
|
|
@@ -319,6 +326,9 @@ const ASSETS_BINDING = ${binding};
|
|
|
319
326
|
const HOME_LINK_HEADER = ${homeLinkHeader};
|
|
320
327
|
const HOME_TOKENS = ${homeTokens};
|
|
321
328
|
const REDIRECTS = ${redirects};
|
|
329
|
+
const PATTERN_REDIRECTS = ${patternRedirects}.map(
|
|
330
|
+
([source, location, status]) => [new RegExp(source, "u"), location, status]
|
|
331
|
+
);
|
|
322
332
|
const NOT_FOUND = ${notFound};
|
|
323
333
|
const SVG_ASSET_HEADERS = ${JSON.stringify(SVG_ASSET_HEADERS)};
|
|
324
334
|
|
|
@@ -335,11 +345,31 @@ const isSvgAsset = (pathname) => {
|
|
|
335
345
|
// reads — a worker-first route never reaches it. Answering from this baked-in
|
|
336
346
|
// copy keeps the exact configured status; delegating instead would let Astro's
|
|
337
347
|
// SSR handler default a GET to a permanent 301.
|
|
348
|
+
// An exact path is looked up first, then each pattern is tried in order, its
|
|
349
|
+
// Location template filled with the captures, encoded so a capture can't grow
|
|
350
|
+
// a query or fragment.
|
|
338
351
|
const redirectFor = (pathname) => {
|
|
339
352
|
const trimmed =
|
|
340
353
|
pathname !== "/" && pathname.endsWith("/") ? pathname.slice(0, -1) : pathname;
|
|
341
354
|
const path = safeDecode(trimmed);
|
|
342
|
-
|
|
355
|
+
if (Object.hasOwn(REDIRECTS, path)) {
|
|
356
|
+
return REDIRECTS[path];
|
|
357
|
+
}
|
|
358
|
+
const decoded = safeDecode(pathname);
|
|
359
|
+
for (const [pattern, location, status] of PATTERN_REDIRECTS) {
|
|
360
|
+
const match = pattern.exec(decoded);
|
|
361
|
+
if (match) {
|
|
362
|
+
return [
|
|
363
|
+
location.replace(/\\$(\\d+)/gu, (_reference, index) =>
|
|
364
|
+
encodeURI(match[Number(index)] ?? "")
|
|
365
|
+
.replaceAll("?", "%3F")
|
|
366
|
+
.replaceAll("#", "%23")
|
|
367
|
+
),
|
|
368
|
+
status,
|
|
369
|
+
];
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return null;
|
|
343
373
|
};
|
|
344
374
|
|
|
345
375
|
// The request path with the deployment base removed: the whole path on a
|
package/src/deploy/headers.ts
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
API_CATALOG_TYPE,
|
|
5
5
|
hasApiCatalog,
|
|
6
6
|
} from "../ai/api-catalog.ts";
|
|
7
|
+
import { servesSkillsIndex } from "../ai/skills.ts";
|
|
7
8
|
import {
|
|
8
9
|
SIGNATURES_DIRECTORY_PATH,
|
|
9
10
|
SIGNATURES_DIRECTORY_TYPE,
|
|
@@ -128,7 +129,7 @@ export const headerRules = (
|
|
|
128
129
|
}
|
|
129
130
|
// Published skills live at the deployment base, and the RFC wants their
|
|
130
131
|
// archives served as application/gzip explicitly.
|
|
131
|
-
if (config
|
|
132
|
+
if (servesSkillsIndex(config)) {
|
|
132
133
|
rules.push(
|
|
133
134
|
{
|
|
134
135
|
name: "Content-Type",
|
|
@@ -15,6 +15,8 @@ import {
|
|
|
15
15
|
} from "../ai/web-bot-auth.ts";
|
|
16
16
|
import { normalizeBasePath, normalizePath } from "../core/base-path.ts";
|
|
17
17
|
import type { BlumeProject } from "../core/project-graph.ts";
|
|
18
|
+
import { compileRedirects, isPatternPath } from "../core/redirect-patterns.ts";
|
|
19
|
+
import type { CompiledRedirect } from "../core/redirect-patterns.ts";
|
|
18
20
|
import type { ResolvedConfig } from "../core/schema.ts";
|
|
19
21
|
import { SVG_ASSET_HEADERS, svgAssetPath } from "./headers.ts";
|
|
20
22
|
import { distDir } from "./platforms/paths.ts";
|
|
@@ -106,11 +108,12 @@ const patternRules = (
|
|
|
106
108
|
export type NodeRedirects = Record<string, [string, number]>;
|
|
107
109
|
|
|
108
110
|
/**
|
|
109
|
-
* The configured redirects, based the way the host matches them (see
|
|
111
|
+
* The configured exact redirects, based the way the host matches them (see
|
|
110
112
|
* `platformRedirects`) and keyed for the wrapper's lookup, the `Location`
|
|
111
113
|
* percent-encoded as Astro sends it. A redirect at a content route's own path
|
|
112
114
|
* is left out: the prerendered page owns that URL, and the static handler
|
|
113
|
-
* serves it before Astro's redirect ever could.
|
|
115
|
+
* serves it before Astro's redirect ever could. Patterns are compiled apart
|
|
116
|
+
* ({@link nodePatternRedirects}).
|
|
114
117
|
*/
|
|
115
118
|
export const nodeRedirects = (project: BlumeProject): NodeRedirects => {
|
|
116
119
|
const { config } = project;
|
|
@@ -125,7 +128,13 @@ export const nodeRedirects = (project: BlumeProject): NodeRedirects => {
|
|
|
125
128
|
);
|
|
126
129
|
return Object.fromEntries(
|
|
127
130
|
platformRedirects(project)
|
|
128
|
-
.filter(
|
|
131
|
+
.filter(
|
|
132
|
+
(redirect) =>
|
|
133
|
+
!(
|
|
134
|
+
isPatternPath(redirect.from) ||
|
|
135
|
+
pages.has(normalizePath(redirect.from))
|
|
136
|
+
)
|
|
137
|
+
)
|
|
129
138
|
.map((redirect) => [
|
|
130
139
|
normalizePath(redirect.from),
|
|
131
140
|
[encodeURI(redirect.to), redirect.status],
|
|
@@ -133,12 +142,27 @@ export const nodeRedirects = (project: BlumeProject): NodeRedirects => {
|
|
|
133
142
|
);
|
|
134
143
|
};
|
|
135
144
|
|
|
145
|
+
/**
|
|
146
|
+
* The configured pattern redirects (`/beta/:slug*`), based the way the host
|
|
147
|
+
* matches them and compiled for the wrapper, which tries them in order after
|
|
148
|
+
* the exact ones. None can match a page: the scan rejects a pattern that
|
|
149
|
+
* does (`BLUME_REDIRECT_MATCHES_PAGE`).
|
|
150
|
+
*/
|
|
151
|
+
export const nodePatternRedirects = (
|
|
152
|
+
project: BlumeProject
|
|
153
|
+
): CompiledRedirect[] =>
|
|
154
|
+
project.config.redirects.length === 0
|
|
155
|
+
? []
|
|
156
|
+
: compileRedirects(platformRedirects(project));
|
|
157
|
+
|
|
136
158
|
/** What the wrapper needs beyond the header rules. */
|
|
137
159
|
export interface NodeEntryOptions {
|
|
138
160
|
/** `deployment.base`, normalized (`""` at the root). */
|
|
139
161
|
base?: string;
|
|
140
162
|
/** The configured redirects, from {@link nodeRedirects}. */
|
|
141
163
|
redirects?: NodeRedirects;
|
|
164
|
+
/** The configured pattern redirects, from {@link nodePatternRedirects}. */
|
|
165
|
+
patternRedirects?: CompiledRedirect[];
|
|
142
166
|
}
|
|
143
167
|
|
|
144
168
|
/**
|
|
@@ -177,6 +201,9 @@ const BASE = ${JSON.stringify(options.base ?? "")};
|
|
|
177
201
|
const RULES = new Map(${JSON.stringify(exactRules(rules))});
|
|
178
202
|
const PATTERNS = ${JSON.stringify(patternRules(rules))};
|
|
179
203
|
const REDIRECTS = ${JSON.stringify(options.redirects ?? {})};
|
|
204
|
+
const PATTERN_REDIRECTS = ${JSON.stringify(options.patternRedirects ?? [])}.map(
|
|
205
|
+
([source, location, status]) => [new RegExp(source, "u"), location, status]
|
|
206
|
+
);
|
|
180
207
|
const safeDecode = (path) => {
|
|
181
208
|
try {
|
|
182
209
|
return decodeURIComponent(path);
|
|
@@ -210,16 +237,37 @@ const applyHeaders = (req, res) => {
|
|
|
210
237
|
}
|
|
211
238
|
}
|
|
212
239
|
};
|
|
240
|
+
// A pattern redirect's Location: its template, each $n filled with that
|
|
241
|
+
// capture, encoded so it can't grow a query or fragment.
|
|
242
|
+
const fillLocation = (location, match) =>
|
|
243
|
+
location.replace(/\\$(\\d+)/gu, (_reference, index) =>
|
|
244
|
+
encodeURI(match[Number(index)] ?? "")
|
|
245
|
+
.replaceAll("?", "%3F")
|
|
246
|
+
.replaceAll("#", "%23")
|
|
247
|
+
);
|
|
248
|
+
// The configured redirect for a decoded path: an exact one, else the first
|
|
249
|
+
// pattern that matches it.
|
|
250
|
+
const redirectFor = (path) => {
|
|
251
|
+
const trimmed = path !== "/" && path.endsWith("/") ? path.slice(0, -1) : path;
|
|
252
|
+
if (Object.hasOwn(REDIRECTS, trimmed)) {
|
|
253
|
+
return REDIRECTS[trimmed];
|
|
254
|
+
}
|
|
255
|
+
for (const [pattern, location, status] of PATTERN_REDIRECTS) {
|
|
256
|
+
const match = pattern.exec(path);
|
|
257
|
+
if (match) {
|
|
258
|
+
return [fillLocation(location, match), status];
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return null;
|
|
262
|
+
};
|
|
213
263
|
// Answer a configured redirect with its exact status; Astro's handler would
|
|
214
264
|
// send a 301 (a 308 for other methods) whatever the configured one.
|
|
215
265
|
const answerRedirect = (req, res) => {
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
const key = safeDecode(trimmed);
|
|
219
|
-
if (!Object.hasOwn(REDIRECTS, key)) {
|
|
266
|
+
const redirect = redirectFor(safeDecode(urlPath(req)));
|
|
267
|
+
if (redirect === null) {
|
|
220
268
|
return false;
|
|
221
269
|
}
|
|
222
|
-
const [location, status] =
|
|
270
|
+
const [location, status] = redirect;
|
|
223
271
|
res.writeHead(status, { location });
|
|
224
272
|
res.end();
|
|
225
273
|
return true;
|
|
@@ -291,7 +339,12 @@ export const wrapNodeEntry = async (
|
|
|
291
339
|
: []),
|
|
292
340
|
];
|
|
293
341
|
const redirects = nodeRedirects(project);
|
|
294
|
-
|
|
342
|
+
const patternRedirects = nodePatternRedirects(project);
|
|
343
|
+
if (
|
|
344
|
+
rules.length === 0 &&
|
|
345
|
+
Object.keys(redirects).length === 0 &&
|
|
346
|
+
patternRedirects.length === 0
|
|
347
|
+
) {
|
|
295
348
|
return;
|
|
296
349
|
}
|
|
297
350
|
const serverDir = join(distDir(project.context), "server");
|
|
@@ -311,6 +364,7 @@ export const wrapNodeEntry = async (
|
|
|
311
364
|
entry,
|
|
312
365
|
nodeEntryWrapper(rules, {
|
|
313
366
|
base: normalizeBasePath(project.config.deployment.options.base),
|
|
367
|
+
patternRedirects,
|
|
314
368
|
redirects,
|
|
315
369
|
}),
|
|
316
370
|
"utf-8"
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
import { normalizeBasePath } from "../../core/base-path.ts";
|
|
15
15
|
import type { BlumeProject } from "../../core/project-graph.ts";
|
|
16
16
|
import type { ProjectContext } from "../../core/types.ts";
|
|
17
|
+
import { withRateLimitBinding } from "../../ratelimit/wrangler.ts";
|
|
17
18
|
import { CLOUDFLARE_ADAPTER_PACKAGE } from "../adapters/cloudflare.ts";
|
|
18
19
|
import {
|
|
19
20
|
injectWorkerNegotiation,
|
|
@@ -273,6 +274,36 @@ export const nameCloudflareWorker = async (
|
|
|
273
274
|
);
|
|
274
275
|
};
|
|
275
276
|
|
|
277
|
+
/**
|
|
278
|
+
* Declare the Workers rate limiting binding `rateLimit: cloudflare()` counts
|
|
279
|
+
* with in the built Worker's config, which `wrangler deploy` reads.
|
|
280
|
+
*/
|
|
281
|
+
export const bindCloudflareRateLimit = async (
|
|
282
|
+
project: BlumeProject,
|
|
283
|
+
log: BuildLog
|
|
284
|
+
): Promise<void> => {
|
|
285
|
+
const { rateLimit } = project.config;
|
|
286
|
+
if (rateLimit?.kind !== "cloudflare") {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
const wranglerPath = join(
|
|
290
|
+
distDir(project.context),
|
|
291
|
+
"server",
|
|
292
|
+
"wrangler.json"
|
|
293
|
+
);
|
|
294
|
+
const bound = existsSync(wranglerPath)
|
|
295
|
+
? withRateLimitBinding(await readFile(wranglerPath, "utf-8"), rateLimit)
|
|
296
|
+
: null;
|
|
297
|
+
if (bound === null) {
|
|
298
|
+
log.warn(
|
|
299
|
+
"Could not declare the rate limiting binding in dist/server/wrangler.json — the routes count in memory instead."
|
|
300
|
+
);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
await writeFile(wranglerPath, bound, "utf-8");
|
|
304
|
+
log.success("Declared the Workers rate limiting binding");
|
|
305
|
+
};
|
|
306
|
+
|
|
276
307
|
/**
|
|
277
308
|
* Cloudflare Workers and Pages. A server build emits the Worker into
|
|
278
309
|
* `dist/server` and serves `dist/client` through the ASSETS binding, which
|
|
@@ -298,6 +329,7 @@ export const cloudflarePlatform: DeployPlatform = {
|
|
|
298
329
|
if (!isolated) {
|
|
299
330
|
await nameCloudflareWorker(project, log);
|
|
300
331
|
await emitCloudflareNegotiation(project, log);
|
|
332
|
+
await bindCloudflareRateLimit(project, log);
|
|
301
333
|
await emitCloudflareDeployConfig(project.context);
|
|
302
334
|
}
|
|
303
335
|
return true;
|
|
@@ -9,7 +9,11 @@ import type { BlumeProject } from "../../core/project-graph.ts";
|
|
|
9
9
|
import { NETLIFY_ADAPTER_PACKAGE } from "../adapters/netlify.ts";
|
|
10
10
|
import { buildNetlifyConfigHeaders } from "../headers.ts";
|
|
11
11
|
import type { NetlifyConfigHeaders } from "../headers.ts";
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
buildNetlifyRedirects,
|
|
14
|
+
netlifyPatternRedirects,
|
|
15
|
+
platformRedirects,
|
|
16
|
+
} from "../redirects.ts";
|
|
13
17
|
import { distDir, toSiteUrl } from "./paths.ts";
|
|
14
18
|
import type { BuildLog, DeployPlatform, RedirectFile } from "./types.ts";
|
|
15
19
|
|
|
@@ -76,6 +80,33 @@ export const emitNetlifyHeaders = async (
|
|
|
76
80
|
await writeFile(configPath, JSON.stringify(frameworks), "utf-8");
|
|
77
81
|
};
|
|
78
82
|
|
|
83
|
+
/**
|
|
84
|
+
* Carry the pattern redirects (`/beta/:slug*`) into a server build, as
|
|
85
|
+
* `redirects` in the Frameworks API config: Astro's `redirects` carry only
|
|
86
|
+
* the exact ones, since it can't prerender a pattern's pages, so the adapter
|
|
87
|
+
* never writes these. Netlify appends them after any rules of the user's own.
|
|
88
|
+
*/
|
|
89
|
+
export const emitNetlifyPatternRedirects = async (
|
|
90
|
+
project: BlumeProject
|
|
91
|
+
): Promise<void> => {
|
|
92
|
+
const ours = netlifyPatternRedirects(platformRedirects(project));
|
|
93
|
+
const configPath = join(project.context.root, NETLIFY_CONFIG_FILE);
|
|
94
|
+
if (ours.length === 0 || !existsSync(configPath)) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
// Only `redirects` is read; every other key rides through as parsed.
|
|
98
|
+
const frameworks: { redirects?: { from: string }[] } = JSON.parse(
|
|
99
|
+
await readFile(configPath, "utf-8")
|
|
100
|
+
);
|
|
101
|
+
// A second pass over the same build replaces its own entries.
|
|
102
|
+
const froms = new Set(ours.map((entry) => entry.from));
|
|
103
|
+
frameworks.redirects = [
|
|
104
|
+
...(frameworks.redirects ?? []).filter((entry) => !froms.has(entry.from)),
|
|
105
|
+
...ours,
|
|
106
|
+
];
|
|
107
|
+
await writeFile(configPath, JSON.stringify(frameworks), "utf-8");
|
|
108
|
+
};
|
|
109
|
+
|
|
79
110
|
/**
|
|
80
111
|
* Netlify. A server build runs on Netlify Functions from the Frameworks API
|
|
81
112
|
* tree the adapter writes to `.netlify/v1`, relative to the Astro root —
|
|
@@ -106,6 +137,7 @@ export const netlifyPlatform: DeployPlatform = {
|
|
|
106
137
|
// never surfaces it.
|
|
107
138
|
if (!isolated) {
|
|
108
139
|
await emitNetlifyHeaders(project, log);
|
|
140
|
+
await emitNetlifyPatternRedirects(project);
|
|
109
141
|
}
|
|
110
142
|
return true;
|
|
111
143
|
},
|