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
package/src/audit/report.ts
CHANGED
|
@@ -33,12 +33,47 @@ const TIER_FLAGS: { tier: AuditTier; flag: string }[] = [
|
|
|
33
33
|
interface CheckRollup {
|
|
34
34
|
id: CheckId;
|
|
35
35
|
count: number;
|
|
36
|
+
/** Distinct affected URLs — one page can carry several findings. */
|
|
37
|
+
pages: number;
|
|
36
38
|
severity: DiagnosticSeverity;
|
|
37
39
|
category: AuditCategory;
|
|
38
40
|
title: string;
|
|
39
41
|
findings: Diagnostic[];
|
|
40
42
|
}
|
|
41
43
|
|
|
44
|
+
const SEVERITY_ORDER = {
|
|
45
|
+
error: 0,
|
|
46
|
+
info: 2,
|
|
47
|
+
warning: 1,
|
|
48
|
+
} satisfies Record<DiagnosticSeverity, number>;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The group's severity: the worst of its findings'. A check can downgrade a
|
|
52
|
+
* finding at runtime (an external link answering 503 is a warning, not the
|
|
53
|
+
* catalog's error), and the group's glyph must agree with the summary counts.
|
|
54
|
+
*/
|
|
55
|
+
const worstSeverity = (findings: Diagnostic[]): DiagnosticSeverity => {
|
|
56
|
+
let worst: DiagnosticSeverity = "info";
|
|
57
|
+
for (const finding of findings) {
|
|
58
|
+
if (SEVERITY_ORDER[finding.severity] < SEVERITY_ORDER[worst]) {
|
|
59
|
+
worst = finding.severity;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return worst;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/** The first finding per affected URL, in report order. */
|
|
66
|
+
const firstPerPage = (findings: Diagnostic[]): Diagnostic[] => {
|
|
67
|
+
const seen = new Set<string | undefined>();
|
|
68
|
+
return findings.filter((finding) => {
|
|
69
|
+
if (seen.has(finding.url)) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
seen.add(finding.url);
|
|
73
|
+
return true;
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
|
|
42
77
|
/**
|
|
43
78
|
* Group findings by check. This is the difference between a report people read
|
|
44
79
|
* and one they close: 214 pages × 6 findings is an unreadable wall, but "Meta
|
|
@@ -55,22 +90,18 @@ export const rollup = (diagnostics: Diagnostic[]): CheckRollup[] => {
|
|
|
55
90
|
}
|
|
56
91
|
}
|
|
57
92
|
|
|
58
|
-
const order = {
|
|
59
|
-
error: 0,
|
|
60
|
-
info: 2,
|
|
61
|
-
warning: 1,
|
|
62
|
-
} satisfies Record<DiagnosticSeverity, number>;
|
|
63
93
|
const checks = [...groups.entries()].map(([id, findings]) => {
|
|
64
94
|
// SAFETY: audit findings are only ever created through `finding()`, whose
|
|
65
95
|
// codes are the check catalog's ids.
|
|
66
96
|
const checkId = id as CheckId;
|
|
67
|
-
const { category,
|
|
97
|
+
const { category, title } = checkMeta(checkId);
|
|
68
98
|
return {
|
|
69
99
|
category,
|
|
70
100
|
count: findings.length,
|
|
71
101
|
findings,
|
|
72
102
|
id: checkId,
|
|
73
|
-
|
|
103
|
+
pages: firstPerPage(findings).length,
|
|
104
|
+
severity: worstSeverity(findings),
|
|
74
105
|
title,
|
|
75
106
|
};
|
|
76
107
|
});
|
|
@@ -80,7 +111,7 @@ export const rollup = (diagnostics: Diagnostic[]): CheckRollup[] => {
|
|
|
80
111
|
// and print "content" three separate times.
|
|
81
112
|
const worst = new Map<AuditCategory, number>();
|
|
82
113
|
for (const check of checks) {
|
|
83
|
-
const rank =
|
|
114
|
+
const rank = SEVERITY_ORDER[check.severity];
|
|
84
115
|
worst.set(
|
|
85
116
|
check.category,
|
|
86
117
|
Math.min(worst.get(check.category) ?? rank, rank)
|
|
@@ -91,7 +122,7 @@ export const rollup = (diagnostics: Diagnostic[]): CheckRollup[] => {
|
|
|
91
122
|
(a, b) =>
|
|
92
123
|
(worst.get(a.category) ?? 0) - (worst.get(b.category) ?? 0) ||
|
|
93
124
|
a.category.localeCompare(b.category) ||
|
|
94
|
-
|
|
125
|
+
SEVERITY_ORDER[a.severity] - SEVERITY_ORDER[b.severity] ||
|
|
95
126
|
b.count - a.count
|
|
96
127
|
);
|
|
97
128
|
};
|
|
@@ -176,14 +207,16 @@ export const formatReport = (
|
|
|
176
207
|
}
|
|
177
208
|
|
|
178
209
|
const color = SEVERITY_COLOR[group.severity];
|
|
179
|
-
const pages = `${group.
|
|
210
|
+
const pages = `${group.pages} page${group.pages === 1 ? "" : "s"}`;
|
|
180
211
|
lines.push(
|
|
181
212
|
` ${color(`${GLYPH[group.severity]} ${group.title}`)} ${colors.dim(pages)}`
|
|
182
213
|
);
|
|
183
214
|
|
|
215
|
+
// The preview lists affected pages, each once; --verbose lists every
|
|
216
|
+
// finding, since each one's message names a different specific.
|
|
184
217
|
const shown = options.verbose
|
|
185
218
|
? group.findings
|
|
186
|
-
: group.findings.slice(0, PREVIEW);
|
|
219
|
+
: firstPerPage(group.findings).slice(0, PREVIEW);
|
|
187
220
|
for (const diagnostic of shown) {
|
|
188
221
|
lines.push(findingLine(diagnostic, root));
|
|
189
222
|
// The message names the specifics the rolled-up line can't — which target
|
|
@@ -192,16 +225,20 @@ export const formatReport = (
|
|
|
192
225
|
lines.push(` ${colors.dim(diagnostic.message)}`);
|
|
193
226
|
}
|
|
194
227
|
}
|
|
195
|
-
const hidden = group.
|
|
228
|
+
const hidden = options.verbose ? 0 : group.pages - shown.length;
|
|
196
229
|
if (hidden > 0) {
|
|
197
230
|
lines.push(` ${colors.dim(`… and ${hidden} more (--verbose)`)}`);
|
|
198
231
|
}
|
|
199
232
|
|
|
200
|
-
//
|
|
201
|
-
//
|
|
202
|
-
|
|
203
|
-
const
|
|
204
|
-
|
|
233
|
+
// Findings share the catalog's fix unless one overrode it (a navigation
|
|
234
|
+
// entry, a page generated from an API spec), so each distinct fix is
|
|
235
|
+
// printed once.
|
|
236
|
+
const fixes = new Set(
|
|
237
|
+
group.findings.flatMap((finding) =>
|
|
238
|
+
finding.suggestion ? [finding.suggestion] : []
|
|
239
|
+
)
|
|
240
|
+
);
|
|
241
|
+
for (const fix of fixes) {
|
|
205
242
|
lines.push(` ${colors.cyan(`fix: ${fix}`)}`);
|
|
206
243
|
}
|
|
207
244
|
lines.push("");
|
package/src/audit/run.ts
CHANGED
|
@@ -2,10 +2,12 @@ import { readFile } from "node:fs/promises";
|
|
|
2
2
|
|
|
3
3
|
import pMap from "p-map";
|
|
4
4
|
|
|
5
|
-
import { normalizeBasePath
|
|
5
|
+
import { normalizeBasePath } from "../core/base-path.ts";
|
|
6
|
+
import { routeSetFor } from "../core/locale-links.ts";
|
|
6
7
|
import type { BlumeProject } from "../core/project-graph.ts";
|
|
7
8
|
import type { Diagnostic } from "../core/types.ts";
|
|
8
9
|
import { deployStaticDir } from "../deploy/adapter-output.ts";
|
|
10
|
+
import { applyBaseToAstroRedirects } from "../deploy/redirects.ts";
|
|
9
11
|
import { CHECKS } from "./catalog.ts";
|
|
10
12
|
import type { CheckId } from "./catalog.ts";
|
|
11
13
|
import { assetChecks } from "./checks/assets.ts";
|
|
@@ -137,6 +139,7 @@ export const runAudit = async (options: AuditOptions): Promise<AuditResult> => {
|
|
|
137
139
|
|
|
138
140
|
const crawl = await crawlStaticDir({
|
|
139
141
|
basePath,
|
|
142
|
+
deployBase: normalizeBasePath(project.config.deployment.options.base),
|
|
140
143
|
manifest: project.manifest,
|
|
141
144
|
staticDir,
|
|
142
145
|
});
|
|
@@ -161,14 +164,15 @@ export const runAudit = async (options: AuditOptions): Promise<AuditResult> => {
|
|
|
161
164
|
redirects: resolveRedirects(
|
|
162
165
|
// Redirects are authored as if mounted at root; the built page URLs they
|
|
163
166
|
// are checked against carry `basePath` (it's a real directory in the
|
|
164
|
-
// build), so both sides gain it here
|
|
165
|
-
// `
|
|
166
|
-
//
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
167
|
+
// build), so both sides gain it here, based exactly as the build bases
|
|
168
|
+
// them — a `to` naming a public file stays at the root, and external
|
|
169
|
+
// `to` URLs pass through.
|
|
170
|
+
applyBaseToAstroRedirects(
|
|
171
|
+
project.config.redirects,
|
|
172
|
+
basePath,
|
|
173
|
+
"",
|
|
174
|
+
routeSetFor(project.manifest.routes)
|
|
175
|
+
),
|
|
172
176
|
// Pages, static files, and server routes alike: a redirect may
|
|
173
177
|
// legitimately land on a served asset (`/old-whitepaper` ->
|
|
174
178
|
// `/files/whitepaper.pdf`), and the same predicate the link and llms.txt
|
package/src/audit/snapshot.ts
CHANGED
|
@@ -196,8 +196,13 @@ export const buildSnapshot = (options: {
|
|
|
196
196
|
scripts: collectAssets(document, "script[src]", "src"),
|
|
197
197
|
source: route?.sourcePath,
|
|
198
198
|
styles: collectAssets(document, 'link[rel="stylesheet"][href]', "href"),
|
|
199
|
+
// Only the document's own titles: an accessible inline SVG carries a
|
|
200
|
+
// `<title>` of its own (an SVG-namespace element, not the page title), and
|
|
201
|
+
// counting it would flag BLUME_AUDIT_TITLE_MULTIPLE — or stand in for a
|
|
202
|
+
// head `<title>` that is genuinely missing.
|
|
199
203
|
titles: document
|
|
200
204
|
.querySelectorAll("title")
|
|
205
|
+
.filter((title) => !title.closest("svg"))
|
|
201
206
|
.map((title) => title.text.trim())
|
|
202
207
|
.filter((text) => text.length > 0),
|
|
203
208
|
twitter: prefixedMeta(document, "name", "twitter:"),
|
package/src/audit/types.ts
CHANGED
|
@@ -113,6 +113,16 @@ export interface RedirectResolution {
|
|
|
113
113
|
outcome: "ok" | "loop" | "broken" | "chain";
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
/** One urlset file behind a sitemap, for the per-file size limits. */
|
|
117
|
+
export interface SitemapPart {
|
|
118
|
+
file: string;
|
|
119
|
+
/** Site path the file is served at, e.g. `/sitemap-1.xml`. */
|
|
120
|
+
url: string;
|
|
121
|
+
bytes: number;
|
|
122
|
+
/** How many `<url>` entries the file lists. */
|
|
123
|
+
urls: number;
|
|
124
|
+
}
|
|
125
|
+
|
|
116
126
|
/** A parsed `sitemap.xml`. */
|
|
117
127
|
export interface SitemapDoc {
|
|
118
128
|
file: string;
|
|
@@ -121,6 +131,14 @@ export interface SitemapDoc {
|
|
|
121
131
|
urls: string[];
|
|
122
132
|
/** Each `<url>` block's `<lastmod>`, keyed by its `<loc>`. */
|
|
123
133
|
lastmod?: Map<string, string>;
|
|
134
|
+
/**
|
|
135
|
+
* The child sitemaps' `<loc>`s, when `sitemap.xml` is a sitemap index —
|
|
136
|
+
* Blume writes one past 50,000 URLs. The crawler reads each child from the
|
|
137
|
+
* build and merges its URLs into `urls` and `lastmod`.
|
|
138
|
+
*/
|
|
139
|
+
sitemaps?: string[];
|
|
140
|
+
/** The index's child urlset files, once the crawler has read them. */
|
|
141
|
+
parts?: SitemapPart[];
|
|
124
142
|
/** Parse failure, when the document isn't usable. */
|
|
125
143
|
error?: string;
|
|
126
144
|
}
|
package/src/audit/url.ts
CHANGED
|
@@ -43,11 +43,21 @@ export type ResolvedHref =
|
|
|
43
43
|
| { kind: "internal"; path: string; hash: string }
|
|
44
44
|
/** An absolute URL that resolves back to this site — should have been a path. */
|
|
45
45
|
| { kind: "self-origin"; path: string; hash: string }
|
|
46
|
+
/**
|
|
47
|
+
* A path on this origin that lacks `deployment.base`. The host serves the
|
|
48
|
+
* whole build under the base, so nothing answers here — `path` is the path
|
|
49
|
+
* as requested, base missing.
|
|
50
|
+
*/
|
|
51
|
+
| { kind: "outside-base"; path: string }
|
|
46
52
|
/** An absolute URL on another origin. */
|
|
47
53
|
| { kind: "external"; url: string }
|
|
48
54
|
/** In-page anchor, `mailto:`, `tel:`, `javascript:`, data URI — not a page link. */
|
|
49
55
|
| { kind: "ignored" };
|
|
50
56
|
|
|
57
|
+
/** Whether a served path sits under the deployment base (always, without one). */
|
|
58
|
+
const underBase = (deployBase: string, path: string): boolean =>
|
|
59
|
+
!deployBase || path === deployBase || path.startsWith(`${deployBase}/`);
|
|
60
|
+
|
|
51
61
|
const NON_HTTP_SCHEME = /^(?!https?:)[a-z][a-z0-9+.-]*:/iu;
|
|
52
62
|
|
|
53
63
|
/**
|
|
@@ -65,6 +75,20 @@ export const decodePath = (path: string): string => {
|
|
|
65
75
|
}
|
|
66
76
|
};
|
|
67
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Whether a `--url` value is an absolute http(s) URL the network tier can
|
|
80
|
+
* probe. A bare host (`example.com`) has no scheme, and `localhost:4321`
|
|
81
|
+
* parses with `localhost:` as its scheme — both are rejected.
|
|
82
|
+
*/
|
|
83
|
+
export const isHttpUrl = (input: string): boolean => {
|
|
84
|
+
try {
|
|
85
|
+
const { protocol } = new URL(input);
|
|
86
|
+
return protocol === "http:" || protocol === "https:";
|
|
87
|
+
} catch {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
68
92
|
/** The origin of `deployment.site`, or null when no site is configured. */
|
|
69
93
|
export const siteOrigin = (site?: string): string | null => {
|
|
70
94
|
if (!site) {
|
|
@@ -113,12 +137,14 @@ export const resolveHref = (
|
|
|
113
137
|
return { kind: "ignored" };
|
|
114
138
|
}
|
|
115
139
|
if (origin && parsed.origin === origin) {
|
|
140
|
+
const served = decodePath(parsed.pathname);
|
|
141
|
+
if (!underBase(deployBase, served)) {
|
|
142
|
+
return { kind: "outside-base", path: normalizePath(served) };
|
|
143
|
+
}
|
|
116
144
|
return {
|
|
117
145
|
hash: parsed.hash.slice(1),
|
|
118
146
|
kind: "self-origin",
|
|
119
|
-
path: normalizePath(
|
|
120
|
-
stripBasePath(deployBase, decodePath(parsed.pathname))
|
|
121
|
-
),
|
|
147
|
+
path: normalizePath(stripBasePath(deployBase, served)),
|
|
122
148
|
};
|
|
123
149
|
}
|
|
124
150
|
return { kind: "external", url: parsed.toString() };
|
|
@@ -140,11 +166,16 @@ export const resolveHref = (
|
|
|
140
166
|
} catch {
|
|
141
167
|
return { kind: "ignored" };
|
|
142
168
|
}
|
|
169
|
+
const served = decodePath(resolved.pathname);
|
|
170
|
+
// Only a root-relative href names the served path outright; a relative one
|
|
171
|
+
// (`./auth`) was resolved against the base-less page URL above, so it
|
|
172
|
+
// inherits the page's base by construction.
|
|
173
|
+
if (target.startsWith("/") && !underBase(deployBase, served)) {
|
|
174
|
+
return { kind: "outside-base", path: normalizePath(served) };
|
|
175
|
+
}
|
|
143
176
|
return {
|
|
144
177
|
hash: resolved.hash.slice(1),
|
|
145
178
|
kind: "internal",
|
|
146
|
-
path: normalizePath(
|
|
147
|
-
stripBasePath(deployBase, decodePath(resolved.pathname))
|
|
148
|
-
),
|
|
179
|
+
path: normalizePath(stripBasePath(deployBase, served)),
|
|
149
180
|
};
|
|
150
181
|
};
|
package/src/cli/args.ts
CHANGED
|
@@ -21,3 +21,35 @@ export const parsePort = (value?: string): number | undefined => {
|
|
|
21
21
|
}
|
|
22
22
|
return port;
|
|
23
23
|
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The longest `--timeout` a timer can hold, in seconds. Node clamps a
|
|
27
|
+
* `setTimeout` delay above 2^31−1 ms (about 24.8 days) to 1 ms, so a larger
|
|
28
|
+
* value would time every agent out the moment it starts.
|
|
29
|
+
*/
|
|
30
|
+
export const MAX_TIMEOUT_S = Math.floor(2_147_483_647 / 1000);
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Parse a `--timeout` value in whole seconds, or return `fallback` when unset.
|
|
34
|
+
* A non-integer, non-positive, or too-long value exits with an error.
|
|
35
|
+
*/
|
|
36
|
+
export const parseTimeoutSeconds = (
|
|
37
|
+
value: string | undefined,
|
|
38
|
+
fallback: number
|
|
39
|
+
): number => {
|
|
40
|
+
if (value === undefined) {
|
|
41
|
+
return fallback;
|
|
42
|
+
}
|
|
43
|
+
const timeout = Number(value);
|
|
44
|
+
if (!Number.isInteger(timeout) || timeout <= 0) {
|
|
45
|
+
logger.error(`Invalid --timeout "${value}" (whole seconds).`);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
if (timeout > MAX_TIMEOUT_S) {
|
|
49
|
+
logger.error(
|
|
50
|
+
`Invalid --timeout "${value}" (at most ${MAX_TIMEOUT_S} seconds, about 24 days).`
|
|
51
|
+
);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
return timeout;
|
|
55
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
import type { Diagnostic } from "../core/types.ts";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The fields of an `astro build` rejection the report reads. Rolldown bundles
|
|
7
|
+
* every plugin failure (an MDX compile error, say) into one error whose
|
|
8
|
+
* `errors` holds each; a failure carries `loc` where the compiler pinned it,
|
|
9
|
+
* or only `id`, the module that failed.
|
|
10
|
+
*/
|
|
11
|
+
const buildErrorSchema = z.looseObject({
|
|
12
|
+
errors: z.array(z.instanceof(Error)).optional(),
|
|
13
|
+
id: z.string().optional(),
|
|
14
|
+
loc: z
|
|
15
|
+
.looseObject({
|
|
16
|
+
column: z.number().optional(),
|
|
17
|
+
file: z.string().optional(),
|
|
18
|
+
line: z.number().optional(),
|
|
19
|
+
})
|
|
20
|
+
.optional(),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
/** One failure as a diagnostic at the file (and position) it names. */
|
|
24
|
+
const failureDiagnostic = (error: Error): Diagnostic => {
|
|
25
|
+
const parsed = buildErrorSchema.safeParse(error);
|
|
26
|
+
const { id, loc } = parsed.success ? parsed.data : {};
|
|
27
|
+
// A named error (`MDXError`, `CompilerError`) says which step failed.
|
|
28
|
+
const name = error.name === "Error" ? "" : `${error.name}: `;
|
|
29
|
+
return {
|
|
30
|
+
code: "BLUME_BUILD_FAILED",
|
|
31
|
+
column: loc?.column,
|
|
32
|
+
file: loc?.file ?? id,
|
|
33
|
+
line: loc?.line,
|
|
34
|
+
message: `${name}${error.message}`,
|
|
35
|
+
severity: "error",
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* What `astro build` rejected with, as build diagnostics: one per failure
|
|
41
|
+
* Rolldown collected, else the error itself. A page Blume's own checks pass
|
|
42
|
+
* can still fail Astro's compile (MDX that doesn't parse) or render, and that
|
|
43
|
+
* is a problem in the site, reported at its file, rather than an internal
|
|
44
|
+
* error blaming Blume.
|
|
45
|
+
*/
|
|
46
|
+
export const astroBuildDiagnostics = (error: Error): Diagnostic[] => {
|
|
47
|
+
const parsed = buildErrorSchema.safeParse(error);
|
|
48
|
+
const failures = parsed.success ? (parsed.data.errors ?? []) : [];
|
|
49
|
+
return (failures.length > 0 ? failures : [error]).map(failureDiagnostic);
|
|
50
|
+
};
|
|
@@ -11,6 +11,7 @@ import { formatCatalog, formatReport, reportJson } from "../../audit/report.ts";
|
|
|
11
11
|
import { NoBuildError, runAudit } from "../../audit/run.ts";
|
|
12
12
|
import type { AuditResult } from "../../audit/run.ts";
|
|
13
13
|
import { checkTerms, unknownCheckTerms } from "../../audit/terms.ts";
|
|
14
|
+
import { isHttpUrl } from "../../audit/url.ts";
|
|
14
15
|
import { BlumeError } from "../../core/diagnostics.ts";
|
|
15
16
|
import { scanProject } from "../../core/project-graph.ts";
|
|
16
17
|
import type { DiagnosticSeverity } from "../../core/types.ts";
|
|
@@ -162,6 +163,12 @@ export const auditCommand = defineCommand({
|
|
|
162
163
|
logger.error(`--json and --${agent} are mutually exclusive.`);
|
|
163
164
|
process.exit(1);
|
|
164
165
|
}
|
|
166
|
+
if (args.url !== undefined && !isHttpUrl(args.url)) {
|
|
167
|
+
logger.error(
|
|
168
|
+
`Invalid --url "${args.url}": pass the deployment's full URL, scheme included (e.g. https://${args.url}).`
|
|
169
|
+
);
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
165
172
|
const only = splitTerms(args.only);
|
|
166
173
|
const skip = splitTerms(args.skip);
|
|
167
174
|
rejectUnknownTerms([...only, ...skip]);
|
|
@@ -231,7 +238,10 @@ export const auditCommand = defineCommand({
|
|
|
231
238
|
process.stderr.write(formatReport(result, root, { verbose: args.verbose }));
|
|
232
239
|
|
|
233
240
|
if (shouldFail(result, gate)) {
|
|
234
|
-
process.exit
|
|
241
|
+
// Set the code and return rather than `process.exit`, which doesn't wait
|
|
242
|
+
// for a piped stderr: a long report was cut off after the first 64 KiB,
|
|
243
|
+
// in exactly the CI logs that need to show every finding.
|
|
244
|
+
process.exitCode = 1;
|
|
235
245
|
}
|
|
236
246
|
},
|
|
237
247
|
});
|
|
@@ -6,6 +6,7 @@ import { defineCommand } from "citty";
|
|
|
6
6
|
import { join } from "pathe";
|
|
7
7
|
|
|
8
8
|
import { publishBuildProject } from "../../astro/integration.ts";
|
|
9
|
+
import { BlumeError } from "../../core/diagnostics.ts";
|
|
9
10
|
import { ensureGitignore } from "../../core/gitignore.ts";
|
|
10
11
|
import type { BlumeProject } from "../../core/project-graph.ts";
|
|
11
12
|
import { serverFeatures } from "../../core/server-features.ts";
|
|
@@ -21,10 +22,11 @@ import {
|
|
|
21
22
|
removedBuildFlags,
|
|
22
23
|
removedBuildFlagsAdvice,
|
|
23
24
|
} from "../../upgrade/upgrade.ts";
|
|
25
|
+
import { astroBuildDiagnostics } from "../build-failure.ts";
|
|
24
26
|
import { commandMeta } from "../command-meta.ts";
|
|
25
27
|
import { refuseIfDevRunning } from "../dev-lock.ts";
|
|
26
28
|
import { refuseIfEjected } from "../eject-scripts.ts";
|
|
27
|
-
import { logger } from "../log.ts";
|
|
29
|
+
import { logger, reportDiagnostics } from "../log.ts";
|
|
28
30
|
import { prepareProject } from "../prepare.ts";
|
|
29
31
|
|
|
30
32
|
const BUDGET_JS = "budget-js";
|
|
@@ -333,10 +335,22 @@ export const buildCommand = defineCommand({
|
|
|
333
335
|
publishBuildProject(project);
|
|
334
336
|
}
|
|
335
337
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
338
|
+
try {
|
|
339
|
+
await build({
|
|
340
|
+
logLevel: "info",
|
|
341
|
+
root: project.context.outDir,
|
|
342
|
+
});
|
|
343
|
+
} catch (error) {
|
|
344
|
+
// Astro rejects when the site fails to compile or render (MDX that
|
|
345
|
+
// doesn't parse, a component that throws): report that as a build error
|
|
346
|
+
// at the file it names, not as an internal error blaming Blume. A
|
|
347
|
+
// BlumeError keeps its own diagnostic.
|
|
348
|
+
if (error instanceof BlumeError || !(error instanceof Error)) {
|
|
349
|
+
throw error;
|
|
350
|
+
}
|
|
351
|
+
reportDiagnostics(astroBuildDiagnostics(error), root);
|
|
352
|
+
process.exit(1);
|
|
353
|
+
}
|
|
340
354
|
|
|
341
355
|
// A real build also prunes the cache; an isolated verify must not evict
|
|
342
356
|
// cards a live dev server is still serving.
|
|
@@ -8,6 +8,7 @@ import { join } from "pathe";
|
|
|
8
8
|
import { ensureGitignore } from "../../core/gitignore.ts";
|
|
9
9
|
import { commandMeta } from "../command-meta.ts";
|
|
10
10
|
import { refuseIfDevRunning } from "../dev-lock.ts";
|
|
11
|
+
import { refuseIfEjected } from "../eject-scripts.ts";
|
|
11
12
|
import { logger } from "../log.ts";
|
|
12
13
|
import { prepareProject } from "../prepare.ts";
|
|
13
14
|
|
|
@@ -30,6 +31,7 @@ export const checkCommand = defineCommand({
|
|
|
30
31
|
meta: commandMeta.check,
|
|
31
32
|
async run({ args }) {
|
|
32
33
|
const root = process.cwd();
|
|
34
|
+
await refuseIfEjected(root, "check");
|
|
33
35
|
|
|
34
36
|
// `blume check` regenerates `.blume` just like `build`, so it must refuse a
|
|
35
37
|
// live dev server unless isolated. `--isolated` (or BLUME_RUNTIME_DIR)
|
|
@@ -187,7 +187,9 @@ export const doctorCommand = defineCommand({
|
|
|
187
187
|
logger.success("No problems found.");
|
|
188
188
|
}
|
|
189
189
|
if (hadErrors) {
|
|
190
|
-
process.exit
|
|
190
|
+
// Set the code and return rather than `process.exit`, which doesn't wait
|
|
191
|
+
// for a piped stderr: a long diagnostic list would be cut off.
|
|
192
|
+
process.exitCode = 1;
|
|
191
193
|
}
|
|
192
194
|
},
|
|
193
195
|
});
|
package/src/cli/commands/eval.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
|
-
import {
|
|
4
|
+
import { resolve } from "pathe";
|
|
5
5
|
|
|
6
6
|
import { AGENTS, launchAgent } from "../../audit/agent.ts";
|
|
7
7
|
import type { AgentKind } from "../../audit/agent.ts";
|
|
@@ -19,9 +19,10 @@ import {
|
|
|
19
19
|
warningLines,
|
|
20
20
|
writeEvalReport,
|
|
21
21
|
} from "../../eval/report.ts";
|
|
22
|
-
import { runEval } from "../../eval/run.ts";
|
|
22
|
+
import { passFraction, runEval } from "../../eval/run.ts";
|
|
23
23
|
import type { EvalResult } from "../../eval/run.ts";
|
|
24
24
|
import { EvalsFileError, loadEvalsFile } from "../../eval/schema.ts";
|
|
25
|
+
import { parseTimeoutSeconds } from "../args.ts";
|
|
25
26
|
import { commandMeta } from "../command-meta.ts";
|
|
26
27
|
import { reportInternalError } from "../internal-error.ts";
|
|
27
28
|
import { flushStdout, logger, reportDiagnostics } from "../log.ts";
|
|
@@ -63,12 +64,6 @@ const launchAgentCode = async (
|
|
|
63
64
|
}
|
|
64
65
|
};
|
|
65
66
|
|
|
66
|
-
/** The fraction of run (non-skipped) questions that passed. */
|
|
67
|
-
export const passFraction = (result: EvalResult): number => {
|
|
68
|
-
const ran = result.results.length - result.counts.skip;
|
|
69
|
-
return ran === 0 ? 1 : result.counts.pass / ran;
|
|
70
|
-
};
|
|
71
|
-
|
|
72
67
|
interface EvalFlags {
|
|
73
68
|
action?: string;
|
|
74
69
|
agent: string;
|
|
@@ -94,16 +89,18 @@ const parseFlags = (args: EvalFlags) => {
|
|
|
94
89
|
process.exit(1);
|
|
95
90
|
}
|
|
96
91
|
const threshold = args.threshold === undefined ? 1 : Number(args.threshold);
|
|
97
|
-
|
|
92
|
+
// A bare `--threshold` (or `--threshold $UNSET_VAR`) arrives as "", which
|
|
93
|
+
// `Number` reads as 0 — a gate that passes every run — so it's rejected.
|
|
94
|
+
if (
|
|
95
|
+
args.threshold?.trim() === "" ||
|
|
96
|
+
!Number.isFinite(threshold) ||
|
|
97
|
+
threshold < 0 ||
|
|
98
|
+
threshold > 1
|
|
99
|
+
) {
|
|
98
100
|
logger.error(`Invalid --threshold "${args.threshold}" (use 0..1).`);
|
|
99
101
|
process.exit(1);
|
|
100
102
|
}
|
|
101
|
-
const timeoutS =
|
|
102
|
-
args.timeout === undefined ? DEFAULT_TIMEOUT_S : Number(args.timeout);
|
|
103
|
-
if (!Number.isInteger(timeoutS) || timeoutS <= 0) {
|
|
104
|
-
logger.error(`Invalid --timeout "${args.timeout}" (whole seconds).`);
|
|
105
|
-
process.exit(1);
|
|
106
|
-
}
|
|
103
|
+
const timeoutS = parseTimeoutSeconds(args.timeout, DEFAULT_TIMEOUT_S);
|
|
107
104
|
return { agent: args.agent, threshold, timeoutS };
|
|
108
105
|
};
|
|
109
106
|
|
|
@@ -131,7 +128,7 @@ const runFixHandoff = async (
|
|
|
131
128
|
|
|
132
129
|
/** `blume eval init`: draft a starter evals file via the interactive agent. */
|
|
133
130
|
const runInit = async (agent: AgentKind, file: string): Promise<void> => {
|
|
134
|
-
const path =
|
|
131
|
+
const path = resolve(process.cwd(), file);
|
|
135
132
|
if (existsSync(path)) {
|
|
136
133
|
logger.error(
|
|
137
134
|
`${file} already exists — edit it directly, or pass --file to draft elsewhere.`
|
|
@@ -199,7 +196,9 @@ export const evalCommand = defineCommand({
|
|
|
199
196
|
// and never regenerates the runtime, so it doesn't contend with a
|
|
200
197
|
// running dev server. Same reasoning as `blume audit`.
|
|
201
198
|
const project = await scanProject(root, { mode: "build" });
|
|
202
|
-
|
|
199
|
+
// `resolve`, not `join`: an absolute `--file` (`/ci/evals.yaml`) is
|
|
200
|
+
// used as given instead of being nested under the project root.
|
|
201
|
+
const evalsPath = resolve(root, args.file);
|
|
203
202
|
const { evals, raw } = await loadEvalsFile(evalsPath);
|
|
204
203
|
|
|
205
204
|
process.stderr.write(`${headerLine(evals.questions.length, agent)}\n\n`);
|
|
@@ -276,7 +275,10 @@ export const evalCommand = defineCommand({
|
|
|
276
275
|
}
|
|
277
276
|
|
|
278
277
|
if (failed) {
|
|
279
|
-
process.exit
|
|
278
|
+
// Set the code and return rather than `process.exit`, which doesn't wait
|
|
279
|
+
// for a piped stderr: a long `--verbose` report was cut off mid-write, in
|
|
280
|
+
// exactly the CI logs that need to show every failure.
|
|
281
|
+
process.exitCode = 1;
|
|
280
282
|
}
|
|
281
283
|
},
|
|
282
284
|
});
|
package/src/cli/commands/init.ts
CHANGED
|
@@ -11,9 +11,11 @@ import { collectAnswers } from "../init/questions.ts";
|
|
|
11
11
|
import {
|
|
12
12
|
applyPlan,
|
|
13
13
|
buildPlan,
|
|
14
|
+
cdCommand,
|
|
14
15
|
commandsFor,
|
|
15
16
|
detectPackageManager,
|
|
16
17
|
nextSteps,
|
|
18
|
+
outsidePnpmWorkspace,
|
|
17
19
|
PACKAGE_MANAGERS,
|
|
18
20
|
readExistingPackage,
|
|
19
21
|
TEMPLATES,
|
|
@@ -25,7 +27,7 @@ import { logger } from "../log.ts";
|
|
|
25
27
|
|
|
26
28
|
/** The `cd` line that opens every next-steps hint, when one is needed. */
|
|
27
29
|
const cdStep = (answers: InitAnswers): string[] =>
|
|
28
|
-
answers.directory === "." ? [] : [
|
|
30
|
+
answers.directory === "." ? [] : [cdCommand(answers.directory)];
|
|
29
31
|
|
|
30
32
|
/**
|
|
31
33
|
* Install the scaffolded project's dependencies. Interactive runs hide the
|
|
@@ -99,8 +101,13 @@ const ejectScaffold = async (
|
|
|
99
101
|
} catch (error) {
|
|
100
102
|
// SAFETY: eject and the script rewrite throw Error instances; only the
|
|
101
103
|
// message is surfaced in the fallback hint.
|
|
104
|
+
const { message } = error as Error;
|
|
105
|
+
// Only a skipped install explains the failure; after an install that ran,
|
|
106
|
+
// the error itself is the reason.
|
|
102
107
|
logger.warn(
|
|
103
|
-
|
|
108
|
+
needsInstall
|
|
109
|
+
? `Scaffolded, but eject needs the project's dependencies installed to load blume.config.ts: ${message}`
|
|
110
|
+
: `Scaffolded, but eject failed: ${message}`
|
|
104
111
|
);
|
|
105
112
|
const steps = [...cd, ...install, `${commands.exec} blume eject --yes`];
|
|
106
113
|
logger.box(`Next steps:\n\n ${steps.join("\n ")}`);
|
|
@@ -229,7 +236,7 @@ export const initCommand = defineCommand({
|
|
|
229
236
|
{ cwd, userAgent: process.env.npm_config_user_agent }
|
|
230
237
|
);
|
|
231
238
|
if (collected === null) {
|
|
232
|
-
clack.cancel("
|
|
239
|
+
clack.cancel("Canceled — nothing was written.");
|
|
233
240
|
process.exit(0);
|
|
234
241
|
}
|
|
235
242
|
answers = collected;
|
|
@@ -279,8 +286,11 @@ export const initCommand = defineCommand({
|
|
|
279
286
|
}
|
|
280
287
|
|
|
281
288
|
// A newly written package.json is the only one `init` knows lists blume;
|
|
282
|
-
// an existing one is left alone, and so are its dependencies.
|
|
283
|
-
|
|
289
|
+
// an existing one is left alone, and so are its dependencies. In a pnpm
|
|
290
|
+
// workspace that doesn't list the folder, an install would exit 0 without
|
|
291
|
+
// installing it, so that one waits until the note's change is made.
|
|
292
|
+
const shouldInstall =
|
|
293
|
+
createdPackage && args.install && !outsidePnpmWorkspace(root, answers);
|
|
284
294
|
if (note) {
|
|
285
295
|
(interactive ? clack.log : logger).warn(note);
|
|
286
296
|
}
|