blume 2.0.0 → 2.0.2
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 -1
- package/CHANGELOG.md +110 -0
- package/README.md +2 -2
- package/dist/cli/{chunk-f2972sbt.js → chunk-00gs3wqs.js} +1 -1
- package/dist/cli/{chunk-fa25z98p.js → chunk-273ygyr4.js} +4 -3
- package/dist/cli/chunk-273ygyr4.js.map +11 -0
- package/dist/cli/{chunk-pat2zzwc.js → chunk-3yce002v.js} +21 -8
- package/dist/cli/{chunk-pat2zzwc.js.map → chunk-3yce002v.js.map} +4 -4
- package/dist/cli/{chunk-d1tadaw7.js → chunk-4e9b9ra6.js} +17 -7
- package/dist/cli/chunk-4e9b9ra6.js.map +10 -0
- package/dist/cli/{chunk-zg2gtj10.js → chunk-5r8g91qn.js} +352 -676
- package/dist/cli/chunk-5r8g91qn.js.map +34 -0
- package/dist/cli/{chunk-11j0384y.js → chunk-6dtt0zfn.js} +14 -14
- package/dist/cli/{chunk-11j0384y.js.map → chunk-6dtt0zfn.js.map} +3 -3
- package/dist/cli/{chunk-bw22s759.js → chunk-6k4ftwze.js} +38 -19
- package/dist/cli/{chunk-bw22s759.js.map → chunk-6k4ftwze.js.map} +4 -4
- package/dist/cli/{chunk-sqn5t4q0.js → chunk-7mbqtmgb.js} +16 -7
- package/dist/cli/chunk-7mbqtmgb.js.map +10 -0
- package/dist/cli/{chunk-79njf86q.js → chunk-7vtckvaw.js} +21 -19
- package/dist/cli/chunk-7vtckvaw.js.map +11 -0
- package/dist/cli/{chunk-fh5hj5jt.js → chunk-8g8ytmgx.js} +17 -18
- package/dist/cli/{chunk-fh5hj5jt.js.map → chunk-8g8ytmgx.js.map} +2 -2
- package/dist/cli/{chunk-1w8dp3qb.js → chunk-91ws1n6j.js} +18 -15
- package/dist/cli/chunk-91ws1n6j.js.map +10 -0
- package/dist/cli/{chunk-7ez8ny0t.js → chunk-bbnwccaz.js} +2 -2
- package/dist/cli/{chunk-5a2z0198.js → chunk-bfwp9vp6.js} +18 -10
- package/dist/cli/chunk-bfwp9vp6.js.map +10 -0
- package/dist/cli/{chunk-ernrthtr.js → chunk-d1v5rhy0.js} +14 -15
- package/dist/cli/{chunk-ernrthtr.js.map → chunk-d1v5rhy0.js.map} +2 -2
- package/dist/cli/{chunk-a9kptbw5.js → chunk-ddndchfr.js} +21 -6
- package/dist/cli/chunk-ddndchfr.js.map +14 -0
- package/dist/cli/{chunk-zxccj738.js → chunk-esh98wmb.js} +1 -1
- package/dist/cli/{chunk-88cpgt6h.js → chunk-fsmrqk8a.js} +1 -1
- package/dist/cli/{chunk-b5aj94ah.js → chunk-g698a744.js} +5 -5
- package/dist/cli/{chunk-jts8mvcz.js → chunk-gs7r695n.js} +9 -3
- package/dist/cli/{chunk-jts8mvcz.js.map → chunk-gs7r695n.js.map} +3 -3
- package/dist/cli/{chunk-41za066z.js → chunk-h2ez8dzb.js} +4 -4
- package/dist/cli/{chunk-by2290sx.js → chunk-h7k3nq3v.js} +2 -2
- package/dist/cli/{chunk-j8mw0za6.js → chunk-hqp2ajnh.js} +252 -123
- package/dist/cli/chunk-hqp2ajnh.js.map +35 -0
- package/dist/cli/{chunk-mnqj32sj.js → chunk-hr8ne106.js} +119 -50
- package/dist/cli/chunk-hr8ne106.js.map +13 -0
- package/dist/cli/{chunk-mwt1k8n7.js → chunk-j85scx15.js} +74 -30
- package/dist/cli/chunk-j85scx15.js.map +10 -0
- package/dist/cli/{chunk-z01ze5c1.js → chunk-k7pj68a8.js} +63 -22
- package/dist/cli/chunk-k7pj68a8.js.map +11 -0
- package/dist/cli/{chunk-2q1dwty4.js → chunk-mqc662a6.js} +8 -8
- package/dist/cli/{chunk-2q1dwty4.js.map → chunk-mqc662a6.js.map} +4 -4
- package/dist/cli/{chunk-y3e45rc8.js → chunk-n1yg3tj3.js} +4 -4
- package/dist/cli/{chunk-bctazmbk.js → chunk-nfcyttvj.js} +17 -6
- package/dist/cli/chunk-nfcyttvj.js.map +10 -0
- package/dist/cli/{chunk-6k8vp3ta.js → chunk-pbg5a4s3.js} +60 -28
- package/dist/cli/chunk-pbg5a4s3.js.map +19 -0
- package/dist/cli/{chunk-d80hr03s.js → chunk-ppzjqwx2.js} +24 -17
- package/dist/cli/{chunk-d80hr03s.js.map → chunk-ppzjqwx2.js.map} +4 -4
- package/dist/cli/{chunk-beat36xx.js → chunk-qkb5a8sa.js} +25 -10
- package/dist/cli/chunk-qkb5a8sa.js.map +10 -0
- package/dist/cli/{chunk-bnbmcwfb.js → chunk-sqw4ekg1.js} +5 -5
- package/dist/cli/{chunk-bnbmcwfb.js.map → chunk-sqw4ekg1.js.map} +3 -3
- package/dist/cli/{chunk-xaz13gwg.js → chunk-v6ya5kcb.js} +3268 -1150
- package/dist/cli/chunk-v6ya5kcb.js.map +189 -0
- package/dist/cli/{chunk-tzne8qfq.js → chunk-w4bxdvsa.js} +18 -15
- package/dist/cli/chunk-w4bxdvsa.js.map +10 -0
- package/dist/cli/{chunk-f7t03s3g.js → chunk-wdrt2k2v.js} +2 -2
- package/dist/cli/{chunk-z1f5arsg.js → chunk-xh43dwgw.js} +170 -55
- package/dist/cli/chunk-xh43dwgw.js.map +36 -0
- package/dist/cli/{chunk-pnnvybbk.js → chunk-zp79m0ts.js} +5 -5
- package/dist/cli/{chunk-pnnvybbk.js.map → chunk-zp79m0ts.js.map} +2 -2
- package/dist/cli/index.js +160 -35
- package/dist/cli/index.js.map +4 -4
- package/dist/types/ai/agent-readability.d.ts +1 -1
- package/dist/types/ai/agent-surface.d.ts +32 -0
- package/dist/types/ai/api/paths.d.ts +1 -1
- package/dist/types/ai/api-catalog.d.ts +7 -1
- package/dist/types/ai/ask-context.d.ts +14 -7
- package/dist/types/ai/ask.d.ts +43 -43
- package/dist/types/ai/component-markdown.d.ts +4 -4
- package/dist/types/ai/index.d.ts +3 -3
- package/dist/types/ai/openapi-components.d.ts +1 -1
- package/dist/types/ai/relative-links.d.ts +9 -4
- package/dist/types/ai/serializers.d.ts +1 -1
- package/dist/types/ai/static-expression.d.ts +28 -0
- package/dist/types/ai/visibility.d.ts +1 -1
- package/dist/types/analytics/databuddy.d.ts +43 -0
- package/dist/types/analytics/index.d.ts +2 -0
- package/dist/types/analytics/schema.d.ts +14 -0
- 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 +9 -0
- package/dist/types/core/config-input.d.ts +21 -21
- package/dist/types/core/config.d.ts +5 -5
- package/dist/types/core/data.d.ts +3 -3
- package/dist/types/core/graph.d.ts +2 -0
- package/dist/types/core/i18n-ui.d.ts +37 -8
- package/dist/types/core/i18n.d.ts +7 -1
- 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 +5 -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 +57 -18
- package/dist/types/core/sources/github-releases.d.ts +5 -0
- package/dist/types/core/sources/lower.d.ts +14 -2
- package/dist/types/core/sources/normalize.d.ts +10 -1
- 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/types.d.ts +9 -0
- package/dist/types/core/unrecognized-keys.d.ts +1 -1
- package/dist/types/deploy/adapters/node.d.ts +5 -2
- package/dist/types/deploy/adapters/types.d.ts +7 -0
- package/dist/types/deploy/cloudflare-negotiation.d.ts +3 -2
- package/dist/types/deploy/headers.d.ts +31 -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 +7 -0
- package/dist/types/deploy/platforms/vercel.d.ts +3 -2
- package/dist/types/deploy/redirects.d.ts +7 -2
- package/dist/types/deploy/vercel-negotiation.d.ts +3 -2
- package/dist/types/openapi/model.d.ts +20 -6
- package/dist/types/search/documents.d.ts +1 -1
- package/dist/types/search/orama-index.d.ts +1 -1
- package/dist/types/search/sync/algolia.d.ts +3 -1
- package/docs/01-quickstart.mdx +3 -2
- package/docs/02-deployment.mdx +21 -12
- package/docs/03-upgrading.mdx +22 -9
- package/docs/04-migrating.mdx +4 -4
- package/docs/08-faq.mdx +12 -5
- package/docs/advanced/changelog.mdx +1 -1
- package/docs/advanced/custom-pages.mdx +5 -3
- package/docs/advanced/skills.mdx +1 -1
- package/docs/cli/audit.mdx +5 -5
- package/docs/cli/doctor.mdx +4 -4
- package/docs/cli/evals.mdx +9 -9
- package/docs/cli/index.mdx +6 -3
- package/docs/cli/translate.mdx +8 -8
- package/docs/configuration/analytics.mdx +23 -2
- package/docs/configuration/{ask-ai.mdx → assistant.mdx} +20 -20
- package/docs/configuration/customization.mdx +5 -4
- package/docs/configuration/index.mdx +7 -5
- package/docs/configuration/meta.ts +1 -1
- package/docs/configuration/search.mdx +2 -2
- package/docs/content/components.mdx +1 -1
- package/docs/content/frontmatter.mdx +5 -1
- package/docs/content/i18n.mdx +3 -3
- package/docs/content/index.mdx +4 -2
- package/docs/content/islands.mdx +1 -1
- package/docs/content/meta.mdx +5 -3
- package/docs/content/navigation.mdx +29 -4
- package/docs/content/sources.mdx +18 -12
- package/docs/content/versioning.mdx +1 -0
- package/docs/discoverability/agent-discovery.mdx +22 -8
- package/docs/discoverability/index.mdx +3 -3
- package/docs/discoverability/llms-txt.mdx +2 -5
- package/docs/discoverability/markdown.mdx +5 -3
- package/docs/discoverability/mcp.mdx +4 -0
- package/docs/discoverability/metadata.mdx +3 -2
- package/docs/discoverability/open-graph.mdx +6 -4
- package/docs/discoverability/rss.mdx +3 -1
- package/docs/index.mdx +2 -2
- package/docs/references/asyncapi.mdx +1 -1
- package/docs/references/graphql.mdx +2 -2
- package/docs/references/openapi.mdx +3 -3
- package/package.json +1 -1
- package/skills/blume/SKILL.md +5 -5
- package/skills/blume-migrate/SKILL.md +6 -6
- package/skills/blume-migrate/references/docusaurus.md +6 -6
- 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 +1 -1
- package/skills/blume-migrate/references/starlight.md +5 -5
- package/skills/blume-migrate/scripts/mintlify-codemod.mjs +2 -2
- package/skills/blume-update-docs/references/audit-checklist.md +1 -1
- package/src/ai/agent-readability.ts +4 -4
- package/src/ai/agent-surface.ts +56 -0
- package/src/ai/api/paths.ts +1 -1
- package/src/ai/api/spec.ts +15 -2
- package/src/ai/api-catalog.ts +7 -1
- package/src/ai/ask-context.ts +21 -9
- package/src/ai/ask-data.ts +28 -14
- package/src/ai/ask.ts +84 -71
- package/src/ai/component-markdown.ts +43 -37
- package/src/ai/cors.ts +3 -3
- package/src/ai/index.ts +16 -16
- package/src/ai/link-headers.ts +8 -3
- package/src/ai/llms.ts +4 -3
- package/src/ai/markdown.ts +23 -11
- package/src/ai/mcp/server.ts +78 -7
- package/src/ai/openapi-components.ts +1 -1
- package/src/ai/relative-links.ts +78 -10
- package/src/ai/serializers.ts +1 -1
- package/src/ai/static-expression.ts +416 -0
- package/src/ai/visibility.ts +46 -15
- package/src/analytics/databuddy.ts +67 -0
- package/src/analytics/head.ts +4 -0
- package/src/analytics/index.ts +2 -0
- package/src/analytics/posthog.ts +21 -3
- package/src/analytics/schema.ts +2 -0
- package/src/astro/generate.ts +90 -45
- package/src/astro/module-types.ts +1 -1
- package/src/astro/runtime-deps.ts +6 -6
- package/src/astro/templates.ts +87 -49
- 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 +14 -0
- 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/report.ts +54 -17
- package/src/audit/run.ts +1 -0
- package/src/audit/types.ts +18 -0
- package/src/audit/url.ts +37 -6
- package/src/blume-modules.d.ts +2 -2
- package/src/cli/build-failure.ts +50 -0
- package/src/cli/commands/audit.ts +8 -1
- package/src/cli/commands/build.ts +19 -5
- package/src/cli/commands/check.ts +2 -0
- package/src/cli/commands/doctor.ts +7 -5
- package/src/cli/commands/eval.ts +4 -10
- package/src/cli/commands/init.ts +15 -5
- package/src/cli/commands/migrate.ts +2 -2
- package/src/cli/commands/preview.ts +15 -0
- package/src/cli/commands/sync.ts +2 -0
- package/src/cli/commands/translate.ts +12 -9
- package/src/cli/commands/upgrade.ts +13 -2
- 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/required-secrets.ts +3 -3
- package/src/components/content/AccordionItem.astro +26 -22
- package/src/components/content/Badge.astro +2 -9
- package/src/components/content/Card.astro +2 -2
- package/src/components/content/Component.astro +9 -3
- 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/Tabs.astro +54 -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 +17 -0
- package/src/components/content/base-href.ts +18 -3
- package/src/components/content/inline-markdown.ts +27 -7
- package/src/components/copy-feedback.ts +36 -9
- package/src/components/islands/{AskAI.astro → Assistant.astro} +10 -10
- package/src/components/islands/{ask-ai.tsx → assistant.tsx} +39 -25
- package/src/components/islands/hooks.ts +21 -14
- package/src/components/islands/webmcp.ts +12 -8
- package/src/components/layout/Banner.astro +23 -4
- package/src/components/layout/Header.astro +32 -19
- package/src/components/layout/Logo.astro +5 -0
- package/src/components/layout/NavSelector.astro +6 -1
- package/src/components/layout/NavTabMenu.astro +133 -0
- package/src/components/layout/NavTree.astro +15 -6
- package/src/components/layout/NavTreeCache.astro +5 -2
- package/src/components/layout/NavTreeScript.astro +45 -5
- package/src/components/layout/PageLayout.astro +28 -14
- package/src/components/layout/Pagination.astro +7 -7
- package/src/components/layout/ReferenceLayout.astro +5 -2
- package/src/components/layout/RootLayout.astro +27 -14
- package/src/components/layout/Search.astro +18 -14
- package/src/components/layout/analytics-client.ts +3 -1
- package/src/components/layout/drawer-inert.ts +1 -1
- package/src/components/openapi/GraphqlType.astro +11 -3
- package/src/components/openapi/MessageComposer.astro +1 -1
- package/src/components/openapi/Operation.astro +21 -5
- package/src/components/openapi/PanelTabs.astro +4 -1
- package/src/components/openapi/Playground.astro +8 -2
- package/src/components/openapi/RequestPanel.astro +9 -5
- package/src/components/openapi/SchemaProperty.astro +11 -33
- package/src/components/openapi/SchemaTable.astro +27 -52
- package/src/components/openapi/description.ts +2 -2
- package/src/components/openapi/helpers.ts +90 -12
- package/src/components/openapi/message-composer.ts +8 -0
- package/src/components/openapi/message-model.ts +12 -2
- package/src/components/openapi/message.ts +4 -1
- package/src/components/openapi/operation-model.ts +40 -8
- package/src/components/openapi/panel.ts +29 -5
- package/src/components/openapi/playground-client.ts +37 -7
- package/src/components/openapi/playground-schema.ts +25 -6
- package/src/components/openapi/request.ts +2 -0
- package/src/components/openapi/schema-tree.ts +203 -0
- package/src/components/openapi/snippets.ts +30 -15
- package/src/components/openapi/validate-json.ts +1 -1
- package/src/core/base-path.ts +15 -0
- package/src/core/code-fences.ts +1 -1
- package/src/core/config-input.ts +23 -23
- package/src/core/config.ts +21 -7
- package/src/core/data.ts +3 -3
- package/src/core/diagnostics.ts +217 -30
- package/src/core/graph.ts +120 -11
- package/src/core/i18n-ui.ts +74 -11
- package/src/core/i18n.ts +13 -2
- package/src/core/links.ts +7 -4
- package/src/core/load-module.ts +20 -0
- package/src/core/locale-links.ts +17 -17
- package/src/core/manifest.ts +3 -2
- package/src/core/meta.ts +69 -6
- package/src/core/nav-diagnostics.ts +56 -1
- package/src/core/navigation.ts +245 -76
- package/src/core/ordering-prefix.ts +27 -0
- package/src/core/project-graph.ts +23 -1
- package/src/core/request-body.ts +1 -1
- package/src/core/safe-href.ts +53 -1
- package/src/core/safe-links.ts +11 -2
- package/src/core/schema.ts +95 -34
- package/src/core/server-features.ts +2 -2
- package/src/core/sources/assets.ts +83 -41
- package/src/core/sources/contentful-rich-text.ts +3 -2
- package/src/core/sources/contentful.ts +25 -12
- package/src/core/sources/filesystem.ts +5 -1
- package/src/core/sources/github-releases.ts +106 -5
- package/src/core/sources/lexical.ts +9 -4
- package/src/core/sources/lower.ts +79 -26
- package/src/core/sources/mdx-remote.ts +1 -0
- package/src/core/sources/normalize.ts +132 -30
- package/src/core/sources/notion.ts +26 -10
- package/src/core/sources/obsidian.ts +17 -3
- package/src/core/sources/payload.ts +1 -0
- package/src/core/sources/portable-text.ts +63 -44
- 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 +9 -4
- package/src/core/sources/strapi.ts +1 -0
- package/src/core/sources/types.ts +31 -0
- package/src/core/types.ts +9 -0
- package/src/core/ui-packs/ar.ts +17 -5
- package/src/core/ui-packs/bg.ts +17 -5
- package/src/core/ui-packs/bn.ts +17 -5
- package/src/core/ui-packs/ca.ts +17 -5
- package/src/core/ui-packs/cs.ts +17 -5
- package/src/core/ui-packs/da.ts +17 -5
- package/src/core/ui-packs/de.ts +17 -5
- package/src/core/ui-packs/el.ts +17 -5
- package/src/core/ui-packs/es.ts +17 -5
- package/src/core/ui-packs/fa.ts +17 -5
- package/src/core/ui-packs/fi.ts +17 -5
- package/src/core/ui-packs/fr.ts +17 -5
- package/src/core/ui-packs/he.ts +17 -5
- package/src/core/ui-packs/hi.ts +17 -5
- package/src/core/ui-packs/hr.ts +17 -5
- package/src/core/ui-packs/hu.ts +17 -5
- package/src/core/ui-packs/id.ts +17 -5
- package/src/core/ui-packs/it.ts +17 -5
- package/src/core/ui-packs/ja.ts +17 -5
- package/src/core/ui-packs/ko.ts +17 -5
- package/src/core/ui-packs/nl.ts +17 -5
- package/src/core/ui-packs/no.ts +17 -5
- package/src/core/ui-packs/pl.ts +17 -5
- package/src/core/ui-packs/pt-br.ts +17 -5
- package/src/core/ui-packs/pt.ts +17 -5
- package/src/core/ui-packs/ro.ts +17 -5
- package/src/core/ui-packs/ru.ts +17 -5
- package/src/core/ui-packs/sk.ts +17 -5
- package/src/core/ui-packs/sr.ts +17 -5
- package/src/core/ui-packs/sv.ts +17 -5
- package/src/core/ui-packs/th.ts +17 -5
- package/src/core/ui-packs/tr.ts +17 -5
- package/src/core/ui-packs/uk.ts +17 -5
- package/src/core/ui-packs/vi.ts +17 -5
- package/src/core/ui-packs/zh-tw.ts +17 -5
- package/src/core/ui-packs/zh.ts +17 -5
- package/src/core/unrecognized-keys.ts +1 -1
- package/src/core/version-cut.ts +17 -2
- 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 +25 -5
- package/src/deploy/cloudflare-negotiation.ts +23 -4
- package/src/deploy/headers.ts +67 -51
- package/src/deploy/node-headers.ts +148 -27
- package/src/deploy/platforms/cloudflare.ts +1 -0
- package/src/deploy/platforms/netlify.ts +82 -5
- package/src/deploy/platforms/node.ts +7 -5
- package/src/deploy/platforms/static.ts +1 -0
- package/src/deploy/platforms/types.ts +7 -0
- package/src/deploy/platforms/vercel.ts +9 -3
- package/src/deploy/redirects.ts +14 -3
- 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 +55 -8
- package/src/markdown/index.ts +6 -3
- package/src/markdown/relative-links.ts +3 -23
- package/src/markdown/route-snapshot.ts +37 -0
- package/src/og/card.ts +20 -4
- package/src/og/derive.ts +145 -4
- package/src/openapi/graphql-build.ts +28 -2
- package/src/openapi/model.ts +77 -13
- package/src/openapi/render-mdx.ts +10 -1
- package/src/registry/eject.ts +135 -37
- package/src/search/documents.ts +24 -3
- package/src/search/orama-index.ts +1 -1
- package/src/search/sync/algolia.ts +36 -2
- 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 +11 -3
- package/src/translate/report.ts +1 -1
- package/src/translate/run.ts +11 -5
- package/src/translate/validate.ts +10 -1
- package/src/translate/work-list.ts +36 -3
- package/src/upgrade/upgrade.ts +37 -5
- package/dist/cli/chunk-1w8dp3qb.js.map +0 -10
- package/dist/cli/chunk-5a2z0198.js.map +0 -10
- package/dist/cli/chunk-6k8vp3ta.js.map +0 -19
- package/dist/cli/chunk-79njf86q.js.map +0 -11
- package/dist/cli/chunk-a9kptbw5.js.map +0 -14
- package/dist/cli/chunk-abh8yjkn.js +0 -31
- package/dist/cli/chunk-abh8yjkn.js.map +0 -10
- package/dist/cli/chunk-bctazmbk.js.map +0 -10
- package/dist/cli/chunk-beat36xx.js.map +0 -10
- package/dist/cli/chunk-d1tadaw7.js.map +0 -10
- package/dist/cli/chunk-fa25z98p.js.map +0 -11
- package/dist/cli/chunk-j8mw0za6.js.map +0 -35
- package/dist/cli/chunk-mnqj32sj.js.map +0 -12
- package/dist/cli/chunk-mwt1k8n7.js.map +0 -10
- package/dist/cli/chunk-nk3ts2xk.js +0 -51
- package/dist/cli/chunk-nk3ts2xk.js.map +0 -10
- package/dist/cli/chunk-sqn5t4q0.js.map +0 -10
- package/dist/cli/chunk-tzne8qfq.js.map +0 -10
- package/dist/cli/chunk-xaz13gwg.js.map +0 -182
- package/dist/cli/chunk-z01ze5c1.js.map +0 -10
- package/dist/cli/chunk-z1f5arsg.js.map +0 -36
- package/dist/cli/chunk-zg2gtj10.js.map +0 -35
- /package/dist/cli/{chunk-f2972sbt.js.map → chunk-00gs3wqs.js.map} +0 -0
- /package/dist/cli/{chunk-7ez8ny0t.js.map → chunk-bbnwccaz.js.map} +0 -0
- /package/dist/cli/{chunk-zxccj738.js.map → chunk-esh98wmb.js.map} +0 -0
- /package/dist/cli/{chunk-88cpgt6h.js.map → chunk-fsmrqk8a.js.map} +0 -0
- /package/dist/cli/{chunk-b5aj94ah.js.map → chunk-g698a744.js.map} +0 -0
- /package/dist/cli/{chunk-41za066z.js.map → chunk-h2ez8dzb.js.map} +0 -0
- /package/dist/cli/{chunk-by2290sx.js.map → chunk-h7k3nq3v.js.map} +0 -0
- /package/dist/cli/{chunk-y3e45rc8.js.map → chunk-n1yg3tj3.js.map} +0 -0
- /package/dist/cli/{chunk-f7t03s3g.js.map → chunk-wdrt2k2v.js.map} +0 -0
|
@@ -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(config)
|
|
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
|
};
|
|
@@ -305,6 +305,7 @@ export const cloudflarePlatform: DeployPlatform = {
|
|
|
305
305
|
},
|
|
306
306
|
kind: "cloudflare",
|
|
307
307
|
negotiatesMarkdown: true,
|
|
308
|
+
previewDeploy: null,
|
|
308
309
|
readsHeaderFiles: { server: true, static: true },
|
|
309
310
|
redirectFiles: [REDIRECTS_FILE],
|
|
310
311
|
serverOutputDir: distDir,
|
|
@@ -1,14 +1,81 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
|
|
4
|
+
import { join } from "pathe";
|
|
5
|
+
|
|
6
|
+
import { buildHomeLinkHeader } from "../../ai/link-headers.ts";
|
|
7
|
+
import { markdownRoutePaths } from "../../ai/markdown.ts";
|
|
8
|
+
import type { BlumeProject } from "../../core/project-graph.ts";
|
|
1
9
|
import { NETLIFY_ADAPTER_PACKAGE } from "../adapters/netlify.ts";
|
|
10
|
+
import { buildNetlifyConfigHeaders } from "../headers.ts";
|
|
11
|
+
import type { NetlifyConfigHeaders } from "../headers.ts";
|
|
2
12
|
import { buildNetlifyRedirects } from "../redirects.ts";
|
|
3
13
|
import { distDir, toSiteUrl } from "./paths.ts";
|
|
4
|
-
import type { DeployPlatform, RedirectFile } from "./types.ts";
|
|
14
|
+
import type { BuildLog, DeployPlatform, RedirectFile } from "./types.ts";
|
|
5
15
|
|
|
6
|
-
/**
|
|
16
|
+
/**
|
|
17
|
+
* `_redirects`, the format Netlify and Cloudflare Pages/Workers read, in the
|
|
18
|
+
* form both accept: a static build for Cloudflare, or for no named host.
|
|
19
|
+
*/
|
|
7
20
|
export const REDIRECTS_FILE: RedirectFile = {
|
|
8
21
|
build: buildNetlifyRedirects,
|
|
9
22
|
name: "_redirects",
|
|
10
23
|
};
|
|
11
24
|
|
|
25
|
+
/**
|
|
26
|
+
* `_redirects` for a `netlify()` static build, every rule forced (`301!`).
|
|
27
|
+
* Astro writes a redirect page at each `from` (`dist/old/index.html`), and
|
|
28
|
+
* Netlify serves a file that exists ahead of an unforced rule, so without the
|
|
29
|
+
* flag that page would answer with a 200 instead of the redirect.
|
|
30
|
+
*/
|
|
31
|
+
export const NETLIFY_REDIRECTS_FILE: RedirectFile = {
|
|
32
|
+
build: (redirects) => buildNetlifyRedirects(redirects, true),
|
|
33
|
+
name: "_redirects",
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/** The Frameworks API config, once the build surfaced it to the project root. */
|
|
37
|
+
export const NETLIFY_CONFIG_FILE = join(".netlify", "v1", "config.json");
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Carry the static header rules into a server build. A static `netlify()`
|
|
41
|
+
* build gets them from `_headers` (see `deploy/artifacts.ts`), but a server
|
|
42
|
+
* build's header rules go in the Frameworks API config the adapter writes
|
|
43
|
+
* (`.netlify/v1/config.json`, beside its own `Cache-Control` rule for
|
|
44
|
+
* `/_astro/*`), which follows the same rules as `netlify.toml`'s
|
|
45
|
+
* `[[headers]]`: the charset on the raw Markdown and text files, the homepage
|
|
46
|
+
* `Link` header, the discovery files' media types and CORS header, and the
|
|
47
|
+
* sandbox on downloaded SVGs, which Netlify serves from the publish directory
|
|
48
|
+
* as static files, so the prerendered endpoint's own headers never ship.
|
|
49
|
+
*/
|
|
50
|
+
export const emitNetlifyHeaders = async (
|
|
51
|
+
project: BlumeProject,
|
|
52
|
+
log: BuildLog
|
|
53
|
+
): Promise<void> => {
|
|
54
|
+
const { config, context } = project;
|
|
55
|
+
const configPath = join(context.root, NETLIFY_CONFIG_FILE);
|
|
56
|
+
if (!existsSync(configPath)) {
|
|
57
|
+
log.warn(
|
|
58
|
+
`Could not find Netlify's deploy config at ${configPath}, so the site is served without its header rules (the homepage Link header, the discovery files' media types and CORS header, and the sandbox on downloaded SVGs).`
|
|
59
|
+
);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
// Only `headers` is read; every other key rides through as parsed.
|
|
63
|
+
const frameworks: { headers?: NetlifyConfigHeaders[] } = JSON.parse(
|
|
64
|
+
await readFile(configPath, "utf-8")
|
|
65
|
+
);
|
|
66
|
+
const ours = buildNetlifyConfigHeaders(
|
|
67
|
+
config,
|
|
68
|
+
buildHomeLinkHeader(config, markdownRoutePaths(project))
|
|
69
|
+
);
|
|
70
|
+
// A second pass over the same build replaces its own entries.
|
|
71
|
+
const paths = new Set(ours.map((entry) => entry.for));
|
|
72
|
+
frameworks.headers = [
|
|
73
|
+
...(frameworks.headers ?? []).filter((entry) => !paths.has(entry.for)),
|
|
74
|
+
...ours,
|
|
75
|
+
];
|
|
76
|
+
await writeFile(configPath, JSON.stringify(frameworks), "utf-8");
|
|
77
|
+
};
|
|
78
|
+
|
|
12
79
|
/**
|
|
13
80
|
* Netlify. A server build runs on Netlify Functions from the Frameworks API
|
|
14
81
|
* tree the adapter writes to `.netlify/v1`, relative to the Astro root —
|
|
@@ -34,7 +101,14 @@ export const netlifyPlatform: DeployPlatform = {
|
|
|
34
101
|
toSiteUrl(env.DEPLOY_PRIME_URL) ??
|
|
35
102
|
toSiteUrl(env.DEPLOY_URL),
|
|
36
103
|
},
|
|
37
|
-
finalizeBuild:
|
|
104
|
+
finalizeBuild: async ({ isolated, log, project }) => {
|
|
105
|
+
// The Frameworks API config is a deploy artifact; an isolated verify
|
|
106
|
+
// never surfaces it.
|
|
107
|
+
if (!isolated) {
|
|
108
|
+
await emitNetlifyHeaders(project, log);
|
|
109
|
+
}
|
|
110
|
+
return true;
|
|
111
|
+
},
|
|
38
112
|
hiddenRuntime: {
|
|
39
113
|
ignoreDir: ".netlify/",
|
|
40
114
|
showProjectRoot: false,
|
|
@@ -42,11 +116,14 @@ export const netlifyPlatform: DeployPlatform = {
|
|
|
42
116
|
},
|
|
43
117
|
kind: "netlify",
|
|
44
118
|
negotiatesMarkdown: false,
|
|
119
|
+
// @astrojs/netlify declares no preview entrypoint.
|
|
120
|
+
previewDeploy: "netlify deploy",
|
|
45
121
|
// Netlify reads `_headers` from the publish directory of a static deploy.
|
|
46
122
|
// A server build's static assets ride the Frameworks API tree instead,
|
|
47
|
-
// where the file has never been applied
|
|
123
|
+
// where the file has never been applied, so `finalizeBuild` writes the
|
|
124
|
+
// same rules into that tree's config.
|
|
48
125
|
readsHeaderFiles: { server: false, static: true },
|
|
49
|
-
redirectFiles: [
|
|
126
|
+
redirectFiles: [NETLIFY_REDIRECTS_FILE],
|
|
50
127
|
serverOutputDir: distDir,
|
|
51
128
|
serverStaticDir: distDir,
|
|
52
129
|
};
|
|
@@ -6,11 +6,12 @@ import type { DeployPlatform } from "./types.ts";
|
|
|
6
6
|
/**
|
|
7
7
|
* A self-hosted Node server. The standalone server in `dist/server` serves
|
|
8
8
|
* `dist/client` through its own static handler, which has no `_headers` or
|
|
9
|
-
* `_redirects` support
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
9
|
+
* `_redirects` support, so a file written there would be inert. A wrapper
|
|
10
|
+
* around the server entry (`deploy/node-headers.ts`) sets the headers the
|
|
11
|
+
* discovery files and downloaded SVGs need instead, and answers the
|
|
12
|
+
* configured redirects with their exact status, which Astro's own handler
|
|
13
|
+
* would default to 301 or 308. No platform env to detect: the site URL has to
|
|
14
|
+
* be configured.
|
|
14
15
|
*/
|
|
15
16
|
export const nodePlatform: DeployPlatform = {
|
|
16
17
|
astro: {
|
|
@@ -33,6 +34,7 @@ export const nodePlatform: DeployPlatform = {
|
|
|
33
34
|
},
|
|
34
35
|
kind: "node",
|
|
35
36
|
negotiatesMarkdown: false,
|
|
37
|
+
previewDeploy: null,
|
|
36
38
|
readsHeaderFiles: { server: false, static: false },
|
|
37
39
|
redirectFiles: [],
|
|
38
40
|
serverOutputDir: distDir,
|
|
@@ -22,6 +22,7 @@ export const staticPlatform: DeployPlatform = {
|
|
|
22
22
|
},
|
|
23
23
|
kind: "static",
|
|
24
24
|
negotiatesMarkdown: false,
|
|
25
|
+
previewDeploy: null,
|
|
25
26
|
readsHeaderFiles: { server: false, static: true },
|
|
26
27
|
redirectFiles: [REDIRECTS_FILE, VERCEL_JSON_FILE],
|
|
27
28
|
serverOutputDir: distDir,
|
|
@@ -98,6 +98,13 @@ export interface DeployPlatform {
|
|
|
98
98
|
kind: DeployAdapterKind;
|
|
99
99
|
/** Whether a server build honors `Accept: text/markdown` at the content URLs. */
|
|
100
100
|
negotiatesMarkdown: boolean;
|
|
101
|
+
/**
|
|
102
|
+
* The host CLI command that deploys a preview, for a platform whose
|
|
103
|
+
* `@astrojs/*` adapter has no local preview server: Astro's `preview`
|
|
104
|
+
* throws for its server build, so `blume preview` points here instead. Null
|
|
105
|
+
* when the adapter can preview a server build locally, or there is none.
|
|
106
|
+
*/
|
|
107
|
+
previewDeploy: string | null;
|
|
101
108
|
/**
|
|
102
109
|
* Whether the platform applies a `_headers` file to the static assets it
|
|
103
110
|
* serves, per output mode.
|
|
@@ -124,7 +124,10 @@ export const emitVercelNegotiation = async (
|
|
|
124
124
|
json: existsSync(join(staticDir, "404.json")),
|
|
125
125
|
markdown: existsSync(join(staticDir, "404.md")),
|
|
126
126
|
},
|
|
127
|
-
crossOriginDiscoveryPaths(config)
|
|
127
|
+
crossOriginDiscoveryPaths(config),
|
|
128
|
+
// Downloaded content assets are prerendered static files; only a build
|
|
129
|
+
// that has them needs the SVG sandbox route.
|
|
130
|
+
existsSync(join(staticDir, "blume-assets"))
|
|
128
131
|
);
|
|
129
132
|
if (injected === null) {
|
|
130
133
|
log.warn(
|
|
@@ -144,8 +147,9 @@ export const emitVercelNegotiation = async (
|
|
|
144
147
|
* `@vercel/nft` dependency trace is rooted there too, and tracing from the
|
|
145
148
|
* hidden runtime silently drops the function's chunks and `node_modules`.
|
|
146
149
|
* Static assets are served from the tree's `static/` half, so the deploy
|
|
147
|
-
* artifacts are written there; headers
|
|
148
|
-
*
|
|
150
|
+
* artifacts are written there; headers (the discovery files', the sandbox on
|
|
151
|
+
* downloaded SVGs) arrive through the routing config rather than a
|
|
152
|
+
* `_headers` file, which Vercel never reads.
|
|
149
153
|
*/
|
|
150
154
|
export const vercelPlatform: DeployPlatform = {
|
|
151
155
|
astro: {
|
|
@@ -186,6 +190,8 @@ export const vercelPlatform: DeployPlatform = {
|
|
|
186
190
|
},
|
|
187
191
|
kind: "vercel",
|
|
188
192
|
negotiatesMarkdown: true,
|
|
193
|
+
// @astrojs/vercel declares no preview entrypoint.
|
|
194
|
+
previewDeploy: "vercel deploy",
|
|
189
195
|
readsHeaderFiles: { server: false, static: false },
|
|
190
196
|
redirectFiles: [VERCEL_JSON_FILE],
|
|
191
197
|
serverOutputDir: buildOutputDir,
|
package/src/deploy/redirects.ts
CHANGED
|
@@ -82,10 +82,21 @@ export const platformRedirects = (config: ResolvedConfig): Redirect[] =>
|
|
|
82
82
|
config.deployment.options.base ?? ""
|
|
83
83
|
);
|
|
84
84
|
|
|
85
|
-
/**
|
|
86
|
-
|
|
85
|
+
/**
|
|
86
|
+
* `_redirects` text (Netlify + Cloudflare Pages): `from to status` per line.
|
|
87
|
+
* `force` appends Netlify's `!` to each status (`301!`), so the rule wins over
|
|
88
|
+
* the redirect page Astro writes at `from`; Cloudflare, which always applies
|
|
89
|
+
* its rules first, rejects a line carrying it.
|
|
90
|
+
*/
|
|
91
|
+
export const buildNetlifyRedirects = (
|
|
92
|
+
redirects: Redirect[],
|
|
93
|
+
force = false
|
|
94
|
+
): string =>
|
|
87
95
|
`${redirects
|
|
88
|
-
.map(
|
|
96
|
+
.map(
|
|
97
|
+
(redirect) =>
|
|
98
|
+
`${redirect.from} ${redirect.to} ${redirect.status}${force ? "!" : ""}`
|
|
99
|
+
)
|
|
89
100
|
.join("\n")}\n`;
|
|
90
101
|
|
|
91
102
|
/** The `vercel.json` Blume writes for a static deploy of `dist/`. */
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
* prerendered problem-details 404.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
+
import { SVG_ASSET_HEADERS } from "./headers.ts";
|
|
20
|
+
|
|
19
21
|
/**
|
|
20
22
|
* Regex for the `accept` header condition. Written to hold under both matching
|
|
21
23
|
* semantics a router may apply — full-string and substring — by anchoring the
|
|
@@ -248,6 +250,30 @@ const corsRoute = (path: string): VercelRoute => ({
|
|
|
248
250
|
src: `^${routePattern(path)}$`,
|
|
249
251
|
});
|
|
250
252
|
|
|
253
|
+
/**
|
|
254
|
+
* The `src` of the route stamping the content-source SVG headers: every `.svg`
|
|
255
|
+
* under `/blume-assets/`. The adapter copies the static files into the Build
|
|
256
|
+
* Output tree without the deployment base, like every path matched here.
|
|
257
|
+
*/
|
|
258
|
+
const SVG_ASSET_SRC = String.raw`^/blume-assets/.+\.svg$`;
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* A main-phase `continue` route stamping the sandbox headers
|
|
262
|
+
* (`deploy/headers.ts`) on a downloaded SVG, which Vercel serves from the
|
|
263
|
+
* static layer — where the prerendered asset endpoint's own headers never
|
|
264
|
+
* reach.
|
|
265
|
+
*/
|
|
266
|
+
const svgAssetRoute = (): VercelRoute => ({
|
|
267
|
+
continue: true,
|
|
268
|
+
headers: Object.fromEntries(
|
|
269
|
+
Object.entries(SVG_ASSET_HEADERS).map(([name, value]) => [
|
|
270
|
+
name.toLowerCase(),
|
|
271
|
+
value,
|
|
272
|
+
])
|
|
273
|
+
),
|
|
274
|
+
src: SVG_ASSET_SRC,
|
|
275
|
+
});
|
|
276
|
+
|
|
251
277
|
/** Whether a route is a `corsRoute` — the same three-field shape test as the others. */
|
|
252
278
|
const isCorsRoute = (route: VercelRoute): boolean =>
|
|
253
279
|
route.continue === true &&
|
|
@@ -279,7 +305,8 @@ const isNegotiationRoute = (route: VercelRoute): boolean =>
|
|
|
279
305
|
isString(route.headers?.link) &&
|
|
280
306
|
route.src === HOME_SRC &&
|
|
281
307
|
Object.keys(route).length === 3) ||
|
|
282
|
-
isCorsRoute(route)
|
|
308
|
+
isCorsRoute(route) ||
|
|
309
|
+
(route.continue === true && route.src === SVG_ASSET_SRC);
|
|
283
310
|
|
|
284
311
|
/**
|
|
285
312
|
* Splice the negotiation routes into a Build Output `config.json`, plus — when
|
|
@@ -297,7 +324,8 @@ const isNegotiationRoute = (route: VercelRoute): boolean =>
|
|
|
297
324
|
* `404.md`, `notFound.json` for `404.json`), its routes go into the miss
|
|
298
325
|
* phase right before the adapter's `/404.html` fallback — and nowhere when
|
|
299
326
|
* that fallback is absent, since a `dest` with no file behind it would serve
|
|
300
|
-
* nothing.
|
|
327
|
+
* nothing. With `svgAssets`, a main-phase route also sandboxes the SVGs a
|
|
328
|
+
* content source downloaded (see {@link svgAssetRoute}). Returns the updated JSON
|
|
301
329
|
* text (tab-indented, like the adapter's own output), or `null` when there is
|
|
302
330
|
* nowhere safe to splice: an unparsable config, no `routes` array, or no
|
|
303
331
|
* `handle: "filesystem"` marker to anchor the splice.
|
|
@@ -309,7 +337,8 @@ export const injectNegotiationRoutes = (
|
|
|
309
337
|
contentTypeOverrides?: Record<string, string>,
|
|
310
338
|
homeTokens?: number,
|
|
311
339
|
notFound: NotFoundVariants = {},
|
|
312
|
-
corsPaths: readonly string[] = []
|
|
340
|
+
corsPaths: readonly string[] = [],
|
|
341
|
+
svgAssets = false
|
|
313
342
|
): string | null => {
|
|
314
343
|
const overrideEntries = Object.entries(contentTypeOverrides ?? {});
|
|
315
344
|
let config: {
|
|
@@ -348,6 +377,9 @@ export const injectNegotiationRoutes = (
|
|
|
348
377
|
});
|
|
349
378
|
}
|
|
350
379
|
headerRoutes.push(...corsPaths.map(corsRoute));
|
|
380
|
+
if (svgAssets) {
|
|
381
|
+
headerRoutes.push(svgAssetRoute());
|
|
382
|
+
}
|
|
351
383
|
// Headers first: `continue` routes accumulate, so a request the rewrite
|
|
352
384
|
// route then terminates (Markdown negotiation on the homepage) still carries
|
|
353
385
|
// the Link header.
|
package/src/eval/agents.ts
CHANGED
|
@@ -124,6 +124,14 @@ export const runAgentHeadless = (
|
|
|
124
124
|
}
|
|
125
125
|
});
|
|
126
126
|
|
|
127
|
+
// An agent that exits before reading the whole prompt (it rejected a flag,
|
|
128
|
+
// say) closes the pipe mid-write, and the resulting EPIPE would otherwise
|
|
129
|
+
// be an uncaught exception that aborts the run. The exit itself is the
|
|
130
|
+
// outcome: `close` resolves with the agent's code and stderr, which
|
|
131
|
+
// `readAgentOutput` reports.
|
|
132
|
+
child.stdin?.on("error", () => {
|
|
133
|
+
// Deliberately ignored — see above.
|
|
134
|
+
});
|
|
127
135
|
child.stdin?.end(options.prompt);
|
|
128
136
|
});
|
|
129
137
|
|
|
@@ -208,7 +216,7 @@ const claudeArgs = (context: InvocationContext): string[] => {
|
|
|
208
216
|
|
|
209
217
|
/**
|
|
210
218
|
* Codex config overrides that leave an eval run nothing but its prompt and the
|
|
211
|
-
* docs MCP tools. `--sandbox read-only` blocks writes and the network but still
|
|
219
|
+
* docs MCP tools (and a `blume translate` run nothing but its prompt). `--sandbox read-only` blocks writes and the network but still
|
|
212
220
|
* lets the shell tool read any file the user can, and the docs a reader
|
|
213
221
|
* searches — or the answer a judge grades — can come from a remote source, so
|
|
214
222
|
* an instruction planted there could otherwise pull a local secret into the
|
|
@@ -216,7 +224,7 @@ const claudeArgs = (context: InvocationContext): string[] => {
|
|
|
216
224
|
* local-image tool off, and any subprocess started with no inherited
|
|
217
225
|
* environment, there is nothing on the machine for a run to read.
|
|
218
226
|
*/
|
|
219
|
-
const CODEX_LOCKDOWN = [
|
|
227
|
+
export const CODEX_LOCKDOWN = [
|
|
220
228
|
"-c",
|
|
221
229
|
"features.shell_tool=false",
|
|
222
230
|
"-c",
|
package/src/eval/run.ts
CHANGED
|
@@ -181,6 +181,31 @@ const runQuestion = async (
|
|
|
181
181
|
};
|
|
182
182
|
};
|
|
183
183
|
|
|
184
|
+
/** The findings a missed question produces: failed, or its agent run errored. */
|
|
185
|
+
const MISS_CODES: ReadonlySet<string> = new Set([
|
|
186
|
+
"BLUME_EVAL_QUESTION_ERROR",
|
|
187
|
+
"BLUME_EVAL_QUESTION_FAILED",
|
|
188
|
+
]);
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* The fraction of run (non-skipped) questions that pass the CI gate. A miss on
|
|
192
|
+
* a `severity: warning` question is reported as a warning and never counts
|
|
193
|
+
* against the gate — each miss's finding carries its question's severity, so
|
|
194
|
+
* the gate reads the same findings the JSON summary counts, and the exit code
|
|
195
|
+
* can't disagree with the summary's error total.
|
|
196
|
+
*/
|
|
197
|
+
export const passFraction = (result: EvalResult): number => {
|
|
198
|
+
const ran = result.results.length - result.counts.skip;
|
|
199
|
+
if (ran === 0) {
|
|
200
|
+
return 1;
|
|
201
|
+
}
|
|
202
|
+
const misses = result.diagnostics.filter(
|
|
203
|
+
(diagnostic) =>
|
|
204
|
+
MISS_CODES.has(diagnostic.code) && diagnostic.severity === "error"
|
|
205
|
+
).length;
|
|
206
|
+
return (ran - misses) / ran;
|
|
207
|
+
};
|
|
208
|
+
|
|
184
209
|
/**
|
|
185
210
|
* Run every question through the reader/judge pair, sequentially: each
|
|
186
211
|
* question is already two agent sessions, and a serial run keeps progress
|