blume 2.0.2 → 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 +113 -0
- package/README.md +1 -1
- package/dist/cli/{chunk-6k4ftwze.js → chunk-0gz36akr.js} +18 -2
- package/dist/cli/{chunk-6k4ftwze.js.map → chunk-0gz36akr.js.map} +3 -3
- package/dist/cli/{chunk-zp79m0ts.js → chunk-1n228xqb.js} +2 -2
- package/dist/cli/{chunk-ddndchfr.js → chunk-2f3ndkpw.js} +57 -29
- package/dist/cli/chunk-2f3ndkpw.js.map +14 -0
- package/dist/cli/{chunk-fsmrqk8a.js → chunk-2f7n4qzt.js} +8 -2
- package/dist/cli/{chunk-fsmrqk8a.js.map → chunk-2f7n4qzt.js.map} +3 -3
- package/dist/cli/{chunk-ppzjqwx2.js → chunk-2n4ps1b3.js} +17 -18
- package/dist/cli/{chunk-ppzjqwx2.js.map → chunk-2n4ps1b3.js.map} +3 -3
- package/dist/cli/{chunk-5r8g91qn.js → chunk-43zcf4gz.js} +398 -159
- package/dist/cli/chunk-43zcf4gz.js.map +36 -0
- package/dist/cli/chunk-4ejxbzee.js +39 -0
- package/dist/cli/chunk-4ejxbzee.js.map +10 -0
- package/dist/cli/{chunk-hr8ne106.js → chunk-4h8z9vv5.js} +296 -77
- package/dist/cli/chunk-4h8z9vv5.js.map +16 -0
- package/dist/cli/{chunk-k7pj68a8.js → chunk-5cg2rzgf.js} +18 -16
- package/dist/cli/{chunk-k7pj68a8.js.map → chunk-5cg2rzgf.js.map} +2 -2
- package/dist/cli/{chunk-h2ez8dzb.js → chunk-5ffywsmb.js} +4 -4
- package/dist/cli/{chunk-g698a744.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-3yce002v.js → chunk-713mngdq.js} +15 -8
- package/dist/cli/chunk-713mngdq.js.map +14 -0
- package/dist/cli/{chunk-91ws1n6j.js → chunk-9geymqns.js} +17 -15
- package/dist/cli/{chunk-91ws1n6j.js.map → chunk-9geymqns.js.map} +2 -2
- package/dist/cli/{chunk-6dtt0zfn.js → chunk-9xr4dgee.js} +27 -18
- package/dist/cli/chunk-9xr4dgee.js.map +10 -0
- package/dist/cli/{chunk-sqw4ekg1.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-wdrt2k2v.js → chunk-cmtcnh9f.js} +90 -26
- package/dist/cli/chunk-cmtcnh9f.js.map +10 -0
- package/dist/cli/chunk-cy5bm0j0.js +104 -0
- package/dist/cli/chunk-cy5bm0j0.js.map +11 -0
- package/dist/cli/{chunk-7mbqtmgb.js → chunk-e7vvk5ya.js} +21 -12
- package/dist/cli/chunk-e7vvk5ya.js.map +10 -0
- package/dist/cli/{chunk-qkb5a8sa.js → chunk-g2z9x01v.js} +6 -98
- package/dist/cli/chunk-g2z9x01v.js.map +10 -0
- package/dist/cli/{chunk-273ygyr4.js → chunk-ga0pf4aj.js} +7 -7
- package/dist/cli/{chunk-273ygyr4.js.map → chunk-ga0pf4aj.js.map} +3 -3
- package/dist/cli/{chunk-h7k3nq3v.js → chunk-gp84za0h.js} +2 -2
- package/dist/cli/{chunk-hqp2ajnh.js → chunk-h2a48yhd.js} +195 -73
- package/dist/cli/chunk-h2a48yhd.js.map +36 -0
- package/dist/cli/{chunk-pbg5a4s3.js → chunk-jnwj5kjj.js} +71 -47
- package/dist/cli/chunk-jnwj5kjj.js.map +19 -0
- package/dist/cli/chunk-kwrg63tm.js +31 -0
- package/dist/cli/chunk-kwrg63tm.js.map +10 -0
- package/dist/cli/{chunk-8g8ytmgx.js → chunk-nd1821sk.js} +21 -19
- package/dist/cli/{chunk-8g8ytmgx.js.map → chunk-nd1821sk.js.map} +2 -2
- package/dist/cli/{chunk-w4bxdvsa.js → chunk-ntv4qrt7.js} +17 -15
- package/dist/cli/{chunk-w4bxdvsa.js.map → chunk-ntv4qrt7.js.map} +2 -2
- package/dist/cli/{chunk-bfwp9vp6.js → chunk-p804f3pe.js} +7 -7
- package/dist/cli/{chunk-4e9b9ra6.js → chunk-py62fewh.js} +6 -6
- package/dist/cli/{chunk-4e9b9ra6.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-j85scx15.js → chunk-rekqz4qg.js} +142 -46
- package/dist/cli/chunk-rekqz4qg.js.map +10 -0
- package/dist/cli/{chunk-00gs3wqs.js → chunk-ta27feev.js} +1 -1
- package/dist/cli/{chunk-v6ya5kcb.js → chunk-xkz5xbtv.js} +4031 -1132
- package/dist/cli/chunk-xkz5xbtv.js.map +213 -0
- package/dist/cli/{chunk-bbnwccaz.js → chunk-y4ykbark.js} +2 -2
- package/dist/cli/{chunk-n1yg3tj3.js → chunk-y5h4r2y0.js} +6 -6
- package/dist/cli/{chunk-n1yg3tj3.js.map → chunk-y5h4r2y0.js.map} +3 -3
- package/dist/cli/{chunk-nfcyttvj.js → chunk-yj17vs39.js} +9 -9
- package/dist/cli/{chunk-nfcyttvj.js.map → chunk-yj17vs39.js.map} +1 -1
- package/dist/cli/{chunk-7vtckvaw.js → chunk-yxmk4pec.js} +16 -14
- package/dist/cli/{chunk-7vtckvaw.js.map → chunk-yxmk4pec.js.map} +2 -2
- package/dist/cli/{chunk-gs7r695n.js → chunk-zccbjcj0.js} +22 -8
- package/dist/cli/{chunk-gs7r695n.js.map → chunk-zccbjcj0.js.map} +3 -3
- package/dist/cli/{chunk-xh43dwgw.js → chunk-zf748avv.js} +415 -225
- package/dist/cli/chunk-zf748avv.js.map +37 -0
- package/dist/cli/{chunk-d1v5rhy0.js → chunk-zhvd6adm.js} +16 -14
- package/dist/cli/{chunk-d1v5rhy0.js.map → chunk-zhvd6adm.js.map} +2 -2
- package/dist/cli/index.js +28 -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/link-headers.d.ts +8 -1
- package/dist/types/ai/openapi-components.d.ts +5 -2
- package/dist/types/ai/relative-links.d.ts +9 -7
- package/dist/types/ai/site-skill.d.ts +26 -0
- package/dist/types/ai/skills.d.ts +20 -1
- package/dist/types/ai/tar.d.ts +1 -3
- package/dist/types/analytics/index.d.ts +2 -0
- package/dist/types/analytics/one-dollar-stats.d.ts +48 -0
- package/dist/types/analytics/schema.d.ts +14 -0
- package/dist/types/astro/integration.d.ts +11 -2
- 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/base-path.d.ts +21 -7
- package/dist/types/core/config-input.d.ts +196 -27
- package/dist/types/core/data.d.ts +49 -4
- package/dist/types/core/directive-diagnostics.d.ts +12 -0
- package/dist/types/core/footer.d.ts +8 -0
- package/dist/types/core/heading-markers.d.ts +5 -7
- package/dist/types/core/i18n-ui.d.ts +86 -0
- package/dist/types/core/i18n.d.ts +2 -0
- package/dist/types/core/includes.d.ts +8 -2
- package/dist/types/core/last-modified.d.ts +10 -0
- package/dist/types/core/localizable.d.ts +7 -0
- package/dist/types/core/meta.d.ts +8 -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 +558 -56
- package/dist/types/core/sources/lower.d.ts +29 -16
- package/dist/types/core/sources/normalize.d.ts +13 -1
- package/dist/types/core/sources/read.d.ts +5 -0
- package/dist/types/core/sources/watch.d.ts +12 -5
- package/dist/types/core/standard-schema.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/artifacts.d.ts +6 -4
- package/dist/types/deploy/headers.d.ts +5 -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/types.d.ts +8 -0
- package/dist/types/deploy/platforms/vercel.d.ts +6 -0
- package/dist/types/deploy/redirects.d.ts +61 -22
- package/dist/types/deploy/vercel-negotiation.d.ts +9 -0
- package/dist/types/markdown/code-title.d.ts +17 -0
- package/dist/types/markdown/directives.d.ts +62 -0
- package/dist/types/markdown/features.d.ts +24 -2
- package/dist/types/markdown/mdast.d.ts +63 -0
- 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/asyncapi.d.ts +4 -2
- 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 +44 -6
- package/docs/03-upgrading.mdx +4 -4
- package/docs/04-migrating.mdx +1 -1
- package/docs/08-faq.mdx +1 -1
- package/docs/advanced/changelog.mdx +2 -2
- package/docs/advanced/custom-pages.mdx +3 -3
- package/docs/advanced/skills.mdx +14 -0
- package/docs/cli/audit.mdx +38 -2
- package/docs/cli/evals.mdx +3 -3
- package/docs/cli/index.mdx +1 -0
- package/docs/cli/translate.mdx +3 -3
- package/docs/cli/version.mdx +1 -1
- package/docs/configuration/analytics.mdx +22 -1
- package/docs/configuration/assistant.mdx +188 -36
- package/docs/configuration/consent.mdx +100 -0
- package/docs/configuration/customization.mdx +6 -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 +30 -2
- package/docs/configuration/theming.mdx +3 -1
- package/docs/content/components.mdx +107 -1
- package/docs/content/frontmatter.mdx +77 -1
- package/docs/content/i18n.mdx +40 -3
- package/docs/content/includes.mdx +16 -0
- package/docs/content/index.mdx +4 -2
- package/docs/content/islands.mdx +1 -1
- package/docs/content/meta.mdx +4 -1
- 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 +81 -2
- package/docs/content/variables.mdx +41 -0
- package/docs/content/versioning.mdx +1 -1
- package/docs/discoverability/agent-discovery.mdx +30 -3
- package/docs/discoverability/index.mdx +3 -2
- package/docs/discoverability/llms-txt.mdx +1 -1
- package/docs/discoverability/markdown.mdx +2 -2
- package/docs/discoverability/metadata.mdx +17 -0
- package/docs/discoverability/open-graph.mdx +5 -3
- package/docs/discoverability/rss.mdx +1 -1
- package/docs/references/api-pages.mdx +74 -0
- package/docs/references/asyncapi.mdx +2 -2
- package/docs/references/graphql.mdx +3 -3
- package/docs/references/meta.ts +1 -1
- package/docs/references/openapi.mdx +106 -5
- 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/fumadocs.md +1 -1
- package/skills/blume-migrate/references/mintlify.md +37 -19
- package/skills/blume-migrate/references/nextra.md +3 -3
- package/skills/blume-migrate/references/starlight.md +2 -1
- package/skills/blume-migrate/scripts/mintlify-codemod.mjs +67 -16
- package/skills/blume-write-skill/SKILL.md +82 -0
- package/src/ai/agent-readability.ts +4 -3
- package/src/ai/agent-surface.ts +9 -7
- package/src/ai/ai-catalog.ts +12 -5
- package/src/ai/api/handlers.ts +2 -2
- package/src/ai/api/spec.ts +2 -2
- package/src/ai/api-catalog.ts +6 -2
- 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/changelog-markdown.ts +2 -2
- 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/link-headers.ts +14 -5
- package/src/ai/llms.ts +24 -9
- package/src/ai/markdown.ts +2 -1
- package/src/ai/mcp/data.ts +6 -0
- package/src/ai/mcp/discovery.ts +2 -2
- package/src/ai/mcp/query.ts +2 -2
- package/src/ai/mcp/server.ts +2 -2
- package/src/ai/openapi-components.ts +31 -9
- package/src/ai/relative-links.ts +42 -26
- package/src/ai/serializers.ts +2 -1
- package/src/ai/site-skill.ts +274 -0
- package/src/ai/skills.ts +49 -3
- package/src/ai/tar.ts +139 -12
- package/src/ai/write-skill.ts +97 -0
- package/src/analytics/head.ts +4 -0
- package/src/analytics/index.ts +5 -0
- package/src/analytics/one-dollar-stats.ts +86 -0
- package/src/analytics/schema.ts +2 -0
- package/src/astro/generate.ts +133 -19
- package/src/astro/include-hmr.ts +31 -12
- package/src/astro/include-refresh.ts +19 -8
- package/src/astro/index.ts +1 -0
- package/src/astro/integration.ts +143 -14
- package/src/astro/module-types.ts +6 -0
- package/src/astro/runtime-modules.ts +28 -13
- package/src/astro/templates.ts +364 -67
- 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 +13 -13
- package/src/audit/checks/sitemap.ts +2 -3
- package/src/audit/graph.ts +3 -1
- package/src/audit/redirects.ts +46 -15
- package/src/audit/run.ts +14 -9
- package/src/audit/snapshot.ts +5 -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/args.ts +32 -0
- package/src/cli/command-meta.ts +5 -0
- package/src/cli/commands/audit.ts +4 -1
- package/src/cli/commands/doctor.ts +25 -17
- package/src/cli/commands/eval.ts +19 -11
- package/src/cli/commands/skill.ts +96 -0
- package/src/cli/commands/translate.ts +9 -12
- package/src/cli/commands/validate.ts +3 -1
- package/src/cli/dev-lock.ts +157 -37
- package/src/cli/index.ts +5 -0
- package/src/cli/report-format.ts +11 -6
- package/src/cli/required-secrets.ts +71 -29
- package/src/components/BlumePage.astro +2 -0
- package/src/components/colors.ts +19 -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/Badge.astro +5 -3
- package/src/components/content/Color.astro +1 -0
- package/src/components/content/Component.astro +2 -2
- 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/Tab.astro +0 -1
- package/src/components/content/Tabs.astro +4 -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/badge-color.ts +5 -2
- package/src/components/content/base-href.ts +13 -36
- 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 +125 -18
- package/src/components/islands/base-path.ts +47 -10
- package/src/components/islands/code-question.ts +62 -0
- package/src/components/islands/hooks.ts +145 -40
- package/src/components/islands/support-link.ts +68 -0
- package/src/components/islands/webmcp.ts +4 -2
- package/src/components/layout/Analytics.astro +11 -2
- package/src/components/layout/Banner.astro +14 -10
- package/src/components/layout/Breadcrumbs.astro +11 -4
- 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/DiscoveryLinks.astro +9 -5
- package/src/components/layout/DrawerSwitchers.astro +51 -0
- package/src/components/layout/Empty.astro +2 -2
- package/src/components/layout/Header.astro +124 -82
- package/src/components/layout/LanguageSwitcher.astro +50 -17
- package/src/components/layout/NarrationPlayer.astro +1085 -0
- package/src/components/layout/NavSelector.astro +35 -9
- package/src/components/layout/NavTabMenu.astro +3 -3
- package/src/components/layout/NavTree.astro +16 -6
- package/src/components/layout/PageActions.astro +4 -3
- package/src/components/layout/PageFeedback.astro +54 -49
- package/src/components/layout/PageLayout.astro +78 -9
- package/src/components/layout/Pagination.astro +3 -3
- package/src/components/layout/ReferenceLayout.astro +17 -6
- package/src/components/layout/RelatedPages.astro +42 -0
- package/src/components/layout/RootLayout.astro +313 -50
- package/src/components/layout/Search.astro +64 -30
- package/src/components/layout/SiteFooter.astro +124 -0
- package/src/components/layout/SiteMetatags.astro +16 -0
- package/src/components/layout/VersionBanner.astro +2 -2
- package/src/components/layout/analytics-client.ts +12 -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/layout/toc-active.ts +41 -0
- package/src/components/layout/toc-element.ts +8 -14
- package/src/components/openapi/ApiTagOperations.astro +2 -2
- package/src/components/openapi/AsyncApiOperation.astro +7 -4
- package/src/components/openapi/GraphqlChip.astro +2 -2
- package/src/components/openapi/Operation.astro +163 -13
- package/src/components/openapi/Playground.astro +4 -4
- package/src/components/openapi/RequestBody.astro +6 -4
- package/src/components/openapi/RequestPanel.astro +63 -8
- package/src/components/openapi/Responses.astro +6 -4
- package/src/components/openapi/SchemaTable.astro +7 -0
- package/src/components/openapi/async-snippets.ts +4 -4
- package/src/components/openapi/async.ts +38 -6
- package/src/components/openapi/code-samples.ts +97 -0
- package/src/components/openapi/helpers.ts +19 -9
- package/src/components/openapi/message-composer.ts +6 -1
- package/src/components/openapi/message.ts +17 -2
- package/src/components/openapi/operation-model.ts +81 -11
- package/src/components/openapi/panel.ts +4 -2
- package/src/components/openapi/param-style.ts +181 -0
- package/src/components/openapi/playground-client.ts +73 -16
- package/src/components/openapi/request.ts +190 -26
- package/src/components/openapi/schema-tree.ts +30 -24
- package/src/components/openapi/security.ts +6 -3
- package/src/components/openapi/snippets.ts +953 -13
- package/src/components/openapi/ws-client.ts +18 -2
- 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/base-path.ts +62 -16
- package/src/core/builtin-tags.ts +5 -0
- package/src/core/client-address.ts +19 -0
- package/src/core/config-input.ts +203 -27
- package/src/core/data.ts +44 -4
- package/src/core/diagnostics.ts +5 -2
- package/src/core/directive-diagnostics.ts +99 -0
- package/src/core/footer.ts +26 -0
- package/src/core/frontmatter.ts +21 -18
- package/src/core/graph.ts +39 -36
- package/src/core/heading-markers.ts +5 -18
- package/src/core/i18n-ui.ts +82 -2
- package/src/core/i18n.ts +31 -3
- package/src/core/includes.ts +20 -3
- package/src/core/last-modified.ts +25 -3
- package/src/core/links.ts +16 -2
- package/src/core/locale-links.ts +5 -1
- package/src/core/localizable.ts +26 -0
- package/src/core/meta.ts +33 -19
- package/src/core/navigation.ts +71 -20
- package/src/core/page-modes.ts +56 -0
- package/src/core/project-graph.ts +138 -10
- package/src/core/redirect-patterns.ts +491 -0
- package/src/core/related.ts +131 -0
- package/src/core/schema.ts +342 -68
- package/src/core/sources/contentful-rich-text.ts +27 -20
- package/src/core/sources/filesystem.ts +20 -2
- package/src/core/sources/github-releases.ts +25 -6
- package/src/core/sources/lexical.ts +23 -18
- package/src/core/sources/lower.ts +201 -34
- package/src/core/sources/mdx-remote.ts +51 -16
- package/src/core/sources/normalize.ts +327 -91
- package/src/core/sources/notion.ts +52 -25
- package/src/core/sources/obsidian.ts +23 -6
- package/src/core/sources/portable-text.ts +38 -21
- package/src/core/sources/read.ts +8 -3
- package/src/core/sources/strapi-blocks.ts +20 -16
- package/src/core/sources/watch.ts +20 -7
- package/src/core/standard-schema.ts +10 -6
- 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/core/version-cut.ts +52 -15
- package/src/deploy/artifacts.ts +104 -14
- package/src/deploy/cloudflare-negotiation.ts +39 -17
- package/src/deploy/headers.ts +10 -5
- package/src/deploy/node-headers.ts +64 -10
- package/src/deploy/platforms/cloudflare.ts +44 -3
- package/src/deploy/platforms/netlify.ts +34 -1
- package/src/deploy/platforms/node.ts +1 -0
- package/src/deploy/platforms/static.ts +1 -0
- package/src/deploy/platforms/types.ts +8 -0
- package/src/deploy/platforms/vercel.ts +38 -2
- package/src/deploy/redirects.ts +139 -36
- package/src/deploy/robots.ts +2 -2
- package/src/deploy/rss.ts +4 -3
- package/src/deploy/sitemap.ts +7 -5
- package/src/deploy/vercel-negotiation.ts +36 -0
- package/src/markdown/api-rail.ts +259 -0
- package/src/markdown/base-links.ts +34 -36
- package/src/markdown/code-title.ts +35 -9
- package/src/markdown/directives.ts +242 -36
- package/src/markdown/features.ts +20 -2
- package/src/markdown/fence-meta.ts +2 -0
- package/src/markdown/include.ts +11 -1
- package/src/markdown/index.ts +35 -11
- package/src/markdown/math.ts +4 -4
- package/src/markdown/mdast.ts +5 -2
- package/src/markdown/relative-links.ts +12 -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 +136 -11
- package/src/og/derive.ts +41 -31
- package/src/og/index.ts +1 -0
- package/src/openapi/asyncapi.ts +4 -2
- package/src/openapi/model.ts +77 -36
- package/src/openapi/overlay.ts +256 -0
- package/src/openapi/parse.ts +55 -2
- package/src/openapi/proxy.ts +63 -10
- package/src/openapi/references.ts +21 -5
- package/src/openapi/render-mdx.ts +46 -3
- 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 +201 -28
- package/src/registry/registry.ts +7 -0
- package/src/search/adapters/version-scope.ts +30 -0
- package/src/search/documents.ts +93 -28
- package/src/search/orama-index.ts +28 -3
- package/src/search/pagefind-ranking.ts +39 -0
- package/src/search/popular.ts +2 -1
- 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/jsonld.ts +7 -3
- 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/meta.ts +68 -24
- package/src/translate/prompts.ts +1 -0
- package/src/translate/run.ts +3 -3
- package/src/translate/validate.ts +12 -3
- package/src/translate/work-list.ts +56 -17
- package/src/upgrade/upgrade.ts +4 -171
- package/dist/cli/chunk-3yce002v.js.map +0 -14
- package/dist/cli/chunk-5r8g91qn.js.map +0 -34
- package/dist/cli/chunk-6dtt0zfn.js.map +0 -10
- package/dist/cli/chunk-7mbqtmgb.js.map +0 -10
- package/dist/cli/chunk-ddndchfr.js.map +0 -14
- package/dist/cli/chunk-esh98wmb.js +0 -23
- package/dist/cli/chunk-esh98wmb.js.map +0 -10
- package/dist/cli/chunk-hqp2ajnh.js.map +0 -35
- package/dist/cli/chunk-hr8ne106.js.map +0 -13
- package/dist/cli/chunk-j85scx15.js.map +0 -10
- package/dist/cli/chunk-mqc662a6.js +0 -75
- package/dist/cli/chunk-mqc662a6.js.map +0 -11
- package/dist/cli/chunk-pbg5a4s3.js.map +0 -19
- package/dist/cli/chunk-qkb5a8sa.js.map +0 -10
- package/dist/cli/chunk-v6ya5kcb.js.map +0 -189
- package/dist/cli/chunk-wdrt2k2v.js.map +0 -10
- package/dist/cli/chunk-xh43dwgw.js.map +0 -36
- package/docs/content/sources.mdx +0 -338
- /package/dist/cli/{chunk-zp79m0ts.js.map → chunk-1n228xqb.js.map} +0 -0
- /package/dist/cli/{chunk-h2ez8dzb.js.map → chunk-5ffywsmb.js.map} +0 -0
- /package/dist/cli/{chunk-g698a744.js.map → chunk-5wj2qwn2.js.map} +0 -0
- /package/dist/cli/{chunk-sqw4ekg1.js.map → chunk-amrb6hgh.js.map} +0 -0
- /package/dist/cli/{chunk-h7k3nq3v.js.map → chunk-gp84za0h.js.map} +0 -0
- /package/dist/cli/{chunk-bfwp9vp6.js.map → chunk-p804f3pe.js.map} +0 -0
- /package/dist/cli/{chunk-00gs3wqs.js.map → chunk-ta27feev.js.map} +0 -0
- /package/dist/cli/{chunk-bbnwccaz.js.map → chunk-y4ykbark.js.map} +0 -0
|
@@ -11,8 +11,10 @@ import {
|
|
|
11
11
|
markdownRoutePaths,
|
|
12
12
|
markdownTokenCount,
|
|
13
13
|
} from "../../ai/markdown.ts";
|
|
14
|
+
import { normalizeBasePath } from "../../core/base-path.ts";
|
|
14
15
|
import type { BlumeProject } from "../../core/project-graph.ts";
|
|
15
16
|
import type { ProjectContext } from "../../core/types.ts";
|
|
17
|
+
import { withRateLimitBinding } from "../../ratelimit/wrangler.ts";
|
|
16
18
|
import { CLOUDFLARE_ADAPTER_PACKAGE } from "../adapters/cloudflare.ts";
|
|
17
19
|
import {
|
|
18
20
|
injectWorkerNegotiation,
|
|
@@ -90,8 +92,13 @@ export const emitCloudflareNegotiation = async (
|
|
|
90
92
|
const home = rawMarkdown["/"];
|
|
91
93
|
// The 404 twins the wrapper may substitute for the HTML shell; only wired
|
|
92
94
|
// when the build actually emitted them (a project that owns `/404` gets
|
|
93
|
-
// neither), like the Vercel routing config.
|
|
94
|
-
|
|
95
|
+
// neither), like the Vercel routing config. The adapter writes the
|
|
96
|
+
// prerendered files under the deployment base (`dist/client/<base>/`),
|
|
97
|
+
// where the wrapper fetches them from.
|
|
98
|
+
const staticDir = join(
|
|
99
|
+
clientDir(context),
|
|
100
|
+
normalizeBasePath(config.deployment.options.base)
|
|
101
|
+
);
|
|
95
102
|
const injected = injectWorkerNegotiation(
|
|
96
103
|
await readFile(wranglerPath, "utf-8"),
|
|
97
104
|
{
|
|
@@ -119,7 +126,7 @@ export const emitCloudflareNegotiation = async (
|
|
|
119
126
|
// based the same way the platform files are — it answers any the
|
|
120
127
|
// worker-first rules claim, where `_redirects` is never consulted and
|
|
121
128
|
// Astro would default their status.
|
|
122
|
-
redirects: platformRedirects(
|
|
129
|
+
redirects: platformRedirects(project),
|
|
123
130
|
routePaths,
|
|
124
131
|
}
|
|
125
132
|
);
|
|
@@ -267,6 +274,36 @@ export const nameCloudflareWorker = async (
|
|
|
267
274
|
);
|
|
268
275
|
};
|
|
269
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
|
+
|
|
270
307
|
/**
|
|
271
308
|
* Cloudflare Workers and Pages. A server build emits the Worker into
|
|
272
309
|
* `dist/server` and serves `dist/client` through the ASSETS binding, which
|
|
@@ -292,6 +329,7 @@ export const cloudflarePlatform: DeployPlatform = {
|
|
|
292
329
|
if (!isolated) {
|
|
293
330
|
await nameCloudflareWorker(project, log);
|
|
294
331
|
await emitCloudflareNegotiation(project, log);
|
|
332
|
+
await bindCloudflareRateLimit(project, log);
|
|
295
333
|
await emitCloudflareDeployConfig(project.context);
|
|
296
334
|
}
|
|
297
335
|
return true;
|
|
@@ -308,6 +346,9 @@ export const cloudflarePlatform: DeployPlatform = {
|
|
|
308
346
|
previewDeploy: null,
|
|
309
347
|
readsHeaderFiles: { server: true, static: true },
|
|
310
348
|
redirectFiles: [REDIRECTS_FILE],
|
|
349
|
+
// The adapter points `build.client` at `dist/client/<base>/` for a server
|
|
350
|
+
// build, and hoists its own `_headers`/`_redirects` back up to `dist/client`.
|
|
351
|
+
serverClientUnderBase: true,
|
|
311
352
|
serverOutputDir: distDir,
|
|
312
353
|
serverStaticDir: clientDir,
|
|
313
354
|
};
|
|
@@ -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
|
},
|
|
@@ -124,6 +156,7 @@ export const netlifyPlatform: DeployPlatform = {
|
|
|
124
156
|
// same rules into that tree's config.
|
|
125
157
|
readsHeaderFiles: { server: false, static: true },
|
|
126
158
|
redirectFiles: [NETLIFY_REDIRECTS_FILE],
|
|
159
|
+
serverClientUnderBase: false,
|
|
127
160
|
serverOutputDir: distDir,
|
|
128
161
|
serverStaticDir: distDir,
|
|
129
162
|
};
|
|
@@ -25,6 +25,7 @@ export const staticPlatform: DeployPlatform = {
|
|
|
25
25
|
previewDeploy: null,
|
|
26
26
|
readsHeaderFiles: { server: false, static: true },
|
|
27
27
|
redirectFiles: [REDIRECTS_FILE, VERCEL_JSON_FILE],
|
|
28
|
+
serverClientUnderBase: false,
|
|
28
29
|
serverOutputDir: distDir,
|
|
29
30
|
serverStaticDir: distDir,
|
|
30
31
|
};
|
|
@@ -112,6 +112,14 @@ export interface DeployPlatform {
|
|
|
112
112
|
readsHeaderFiles: Record<DeployOutput, boolean>;
|
|
113
113
|
/** The redirect files a static build writes, beside the manifest. */
|
|
114
114
|
redirectFiles: RedirectFile[];
|
|
115
|
+
/**
|
|
116
|
+
* Whether the adapter moves a server build's client output under
|
|
117
|
+
* `deployment.base` (`dist/client/<base>/`, the directory Astro then hands
|
|
118
|
+
* `astro:build:done`) while the platform keeps serving `serverStaticDir`
|
|
119
|
+
* as the assets root. The files the platform reads there, like `_headers`,
|
|
120
|
+
* belong above the directory Astro reports.
|
|
121
|
+
*/
|
|
122
|
+
serverClientUnderBase: boolean;
|
|
115
123
|
/** Where a server build's deploy bundle lands. */
|
|
116
124
|
serverOutputDir: (context: ProjectContext) => string;
|
|
117
125
|
/** The directory a server build serves as static files. */
|
|
@@ -29,8 +29,15 @@ import {
|
|
|
29
29
|
blumeDependencyNames,
|
|
30
30
|
functionBundleVerdict,
|
|
31
31
|
} from "../function-bundle.ts";
|
|
32
|
-
import {
|
|
33
|
-
|
|
32
|
+
import {
|
|
33
|
+
buildVercelConfig,
|
|
34
|
+
platformRedirects,
|
|
35
|
+
vercelPatternRoutes,
|
|
36
|
+
} from "../redirects.ts";
|
|
37
|
+
import {
|
|
38
|
+
injectNegotiationRoutes,
|
|
39
|
+
injectRedirectRoutes,
|
|
40
|
+
} from "../vercel-negotiation.ts";
|
|
34
41
|
import { adapterRoot, toSiteUrl } from "./paths.ts";
|
|
35
42
|
import type { BuildLog, DeployPlatform, RedirectFile } from "./types.ts";
|
|
36
43
|
|
|
@@ -141,6 +148,33 @@ export const emitVercelNegotiation = async (
|
|
|
141
148
|
);
|
|
142
149
|
};
|
|
143
150
|
|
|
151
|
+
/**
|
|
152
|
+
* Route the pattern redirects (`/beta/:slug*`) in the adapter's Build Output
|
|
153
|
+
* config: Astro's `redirects` carry only the exact ones, since it can't
|
|
154
|
+
* prerender a pattern's pages, so the adapter never sees these.
|
|
155
|
+
*/
|
|
156
|
+
export const emitVercelPatternRedirects = async (
|
|
157
|
+
project: BlumeProject,
|
|
158
|
+
log: BuildLog
|
|
159
|
+
): Promise<void> => {
|
|
160
|
+
const routes = vercelPatternRoutes(platformRedirects(project));
|
|
161
|
+
const configPath = join(buildOutputDir(project.context), "config.json");
|
|
162
|
+
if (routes.length === 0 || !existsSync(configPath)) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
const injected = injectRedirectRoutes(
|
|
166
|
+
await readFile(configPath, "utf-8"),
|
|
167
|
+
routes
|
|
168
|
+
);
|
|
169
|
+
if (injected === null) {
|
|
170
|
+
log.warn(
|
|
171
|
+
"Could not route the pattern redirects in .vercel/output/config.json, so paths they cover answer 404."
|
|
172
|
+
);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
await writeFile(configPath, injected, "utf-8");
|
|
176
|
+
};
|
|
177
|
+
|
|
144
178
|
/**
|
|
145
179
|
* Vercel. A server build's Build Output tree lands at `.vercel/output` — at
|
|
146
180
|
* the project root, because the adapter is handed that root up front: its
|
|
@@ -180,6 +214,7 @@ export const vercelPlatform: DeployPlatform = {
|
|
|
180
214
|
// config is a deploy artifact and stays untouched.
|
|
181
215
|
if (!isolated) {
|
|
182
216
|
await emitVercelNegotiation(project, log);
|
|
217
|
+
await emitVercelPatternRedirects(project, log);
|
|
183
218
|
}
|
|
184
219
|
return true;
|
|
185
220
|
},
|
|
@@ -194,6 +229,7 @@ export const vercelPlatform: DeployPlatform = {
|
|
|
194
229
|
previewDeploy: "vercel deploy",
|
|
195
230
|
readsHeaderFiles: { server: false, static: false },
|
|
196
231
|
redirectFiles: [VERCEL_JSON_FILE],
|
|
232
|
+
serverClientUnderBase: false,
|
|
197
233
|
serverOutputDir: buildOutputDir,
|
|
198
234
|
serverStaticDir: (context) => join(buildOutputDir(context), "static"),
|
|
199
235
|
};
|
package/src/deploy/redirects.ts
CHANGED
|
@@ -1,21 +1,55 @@
|
|
|
1
1
|
import {
|
|
2
2
|
normalizeBasePath,
|
|
3
|
+
withAuthoredBasePath,
|
|
3
4
|
withBasePath,
|
|
4
5
|
withComposedBasePath,
|
|
5
6
|
} from "../core/base-path.ts";
|
|
7
|
+
import { routeSetFor, servesRoute } from "../core/locale-links.ts";
|
|
8
|
+
import type { RouteSet } from "../core/locale-links.ts";
|
|
9
|
+
import type { BlumeProject } from "../core/project-graph.ts";
|
|
10
|
+
import {
|
|
11
|
+
compileRedirects,
|
|
12
|
+
exactFirst,
|
|
13
|
+
expandRedirect,
|
|
14
|
+
underscoreRedirect,
|
|
15
|
+
vercelRedirect,
|
|
16
|
+
} from "../core/redirect-patterns.ts";
|
|
17
|
+
import type { RedirectRule } from "../core/redirect-patterns.ts";
|
|
6
18
|
import type { ResolvedConfig } from "../core/schema.ts";
|
|
19
|
+
import { escapeVercelSource } from "./headers.ts";
|
|
7
20
|
import type { VercelHeader } from "./headers.ts";
|
|
21
|
+
import type { VercelRoute } from "./vercel-negotiation.ts";
|
|
8
22
|
|
|
9
23
|
/**
|
|
10
24
|
* Platform redirect files for a static build. Astro already emits redirect HTML
|
|
11
25
|
* (meta-refresh) pages for static output, but that's a soft client redirect.
|
|
12
26
|
* These give the host a real HTTP 3xx: Netlify/Cloudflare read `_redirects`,
|
|
13
27
|
* Vercel reads `vercel.json`, and `blume-redirects.json` is a structured
|
|
14
|
-
* manifest for anything else (Apache/nginx rules, an edge worker).
|
|
28
|
+
* manifest for anything else (Apache/nginx rules, an edge worker). A pattern
|
|
29
|
+
* redirect (`/beta/:slug*`) gets no redirect page, since Astro can't
|
|
30
|
+
* prerender one for paths it doesn't know; each host file carries it in that
|
|
31
|
+
* host's syntax (see `core/redirect-patterns.ts`).
|
|
15
32
|
*/
|
|
16
33
|
|
|
17
34
|
type Redirect = ResolvedConfig["redirects"][number];
|
|
18
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Base a redirect destination authored as if mounted at root by what it
|
|
38
|
+
* names (see `withAuthoredBasePath`): a page gains the full
|
|
39
|
+
* `{deployment.base}{basePath}` stack, a public file (`/files/spec.pdf`, with
|
|
40
|
+
* no page in `routes`) only the deployment base — `public/` is served there
|
|
41
|
+
* whatever `basePath` is. External URLs pass through.
|
|
42
|
+
*/
|
|
43
|
+
const baseRedirectTarget = (
|
|
44
|
+
to: string,
|
|
45
|
+
basePath: string,
|
|
46
|
+
base: string,
|
|
47
|
+
routes: RouteSet
|
|
48
|
+
): string =>
|
|
49
|
+
withAuthoredBasePath(base, basePath, to, (route) =>
|
|
50
|
+
servesRoute(routes, route)
|
|
51
|
+
);
|
|
52
|
+
|
|
19
53
|
/**
|
|
20
54
|
* Base a redirect for Astro's `redirects` config, where the two sides are not
|
|
21
55
|
* symmetric:
|
|
@@ -23,19 +57,23 @@ type Redirect = ResolvedConfig["redirects"][number];
|
|
|
23
57
|
* - `from` gains only `basePath`. Astro builds the match pattern with
|
|
24
58
|
* `deployment.base` already applied (`getPattern(segments, config.base)`), so
|
|
25
59
|
* adding it here would serve the redirect at `{base}{base}/from`.
|
|
26
|
-
* - `to` gains the full `{deployment.base}{basePath}` stack
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
60
|
+
* - `to` gains the full `{deployment.base}{basePath}` stack, or the deployment
|
|
61
|
+
* base alone for a public file (see {@link baseRedirectTarget}). Astro
|
|
62
|
+
* resolves a destination that matches a known route by regenerating it from
|
|
63
|
+
* that route's segments, which carry no base — and passes an unmatched
|
|
64
|
+
* destination through verbatim. Neither path prepends `base`, so a
|
|
65
|
+
* root-relative `to` escapes the base entirely (withastro/astro#7774, still
|
|
66
|
+
* the behavior in Astro 7).
|
|
31
67
|
*
|
|
32
|
-
* Both sides are authored as if mounted at root;
|
|
33
|
-
*
|
|
68
|
+
* Both sides are authored as if mounted at root; `routes` (the served page
|
|
69
|
+
* routes, carrying `basePath`) tell a dotted page route like `/releases/v1.2`
|
|
70
|
+
* from a file. Idempotent, so a hand-written base isn't doubled.
|
|
34
71
|
*/
|
|
35
72
|
export const applyBaseToAstroRedirects = (
|
|
36
73
|
redirects: Redirect[],
|
|
37
74
|
basePath: string,
|
|
38
|
-
deployBase: string
|
|
75
|
+
deployBase: string,
|
|
76
|
+
routes: RouteSet
|
|
39
77
|
): Redirect[] => {
|
|
40
78
|
// `deployment.base` arrives as the user wrote it (Astro accepts `/base/`,
|
|
41
79
|
// even `base`); normalizing here keeps the composed paths well-formed.
|
|
@@ -44,58 +82,86 @@ export const applyBaseToAstroRedirects = (
|
|
|
44
82
|
? redirects.map((redirect) => ({
|
|
45
83
|
...redirect,
|
|
46
84
|
from: withBasePath(basePath, redirect.from),
|
|
47
|
-
to:
|
|
85
|
+
to: baseRedirectTarget(redirect.to, basePath, base, routes),
|
|
48
86
|
}))
|
|
49
87
|
: redirects;
|
|
50
88
|
};
|
|
51
89
|
|
|
52
90
|
/**
|
|
53
91
|
* Base a redirect for the host platform files below. Unlike Astro's config,
|
|
54
|
-
* these are matched against the real served URL, so
|
|
55
|
-
* `{deployment.base}{basePath}` stack
|
|
92
|
+
* these are matched against the real served URL, so `from` carries the full
|
|
93
|
+
* `{deployment.base}{basePath}` stack, and so does `to` unless it names a
|
|
94
|
+
* public file (see {@link baseRedirectTarget}).
|
|
56
95
|
*/
|
|
57
96
|
export const applyBaseToPlatformRedirects = (
|
|
58
97
|
redirects: Redirect[],
|
|
59
98
|
basePath: string,
|
|
60
|
-
deployBase: string
|
|
99
|
+
deployBase: string,
|
|
100
|
+
routes: RouteSet
|
|
61
101
|
): Redirect[] => {
|
|
62
102
|
const base = normalizeBasePath(deployBase);
|
|
63
103
|
return basePath || base
|
|
64
104
|
? redirects.map((redirect) => ({
|
|
65
105
|
...redirect,
|
|
66
106
|
from: withComposedBasePath(base, basePath, redirect.from),
|
|
67
|
-
to:
|
|
107
|
+
to: baseRedirectTarget(redirect.to, basePath, base, routes),
|
|
68
108
|
}))
|
|
69
109
|
: redirects;
|
|
70
110
|
};
|
|
71
111
|
|
|
72
112
|
/**
|
|
73
113
|
* The configured redirects as the host platform matches them, via
|
|
74
|
-
* {@link applyBaseToPlatformRedirects}
|
|
75
|
-
*
|
|
76
|
-
*
|
|
114
|
+
* {@link applyBaseToPlatformRedirects}, exact paths ahead of patterns: hosts
|
|
115
|
+
* try rules in order, and an exact redirect wins over a pattern that also
|
|
116
|
+
* covers its path. The one basing every consumer must share: the emitted
|
|
117
|
+
* redirect files and the server wrappers all compare these paths against
|
|
118
|
+
* real served URLs.
|
|
77
119
|
*/
|
|
78
|
-
export const platformRedirects = (
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
120
|
+
export const platformRedirects = (project: {
|
|
121
|
+
config: ResolvedConfig;
|
|
122
|
+
manifest: Pick<BlumeProject["manifest"], "routes">;
|
|
123
|
+
}): Redirect[] => {
|
|
124
|
+
const { config } = project;
|
|
125
|
+
return exactFirst(
|
|
126
|
+
applyBaseToPlatformRedirects(
|
|
127
|
+
config.redirects,
|
|
128
|
+
config.basePath,
|
|
129
|
+
config.deployment.options.base ?? "",
|
|
130
|
+
routeSetFor(project.manifest.routes)
|
|
131
|
+
)
|
|
83
132
|
);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* A redirect as the entries a host file lists: itself when its path is
|
|
137
|
+
* exact, else the rules its pattern expands to, in the host's syntax.
|
|
138
|
+
*/
|
|
139
|
+
const hostEntries = <Entry>(
|
|
140
|
+
redirect: Redirect,
|
|
141
|
+
exact: (redirect: Redirect) => Entry,
|
|
142
|
+
pattern: (rule: RedirectRule) => Entry
|
|
143
|
+
): Entry[] => {
|
|
144
|
+
const rules = expandRedirect(redirect);
|
|
145
|
+
return rules.length > 0 ? rules.map(pattern) : [exact(redirect)];
|
|
146
|
+
};
|
|
84
147
|
|
|
85
148
|
/**
|
|
86
|
-
* `_redirects` text (Netlify + Cloudflare Pages): `from to status` per line
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
149
|
+
* `_redirects` text (Netlify + Cloudflare Pages): `from to status` per line,
|
|
150
|
+
* a pattern as `:placeholder`s and a `*` splat the destination reads as
|
|
151
|
+
* `:splat`. `force` appends Netlify's `!` to each status (`301!`), so the rule
|
|
152
|
+
* wins over the redirect page Astro writes at `from`; Cloudflare, which always
|
|
153
|
+
* applies its rules first, rejects a line carrying it.
|
|
90
154
|
*/
|
|
91
155
|
export const buildNetlifyRedirects = (
|
|
92
156
|
redirects: Redirect[],
|
|
93
157
|
force = false
|
|
94
158
|
): string =>
|
|
95
159
|
`${redirects
|
|
96
|
-
.
|
|
97
|
-
(redirect) =>
|
|
98
|
-
|
|
160
|
+
.flatMap((redirect) =>
|
|
161
|
+
hostEntries(redirect, (exact) => exact, underscoreRedirect).map(
|
|
162
|
+
(entry) =>
|
|
163
|
+
`${entry.from} ${entry.to} ${redirect.status}${force ? "!" : ""}`
|
|
164
|
+
)
|
|
99
165
|
)
|
|
100
166
|
.join("\n")}\n`;
|
|
101
167
|
|
|
@@ -110,18 +176,26 @@ interface VercelConfig {
|
|
|
110
176
|
* header rules are given (see `buildVercelHeaders`). Uses `statusCode` (Vercel's
|
|
111
177
|
* alternative to the boolean `permanent`) so the configured code ships exactly:
|
|
112
178
|
* `permanent` would silently coerce a 301 to 308 and a 302 to 307, diverging
|
|
113
|
-
* from the `_redirects` file, which preserves exact codes.
|
|
179
|
+
* from the `_redirects` file, which preserves exact codes. A `source` is a
|
|
180
|
+
* `path-to-regexp` pattern, so each literal run of a `from` path is escaped:
|
|
181
|
+
* unescaped, `/c++-guide` fails the whole config and `/faq(old)` never
|
|
182
|
+
* matches. A pattern's captures are `path-to-regexp` params.
|
|
114
183
|
*/
|
|
115
184
|
export const buildVercelConfig = (
|
|
116
185
|
redirects: Redirect[],
|
|
117
186
|
headers: readonly VercelHeader[] = []
|
|
118
187
|
): string => {
|
|
119
188
|
const config: VercelConfig = {
|
|
120
|
-
redirects: redirects.
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
189
|
+
redirects: redirects.flatMap((redirect) =>
|
|
190
|
+
hostEntries(
|
|
191
|
+
redirect,
|
|
192
|
+
(exact) => ({
|
|
193
|
+
destination: exact.to,
|
|
194
|
+
source: escapeVercelSource(exact.from),
|
|
195
|
+
}),
|
|
196
|
+
(rule) => vercelRedirect(rule, escapeVercelSource)
|
|
197
|
+
).map((entry) => ({ ...entry, statusCode: redirect.status }))
|
|
198
|
+
),
|
|
125
199
|
};
|
|
126
200
|
if (headers.length > 0) {
|
|
127
201
|
config.headers = headers;
|
|
@@ -129,7 +203,36 @@ export const buildVercelConfig = (
|
|
|
129
203
|
return `${JSON.stringify(config, null, 2)}\n`;
|
|
130
204
|
};
|
|
131
205
|
|
|
132
|
-
/**
|
|
206
|
+
/**
|
|
207
|
+
* The pattern redirects as `_redirects` entries, for the `redirects` of a
|
|
208
|
+
* Netlify server build's Frameworks API config (see
|
|
209
|
+
* {@link buildNetlifyRedirects} for the syntax).
|
|
210
|
+
*/
|
|
211
|
+
export const netlifyPatternRedirects = (
|
|
212
|
+
redirects: Redirect[]
|
|
213
|
+
): { from: string; status: number; to: string }[] =>
|
|
214
|
+
redirects.flatMap(expandRedirect).map((rule) => ({
|
|
215
|
+
...underscoreRedirect(rule),
|
|
216
|
+
status: rule.status,
|
|
217
|
+
}));
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* The pattern redirects as Build Output API routes, for a Vercel server
|
|
221
|
+
* build: the adapter routes only the exact redirects Astro's config carries.
|
|
222
|
+
* Each matches the served path, a trailing slash optional, and fills its
|
|
223
|
+
* `Location` from numbered captures, as the adapter's own redirect routes do.
|
|
224
|
+
*/
|
|
225
|
+
export const vercelPatternRoutes = (redirects: Redirect[]): VercelRoute[] =>
|
|
226
|
+
compileRedirects(redirects).map(([src, location, status]) => ({
|
|
227
|
+
headers: { Location: location },
|
|
228
|
+
src,
|
|
229
|
+
status,
|
|
230
|
+
}));
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Structured manifest for hosts that need manual wiring. A pattern stays as
|
|
234
|
+
* configured (`/beta/:slug*`), for the rule it becomes to be written by hand.
|
|
235
|
+
*/
|
|
133
236
|
export const buildRedirectManifest = (redirects: Redirect[]): string =>
|
|
134
237
|
`${JSON.stringify(
|
|
135
238
|
redirects.map((redirect) => ({
|
package/src/deploy/robots.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mountBasePath, normalizeBasePath } from "../core/base-path.ts";
|
|
2
2
|
import type { BlumeProject } from "../core/project-graph.ts";
|
|
3
3
|
import type { ContentSignalPolicy, ContentSignals } from "../core/schema.ts";
|
|
4
4
|
import { absoluteUrl } from "../core/site-url.ts";
|
|
@@ -49,7 +49,7 @@ export const buildRobots = (project: BlumeProject): string | null => {
|
|
|
49
49
|
|
|
50
50
|
const { site } = config.deployment.options;
|
|
51
51
|
if (site && config.seo.sitemap) {
|
|
52
|
-
const sitemapPath =
|
|
52
|
+
const sitemapPath = mountBasePath(
|
|
53
53
|
normalizeBasePath(config.deployment.options.base),
|
|
54
54
|
"/sitemap.xml"
|
|
55
55
|
);
|
package/src/deploy/rss.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// `'` reference rather than `'`, which XML accepts equally.
|
|
3
3
|
import { escape as escapeXml } from "html-escaper";
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { mountBasePath, normalizeBasePath } from "../core/base-path.ts";
|
|
6
6
|
import type { BlumeProject } from "../core/project-graph.ts";
|
|
7
7
|
import { siteRoot } from "../core/site-url.ts";
|
|
8
8
|
import type { PageRecord } from "../core/types.ts";
|
|
@@ -57,7 +57,8 @@ export const buildRssFeeds = (project: BlumeProject): RssFeed[] => {
|
|
|
57
57
|
return [];
|
|
58
58
|
}
|
|
59
59
|
const base = siteRoot(site);
|
|
60
|
-
// Routes carry `basePath`; a `deployment.base` subdirectory is
|
|
60
|
+
// Routes carry `basePath`; a `deployment.base` subdirectory is mounted on top
|
|
61
|
+
// of every one, even a route whose first segment matches it (see sitemap.ts).
|
|
61
62
|
// The feed's own `link`/self URL points at the docs root under that base, while
|
|
62
63
|
// `path` stays base-less (it's also the on-disk output location).
|
|
63
64
|
const deployBase = normalizeBasePath(config.deployment.options.base);
|
|
@@ -80,7 +81,7 @@ export const buildRssFeeds = (project: BlumeProject): RssFeed[] => {
|
|
|
80
81
|
description: page.description,
|
|
81
82
|
// Encode like the sitemap does: a route with spaces or non-ASCII
|
|
82
83
|
// must still yield a valid <link>/<guid> URL after XML decoding.
|
|
83
|
-
link: encodeURI(`${base}${
|
|
84
|
+
link: encodeURI(`${base}${mountBasePath(deployBase, page.route)}`),
|
|
84
85
|
title: page.title,
|
|
85
86
|
}))
|
|
86
87
|
.toSorted((a, b) => (b.date?.getTime() ?? 0) - (a.date?.getTime() ?? 0))
|
package/src/deploy/sitemap.ts
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
discoverPagesSync,
|
|
7
7
|
hasGeneratedChangelog,
|
|
8
8
|
} from "../astro/pages.ts";
|
|
9
|
-
import {
|
|
9
|
+
import { mountBasePath, normalizeBasePath } from "../core/base-path.ts";
|
|
10
10
|
import type { BlumeProject } from "../core/project-graph.ts";
|
|
11
11
|
import { siteRoot } from "../core/site-url.ts";
|
|
12
12
|
|
|
@@ -78,7 +78,9 @@ export const buildSitemapFiles = (
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
const base = siteRoot(site);
|
|
81
|
-
// Routes carry `basePath`; a `deployment.base` subdirectory is
|
|
81
|
+
// Routes carry `basePath`; a `deployment.base` subdirectory is mounted on
|
|
82
|
+
// top of every one, even a route whose first segment matches it: under base
|
|
83
|
+
// `/guides`, `guides/setup.md` is served at `/guides/guides/setup`.
|
|
82
84
|
const deployBase = normalizeBasePath(project.config.deployment.options.base);
|
|
83
85
|
|
|
84
86
|
// Archived-version pages leave the sitemap when the version is noindexed,
|
|
@@ -135,7 +137,7 @@ export const buildSitemapFiles = (
|
|
|
135
137
|
) {
|
|
136
138
|
continue;
|
|
137
139
|
}
|
|
138
|
-
pushUrl(
|
|
140
|
+
pushUrl(mountBasePath(deployBase, page.route), page.lastModified);
|
|
139
141
|
}
|
|
140
142
|
// Custom `.astro` pages and the generated changelog index mount outside
|
|
141
143
|
// `basePath` (they're injected at their pattern — see `blumeIntegration`), so
|
|
@@ -150,7 +152,7 @@ export const buildSitemapFiles = (
|
|
|
150
152
|
extraRoutes.push("/changelog");
|
|
151
153
|
}
|
|
152
154
|
for (const route of extraRoutes) {
|
|
153
|
-
pushUrl(
|
|
155
|
+
pushUrl(mountBasePath(deployBase, route));
|
|
154
156
|
}
|
|
155
157
|
urls.sort();
|
|
156
158
|
|
|
@@ -169,7 +171,7 @@ export const buildSitemapFiles = (
|
|
|
169
171
|
// Chunks sit next to sitemap.xml, so their URLs layer the same deployment
|
|
170
172
|
// base robots.txt uses for the index.
|
|
171
173
|
const loc = escapeXml(
|
|
172
|
-
encodeURI(`${base}${
|
|
174
|
+
encodeURI(`${base}${mountBasePath(deployBase, `/${name}`)}`)
|
|
173
175
|
);
|
|
174
176
|
references.push(` <sitemap><loc>${loc}</loc></sitemap>`);
|
|
175
177
|
}
|
|
@@ -399,3 +399,39 @@ export const injectNegotiationRoutes = (
|
|
|
399
399
|
config.routes = routes;
|
|
400
400
|
return `${JSON.stringify(config, null, "\t")}\n`;
|
|
401
401
|
};
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Splice routes for the pattern redirects (see `vercelPatternRoutes`) into a
|
|
405
|
+
* Build Output `config.json`, in the main phase just ahead of
|
|
406
|
+
* `handle: "filesystem"`: after the exact redirects the adapter puts first,
|
|
407
|
+
* so an exact path still wins. A second pass replaces its own routes. Returns
|
|
408
|
+
* the updated JSON text, or `null` when there is nowhere safe to splice: an
|
|
409
|
+
* unparsable config, no `routes` array, or no `handle: "filesystem"` marker.
|
|
410
|
+
*/
|
|
411
|
+
export const injectRedirectRoutes = (
|
|
412
|
+
configText: string,
|
|
413
|
+
redirectRoutes: readonly VercelRoute[]
|
|
414
|
+
): string | null => {
|
|
415
|
+
let config: { routes?: VercelRoute[] };
|
|
416
|
+
try {
|
|
417
|
+
config = JSON.parse(configText);
|
|
418
|
+
} catch {
|
|
419
|
+
return null;
|
|
420
|
+
}
|
|
421
|
+
if (!Array.isArray(config.routes)) {
|
|
422
|
+
return null;
|
|
423
|
+
}
|
|
424
|
+
const ours = new Set(redirectRoutes.map((route) => route.src));
|
|
425
|
+
const routes = config.routes.filter(
|
|
426
|
+
(route) => !(ours.has(route.src) && isString(route.headers?.Location))
|
|
427
|
+
);
|
|
428
|
+
const filesystemIndex = routes.findIndex(
|
|
429
|
+
(route) => route.handle === "filesystem"
|
|
430
|
+
);
|
|
431
|
+
if (filesystemIndex === -1) {
|
|
432
|
+
return null;
|
|
433
|
+
}
|
|
434
|
+
routes.splice(filesystemIndex, 0, ...redirectRoutes);
|
|
435
|
+
config.routes = routes;
|
|
436
|
+
return `${JSON.stringify(config, null, "\t")}\n`;
|
|
437
|
+
};
|