blume 2.0.1 → 2.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +147 -0
- package/dist/cli/{chunk-qwsrynx5.js → chunk-1d7ve1dm.js} +38 -19
- package/dist/cli/{chunk-qwsrynx5.js.map → chunk-1d7ve1dm.js.map} +4 -4
- package/dist/cli/{chunk-s6jhgk0q.js → chunk-2eytanqx.js} +2 -2
- package/dist/cli/{chunk-kdp5q7ke.js → chunk-2hsdwb9n.js} +18 -19
- package/dist/cli/{chunk-kdp5q7ke.js.map → chunk-2hsdwb9n.js.map} +2 -2
- package/dist/cli/{chunk-hdpx1tax.js → chunk-2z928egk.js} +5 -5
- package/dist/cli/{chunk-jwyddg7y.js → chunk-35d4wj9f.js} +30 -24
- package/dist/cli/chunk-35d4wj9f.js.map +15 -0
- package/dist/cli/{chunk-ah61y8py.js → chunk-364znk6q.js} +2 -2
- package/dist/cli/{chunk-jts8mvcz.js → chunk-3em5wd2y.js} +25 -6
- package/dist/cli/{chunk-jts8mvcz.js.map → chunk-3em5wd2y.js.map} +3 -3
- package/dist/cli/{chunk-s1p84fyh.js → chunk-5f86nr5m.js} +63 -22
- package/dist/cli/chunk-5f86nr5m.js.map +11 -0
- package/dist/cli/{chunk-2hn4b8z7.js → chunk-5m5nmvyq.js} +170 -76
- package/dist/cli/chunk-5m5nmvyq.js.map +13 -0
- package/dist/cli/{chunk-epjnccmv.js → chunk-5xvm6tfj.js} +18 -15
- package/dist/cli/chunk-5xvm6tfj.js.map +10 -0
- package/dist/cli/{chunk-fz5wtpmh.js → chunk-6dsbexzp.js} +18 -15
- package/dist/cli/chunk-6dsbexzp.js.map +10 -0
- package/dist/cli/{chunk-vtk4a6dg.js → chunk-8ktnccpt.js} +1 -1
- package/dist/cli/{chunk-6hsn950k.js → chunk-9t7a85s3.js} +178 -48
- package/dist/cli/chunk-9t7a85s3.js.map +10 -0
- package/dist/cli/{chunk-fxypxtvm.js → chunk-a58773jm.js} +2 -2
- package/dist/cli/{chunk-mb2919y2.js → chunk-acanzt5p.js} +17 -6
- package/dist/cli/chunk-acanzt5p.js.map +10 -0
- package/dist/cli/{chunk-27g6wdth.js → chunk-akbpwfxc.js} +90 -26
- package/dist/cli/chunk-akbpwfxc.js.map +10 -0
- package/dist/cli/{chunk-wm7js3j9.js → chunk-b07cmahc.js} +2 -2
- package/dist/cli/{chunk-wgm7m9qk.js → chunk-c8chx29p.js} +179 -51
- package/dist/cli/chunk-c8chx29p.js.map +36 -0
- package/dist/cli/{chunk-qs4q5p4e.js → chunk-crgn1q09.js} +32 -14
- package/dist/cli/chunk-crgn1q09.js.map +10 -0
- package/dist/cli/chunk-e04dxsz1.js +39 -0
- package/dist/cli/chunk-e04dxsz1.js.map +10 -0
- package/dist/cli/{chunk-yt5n7ppj.js → chunk-ey84smr6.js} +17 -7
- package/dist/cli/chunk-ey84smr6.js.map +10 -0
- package/dist/cli/{chunk-f2z5v128.js → chunk-g4hq16wv.js} +14 -15
- package/dist/cli/{chunk-f2z5v128.js.map → chunk-g4hq16wv.js.map} +2 -2
- package/dist/cli/{chunk-fa25z98p.js → chunk-ga0pf4aj.js} +9 -8
- package/dist/cli/chunk-ga0pf4aj.js.map +11 -0
- package/dist/cli/{chunk-kpf8rrjc.js → chunk-hm3vjy5s.js} +109 -58
- package/dist/cli/chunk-hm3vjy5s.js.map +19 -0
- package/dist/cli/{chunk-fs23ddbb.js → chunk-j85vccga.js} +625 -750
- package/dist/cli/chunk-j85vccga.js.map +36 -0
- package/dist/cli/{chunk-ch6g3ar0.js → chunk-p3v96n38.js} +6 -6
- package/dist/cli/{chunk-ch6g3ar0.js.map → chunk-p3v96n38.js.map} +3 -3
- package/dist/cli/{chunk-q5163e60.js → chunk-p73c0m7w.js} +21 -19
- package/dist/cli/chunk-p73c0m7w.js.map +11 -0
- package/dist/cli/{chunk-dh8cwk36.js → chunk-pehfxfta.js} +24 -9
- package/dist/cli/chunk-pehfxfta.js.map +10 -0
- package/dist/cli/{chunk-qkqwkpte.js → chunk-pv29h0wf.js} +3688 -1313
- package/dist/cli/chunk-pv29h0wf.js.map +190 -0
- package/dist/cli/{chunk-6vm74dry.js → chunk-q58y5e6a.js} +10 -10
- package/dist/cli/{chunk-6vm74dry.js.map → chunk-q58y5e6a.js.map} +3 -3
- package/dist/cli/{chunk-zxcczpyx.js → chunk-r20tn01b.js} +1 -1
- package/dist/cli/{chunk-m3vmjgmq.js → chunk-r9rcc4w7.js} +16 -8
- package/dist/cli/chunk-r9rcc4w7.js.map +10 -0
- package/dist/cli/{chunk-5shv93fd.js → chunk-tkacnehg.js} +2 -2
- package/dist/cli/{chunk-zxh4d9vy.js → chunk-tzmab476.js} +4 -4
- package/dist/cli/{chunk-yw7dm696.js → chunk-vg9r4eb9.js} +24 -11
- package/dist/cli/chunk-vg9r4eb9.js.map +14 -0
- package/dist/cli/{chunk-79jhk4py.js → chunk-wrr3j9w9.js} +260 -132
- package/dist/cli/chunk-wrr3j9w9.js.map +35 -0
- package/dist/cli/{chunk-6crbhc3x.js → chunk-yfyb25rh.js} +54 -27
- package/dist/cli/chunk-yfyb25rh.js.map +14 -0
- package/dist/cli/index.js +162 -35
- package/dist/cli/index.js.map +4 -4
- package/dist/types/ai/agent-surface.d.ts +32 -0
- package/dist/types/ai/api-catalog.d.ts +7 -1
- package/dist/types/ai/ask-context.d.ts +7 -0
- package/dist/types/ai/component-markdown.d.ts +4 -4
- 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 +11 -4
- package/dist/types/ai/skills.d.ts +4 -1
- package/dist/types/ai/static-expression.d.ts +28 -0
- package/dist/types/ai/tar.d.ts +1 -3
- package/dist/types/analytics/databuddy.d.ts +43 -0
- package/dist/types/analytics/index.d.ts +4 -0
- package/dist/types/analytics/one-dollar-stats.d.ts +48 -0
- package/dist/types/analytics/schema.d.ts +28 -0
- package/dist/types/astro/integration.d.ts +3 -2
- package/dist/types/cli/env.d.ts +5 -0
- package/dist/types/cli/init/scaffold.d.ts +19 -3
- package/dist/types/cli/init/starter-spec.d.ts +11 -0
- package/dist/types/core/base-path.d.ts +24 -1
- package/dist/types/core/config-input.d.ts +13 -11
- package/dist/types/core/config.d.ts +2 -2
- package/dist/types/core/directive-diagnostics.d.ts +12 -0
- package/dist/types/core/graph.d.ts +2 -0
- package/dist/types/core/heading-markers.d.ts +5 -7
- package/dist/types/core/i18n-ui.d.ts +31 -0
- package/dist/types/core/i18n.d.ts +9 -1
- package/dist/types/core/last-modified.d.ts +10 -0
- package/dist/types/core/links.d.ts +3 -1
- package/dist/types/core/load-module.d.ts +10 -0
- package/dist/types/core/locale-links.d.ts +12 -2
- package/dist/types/core/meta.d.ts +13 -1
- package/dist/types/core/nav-diagnostics.d.ts +10 -0
- package/dist/types/core/navigation.d.ts +38 -0
- package/dist/types/core/ordering-prefix.d.ts +4 -0
- package/dist/types/core/safe-links.d.ts +3 -1
- package/dist/types/core/schema.d.ts +59 -13
- package/dist/types/core/sources/github-releases.d.ts +5 -0
- package/dist/types/core/sources/lower.d.ts +38 -13
- package/dist/types/core/sources/normalize.d.ts +23 -2
- package/dist/types/core/sources/remote.d.ts +11 -1
- package/dist/types/core/sources/resolve.d.ts +12 -0
- package/dist/types/core/sources/types.d.ts +31 -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 +9 -0
- package/dist/types/deploy/adapters/node.d.ts +5 -2
- package/dist/types/deploy/adapters/types.d.ts +7 -0
- package/dist/types/deploy/artifacts.d.ts +6 -4
- package/dist/types/deploy/cloudflare-negotiation.d.ts +3 -2
- package/dist/types/deploy/headers.d.ts +36 -7
- package/dist/types/deploy/node-headers.d.ts +43 -8
- package/dist/types/deploy/platforms/netlify.d.ts +27 -2
- package/dist/types/deploy/platforms/node.d.ts +6 -5
- package/dist/types/deploy/platforms/types.d.ts +15 -0
- package/dist/types/deploy/platforms/vercel.d.ts +3 -2
- package/dist/types/deploy/redirects.d.ts +31 -15
- package/dist/types/deploy/vercel-negotiation.d.ts +3 -2
- package/dist/types/markdown/directives.d.ts +62 -0
- package/dist/types/markdown/features.d.ts +21 -0
- package/dist/types/markdown/mdast.d.ts +63 -0
- package/dist/types/openapi/asyncapi.d.ts +4 -2
- package/dist/types/openapi/model.d.ts +20 -6
- package/dist/types/search/sync/algolia.d.ts +3 -1
- package/docs/01-quickstart.mdx +3 -2
- package/docs/02-deployment.mdx +20 -9
- package/docs/08-faq.mdx +10 -3
- package/docs/advanced/changelog.mdx +1 -1
- package/docs/advanced/custom-pages.mdx +7 -5
- package/docs/cli/audit.mdx +19 -3
- package/docs/cli/doctor.mdx +2 -2
- package/docs/cli/evals.mdx +4 -4
- package/docs/cli/index.mdx +4 -1
- package/docs/cli/translate.mdx +4 -4
- package/docs/cli/version.mdx +1 -1
- package/docs/configuration/analytics.mdx +42 -2
- package/docs/configuration/assistant.mdx +1 -1
- package/docs/configuration/customization.mdx +6 -3
- package/docs/configuration/index.mdx +5 -3
- package/docs/configuration/search.mdx +2 -2
- package/docs/content/components.mdx +2 -2
- package/docs/content/frontmatter.mdx +5 -1
- package/docs/content/i18n.mdx +3 -1
- package/docs/content/index.mdx +8 -4
- package/docs/content/islands.mdx +1 -1
- package/docs/content/meta.mdx +7 -3
- package/docs/content/navigation.mdx +29 -4
- package/docs/content/sources.mdx +18 -16
- package/docs/content/syntax.mdx +14 -0
- package/docs/content/versioning.mdx +2 -1
- package/docs/discoverability/agent-discovery.mdx +22 -8
- package/docs/discoverability/index.mdx +4 -3
- package/docs/discoverability/llms-txt.mdx +2 -5
- package/docs/discoverability/markdown.mdx +6 -4
- package/docs/discoverability/mcp.mdx +4 -0
- package/docs/discoverability/metadata.mdx +3 -2
- package/docs/discoverability/open-graph.mdx +8 -4
- package/docs/discoverability/rss.mdx +4 -2
- package/docs/references/asyncapi.mdx +2 -2
- package/docs/references/graphql.mdx +2 -2
- package/docs/references/openapi.mdx +6 -4
- package/package.json +1 -1
- package/skills/blume-migrate/SKILL.md +6 -6
- package/skills/blume-migrate/references/docusaurus.md +6 -6
- package/skills/blume-migrate/references/fumadocs.md +1 -1
- package/skills/blume-migrate/references/mintlify.md +3 -3
- package/skills/blume-migrate/references/monorepo.md +1 -1
- package/skills/blume-migrate/references/nextra.md +2 -2
- package/skills/blume-migrate/references/starlight.md +5 -5
- package/skills/blume-migrate/scripts/mintlify-codemod.mjs +9 -4
- package/skills/blume-update-docs/references/audit-checklist.md +1 -1
- package/src/ai/agent-readability.ts +2 -2
- package/src/ai/agent-surface.ts +56 -0
- package/src/ai/ai-catalog.ts +6 -2
- package/src/ai/api/handlers.ts +2 -2
- package/src/ai/api/spec.ts +17 -4
- package/src/ai/api-catalog.ts +13 -3
- package/src/ai/ask-context.ts +14 -2
- package/src/ai/ask-data.ts +26 -12
- package/src/ai/changelog-markdown.ts +2 -2
- package/src/ai/component-markdown.ts +43 -37
- package/src/ai/link-headers.ts +22 -8
- package/src/ai/llms.ts +22 -10
- package/src/ai/markdown.ts +23 -11
- package/src/ai/mcp/discovery.ts +2 -2
- package/src/ai/mcp/query.ts +2 -2
- package/src/ai/mcp/server.ts +80 -9
- package/src/ai/openapi-components.ts +22 -6
- package/src/ai/relative-links.ts +99 -15
- package/src/ai/serializers.ts +2 -1
- package/src/ai/skills.ts +14 -1
- package/src/ai/static-expression.ts +416 -0
- package/src/ai/tar.ts +139 -12
- package/src/ai/visibility.ts +45 -14
- package/src/analytics/databuddy.ts +67 -0
- package/src/analytics/head.ts +8 -0
- package/src/analytics/index.ts +7 -0
- package/src/analytics/one-dollar-stats.ts +86 -0
- package/src/analytics/posthog.ts +21 -3
- package/src/analytics/schema.ts +4 -0
- package/src/astro/generate.ts +72 -28
- package/src/astro/include-hmr.ts +31 -12
- package/src/astro/include-refresh.ts +19 -8
- package/src/astro/integration.ts +108 -14
- package/src/astro/runtime-modules.ts +28 -13
- package/src/astro/templates.ts +139 -57
- package/src/audit/catalog.ts +2 -2
- package/src/audit/checks/assets.ts +25 -5
- package/src/audit/checks/content.ts +20 -2
- package/src/audit/checks/i18n.ts +46 -8
- package/src/audit/checks/indexability.ts +39 -19
- package/src/audit/checks/links.ts +22 -1
- package/src/audit/checks/llms.ts +6 -3
- package/src/audit/checks/network.ts +3 -1
- package/src/audit/checks/og-image.ts +10 -0
- package/src/audit/checks/robots.ts +6 -1
- package/src/audit/checks/sitemap.ts +56 -31
- package/src/audit/checks/social.ts +21 -2
- package/src/audit/crawl.ts +88 -15
- package/src/audit/graph.ts +3 -1
- package/src/audit/report.ts +54 -17
- package/src/audit/run.ts +13 -9
- package/src/audit/snapshot.ts +5 -0
- package/src/audit/types.ts +18 -0
- package/src/audit/url.ts +37 -6
- package/src/cli/args.ts +32 -0
- package/src/cli/build-failure.ts +50 -0
- package/src/cli/commands/audit.ts +11 -1
- package/src/cli/commands/build.ts +19 -5
- package/src/cli/commands/check.ts +2 -0
- package/src/cli/commands/doctor.ts +3 -1
- package/src/cli/commands/eval.ts +20 -18
- package/src/cli/commands/init.ts +15 -5
- package/src/cli/commands/preview.ts +15 -0
- package/src/cli/commands/sync.ts +2 -0
- package/src/cli/commands/translate.ts +18 -18
- package/src/cli/commands/upgrade.ts +11 -0
- package/src/cli/commands/validate.ts +3 -1
- package/src/cli/dev-lock.ts +157 -37
- package/src/cli/eject-scripts.ts +32 -7
- package/src/cli/env.ts +12 -1
- package/src/cli/init/scaffold.ts +117 -11
- package/src/cli/init/starter-spec.ts +235 -0
- package/src/cli/report-format.ts +11 -6
- package/src/components/colors.ts +19 -0
- package/src/components/content/AccordionItem.astro +26 -22
- package/src/components/content/Badge.astro +7 -12
- package/src/components/content/Card.astro +2 -2
- package/src/components/content/Component.astro +11 -5
- package/src/components/content/Expandable.astro +5 -1
- package/src/components/content/Frame.astro +2 -7
- package/src/components/content/GithubInfo.astro +12 -2
- package/src/components/content/Prompt.astro +2 -7
- package/src/components/content/Tab.astro +0 -1
- package/src/components/content/Tabs.astro +58 -5
- package/src/components/content/Tile.astro +1 -1
- package/src/components/content/Tooltip.astro +69 -7
- package/src/components/content/Tree.astro +7 -2
- package/src/components/content/TypeTable.astro +10 -5
- package/src/components/content/Update.astro +8 -2
- package/src/components/content/auto-type-table.ts +4 -1
- package/src/components/content/badge-color.ts +20 -0
- package/src/components/content/base-href.ts +14 -22
- package/src/components/content/inline-markdown.ts +27 -7
- package/src/components/copy-feedback.ts +35 -8
- package/src/components/islands/assistant.tsx +37 -6
- package/src/components/islands/base-path.ts +47 -10
- package/src/components/islands/hooks.ts +42 -21
- package/src/components/islands/webmcp.ts +16 -10
- package/src/components/layout/Banner.astro +23 -4
- package/src/components/layout/Breadcrumbs.astro +5 -2
- package/src/components/layout/DiscoveryLinks.astro +9 -5
- package/src/components/layout/Header.astro +23 -10
- package/src/components/layout/LanguageSwitcher.astro +2 -2
- package/src/components/layout/Logo.astro +5 -0
- package/src/components/layout/NavSelector.astro +8 -3
- package/src/components/layout/NavTabMenu.astro +133 -0
- package/src/components/layout/NavTree.astro +31 -12
- package/src/components/layout/NavTreeCache.astro +5 -2
- package/src/components/layout/NavTreeScript.astro +45 -5
- package/src/components/layout/PageActions.astro +4 -3
- package/src/components/layout/PageLayout.astro +28 -13
- package/src/components/layout/Pagination.astro +3 -3
- package/src/components/layout/ReferenceLayout.astro +4 -1
- package/src/components/layout/RootLayout.astro +25 -12
- package/src/components/layout/Search.astro +38 -14
- package/src/components/layout/VersionBanner.astro +2 -2
- package/src/components/layout/analytics-client.ts +14 -0
- 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/GraphqlType.astro +11 -3
- package/src/components/openapi/MessageComposer.astro +1 -1
- package/src/components/openapi/Operation.astro +23 -5
- package/src/components/openapi/PanelTabs.astro +4 -1
- package/src/components/openapi/Playground.astro +8 -2
- package/src/components/openapi/RequestPanel.astro +13 -6
- package/src/components/openapi/SchemaProperty.astro +11 -33
- package/src/components/openapi/SchemaTable.astro +34 -52
- package/src/components/openapi/async.ts +38 -6
- package/src/components/openapi/helpers.ts +100 -12
- package/src/components/openapi/message-composer.ts +14 -1
- package/src/components/openapi/message-model.ts +12 -2
- package/src/components/openapi/message.ts +21 -3
- package/src/components/openapi/operation-model.ts +119 -17
- package/src/components/openapi/panel.ts +31 -5
- package/src/components/openapi/param-style.ts +181 -0
- package/src/components/openapi/playground-client.ts +110 -23
- package/src/components/openapi/playground-schema.ts +25 -6
- package/src/components/openapi/request.ts +192 -26
- package/src/components/openapi/schema-tree.ts +209 -0
- package/src/components/openapi/snippets.ts +113 -19
- package/src/components/openapi/validate-json.ts +1 -1
- package/src/components/openapi/ws-client.ts +18 -2
- package/src/core/base-path.ts +69 -8
- package/src/core/config-input.ts +13 -11
- package/src/core/config.ts +18 -4
- package/src/core/diagnostics.ts +219 -30
- package/src/core/directive-diagnostics.ts +99 -0
- package/src/core/frontmatter.ts +21 -18
- package/src/core/graph.ts +120 -11
- package/src/core/heading-markers.ts +5 -18
- package/src/core/i18n-ui.ts +39 -2
- package/src/core/i18n.ts +44 -5
- package/src/core/last-modified.ts +25 -3
- package/src/core/links.ts +7 -4
- package/src/core/load-module.ts +20 -0
- package/src/core/locale-links.ts +22 -18
- package/src/core/manifest.ts +3 -2
- package/src/core/meta.ts +89 -12
- package/src/core/nav-diagnostics.ts +56 -1
- package/src/core/navigation.ts +272 -83
- package/src/core/ordering-prefix.ts +27 -0
- package/src/core/project-graph.ts +50 -6
- package/src/core/safe-href.ts +53 -1
- package/src/core/safe-links.ts +11 -2
- package/src/core/schema.ts +94 -18
- package/src/core/sources/assets.ts +83 -41
- package/src/core/sources/contentful-rich-text.ts +28 -20
- package/src/core/sources/contentful.ts +25 -12
- package/src/core/sources/filesystem.ts +25 -3
- package/src/core/sources/github-releases.ts +131 -11
- package/src/core/sources/lexical.ts +30 -20
- package/src/core/sources/lower.ts +276 -56
- package/src/core/sources/mdx-remote.ts +52 -16
- package/src/core/sources/normalize.ts +456 -119
- package/src/core/sources/notion.ts +77 -34
- package/src/core/sources/obsidian.ts +40 -9
- package/src/core/sources/payload.ts +1 -0
- package/src/core/sources/portable-text.ts +85 -49
- package/src/core/sources/remote.ts +18 -2
- package/src/core/sources/resolve.ts +52 -33
- package/src/core/sources/sanity.ts +1 -0
- package/src/core/sources/strapi-blocks.ts +28 -19
- package/src/core/sources/strapi.ts +1 -0
- package/src/core/sources/types.ts +31 -0
- package/src/core/sources/watch.ts +20 -7
- package/src/core/standard-schema.ts +10 -6
- package/src/core/types.ts +9 -0
- package/src/core/ui-packs/ar.ts +13 -0
- package/src/core/ui-packs/bg.ts +13 -0
- package/src/core/ui-packs/bn.ts +13 -0
- package/src/core/ui-packs/ca.ts +13 -0
- package/src/core/ui-packs/cs.ts +13 -0
- package/src/core/ui-packs/da.ts +13 -0
- package/src/core/ui-packs/de.ts +13 -0
- package/src/core/ui-packs/el.ts +13 -0
- package/src/core/ui-packs/es.ts +13 -0
- package/src/core/ui-packs/fa.ts +13 -0
- package/src/core/ui-packs/fi.ts +13 -0
- package/src/core/ui-packs/fr.ts +13 -0
- package/src/core/ui-packs/he.ts +13 -0
- package/src/core/ui-packs/hi.ts +13 -0
- package/src/core/ui-packs/hr.ts +13 -0
- package/src/core/ui-packs/hu.ts +13 -0
- package/src/core/ui-packs/id.ts +13 -0
- package/src/core/ui-packs/it.ts +13 -0
- package/src/core/ui-packs/ja.ts +13 -0
- package/src/core/ui-packs/ko.ts +13 -0
- package/src/core/ui-packs/nl.ts +13 -0
- package/src/core/ui-packs/no.ts +13 -0
- package/src/core/ui-packs/pl.ts +13 -0
- package/src/core/ui-packs/pt-br.ts +13 -0
- package/src/core/ui-packs/pt.ts +13 -0
- package/src/core/ui-packs/ro.ts +13 -0
- package/src/core/ui-packs/ru.ts +13 -0
- package/src/core/ui-packs/sk.ts +13 -0
- package/src/core/ui-packs/sr.ts +13 -0
- package/src/core/ui-packs/sv.ts +13 -0
- package/src/core/ui-packs/th.ts +13 -0
- package/src/core/ui-packs/tr.ts +13 -0
- package/src/core/ui-packs/uk.ts +13 -0
- package/src/core/ui-packs/vi.ts +13 -0
- package/src/core/ui-packs/zh-tw.ts +13 -0
- package/src/core/ui-packs/zh.ts +13 -0
- package/src/core/version-cut.ts +69 -17
- package/src/core/versions.ts +4 -1
- package/src/deploy/adapters/node.ts +5 -2
- package/src/deploy/adapters/registry.ts +2 -1
- package/src/deploy/adapters/types.ts +13 -1
- package/src/deploy/artifacts.ts +52 -11
- package/src/deploy/cloudflare-negotiation.ts +27 -16
- package/src/deploy/headers.ts +75 -55
- package/src/deploy/node-headers.ts +148 -27
- package/src/deploy/platforms/cloudflare.ts +13 -3
- package/src/deploy/platforms/netlify.ts +83 -5
- package/src/deploy/platforms/node.ts +8 -5
- package/src/deploy/platforms/static.ts +2 -0
- package/src/deploy/platforms/types.ts +15 -0
- package/src/deploy/platforms/vercel.ts +10 -3
- package/src/deploy/redirects.ts +68 -21
- 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 +35 -3
- package/src/eval/agents.ts +10 -2
- package/src/eval/run.ts +25 -0
- package/src/markdown/base-links.ts +74 -29
- package/src/markdown/directives.ts +242 -36
- package/src/markdown/features.ts +17 -0
- package/src/markdown/index.ts +13 -9
- package/src/markdown/mdast.ts +5 -2
- package/src/markdown/relative-links.ts +15 -27
- package/src/markdown/route-snapshot.ts +37 -0
- package/src/og/card.ts +149 -9
- package/src/og/derive.ts +156 -5
- package/src/og/index.ts +1 -0
- package/src/openapi/asyncapi.ts +4 -2
- package/src/openapi/graphql-build.ts +28 -2
- package/src/openapi/model.ts +99 -27
- package/src/openapi/proxy.ts +63 -10
- package/src/openapi/render-mdx.ts +51 -3
- package/src/registry/eject.ts +297 -43
- package/src/search/adapters/version-scope.ts +30 -0
- package/src/search/documents.ts +89 -27
- package/src/search/popular.ts +2 -1
- package/src/search/sync/algolia.ts +36 -2
- package/src/seo/jsonld.ts +7 -3
- package/src/sources/registry.ts +5 -0
- package/src/theme/entry.ts +11 -4
- package/src/translate/agents.ts +6 -1
- package/src/translate/ledger.ts +26 -3
- package/src/translate/meta.ts +76 -24
- package/src/translate/run.ts +14 -8
- package/src/translate/validate.ts +14 -2
- package/src/translate/work-list.ts +91 -19
- package/src/upgrade/upgrade.ts +36 -4
- package/dist/cli/chunk-27g6wdth.js.map +0 -10
- package/dist/cli/chunk-2hn4b8z7.js.map +0 -12
- package/dist/cli/chunk-6crbhc3x.js.map +0 -14
- package/dist/cli/chunk-6hsn950k.js.map +0 -10
- package/dist/cli/chunk-79jhk4py.js.map +0 -35
- package/dist/cli/chunk-82bbrxdn.js +0 -51
- package/dist/cli/chunk-82bbrxdn.js.map +0 -10
- package/dist/cli/chunk-abh8yjkn.js +0 -31
- package/dist/cli/chunk-abh8yjkn.js.map +0 -10
- package/dist/cli/chunk-ce574jw2.js +0 -23
- package/dist/cli/chunk-ce574jw2.js.map +0 -10
- package/dist/cli/chunk-dh8cwk36.js.map +0 -10
- package/dist/cli/chunk-epjnccmv.js.map +0 -10
- package/dist/cli/chunk-fa25z98p.js.map +0 -11
- package/dist/cli/chunk-fs23ddbb.js.map +0 -35
- package/dist/cli/chunk-fz5wtpmh.js.map +0 -10
- package/dist/cli/chunk-jwyddg7y.js.map +0 -15
- package/dist/cli/chunk-kpf8rrjc.js.map +0 -19
- package/dist/cli/chunk-m3vmjgmq.js.map +0 -10
- package/dist/cli/chunk-mb2919y2.js.map +0 -10
- package/dist/cli/chunk-q5163e60.js.map +0 -11
- package/dist/cli/chunk-qkqwkpte.js.map +0 -182
- package/dist/cli/chunk-qs4q5p4e.js.map +0 -10
- package/dist/cli/chunk-s1p84fyh.js.map +0 -10
- package/dist/cli/chunk-wgm7m9qk.js.map +0 -36
- package/dist/cli/chunk-yt5n7ppj.js.map +0 -10
- package/dist/cli/chunk-yw7dm696.js.map +0 -14
- /package/dist/cli/{chunk-s6jhgk0q.js.map → chunk-2eytanqx.js.map} +0 -0
- /package/dist/cli/{chunk-hdpx1tax.js.map → chunk-2z928egk.js.map} +0 -0
- /package/dist/cli/{chunk-ah61y8py.js.map → chunk-364znk6q.js.map} +0 -0
- /package/dist/cli/{chunk-vtk4a6dg.js.map → chunk-8ktnccpt.js.map} +0 -0
- /package/dist/cli/{chunk-fxypxtvm.js.map → chunk-a58773jm.js.map} +0 -0
- /package/dist/cli/{chunk-wm7js3j9.js.map → chunk-b07cmahc.js.map} +0 -0
- /package/dist/cli/{chunk-zxcczpyx.js.map → chunk-r20tn01b.js.map} +0 -0
- /package/dist/cli/{chunk-5shv93fd.js.map → chunk-tkacnehg.js.map} +0 -0
- /package/dist/cli/{chunk-zxh4d9vy.js.map → chunk-tzmab476.js.map} +0 -0
|
@@ -39,8 +39,9 @@
|
|
|
39
39
|
*
|
|
40
40
|
* Cloudflare does not apply `_headers` to worker-first routes, so the wrapper
|
|
41
41
|
* also re-stamps what the static layer would otherwise add on the routes it
|
|
42
|
-
* takes over: the homepage agent-discovery `Link` header
|
|
43
|
-
* `charset=utf-8
|
|
42
|
+
* takes over: the homepage agent-discovery `Link` header, the Markdown
|
|
43
|
+
* `charset=utf-8`, and the sandbox on the SVGs a content source downloaded
|
|
44
|
+
* (see `deploy/headers.ts`). The raw `.md`/`.mdx` URLs are
|
|
44
45
|
* exempted from worker-first routing with negative rules, keeping their
|
|
45
46
|
* `_headers` treatment and their zero-Worker serving path.
|
|
46
47
|
*
|
|
@@ -83,7 +84,8 @@ import {
|
|
|
83
84
|
API_PAGES_PATH,
|
|
84
85
|
OPENAPI_PATH,
|
|
85
86
|
} from "../ai/api/paths.ts";
|
|
86
|
-
import { normalizePath } from "../core/base-path.ts";
|
|
87
|
+
import { normalizeBasePath, normalizePath } from "../core/base-path.ts";
|
|
88
|
+
import { CONTENT_ASSETS_ROOT, SVG_ASSET_HEADERS } from "./headers.ts";
|
|
87
89
|
import type { NotFoundVariants } from "./vercel-negotiation.ts";
|
|
88
90
|
|
|
89
91
|
/** Filename of the generated wrapper Worker, next to the adapter's entry. */
|
|
@@ -130,14 +132,6 @@ const STATIC_EXEMPTIONS = [
|
|
|
130
132
|
API_NAVIGATION_PATH,
|
|
131
133
|
];
|
|
132
134
|
|
|
133
|
-
/**
|
|
134
|
-
* The deployment base as a rule/URL prefix: trailing slash stripped, empty
|
|
135
|
-
* for a root deploy — the same normalization the dev middleware applies in
|
|
136
|
-
* `astro/markdown-negotiation.ts`.
|
|
137
|
-
*/
|
|
138
|
-
const basePrefix = (base?: string): string =>
|
|
139
|
-
base && base !== "/" ? base.replace(/\/$/u, "") : "";
|
|
140
|
-
|
|
141
135
|
/** A value as `JSON.parse` produces it (the wrangler config's value space). */
|
|
142
136
|
type JsonValue =
|
|
143
137
|
| string
|
|
@@ -178,7 +172,7 @@ const ruleBody = (rule: string): string =>
|
|
|
178
172
|
* status (see the module comment).
|
|
179
173
|
*/
|
|
180
174
|
export const buildRunWorkerFirstRules = (base?: string): string[] => {
|
|
181
|
-
const prefix = encodeURI(
|
|
175
|
+
const prefix = encodeURI(normalizeBasePath(base));
|
|
182
176
|
const exemptions = STATIC_EXEMPTIONS.map((path) => `!${prefix}${path}`);
|
|
183
177
|
return prefix
|
|
184
178
|
? [prefix, `${prefix}/*`, ...exemptions]
|
|
@@ -280,7 +274,7 @@ export const buildNegotiationWorker = (
|
|
|
280
274
|
const routes = JSON.stringify(options.routePaths);
|
|
281
275
|
const pageJson = JSON.stringify(options.pageJsonPaths ?? []);
|
|
282
276
|
const binding = JSON.stringify(options.assetsBinding);
|
|
283
|
-
const prefix = JSON.stringify(encodeURI(
|
|
277
|
+
const prefix = JSON.stringify(encodeURI(normalizeBasePath(options.base)));
|
|
284
278
|
const homeLinkHeader = JSON.stringify(options.homeLinkHeader ?? null);
|
|
285
279
|
const homeTokens = JSON.stringify(
|
|
286
280
|
options.homeTokens === undefined ? null : String(options.homeTokens)
|
|
@@ -314,8 +308,8 @@ export const buildNegotiationWorker = (
|
|
|
314
308
|
// request is delegated to the Astro Worker untouched — except a missing
|
|
315
309
|
// page, whose HTML 404 shell is swapped for the prerendered Markdown or JSON
|
|
316
310
|
// 404 twin when the client prefers one. \`_headers\` does not apply to
|
|
317
|
-
// worker-first routes, so the homepage Link header
|
|
318
|
-
// are re-stamped here.
|
|
311
|
+
// worker-first routes, so the homepage Link header, the Markdown charset, and
|
|
312
|
+
// the sandbox on downloaded SVGs are re-stamped here.
|
|
319
313
|
import server from ${JSON.stringify(options.mainSpecifier)};
|
|
320
314
|
|
|
321
315
|
const ROUTES = new Set(${routes});
|
|
@@ -326,6 +320,16 @@ const HOME_LINK_HEADER = ${homeLinkHeader};
|
|
|
326
320
|
const HOME_TOKENS = ${homeTokens};
|
|
327
321
|
const REDIRECTS = ${redirects};
|
|
328
322
|
const NOT_FOUND = ${notFound};
|
|
323
|
+
const SVG_ASSET_HEADERS = ${JSON.stringify(SVG_ASSET_HEADERS)};
|
|
324
|
+
|
|
325
|
+
// An SVG a content source downloaded: a document that could run script as the
|
|
326
|
+
// docs site if opened directly, so it goes out sandboxed. Matched on the
|
|
327
|
+
// decoded path, ignoring case — the file the assets binding serves.
|
|
328
|
+
const isSvgAsset = (pathname) => {
|
|
329
|
+
const path = safeDecode(pathname).toLowerCase();
|
|
330
|
+
const prefix = (safeDecode(BASE_PREFIX) + "${CONTENT_ASSETS_ROOT}/").toLowerCase();
|
|
331
|
+
return path.startsWith(prefix) && path.endsWith(".svg");
|
|
332
|
+
};
|
|
329
333
|
|
|
330
334
|
// Configured redirects live in \`_redirects\`, which only the static layer
|
|
331
335
|
// reads — a worker-first route never reaches it. Answering from this baked-in
|
|
@@ -526,6 +530,13 @@ export default {
|
|
|
526
530
|
status: redirect[1],
|
|
527
531
|
});
|
|
528
532
|
}
|
|
533
|
+
if (isSvgAsset(url.pathname)) {
|
|
534
|
+
return withHeaders(await server.fetch(request, env, context), (headers) => {
|
|
535
|
+
for (const [name, value] of Object.entries(SVG_ASSET_HEADERS)) {
|
|
536
|
+
headers.set(name, value);
|
|
537
|
+
}
|
|
538
|
+
});
|
|
539
|
+
}
|
|
529
540
|
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
530
541
|
return server.fetch(request, env, context);
|
|
531
542
|
}
|
|
@@ -661,7 +672,7 @@ export const injectWorkerNegotiation = (
|
|
|
661
672
|
// The content-route guard compares against redirect `from`s, which carry the
|
|
662
673
|
// full `{deployment.base}{basePath}` stack; the routes carry only
|
|
663
674
|
// `basePath`, so the deployment base is applied here.
|
|
664
|
-
const deployPrefix =
|
|
675
|
+
const deployPrefix = normalizeBasePath(options.base);
|
|
665
676
|
const guardRoutes = new Set(
|
|
666
677
|
contentRoutePaths.map((route) =>
|
|
667
678
|
normalizePath(
|
package/src/deploy/headers.ts
CHANGED
|
@@ -29,45 +29,44 @@ import type { ResolvedConfig } from "../core/schema.ts";
|
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
31
|
* One rule per served extension. `.mdx` uses `text/markdown` to match the
|
|
32
|
-
* runtime endpoint, which serves both variants as `text/markdown`.
|
|
33
|
-
*
|
|
34
|
-
* the
|
|
35
|
-
*
|
|
36
|
-
* `
|
|
32
|
+
* runtime endpoint, which serves both variants as `text/markdown`. Every rule
|
|
33
|
+
* sits at the deployment base, not under `basePath`: most Markdown mirrors
|
|
34
|
+
* live under the page routes, but the section home's mirror (`/docs.md`) sits
|
|
35
|
+
* beside `basePath`, and the synthesized `/index.md`, `/changelog.md`, and
|
|
36
|
+
* `/404.md` sit at the root, as do the `.txt` files (`llms.txt`,
|
|
37
|
+
* `llms-full.txt`).
|
|
37
38
|
*/
|
|
38
|
-
const HEADER_RULES: readonly {
|
|
39
|
-
contentType:
|
|
40
|
-
ext:
|
|
41
|
-
|
|
42
|
-
}[] = [
|
|
43
|
-
{
|
|
44
|
-
contentType: "text/markdown; charset=utf-8",
|
|
45
|
-
ext: "md",
|
|
46
|
-
underBasePath: true,
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
contentType: "text/markdown; charset=utf-8",
|
|
50
|
-
ext: "mdx",
|
|
51
|
-
underBasePath: true,
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
contentType: "text/plain; charset=utf-8",
|
|
55
|
-
ext: "txt",
|
|
56
|
-
underBasePath: false,
|
|
57
|
-
},
|
|
39
|
+
const HEADER_RULES: readonly { contentType: string; ext: string }[] = [
|
|
40
|
+
{ contentType: "text/markdown; charset=utf-8", ext: "md" },
|
|
41
|
+
{ contentType: "text/markdown; charset=utf-8", ext: "mdx" },
|
|
42
|
+
{ contentType: "text/plain; charset=utf-8", ext: "txt" },
|
|
58
43
|
];
|
|
59
44
|
|
|
60
45
|
/** Where the generated endpoint serves content-source images from. */
|
|
61
|
-
const CONTENT_ASSETS_ROOT = "/blume-assets";
|
|
46
|
+
export const CONTENT_ASSETS_ROOT = "/blume-assets";
|
|
62
47
|
|
|
63
48
|
/**
|
|
64
49
|
* The policy a content-source SVG is served with: an opaque origin with
|
|
65
50
|
* scripts off, so an uploaded SVG that carries a `<script>` stays inert when
|
|
66
|
-
* opened directly.
|
|
67
|
-
* {@link headerRules} carries it to static hosts.
|
|
51
|
+
* opened directly.
|
|
68
52
|
*/
|
|
69
53
|
export const SVG_ASSET_POLICY = "sandbox";
|
|
70
54
|
|
|
55
|
+
/**
|
|
56
|
+
* The headers a content-source SVG is served with: the sandbox policy, and no
|
|
57
|
+
* MIME sniffing. The endpoint sends them from the dev runtime; every host
|
|
58
|
+
* gets them for the built files — static hosts through {@link headerRules},
|
|
59
|
+
* server builds through each platform's own mechanism (`deploy/platforms`).
|
|
60
|
+
*/
|
|
61
|
+
export const SVG_ASSET_HEADERS = {
|
|
62
|
+
"Content-Security-Policy": SVG_ASSET_POLICY,
|
|
63
|
+
"X-Content-Type-Options": "nosniff",
|
|
64
|
+
} satisfies Record<string, string>;
|
|
65
|
+
|
|
66
|
+
/** The served glob of content-source SVGs, under the deployment base. */
|
|
67
|
+
export const svgAssetPath = (config: ResolvedConfig): string =>
|
|
68
|
+
`${normalizeBasePath(config.deployment.options.base)}${CONTENT_ASSETS_ROOT}/*.svg`;
|
|
69
|
+
|
|
71
70
|
/**
|
|
72
71
|
* One static-host header rule: a served path (a `*` glob spanning path
|
|
73
72
|
* segments, or an exact path) and the header it sets there.
|
|
@@ -80,11 +79,9 @@ export interface HeaderRule {
|
|
|
80
79
|
|
|
81
80
|
/**
|
|
82
81
|
* The header rules every static host should apply, in `_headers` glob syntax.
|
|
83
|
-
* The glob carries the
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
* wildcard spans path segments, so a nested route like `/docs/ja/intro.md`
|
|
87
|
-
* matches too.
|
|
82
|
+
* The glob carries the deployment base so the rules still match once the site
|
|
83
|
+
* is mounted under a subpath (`/docs/*.md`); the wildcard spans path
|
|
84
|
+
* segments, so a nested route like `/docs/ja/intro.md` matches too.
|
|
88
85
|
*
|
|
89
86
|
* When a homepage `Link` header is provided (see `ai/link-headers.ts`), an
|
|
90
87
|
* exact-path rule for the root page advertises the agent-discovery resources —
|
|
@@ -95,16 +92,11 @@ export const headerRules = (
|
|
|
95
92
|
homeLinkHeader?: string | null
|
|
96
93
|
): HeaderRule[] => {
|
|
97
94
|
const deployBase = normalizeBasePath(config.deployment.options.base);
|
|
98
|
-
const rules: HeaderRule[] = HEADER_RULES.map((rule) => {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
name: "Content-Type",
|
|
104
|
-
path: `${prefix}/*.${rule.ext}`,
|
|
105
|
-
value: rule.contentType,
|
|
106
|
-
};
|
|
107
|
-
});
|
|
95
|
+
const rules: HeaderRule[] = HEADER_RULES.map((rule) => ({
|
|
96
|
+
name: "Content-Type",
|
|
97
|
+
path: `${deployBase}/*.${rule.ext}`,
|
|
98
|
+
value: rule.contentType,
|
|
99
|
+
}));
|
|
108
100
|
if (homeLinkHeader) {
|
|
109
101
|
rules.push({ name: "Link", path: `${deployBase}/`, value: homeLinkHeader });
|
|
110
102
|
}
|
|
@@ -134,9 +126,8 @@ export const headerRules = (
|
|
|
134
126
|
value: "*",
|
|
135
127
|
});
|
|
136
128
|
}
|
|
137
|
-
// Published skills live at the deployment base,
|
|
138
|
-
//
|
|
139
|
-
// RFC wants archives served as application/gzip explicitly.
|
|
129
|
+
// Published skills live at the deployment base, and the RFC wants their
|
|
130
|
+
// archives served as application/gzip explicitly.
|
|
140
131
|
if (config.agents.skills) {
|
|
141
132
|
rules.push(
|
|
142
133
|
{
|
|
@@ -155,11 +146,9 @@ export const headerRules = (
|
|
|
155
146
|
// are served from the docs origin, and an SVG opened directly is a document
|
|
156
147
|
// that can run script. A sandbox policy keeps it inert; an `<img>` that
|
|
157
148
|
// embeds it is unaffected.
|
|
158
|
-
|
|
159
|
-
name:
|
|
160
|
-
|
|
161
|
-
value: SVG_ASSET_POLICY,
|
|
162
|
-
});
|
|
149
|
+
for (const [name, value] of Object.entries(SVG_ASSET_HEADERS)) {
|
|
150
|
+
rules.push({ name, path: svgAssetPath(config), value });
|
|
151
|
+
}
|
|
163
152
|
return rules;
|
|
164
153
|
};
|
|
165
154
|
|
|
@@ -175,6 +164,33 @@ export const buildNetlifyHeaders = (
|
|
|
175
164
|
.map((rule) => `${rule.path}\n ${rule.name}: ${rule.value}`)
|
|
176
165
|
.join("\n")}\n`;
|
|
177
166
|
|
|
167
|
+
/** One entry of Netlify's Frameworks API config `headers` array. */
|
|
168
|
+
export interface NetlifyConfigHeaders {
|
|
169
|
+
for: string;
|
|
170
|
+
values: Record<string, string>;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* The {@link headerRules} as Netlify Frameworks API `headers` entries
|
|
175
|
+
* (`.netlify/v1/config.json`), one per path with every header it sets. A
|
|
176
|
+
* `netlify()` server build reads no `_headers` file, so it carries the same
|
|
177
|
+
* rules here, in the `for`/`values` form that follows `netlify.toml`'s
|
|
178
|
+
* `[[headers]]` and the same path syntax as `_headers`.
|
|
179
|
+
*/
|
|
180
|
+
export const buildNetlifyConfigHeaders = (
|
|
181
|
+
config: ResolvedConfig,
|
|
182
|
+
homeLinkHeader?: string | null
|
|
183
|
+
): NetlifyConfigHeaders[] => {
|
|
184
|
+
const byPath = new Map<string, Record<string, string>>();
|
|
185
|
+
for (const rule of headerRules(config, homeLinkHeader)) {
|
|
186
|
+
byPath.set(rule.path, {
|
|
187
|
+
...byPath.get(rule.path),
|
|
188
|
+
[rule.name]: rule.value,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
return [...byPath].map(([path, values]) => ({ for: path, values }));
|
|
192
|
+
};
|
|
193
|
+
|
|
178
194
|
/** One entry of `vercel.json`'s `headers` array. */
|
|
179
195
|
export interface VercelHeader {
|
|
180
196
|
headers: { key: string; value: string }[];
|
|
@@ -184,6 +200,13 @@ export interface VercelHeader {
|
|
|
184
200
|
/** The characters `path-to-regexp` would read as syntax in a literal path. */
|
|
185
201
|
const SOURCE_SYNTAX = /[\\(){}?+:]/gu;
|
|
186
202
|
|
|
203
|
+
/**
|
|
204
|
+
* A literal path as `path-to-regexp` source text — what a `vercel.json`
|
|
205
|
+
* `source` is — with each syntax character escaped, so it matches only itself.
|
|
206
|
+
*/
|
|
207
|
+
export const escapeVercelSource = (path: string): string =>
|
|
208
|
+
path.replaceAll(SOURCE_SYNTAX, String.raw`\$&`);
|
|
209
|
+
|
|
187
210
|
/**
|
|
188
211
|
* A `_headers` path as a `vercel.json` `source`, which `path-to-regexp` reads:
|
|
189
212
|
* the literal parts are escaped, and a `*` glob becomes the `(.*)` group that
|
|
@@ -193,10 +216,7 @@ const SOURCE_SYNTAX = /[\\(){}?+:]/gu;
|
|
|
193
216
|
const vercelSource = (path: string): string => {
|
|
194
217
|
const trimmed =
|
|
195
218
|
path.length > 1 && path.endsWith("/") ? path.slice(0, -1) : path;
|
|
196
|
-
return trimmed
|
|
197
|
-
.split("*")
|
|
198
|
-
.map((part) => part.replaceAll(SOURCE_SYNTAX, String.raw`\$&`))
|
|
199
|
-
.join("(.*)");
|
|
219
|
+
return trimmed.split("*").map(escapeVercelSource).join("(.*)");
|
|
200
220
|
};
|
|
201
221
|
|
|
202
222
|
/**
|
|
@@ -13,12 +13,13 @@ import {
|
|
|
13
13
|
SIGNATURES_DIRECTORY_PATH,
|
|
14
14
|
SIGNATURES_DIRECTORY_TYPE,
|
|
15
15
|
} from "../ai/web-bot-auth.ts";
|
|
16
|
-
import { normalizeBasePath } from "../core/base-path.ts";
|
|
16
|
+
import { normalizeBasePath, normalizePath } from "../core/base-path.ts";
|
|
17
17
|
import type { BlumeProject } from "../core/project-graph.ts";
|
|
18
18
|
import type { ResolvedConfig } from "../core/schema.ts";
|
|
19
|
-
import {
|
|
19
|
+
import { SVG_ASSET_HEADERS, svgAssetPath } from "./headers.ts";
|
|
20
20
|
import { distDir } from "./platforms/paths.ts";
|
|
21
21
|
import type { BuildLog } from "./platforms/types.ts";
|
|
22
|
+
import { platformRedirects } from "./redirects.ts";
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
25
|
* The response headers the Node standalone server can't set itself. Its static
|
|
@@ -29,6 +30,12 @@ import type { BuildLog } from "./platforms/types.ts";
|
|
|
29
30
|
* (`deploy/headers.ts`) that a Node server has no file to read from. The Markdown
|
|
30
31
|
* and text charsets need no rule here: `send` already adds `charset=UTF-8` to
|
|
31
32
|
* `text/*` types.
|
|
33
|
+
*
|
|
34
|
+
* The same wrapper answers the configured redirects. Astro's own handler
|
|
35
|
+
* honors a redirect's configured status only when its destination resolves
|
|
36
|
+
* to a discrete route, and Blume serves every page from `[...slug]`, so a
|
|
37
|
+
* `302` or `307` would go out as a `301` or `308` — permanent redirects that
|
|
38
|
+
* browsers cache.
|
|
32
39
|
*/
|
|
33
40
|
|
|
34
41
|
/** The headers one exact served path (deployment base included) gets. */
|
|
@@ -93,35 +100,130 @@ const patternRules = (
|
|
|
93
100
|
});
|
|
94
101
|
|
|
95
102
|
/**
|
|
96
|
-
* The
|
|
97
|
-
*
|
|
98
|
-
|
|
103
|
+
* The configured redirects the wrapper answers, keyed by served path (the
|
|
104
|
+
* trailing slash trimmed), each to its `Location` and exact status.
|
|
105
|
+
*/
|
|
106
|
+
export type NodeRedirects = Record<string, [string, number]>;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The configured redirects, based the way the host matches them (see
|
|
110
|
+
* `platformRedirects`) and keyed for the wrapper's lookup, the `Location`
|
|
111
|
+
* percent-encoded as Astro sends it. A redirect at a content route's own path
|
|
112
|
+
* is left out: the prerendered page owns that URL, and the static handler
|
|
113
|
+
* serves it before Astro's redirect ever could.
|
|
114
|
+
*/
|
|
115
|
+
export const nodeRedirects = (project: BlumeProject): NodeRedirects => {
|
|
116
|
+
const { config } = project;
|
|
117
|
+
if (config.redirects.length === 0) {
|
|
118
|
+
return {};
|
|
119
|
+
}
|
|
120
|
+
const base = normalizeBasePath(config.deployment.options.base);
|
|
121
|
+
const pages = new Set(
|
|
122
|
+
project.manifest.routes.map((route) =>
|
|
123
|
+
normalizePath(`${base}${route.path}`)
|
|
124
|
+
)
|
|
125
|
+
);
|
|
126
|
+
return Object.fromEntries(
|
|
127
|
+
platformRedirects(project)
|
|
128
|
+
.filter((redirect) => !pages.has(normalizePath(redirect.from)))
|
|
129
|
+
.map((redirect) => [
|
|
130
|
+
normalizePath(redirect.from),
|
|
131
|
+
[encodeURI(redirect.to), redirect.status],
|
|
132
|
+
])
|
|
133
|
+
);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
/** What the wrapper needs beyond the header rules. */
|
|
137
|
+
export interface NodeEntryOptions {
|
|
138
|
+
/** `deployment.base`, normalized (`""` at the root). */
|
|
139
|
+
base?: string;
|
|
140
|
+
/** The configured redirects, from {@link nodeRedirects}. */
|
|
141
|
+
redirects?: NodeRedirects;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* The entry that replaces Astro's: it answers a configured redirect with its
|
|
146
|
+
* exact status, and otherwise stamps the rules' headers on the response and
|
|
147
|
+
* hands the request to Astro's handler. `send` leaves a Content-Type that is
|
|
148
|
+
* already set alone, so the registered media type wins.
|
|
149
|
+
*
|
|
150
|
+
* A rule matches the file the static handler will serve, not the raw URL:
|
|
151
|
+
* Astro strips the base when it leads the path and serves the rest from the
|
|
152
|
+
* client directory either way, and `send` decodes and normalizes the path —
|
|
153
|
+
* so `/docs/blume-assets/a.sv%67`, `/blume-assets/a.svg`, and
|
|
154
|
+
* `/docs//x/../blume-assets/a.svg` are all the same SVG. The comparison
|
|
155
|
+
* ignores case, as a case-insensitive disk and `send`'s MIME lookup do.
|
|
99
156
|
*
|
|
100
157
|
* It keeps Astro's contract — the `handler`, `options`, and `startServer`
|
|
101
158
|
* exports `astro preview` and a middleware-mode host import — and its
|
|
102
159
|
* autostart: Astro's entry is imported with autostart off (the env var is read
|
|
103
160
|
* when that module evaluates, so the import has to be dynamic), and the
|
|
104
|
-
* wrapper starts the server itself
|
|
161
|
+
* wrapper starts the server itself, taking over its request listener so a
|
|
162
|
+
* redirect is answered before Astro sees the request.
|
|
105
163
|
*/
|
|
106
|
-
export const nodeEntryWrapper = (
|
|
164
|
+
export const nodeEntryWrapper = (
|
|
165
|
+
rules: readonly NodeHeaderRule[],
|
|
166
|
+
options: NodeEntryOptions = {}
|
|
167
|
+
): string =>
|
|
107
168
|
`${WRAPPER_MARKER}
|
|
108
169
|
// Written by \`blume build\`: Astro's server entry is ./${NODE_ASTRO_ENTRY_FILE}.
|
|
109
|
-
// This wrapper
|
|
110
|
-
//
|
|
111
|
-
//
|
|
170
|
+
// This wrapper answers the configured redirects with their exact status and
|
|
171
|
+
// sets the headers the standalone server's static handler can't (the media
|
|
172
|
+
// types and CORS headers of the .well-known discovery files, the sandbox on
|
|
173
|
+
// downloaded SVGs), then hands every other request to Astro.
|
|
174
|
+
import { posix } from "node:path";
|
|
175
|
+
|
|
176
|
+
const BASE = ${JSON.stringify(options.base ?? "")};
|
|
112
177
|
const RULES = new Map(${JSON.stringify(exactRules(rules))});
|
|
113
178
|
const PATTERNS = ${JSON.stringify(patternRules(rules))};
|
|
179
|
+
const REDIRECTS = ${JSON.stringify(options.redirects ?? {})};
|
|
180
|
+
const safeDecode = (path) => {
|
|
181
|
+
try {
|
|
182
|
+
return decodeURIComponent(path);
|
|
183
|
+
} catch {
|
|
184
|
+
return path;
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
const urlPath = (req) => (req.url ?? "").split("#")[0].split("?")[0];
|
|
188
|
+
// The file the static handler serves for a request, spelled with the base.
|
|
189
|
+
const servedPath = (req) => {
|
|
190
|
+
const path = posix.normalize("/" + safeDecode(urlPath(req)));
|
|
191
|
+
const rest =
|
|
192
|
+
BASE && (path === BASE || path.startsWith(BASE + "/"))
|
|
193
|
+
? path.slice(BASE.length)
|
|
194
|
+
: path;
|
|
195
|
+
return BASE + (rest || "/");
|
|
196
|
+
};
|
|
114
197
|
const applyHeaders = (req, res) => {
|
|
115
|
-
const path = (req
|
|
198
|
+
const path = servedPath(req);
|
|
199
|
+
const lower = path.toLowerCase();
|
|
116
200
|
const headers =
|
|
117
201
|
RULES.get(path) ??
|
|
118
|
-
PATTERNS.find(
|
|
202
|
+
PATTERNS.find(
|
|
203
|
+
([prefix, suffix]) =>
|
|
204
|
+
lower.startsWith(prefix.toLowerCase()) &&
|
|
205
|
+
lower.endsWith(suffix.toLowerCase())
|
|
206
|
+
)?.[2];
|
|
119
207
|
if (headers) {
|
|
120
208
|
for (const [name, value] of Object.entries(headers)) {
|
|
121
209
|
res.setHeader(name, value);
|
|
122
210
|
}
|
|
123
211
|
}
|
|
124
212
|
};
|
|
213
|
+
// Answer a configured redirect with its exact status; Astro's handler would
|
|
214
|
+
// send a 301 (a 308 for other methods) whatever the configured one.
|
|
215
|
+
const answerRedirect = (req, res) => {
|
|
216
|
+
const path = urlPath(req);
|
|
217
|
+
const trimmed = path !== "/" && path.endsWith("/") ? path.slice(0, -1) : path;
|
|
218
|
+
const key = safeDecode(trimmed);
|
|
219
|
+
if (!Object.hasOwn(REDIRECTS, key)) {
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
const [location, status] = REDIRECTS[key];
|
|
223
|
+
res.writeHead(status, { location });
|
|
224
|
+
res.end();
|
|
225
|
+
return true;
|
|
226
|
+
};
|
|
125
227
|
const previous = process.env.ASTRO_NODE_AUTOSTART;
|
|
126
228
|
process.env.ASTRO_NODE_AUTOSTART = "disabled";
|
|
127
229
|
const astro = await import("./${NODE_ASTRO_ENTRY_FILE}");
|
|
@@ -132,17 +234,30 @@ if (previous === undefined) {
|
|
|
132
234
|
}
|
|
133
235
|
export const { options } = astro;
|
|
134
236
|
export const handler = (req, res, ...rest) => {
|
|
237
|
+
if (answerRedirect(req, res)) {
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
135
240
|
applyHeaders(req, res);
|
|
136
241
|
return astro.handler(req, res, ...rest);
|
|
137
242
|
};
|
|
138
243
|
export const startServer = () => {
|
|
139
244
|
const started = astro.startServer();
|
|
140
|
-
//
|
|
141
|
-
// expected; a different shape serves
|
|
142
|
-
// failing to start.
|
|
245
|
+
// Take over the adapter's request listener only where it exposes its HTTP
|
|
246
|
+
// server as expected; a different shape serves without the headers and
|
|
247
|
+
// redirect statuses rather than failing to start.
|
|
143
248
|
const httpServer = started?.server?.server;
|
|
144
|
-
if (typeof httpServer?.
|
|
145
|
-
httpServer.
|
|
249
|
+
if (typeof httpServer?.listeners === "function") {
|
|
250
|
+
const astroListeners = httpServer.listeners("request");
|
|
251
|
+
httpServer.removeAllListeners("request");
|
|
252
|
+
httpServer.on("request", (req, res) => {
|
|
253
|
+
if (answerRedirect(req, res)) {
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
applyHeaders(req, res);
|
|
257
|
+
for (const listener of astroListeners) {
|
|
258
|
+
listener.call(httpServer, req, res);
|
|
259
|
+
}
|
|
260
|
+
});
|
|
146
261
|
}
|
|
147
262
|
return started;
|
|
148
263
|
};
|
|
@@ -152,9 +267,9 @@ if (options.mode === "standalone" && previous !== "disabled") {
|
|
|
152
267
|
`;
|
|
153
268
|
|
|
154
269
|
/**
|
|
155
|
-
* Put the
|
|
156
|
-
*
|
|
157
|
-
*
|
|
270
|
+
* Put the wrapper in front of a Node server build's entry. A build with no
|
|
271
|
+
* rule (no API catalog, AI catalog, MCP server, signatures directory, or
|
|
272
|
+
* downloaded content assets) and no redirect leaves Astro's entry alone.
|
|
158
273
|
*/
|
|
159
274
|
export const wrapNodeEntry = async (
|
|
160
275
|
project: BlumeProject,
|
|
@@ -169,20 +284,21 @@ export const wrapNodeEntry = async (
|
|
|
169
284
|
...(existsSync(assetsDir)
|
|
170
285
|
? [
|
|
171
286
|
{
|
|
172
|
-
headers: {
|
|
173
|
-
path:
|
|
287
|
+
headers: { ...SVG_ASSET_HEADERS },
|
|
288
|
+
path: svgAssetPath(project.config),
|
|
174
289
|
},
|
|
175
290
|
]
|
|
176
291
|
: []),
|
|
177
292
|
];
|
|
178
|
-
|
|
293
|
+
const redirects = nodeRedirects(project);
|
|
294
|
+
if (rules.length === 0 && Object.keys(redirects).length === 0) {
|
|
179
295
|
return;
|
|
180
296
|
}
|
|
181
297
|
const serverDir = join(distDir(project.context), "server");
|
|
182
298
|
const entry = join(serverDir, NODE_ENTRY_FILE);
|
|
183
299
|
if (!existsSync(entry)) {
|
|
184
300
|
log.warn(
|
|
185
|
-
`Could not find the Node server entry at ${entry}, so the
|
|
301
|
+
`Could not find the Node server entry at ${entry}, so the server runs without the headers and redirect statuses Blume sets there.`
|
|
186
302
|
);
|
|
187
303
|
return;
|
|
188
304
|
}
|
|
@@ -191,8 +307,13 @@ export const wrapNodeEntry = async (
|
|
|
191
307
|
return;
|
|
192
308
|
}
|
|
193
309
|
await rename(entry, join(serverDir, NODE_ASTRO_ENTRY_FILE));
|
|
194
|
-
await writeFile(
|
|
195
|
-
|
|
196
|
-
|
|
310
|
+
await writeFile(
|
|
311
|
+
entry,
|
|
312
|
+
nodeEntryWrapper(rules, {
|
|
313
|
+
base: normalizeBasePath(project.config.deployment.options.base),
|
|
314
|
+
redirects,
|
|
315
|
+
}),
|
|
316
|
+
"utf-8"
|
|
197
317
|
);
|
|
318
|
+
log.success("Wired headers and redirects into the Node server entry");
|
|
198
319
|
};
|
|
@@ -11,6 +11,7 @@ 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";
|
|
16
17
|
import { CLOUDFLARE_ADAPTER_PACKAGE } from "../adapters/cloudflare.ts";
|
|
@@ -90,8 +91,13 @@ export const emitCloudflareNegotiation = async (
|
|
|
90
91
|
const home = rawMarkdown["/"];
|
|
91
92
|
// The 404 twins the wrapper may substitute for the HTML shell; only wired
|
|
92
93
|
// when the build actually emitted them (a project that owns `/404` gets
|
|
93
|
-
// neither), like the Vercel routing config.
|
|
94
|
-
|
|
94
|
+
// neither), like the Vercel routing config. The adapter writes the
|
|
95
|
+
// prerendered files under the deployment base (`dist/client/<base>/`),
|
|
96
|
+
// where the wrapper fetches them from.
|
|
97
|
+
const staticDir = join(
|
|
98
|
+
clientDir(context),
|
|
99
|
+
normalizeBasePath(config.deployment.options.base)
|
|
100
|
+
);
|
|
95
101
|
const injected = injectWorkerNegotiation(
|
|
96
102
|
await readFile(wranglerPath, "utf-8"),
|
|
97
103
|
{
|
|
@@ -119,7 +125,7 @@ export const emitCloudflareNegotiation = async (
|
|
|
119
125
|
// based the same way the platform files are — it answers any the
|
|
120
126
|
// worker-first rules claim, where `_redirects` is never consulted and
|
|
121
127
|
// Astro would default their status.
|
|
122
|
-
redirects: platformRedirects(
|
|
128
|
+
redirects: platformRedirects(project),
|
|
123
129
|
routePaths,
|
|
124
130
|
}
|
|
125
131
|
);
|
|
@@ -305,8 +311,12 @@ export const cloudflarePlatform: DeployPlatform = {
|
|
|
305
311
|
},
|
|
306
312
|
kind: "cloudflare",
|
|
307
313
|
negotiatesMarkdown: true,
|
|
314
|
+
previewDeploy: null,
|
|
308
315
|
readsHeaderFiles: { server: true, static: true },
|
|
309
316
|
redirectFiles: [REDIRECTS_FILE],
|
|
317
|
+
// The adapter points `build.client` at `dist/client/<base>/` for a server
|
|
318
|
+
// build, and hoists its own `_headers`/`_redirects` back up to `dist/client`.
|
|
319
|
+
serverClientUnderBase: true,
|
|
310
320
|
serverOutputDir: distDir,
|
|
311
321
|
serverStaticDir: clientDir,
|
|
312
322
|
};
|